Exploring the world of Android :: Part 2
September 17th, 2009 by Tom van Zummeren
|
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 my first blog post about Android I talked about setting up a project using Android. This time I want to discuss a more “advanced” topic: ListView
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 ListAdapter to allow it to display any content. A few simple implementations of that adapter are already available in the SDK:
- ArrayAdapter<T> (for displaying an array of objects, using toString() to display them)
- SimpleAdapter (for displaying a list of Maps)
- SimpleCursorAdapter (for displaying rows fetched from a database table using a Cursor)
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 ListAdapter implementation. In most cases it’s useful to subclass ArrayAdapter
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 getView(int, View, ViewGroup) 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.

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<ImageAndText> {
public ImageAndTextListAdapter(Activity activity, List<ImageAndText> 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”:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"> <ImageView android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/default_image"/> <TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/> </LinearLayout>
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 AsyncTask 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 Thread for each image. In addition we should use Handlers 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<String, SoftReference<Drawable>> imageCache;
public AsyncImageLoader() {
drawableMap = new HashMap<String, SoftReference<Drawable>>();
}
public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) {
if (drawableMap.containsKey(imageUrl)) {
SoftReference<Drawable> 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>(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 SoftReference 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:
http://www.androidguys.com/2008/07/17/fancy-listviews-part-two/
To cache the views returned by findViewById() read this blog entry:
http://www.androidguys.com/2008/07/22/fancy-listviews-part-three/
If we apply the strategies described in Mark Murphy’s blog entries our ImageAndTextListAdapter could look like the following:
public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {
private ListView listView;
private AsyncImageLoader asyncImageLoader;
public ImageAndTextListAdapter(Activity activity, List<ImageAndText> 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 …
Tags: Android, Java, Performance Posted in: Custom Development
Exploring the world of Android :: Part 2的更多相关文章
- Exploring the world of Android :: Part 1
This blog is accidentally find out, it tells the story of one of our friends about the exploration o ...
- Android 异步网络图片加载
ListView异步加载图片 http://www.eoeandroid.com/forum.php?mod=viewthread&tid=161586 [Android分享] ListVie ...
- Android Weekly Notes Issue #316
July 1st, 2018 Android Weekly Issue #316 本期内容包含教你使用Kotlin通过Annotation Processor生成代码文件, JetPack中的Andr ...
- Android Weekly Notes Issue #233
Android Weekly Issue #233 November 27th, 2016 Android Weekly Issue #233 本期内容包括: 用Mockito做RxJava的单元测试 ...
- Android Weekly Notes Issue #228
Android Weekly Issue #228 October 23rd, 2016 Android Weekly Issue #228 本期内容包括: Android 7.1的App Short ...
- Android 7.1 App Shortcuts使用
Android 7.1 App Shortcuts使用 Android 7.1已经发了预览版, 这里是API Overview: API overview. 其中App Shortcuts是新提供的一 ...
- Android Weekly Notes Issue #227
Android Weekly Issue #227 October 16th, 2016 Android Weekly Issue #227. 本期内容包括: Google的Mobile Vision ...
- android调试工具DDMS的使用详解
具体可见http://developer.android.com/tools/debugging/ddms.html. DDMS为IDE和emultor.真正的android设备架起来了一座桥梁.开发 ...
- 使用calabash测试开源中国Android客户端
Calabash-android是支持android的UI自动化测试框架,前面已经介绍过<中文Win7下成功安装calabash-android步骤>,这篇博文尝试测试一个真实应用:开源中 ...
随机推荐
- Android Studio 3.0.1 又见恶心爆的bug。。。xiete
写了个AIDL的东西,结果一直编译不通过: Error:Execution failed for task ':app:compileDebugAidl'. > java.io.IOExcept ...
- MVC part4
SpringMVC 注解 @Controller 负责注册一个bean 到spring 上下文中,bean 的ID 默认为类名称开头字母小写,你也可以自己指定, 如下 方法一: @Controller ...
- 通过AI自学习,Google让Pixel 3的人像模式更优秀
通过AI自学习,Google让Pixel 3的人像模式更优秀 Link: https://news.cnblogs.com/n/613720/ 虽然双摄手机已经在市场上普及,其所带来的人像模式.多倍变 ...
- IE 11中 onpropertychange失效
https://msdn.microsoft.com/zh-cn/library/ie/dn265032(v=vs.85).aspx 将突变事件和属性更改事件迁移到突变观察者 Internet Exp ...
- UITableView性能的优化()
1.0 使用不透明视图 不透明的视图可以极大地提高渲染的速度. 2.0 不要重复创建不必要的cell 也就是我们常说的 循环利用机制 (建立缓冲池) 3.0 减少视图的数目 4.0 不要做多余的绘 ...
- MongoDB模糊查询,以及MongoDB模糊查询时带有括号的情况
模糊查询 记录如下: { "_id" : ObjectId("5c3d486d24aa9a000526367b"), "name" : &q ...
- JS拖拽元素原理及实现代码
一.拖拽的流程动作 ①鼠标按下②鼠标移动③鼠标松开 二.拖拽流程中对应的JS事件 ①鼠标按下会触发onmousedown事件 ②鼠标移动会触发onmousemove事件 ③鼠标松开会触发onmouse ...
- 用R包中heatmap画热图
一:导入R包及需要画热图的数据 library(pheatmap) data<- read.table("F:/R练习/R测试数据/heatmapdata.txt",head ...
- Mysql5.6 导出sql文件数据导入到5.7
由于在linux安装了mysql5.7,在需要导入数据时发现报错,说时间默认值不能为0,因为之前用的是mysql5.6 的版本.经过网上百度查找方法,发现是mysql的sql_mode值的问题,于是就 ...
- CSS实现禁止文字选中
E10平台预览第四版中包含了对 CSS3 新属性 -ms-user-select 的支持,Web 开发人员可以利用这一新属性轻松精确的控制用户可以在网站上选择哪些文本. user-select:non ...