博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Exploring the world of Android :: Part 2
阅读量:5281 次
发布时间:2019-06-14

本文共 12396 字,大约阅读时间需要 41 分钟。

September 17th, 2009 by

|

And I’m back! Reporting live on the glorious adventures in the exciting world of Android. This blog post is the second one in the Android series. This time with code samples! Yeah!

In I talked about setting up a project using Android. This time I want to discuss a more “advanced” topic: performance. A ListView is a view component which allows you to display and scroll through a list of items. It can display simple text in each row, but is also able to display a more complicated structure. In the latter case you will need to make sure your ListView still performs well (read: renders fast and scrolls smoothly). I am going to provide solutions to a few different performance problems when using a ListView

The ListAdapter

If you want to use a ListView, you will have to supply it with a to allow it to display any content. A few simple implementations of that adapter are already available in the SDK:

  • (for displaying an array of objects, using to display them)
  • (for displaying a list of s)
  • (for displaying rows fetched from a database table using a )

These implementations are perfect for displaying very simple lists. But if your list is just a little more complicated than that, you will need to write your own custom implementation. In most cases it’s useful to subclass which already takes care of managing a list of objects. Now you only have to tell it how to render each object in the list. Do this by overriding the method of the ArrayAdapter class.

A simple example

To give you a simple example of a case in which you need to write your own ListAdapter: displaying a list of images with some text next to it.

images with text in a ListView

Example of a ListView containing Youtube search results in the form of images and text

The images need to be on-the-fly downloaded from the internet. Let’s create a class which represents items in the list:

public class ImageAndText {    private String imageUrl;    private String text;    public ImageAndText(String imageUrl, String text) {        this.imageUrl = imageUrl;        this.text = text;    }    public String getImageUrl() {        return imageUrl;    }    public String getText() {        return text;    }}

Now, let’s create an implementation of a ListAdapter that is able to display a list of these ImageAndTexts.

public class ImageAndTextListAdapter extends ArrayAdapter
{ public ImageAndTextListAdapter(Activity activity, List
imageAndTexts) { super(activity, 0, imageAndTexts); } @Override public View getView(int position, View convertView, ViewGroup parent) { Activity activity = (Activity) getContext(); LayoutInflater inflater = activity.getLayoutInflater(); // Inflate the views from XML View rowView = inflater.inflate(R.layout.image_and_text_row, null); ImageAndText imageAndText = getItem(position); // Load the image and set it on the ImageView ImageView imageView = (ImageView) rowView.findViewById(R.id.image); imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl())); // Set the text on the TextView TextView textView = (TextView) rowView.findViewById(R.id.text); textView.setText(imageAndText.getText()); return rowView; } public static Drawable loadImageFromUrl(String url) { InputStream inputStream; try { inputStream = new URL(url).openStream(); } catch (IOException e) { throw new RuntimeException(e); } return Drawable.createFromStream(inputStream, "src"); }}

The views are inflated from an XML file called “image_and_text_row.xml”:

This ListAdapter implementation renders the ImageAndTexts in the ListView like you would expect. The only thing is that this only works for a very small list which doesn’t require scrolling to see all items. If the list of ImageAndTexts gets bigger you will notice that scrolling isn’t as smooth as it should be (in fact, it’s far off!).

Improving performance

The biggest bottleneck in the above example is the fact that the images have to be downloaded from the internet. Because we execute all our code in the same thread as the UI, the UI will get stuck each time an image is being downloaded. If you run the same application using a 3G internet connection instead of WiFi, the performance will even be worse.

To avoid this we want the image to be loaded in a separate thread to not disturb the UI thread too much. To make this happen, we could use an which is designed for cases like this. But in practice, you will notice that the AsyncTask is limited to 10 threads. This number is hardcoded somewhere in the Android SDK so we cannot change this. In this case it’s a limitation we cannot live with, because often more than 10 images are loaded at the same time.

AsyncImageLoader

An alternative is to manually spawn a new for each image. In addition we should use s to deliver the downloaded images to the UI thread. We want to do this because only from the UI thread you are allowed to modify the UI (read: draw an image on the screen). I created a class called AsyncImageLoader which takes care of loading images using Threads and Handlers like I just described. Also it caches images to avoid a single image to be downloaded multiple times.

public class AsyncImageLoader {    private HashMap
> imageCache; public AsyncImageLoader() { drawableMap = new HashMap
>(); } public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) { if (drawableMap.containsKey(imageUrl)) { SoftReference
softReference = imageCache.get(imageUrl); Drawable drawable = softReference.get(); if (drawable != null) { return drawable; } } final Handler handler = new Handler() { @Override public void handleMessage(Message message) { imageCallback.imageLoaded((Drawable) message.obj, imageUrl); } }; new Thread() { @Override public void run() { Drawable drawable = loadImageFromUrl(imageUrl); imageCache.put(imageUrl, new SoftReference
(drawable)); Message message = handler.obtainMessage(0, drawable); handler.sendMessage(message); } }.start(); return null; } public static Drawable loadImageFromUrl(String url) { // ... } public interface ImageCallback { public void imageLoaded(Drawable imageDrawable, String imageUrl); }}

Notice that I used a for caching images, to allow the garbage collector to clean the images from the cache when needed. How it works:

  • Call loadDrawable(imageUrl, imageCallback) providing an anonymous implementation of the ImageCallback interface
  • If the image doesn’t exist in the cache yet, the image is downloaded in a separate thread and the ImageCallback is called as soon as the download is complete.
  • If the image DOES exist in the cache, it is immediately returned and the ImageCallback is never called.

Only one instance of AsyncImageLoader should exist in your application, or else the caching won’t work. If we take the example ImageAndTextListAdapter class we can now replace:

ImageView imageView = (ImageView) rowView.findViewById(R.id.image);imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));

with:

final ImageView imageView = (ImageView) rowView.findViewById(R.id.image);Drawable cachedImage = asyncImageLoader.loadDrawable(imageAndText.getImageUrl(), new ImageCallback() {    public void imageLoaded(Drawable imageDrawable, String imageUrl) {        imageView.setImageDrawable(imageDrawable);    }});imageView.setImageDrawable(cachedImage);

Using this approach, the ListView performs a lot better and feels much more smooth because the UI thread is no longer blocked by the loading of images.

Improve the performance even more

If you tried the solution described above you will notice that the ListView is still not a 100% smooth. You will still notice some little disruptions that make it a little less smooth than it could be. There are two things remaining that can be improved:

  • the expensive call to findViewById()
  • inflating the entire row from XML every time

The solution is obvious: we should cache/reuse these things! Mark Murphy did a very nice job on writing a few blog entries describing how this can be done. To reuse the views which are inflated from XML read this blog entry:

To cache the views returned by findViewById() read this blog entry:

If we apply the strategies described in Mark Murphy’s blog entries our ImageAndTextListAdapter could look like the following:

public class ImageAndTextListAdapter extends ArrayAdapter
{ private ListView listView; private AsyncImageLoader asyncImageLoader; public ImageAndTextListAdapter(Activity activity, List
imageAndTexts, ListView listView) { super(activity, 0, imageAndTexts); this.listView = listView; asyncImageLoader = new AsyncImageLoader(); } @Override public View getView(int position, View convertView, ViewGroup parent) { Activity activity = (Activity) getContext(); // Inflate the views from XML View rowView = convertView; ViewCache viewCache; if (rowView == null) { LayoutInflater inflater = activity.getLayoutInflater(); rowView = inflater.inflate(R.layout.image_and_text_row, null); viewCache = new ViewCache(rowView); rowView.setTag(viewCache); } else { viewCache = (ViewCache) rowView.getTag(); } ImageAndText imageAndText = getItem(position); // Load the image and set it on the ImageView String imageUrl = imageAndText.getImageUrl(); ImageView imageView = viewCache.getImageView(); imageView.setTag(imageUrl); Drawable cachedImage = asyncImageLoader.loadDrawable(imageUrl, new ImageCallback() { public void imageLoaded(Drawable imageDrawable, String imageUrl) { ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl); if (imageViewByTag != null) { imageViewByTag.setImageDrawable(imageDrawable); } } }); imageView.setImageDrawable(cachedImage); // Set the text on the TextView TextView textView = viewCache.getTextView(); textView.setText(imageAndText.getText()); return rowView; }}

There are two things to notice. The first thing is that the drawable is not directly set to the ImageView anymore after loading. Instead, the right ImageView is looked up through it’s tag. This is done because we’re now reusing views and the images might end up on the wrong rows. We need a reference to the ListView to lookup ImageViews by tag.

The other thing to notice, is that this implementation uses an object called ViewCache. This is what the class for that object looks like:

public class ViewCache {    private View baseView;    private TextView textView;    private ImageView imageView;    public ViewCache(View baseView) {        this.baseView = baseView;    }    public TextView getTextView() {        if (textView == null) {            textView = (TextView) baseView.findViewById(R.id.text);        }        return titleView;    }    public ImageView getImageView() {        if (imageView == null) {            imageView = (ImageView) baseView.findViewById(R.id.image);        }        return imageView;    }}

This ViewCache is the same as what Mark Murphy calls a “ViewWrapper” and takes care of caching individual views which normally would have to be looked up every time using the expensive call to findViewById().

To summarize

I’ve shown you how to improve performance of a ListView in three different ways:

  • By loading images in a seperate thread
  • By reusing rows in the list
  • By caching views within a row

It took me quite some time to figure this stuff out, especially the image loading part. So I thought it is all worth mentioning to avoid you having to waste too much time on it.

Next time I will discuss other interesting challenges in the world of Android!

TO BE CONTINUED …

转载于:https://www.cnblogs.com/xuyinghui/p/4665395.html

你可能感兴趣的文章
shell脚本统计文件中单词的个数
查看>>
SPCE061A学习笔记
查看>>
sql 函数
查看>>
hdu 2807 The Shortest Path 矩阵
查看>>
熟悉项目需求,要知道产品增删修改了哪些内容,才会更快更准确的在该项目入手。...
查看>>
JavaScript 变量
查看>>
java实用类
查看>>
smarty模板自定义变量
查看>>
研究称90%的癌症由非健康生活习惯导致
查看>>
命令行启动Win7系统操作部分功能
查看>>
排序sort (一)
查看>>
Parrot虚拟机
查看>>
Teamcenter10 step-by-step installation in Linux env-Oracle Server Patch
查看>>
Struts2学习(三)
查看>>
Callable和Runnable和FutureTask
查看>>
GitHub 多人协作开发 三种方式:
查看>>
文本域添加编辑器
查看>>
Yum安装MySQL以及相关目录路径和修改目录
查看>>
java获取hostIp和hostName
查看>>
关于web服务器和数据库的各种说法(搜集到的)
查看>>