2013-08-25 117 views
2

我有一个自定义的ListView,我显示了一些从本地数据库检索到的武器。我总共有88行,每行调用一个文本和一个图像,每次调用getView()ListView滞后,而滚动速度快,垃圾收集器疯狂,每秒删除一些1M对象。我不明白为什么。ListView性能很慢

在我发布我的Adapter实现之前,有关如何设置图像的一些说明。我的武器类只是一个数据的持有者和获取者。这是多么的名字和图像的数据库中被创建时,收到设定(是它似乎很奇怪,但所有其他解决方案的工作,甚至更慢):

private Weapon buildWeapon(Cursor cursor) { 
    Weapon w = new Weapon(); 
    w.setId(cursor.getLong(0)); 
    w.setName(cursor.getString(1)); 
    w.setImage(Constants.ALL_WEAPON_IMAGES[(int) cursor.getLong(0)-1]); 


    return w; 
} 

所以我有一个包含在所有武器图像的Array形式为R.drawable.somegun。数据结构的实现方式使ID-1始终指向我的Array中的正确可绘制引用。武器类中的图像字段是Integer。现在你有一个想法,我getImage()方法是如何工作的,并在下面进入我的Adapter

public class Weapon_Adapter extends BaseAdapter { 
private List<Weapon> items; 
private LayoutInflater inflater = null; 
private WeaponHolder weaponHolder; 
private Weapon wp; 


static class WeaponHolder { 
    public TextView text; 
    public ImageView image; 
} 

// Context and all weapons of specified class are passed here 

public Weapon_Adapter(List<Weapon> items, Context c) { 
    this.items = (List<Weapon>) items; 
    inflater = LayoutInflater.from(c); 
    Log.d("Adapter:", "Adapter created"); 
} 

@Override 
public int getCount() { 
    return items.size(); 
} 

@Override 
public Weapon getItem(int position) { 
    return items.get(position); 
} 


@Override 
public long getItemId(int position) { 
    return position; 
} 

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 

    wp = (Weapon) getItem(position); 

    if (convertView == null) { 
     convertView = inflater.inflate(R.layout.category_row, null); 
     weaponHolder = new WeaponHolder(); 
     weaponHolder.text = (TextView) convertView 
       .findViewById(R.id.tvCatText); 
     weaponHolder.image = (ImageView) convertView 
       .findViewById(R.id.imgCatImage); 
     convertView.setTag(weaponHolder); 
    } 

     weaponHolder = (WeaponHolder) convertView.getTag(); 


    weaponHolder.text.setText(wp.getName()); 
    weaponHolder.image.setImageResource(wp.getImage()); 
      // weaponHolder.image.setImageResource(R.drawable.ak74m); 




    return convertView; 

}} 

现在奇怪的事情:使用outcommented行静态设置相同的图像的所有项目删除即使不是所有的滞后和GC叫一次!我没有明白它.. wp.getImage()返回完全相同的东西,每个武器只有R.drawable.name是不同的。但是GC在滚动时删除了大量的对象和时间滞后。任何想法我做错了什么?

UPDATE

我搬到设置图像的AsyncTask现在滞后了:

public class AsyncImageSetter extends AsyncTask<Void, Void, Void> { 

private ImageView img; 
private int image_resId; 
private Bitmap bmp; 
private Context c; 

public AsyncImageSetter(Context c, ImageView img, int image_ResId, Bitmap bmp) { 

    this.img = img; 
    this.image_resId = image_ResId; 
    this.bmp = bmp; 
    this.c = c; 

} 

@Override 
protected Void doInBackground(Void... params) { 

    bmp = BitmapFactory.decodeResource(c.getResources(), image_resId); 

    return null; 
} 

@Override 
protected void onPostExecute(Void result) { 

    img.setImageBitmap(bmp); 
    bmp = null; 

    super.onPostExecute(result); 
} 

    } 

然而,滚动整个GC时仍称像疯了似的RAM消耗增加上下列出。现在的问题是:如何优化图像回收以避免RAM使用量增加?

+0

使用延迟加载技术来加载图像。检查此链接http://stackoverflow.com/questions/16789676/caching-images-and-displaying/16978285#16978285可能也会帮助你,如果图像很大尝试缩小图像 – Raghunandan

+0

我读过关于它,它是适合从应用程序之外的地方加载一堆数据。我的图片数量非常有限,全部来自可绘制文件夹。我试图找出为什么outcommented行工作的很快,而我的getImage()方法几乎相同会导致List滞后。图像不是很大,可以通过ImageView的ScaleType参数来缩放 – Droidman

+0

有多大每个图像? – m0skit0

回答

1

如果您修复了低fps问题,并且快速滚动效果到目前为止您确实很好。

当频繁的GC行动仅仅是一个整容问题,而您不面临OutOfMemoryException或其他任何缺点时,那么您应该可以这样做。如果这不是您的选择,您还可以执行另一项操作:除了下采样和缓存之外,在启动AsyncTask之后以及实际检索资源文​​件之前,还可以添加一个小的人工等待时间(50-150ms)。然后,您在您的任务中添加一个取消标志,必须在人工延迟后进行检查。如果它设置为true,则不请求资源文件。

一些(不执行)的代码示例:

class MyImageLoader extends AsyncTask { 
    private boolean cancel = false 

    private Bitmap bitmap; 

    public void cancel() { cancel = true } 

    public void doInBackground() { 
     sleep(100); 
     if(!cancel) { 
      bitmap = BitmapFactory.decodeResource(...); 
     } 
    } 
} 

class Adapter { 

    static class WeaponHolder { 
     public TextView text; 
     public ImageView image; 
     public MyImageLoader loader; 
    } 

    public View getView(int position, View convertView, ViewGroup parent) { 
     WeaponHolder holder; 

     if (convertView == null) { 
      ... 
      holder = new WeaponHolder(); 
     } else { 
      holder = convertView.getTag(); 
      holder.loader.cancel(); // Cancel currently active loading process 
     } 

     holder.loader = new MyImageLoader(); 
     holder.loader.execute(); 

     return convertView; 
    } 
} 

这样大部分的图像将不会从你的内存,如果用户滚动真正快,你可以节省大量的内存读取。

+0

以及我的目标是尽可能减少内存使用量。 – Droidman

+0

我有一个应用程序,严重依赖大型图像的无尽滚动列表。当我添加上面解释的延迟方法时,事情变得更加顺利,低端设备的内存问题也更少。如果延迟足够低,用户将不会感到额外的等待时间。你应该试试看。 – Taig

+0

是啊谢谢,我改变了代码来匹配你的例子(不过延迟了),它在我的SGS2 I9100上运行得非常顺利。我的最低SDK是14,所以我(理论上)不会面对真正的低性能设备。事情是,这不是一个简单的ListView在我的情况。我在ViewPager中有6个ListFragments,每个ListFragments都被填充上面我的代码中显示的数据。所以我关心每个MB的RAM。在正常的布局文件夹中,我的ImageView是150x50dp,但原始位图较大。将它们缩小到我的AsyncTask中是否有意义? – Droidman

2

因为将所有位图加载到Android的内存通常是不切实际的,所以您应该假设您会不时地得到GC。

然而,你可以做什么考虑下一个提示:

  1. 缩减的位图,你需要向他们展示的大小。您可以使用google's waymy way

  2. 检查您放置图像文件的文件夹。很多人把它们放在res/drawable文件夹中,不明白为什么它们会比原来的尺寸大得多(这是因为密度 - 这是mdpi,而设备可能是xhdpi或xxhdpi)。例如,如果图像位于可绘制文件夹中,并且您在xhdpi设备(如galaxy S3)上运行它,则需要(WIDTH * 2)*(HEIGHT * 2)* 4个字节。如果图像为200x200,则其位图对象至少需要400 * 400 * 4 = 640,000个字节。在xxhdpi设备上会更糟糕,比如银河系s4和htc系统。

  3. 考虑使用内存高速缓存,像LruCache

  4. 如果位图不具有透明度好,你看不到任何质量差异,可以考虑使用的RGB_565 config而不是默认的。这将需要每个像素2个字节,而不是每个像素4个字节。

  5. 如果您可以负责足够,您可以使用JNI进行缓存。我为此任务做了一个小代码,here。请阅读我在那里写的所有笔记。

顺便说一句,我已经注意到你使用的图像标识符数组。如果图像的名称中包含一些逻辑(例如:img1,img2,...),则可以使用getResources()。getIdentifier(...)来代替。

+0

我想缓存图像的选项会为我做,你知道任何好的示例/教程/示例缓存图像和在ListView中使用它们吗?我感觉有点失落,因为我从来没有与缓存数据密切合作.. – Droidman

+0

当然,其他的点都没有帮助你?无论如何,这是一个关于缓存位图的链接:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html。请注意,LruCache需要API 12及更高版本,但它可作为兼容性库在某处:http://developer.android.com/reference/android/support/v4/util/LruCache.html。 –