【问题标题】:Scrolling ListView up- and down fast changes the order of the ListView-items快速上下滚动 ListView 会改变 ListView-items 的顺序
【发布时间】: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");
}

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

这个解决方案是每次进入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


    【解决方案1】:

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

    【讨论】:

    • 我的 ImageViews 并没有真正的默认 ImageResource。它们在 UserInput 上发生变化。我有 4 个不同的图像作为我的 ImageViews 的状态。根据您使用缓存的建议,我需要将所有 4 个图像保存在缓存中一次,然后每次仍然在 getView() 中设置 ImageResource,否则它们将是默认的 ImageResource。这会解决内存性能问题,因为我从缓存中检索它们,还是更改 ImageResources 会首先产生性能问题,无论是否使用缓存?
    • 我遇到了类似的问题,将它们放入缓存对我的性能和切换都有帮助,我花了 3 天时间来修复行切换。所以试试看
    • 我使用了一个简单的 ImageLoader 缓存,它只从我的 /res/drawable 文件夹中获取图像一次,然后重新使用它们,现在内存问题已经消失了。所以谢谢你的建议。尽管其他两个答案也建议使用缓存,但我只能接受一个。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      在这种情况下你必须使用惰性列表加载

      https://github.com/thest1/LazyList

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-29
        相关资源
        最近更新 更多