【发布时间】:2014-06-17 12:47:08
【问题描述】:
我有一个带有自定义 ArrayAdapter 和自定义项的 ListView。当我在创建、恢复或使用 getView() 方法缓慢滚动时填充这些项目时,一切正常。
但是当我快速上下滚动时,ListView 中的项目顺序会发生变化。
只有当我在 getView() 方法中使用“正确”的方式处理自定义 ArrayAdapter 时才会发生这种情况:
@Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
MyViewHolder myViewHolder = null;
if(view == null){
view = inflater.inflate(layoutResourceId, parent, false);
// Get the View Elements
// Create a new ViewHolder and save the View-Elements in it
myViewHolder = new MyViewHolder(TextView txtView1, TextView txtView2, ...);
view.setTag(myViewHolder);
}
else
myViewHolder = (MyViewHolder) view.getTag();
// Do something with the Views like changing a text
myViewHolder.myTextView.setText("some text");
}
我确实找到了以下两个链接中提供的快速滚动错误的解决方案:
- When I scroll a listview with a custom adapter too fast up and down, getView() starts behaving oddly. Why?
- List Item (a view) order changes unexpextedly while (fast) scrolling in a ListView
这个解决方案是每次进入getView()方法时,给View充气并创建一个新的ViewHolder:
@Override
public View getView(int position, View convertView, ViewGroup parent){
View view = inflater.inflate(layoutResourceId, parent, false);
// Get the View Elements
view.setTag(new MyViewHolder(TextView txtView1, TextView txtView2, ...));
myViewHolder = (MyViewHolder) view.getTag();
// Do something with the Views like changing a text
myViewHolder.myTextView.setText("some text");
}
这确实解决了滚动错误,但它产生了一个新问题:当我快速上下滚动时,我会遇到内存不足运行时错误和内存泄漏。以下是 Eclipse 的 Logcat 中的信息:
06-17 07:15:30.728: I/dalvikvm-heap(2920): Forcing collection of SoftReferences for 111376-byte allocation
06-17 07:15:30.908: I/dalvikvm-heap(2920): Clamp target GC heap from 17.614MB to 16.000MB
06-17 07:15:30.908: D/dalvikvm(2920): GC_BEFORE_OOM freed 72K, 4% free 15843K/16360K, paused 180ms, total 181ms
06-17 07:15:30.908: E/dalvikvm-heap(2920): Out of memory on a 111376-byte allocation.
06-17 07:15:30.918: I/dalvikvm(2920): "main" prio=5 tid=1 RUNNABLE
06-17 07:15:30.918: I/dalvikvm(2920): | group="main" sCount=0 dsCount=0 obj=0xb4a9cca8 self=0xb730e398
06-17 07:15:30.918: I/dalvikvm(2920): | sysTid=2920 nice=0 sched=0/0 cgrp=apps handle=-1225232044
06-17 07:15:30.918: I/dalvikvm(2920): | state=R schedstat=( 20550000000 16710000000 17134 ) utm=1825 stm=230 core=0
06-17 07:15:30.918: I/dalvikvm(2920): at android.graphics.Bitmap.nativeCreate(Native Method)
06-17 07:15:30.918: I/dalvikvm(2920): at android.graphics.Bitmap.createBitmap(Bitmap.java:809)
06-17 07:15:30.928: I/dalvikvm(2920): at android.graphics.Bitmap.createBitmap(Bitmap.java:769)
06-17 07:15:30.928: I/dalvikvm(2920): at android.view.View.buildDrawingCache(View.java:13608)
06-17 07:15:30.928: I/dalvikvm(2920): at android.view.View.getDrawingCache(View.java:13463)
06-17 07:15:30.938: I/dalvikvm(2920): at android.view.View.draw(View.java:14156)
06-17 07:15:30.938: I/dalvikvm(2920): at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.938: I/dalvikvm(2920): at android.widget.ListView.drawChild(ListView.java:3363)
06-17 07:15:30.948: I/dalvikvm(2920): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.948: I/dalvikvm(2920): at android.widget.AbsListView.dispatchDraw(AbsListView.java:2458)
06-17 07:15:30.948: I/dalvikvm(2920): at android.widget.ListView.dispatchDraw(ListView.java:3358)
06-17 07:15:30.948: I/dalvikvm(2920): at android.view.View.draw(View.java:14468)
06-17 07:15:30.948: I/dalvikvm(2920): at android.widget.AbsListView.draw(AbsListView.java:3817)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.View.draw(View.java:14350)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.View.draw(View.java:14348)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.View.draw(View.java:14348)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.958: I/dalvikvm(2920): at android.view.View.draw(View.java:14468)
06-17 07:15:30.968: I/dalvikvm(2920): at com.android.internal.widget.ActionBarOverlayLayout.draw(ActionBarOverlayLayout.java:381)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.View.draw(View.java:14350)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewGroup.drawChild(ViewGroup.java:3103)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2940)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.View.draw(View.java:14468)
06-17 07:15:30.968: I/dalvikvm(2920): at android.widget.FrameLayout.draw(FrameLayout.java:472)
06-17 07:15:30.968: I/dalvikvm(2920): at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2326)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2496)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewRootImpl.draw(ViewRootImpl.java:2409)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2253)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1883)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1000)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5670)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.Choreographer.doCallbacks(Choreographer.java:574)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.Choreographer.doFrame(Choreographer.java:544)
06-17 07:15:30.968: I/dalvikvm(2920): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
06-17 07:15:30.968: I/dalvikvm(2920): at android.os.Handler.handleCallback(Handler.java:733)
06-17 07:15:30.968: I/dalvikvm(2920): at android.os.Handler.dispatchMessage(Handler.java:95)
06-17 07:15:30.968: I/dalvikvm(2920): at android.os.Looper.loop(Looper.java:136)
06-17 07:15:30.968: I/dalvikvm(2920): at android.app.ActivityThread.main(ActivityThread.java:5017)
06-17 07:15:30.968: I/dalvikvm(2920): at java.lang.reflect.Method.invokeNative(Native Method)
06-17 07:15:30.968: I/dalvikvm(2920): at java.lang.reflect.Method.invoke(Method.java:515)
06-17 07:15:30.968: I/dalvikvm(2920): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
06-17 07:15:30.968: I/dalvikvm(2920): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
06-17 07:15:30.968: I/dalvikvm(2920): at dalvik.system.NativeStart.main(Native Method)
06-17 07:15:30.998: I/Choreographer(2920): Skipped 294 frames! The application may be doing too much work on its main thread.
我上下滚动了大约 10 秒,得到了上面的整个 Stack-Trace,在我的 Logcat 中超过 50 次..
有没有人知道解决快速滚动错误的解决方案,而无需像第二个代码那样每次都创建新视图?
如果不是,什么更可取?忽略这个错误,让它改变顺序,这可能会导致用户抱怨或修复这个错误但有内存泄漏和应用程序崩溃的风险?
解决方案:
根据使用缓存的建议,我首先尝试了 Vigneshwaran Thenraj 的 LazyList 建议。但是,我忘了提到我只使用 /res/drawable 文件夹中的图像,而不使用来自网络的图像。因此,我使用了自己创建的一个非常简单的缓存。因为每次我在模拟器上快速上下滚动时它仍然有点慢,但在真实设备上却不是。
所以,我用来缓存图像的解决方案是:
// ImageLoader class to cache the Images from the Drawable folder
public class ImageLoader
{
// Logcat tag
private static final String TAG = "ImageLoader";
private Context context;
// The HashMap where we save the cached Bitmaps for the ImageViews
// TODO: Find a way to use a synchronized SparseArray to fix the warning below
private Map<Integer, Bitmap> cachedBitmaps = Collections.synchronizedMap(new HashMap<Integer, Bitmap>());
// Default Checkbox Image
private static final int DEFAULT_CHECKBOX = D.CHECKBOX_IMAGES[0];
public ImageLoader(Context c){
context = c;
// Add the default Resource-ID to the WeakHashMap
Bitmap defaultBitmap = BitmapFactory.decodeResource(context.getResources(), DEFAULT_CHECKBOX);
if(defaultBitmap != null){
cachedBitmaps.put(DEFAULT_CHECKBOX, defaultBitmap);
if(D.SHOW_INFO_LOGS)
L.Log(TAG, "Default Image added to the list: " + context.getResources().getResourceEntryName(DEFAULT_CHECKBOX), LogType.INFO);
}
else
L.Log(TAG, "The Default Resource-ID for the Drawable-Image is incorrect and we can't retrieve the Bitmap with the BitmapFactory!", LogType.ERROR);
}
// The method to save the Resource-ID key and it's current Bitmap value in the HashMap
// And also to retrieve the Bitmap with the given Resource-ID
public void imageForImageView(int id, ImageView iv){
// Get the Bitmap from the HashMap (or null if it doesn't exist yet)
Bitmap bitmap = cachedBitmaps.get(id);
// Does this Bitmap already exist in the HashMap?
if(bitmap != null)
// Then assign it to the ImageView
iv.setImageBitmap(bitmap);
// If it doesn't exists yet:
else{
// Create it locally using the BitmapFactory
bitmap = BitmapFactory.decodeResource(context.getResources(), id);
// Does the Drawable with this given Resource-ID exist?
if(bitmap != null){
// Put it in the HashMap
cachedBitmaps.put(id, bitmap);
// and assign it to the ImageView
iv.setImageBitmap(bitmap);
L.Log(TAG, "New Image added to the list: " + context.getResources().getResourceEntryName(id), LogType.INFO);
}
// If it doesn't exist:
else
// Use the default Resource-ID instead
iv.setImageBitmap(cachedBitmaps.get(DEFAULT_CHECKBOX));
}
}
}
将此 ImageLoader 添加到我的 MainActivity:
private static ImageLoader imageLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
... // Do more stuff
imageLoader = new ImageLoader();
}
public static ImageLoader getImageLoader(){
return imageLoader;
}
并在我的 getView() 方法和我更改这些图像的其他地方使用这个 ImageLoader:
MainActivity.getImageLoader().imageForImageView(D.CHECKBOX_IMAGES[currentItem.getCheckState()], imageView);
现在我不再遇到 Logcat 内存泄漏/应用程序崩溃了。
PS:我使用了一个保存 Image-Resouces ID 的 Default 类:
// Class for all the Default values
public class D
{
... // Other defaults
public static final Integer[] CHECKBOX_IMAGES = {
R.drawable.checkbox_unchecked,
R.drawable.checkbox_checked,
R.drawable.checkbox_error,
R.drawable.checkbox_partly
};
}
PSS:还有一个 Logger 类:
import android.util.Log;
// Logger class to show Logcat-messages
public class L
{
// Show normal Logs based on the given LogType
public static void Log(String TAG, String message, LogType type){
if(D.SHOW_LOGS){
switch(type){
case INFO:
Log.i(TAG, message);
break;
case WARNING:
Log.w(TAG, message);
break;
case ERROR:
Log.e(TAG, message);
break;
}
}
}
// Show Exception Error Logs
public static void LogException(String TAG, String message, Throwable ex){
if(D.SHOW_LOGS)
Log.e(TAG, message, ex);
}
}
将 LogType 作为 Enum 并提供三种可用的 LogType。
【问题讨论】:
标签: android listview android-listview scroll out-of-memory