向上和向下滚动ListView会快速更改ListView项的顺序

时间:2014-06-17 12:47:08

标签: android listview android-listview scroll out-of-memory

我有一个带有Custom ArrayAdapter和Custom Items的ListView。当我在创作时,在简历上或当我使用getView()方法慢慢滚动时填充这些项目时,一切都很有效。

但是当我向上和向下快速向下滚动时,项目的顺序会在ListView中更改。

只有当我使用“正确”方式处理自定义ArrayAdapter的getView()方法时,才会发生这种情况:

@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");
}

我确实找到了以下两个链接中提供的这个快速滚动错误的解决方案:

此解决方案是在每次进入getView()方法时对视图进行充气并创建新的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");
}

这确实解决了滚动错误,但它会产生一个新问题:当我快速上下滚动时,我会遇到Out of Memory Runtime错误和Memory Leaks。以下是Logcat of Eclipse中的信息:

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秒钟,在我的Logcat中获得了超过50次的整个Stack-Trace ...

有没有人知道修复快速滚动错误的解决方案,而不是每次都像第二个代码那样创建新的视图?

如果没有,哪个更可取?忽略这个错误并让它改变顺序,这可能会导致用户抱怨或修复此错误但存在内存泄漏和应用程序崩溃的风险?


SOLUTION:

根据使用缓存的建议,我首先尝试了Vigneshwaran Thenraj的LazyList建议。但是,我忘了提到我只使用我/res/drawable文件夹中的图像而不使用网络上的图像。因此,我使用了自己创建的非常简单的缓存。因为当我快速向上和向下滚动时,我每次在模拟器上仍然有点慢时重新充气视图并重新创建ViewHolder,但在真实设备上它不是。

因此,我用来缓存图像的解决方案是:

// 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 Memory Leaks / App崩溃了。

PS:我使用Default类来保存Image-Resouces ID:

// 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作为枚举,并提供三种LogType。

3 个答案:

答案 0 :(得分:1)

如果在视图中有图像,最好将它们添加到缓存中,这样它性能会更高,只能获得一次图像。 这是您为每一行创建新视图的正确方法

答案 1 :(得分:1)

如果您在列表中有图像并且您正在尝试显示这个,那么我建议您使用一些图像加载库,有一个很好的图像加载器通用图像加载器,您可以从以下链接下载示例应用程序并检出,你不会得到使用它的内存问题... 点击here

答案 2 :(得分:1)

在这种情况下,你必须使用lazylist loading

https://github.com/thest1/LazyList

它可以帮助您避免图像位移和快速滚动