使用AsyncTask在ListView中加载图片时出现内存泄漏问题

3
我看到我的小型服务在没有任何活动运行时占用了约15-16 MB的内存,意识到存在内存泄漏。通常情况下(系统启动时),其内存使用量约为3MB,但如果我打开应用程序,则内存使用量会增加,即使关闭它后仍然如此!

仔细观察后发现,随着我打开的活动数量增加,内存使用量也增加了! 因此,我注意到我的活动:在我的应用程序中有3个活动,每个活动都有一个列表视图,其中单元格内容从网络加载,并且每个元素都是这样制作的:

| ImageView | 一些文本 |

我认为问题在于适配器中的异步任务,以下是代码:

public class MyAdapter extends CursorAdapter{
    private final LayoutInflater mInflater;
    private LruCache<String, Bitmap> cachedBitmaps;
    private SimpleDateFormat srcFormat; //I have to convert dates
    private SimpleDateFormat dstFormat;


   @Override
public void bindView(View view, Context context, Cursor c) {
    ViewHolder holder = (ViewHolder)view.getTag();
    if(holder == null){
        holder = new ViewHolder();
        holder.cellImg = (ImageView)view.findViewById(R.id.cellImg); 
        holder.cellProgress = view.findViewById(R.id.cellProgress);
        holder.titleText = (TextView)view.findViewById(R.id.titleText);
        view.setTag(holder);
    }
            try{
        String imageLink = c.getString(c.getColumnIndex("image_link"));
        String guid = c.getString(c.getColumnIndex("guid"));
        Bitmap bitmap = cachedBitmaps.get(guid);

        if(bitmap == null){ //need to download
            ImageView image = holder.cellImg; 
            image.setTag(guid);
            DownloadBitmapTask task = new DownloadBitmapTask(context,image,
                    holder.cellProgress);
            task.execute(new String[]{imageLink});
                     //else I just get it from cache

通过使用占位符和缓存,我确保有效地加载图片...在这里我没有看到任何错误,但是我与您分享这个问题是因为我想要给您一个完整的视图!现在,这就是我认为存在问题的任务:

private class DownloadBitmapTask extends AsyncTask<String, Void, Bitmap>{

    private ImageView img;
    private View progress;
    private String tag;
            //normally you see a progress bar, when image is loaded I substitute it
    public DownloadBitmapTask(Context c,ImageView img,View progress){
        this.progress = progress;
        this.img = img;
        tag = new String(img.getTag().toString());
    }

    @Override
    protected void onPreExecute() {
        progress.setVisibility(View.VISIBLE);
        img.setVisibility(View.INVISIBLE);
    }

    private final int MAX_TRIES = 3;

    @Override
    protected Bitmap doInBackground(String... args) {
        Bitmap bitmap = null;
        int failedTries = 0;
        boolean done = false;
        while((!done) && (failedTries<MAX_TRIES)){
            try{
                URLConnection conn = (new URL(args[0])).openConnection();
                bitmap = BitmapFactory.decodeStream(
                        conn.getInputStream());

                done = true;
            }catch(IOException ex){
                failedTries++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
        }

        return bitmap;
    }



    @Override
    protected void onPostExecute(Bitmap result) {
        progress.setVisibility(View.INVISIBLE);

        if( (result != null) && (img.getTag().toString().equals(tag)) ){
            cachedBitmaps.put((String)img.getTag(), result);
            img.setImageBitmap(result);
            img.setVisibility(View.VISIBLE);
            img.getParent().recomputeViewAttributes(img);
            notifyDataSetChanged();
        }
    }

}

我看不出有任何内存泄漏,但我确定有!请帮帮我 :(
编辑:MAT工具给我的信息是:
类“android.content.res.Resources”,由""加载,占用了5,033,704(38.96%)字节。内存累积在一个由""加载的“java.lang.Object[]”实例中。
它们都是位图,当然啦 :(
3个回答

1

我认为问题出在doInBackground方法中的BitmapFactory。这个解码会消耗大量内存并且也有一些已知的泄漏问题。我总是将整个图像缩放以减少内存消耗,而不是解码整个图像。以下是一个示例:

  //decodes image and scales it to reduce memory consumption
  private static Bitmap decodeImage(InputStream in, InputStream in2){

          //Decode image size
          BitmapFactory.Options o = new BitmapFactory.Options();
          o.inJustDecodeBounds = true;
          BitmapFactory.decodeStream(in,null,o);

          //The new size we want to scale to
          final int REQUIRED_SIZE=100;

          //Find the correct scale value. It should be the power of 2.
          int scale=1;
          while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
              scale*=2;

          //Decode with inSampleSize
          BitmapFactory.Options o2 = new BitmapFactory.Options();
          o2.inSampleSize=scale;
          o2s.inTempStorage = new byte[16*1024];

          return BitmapFactory.decodeStream(in2, null, o2);

  }

注意: 您需要打开两个输入流实例。

其他建议是每当图像不再有用时调用Bitmap中的clear()方法。

希望这可以帮助您!


好的建议,但我下载的图片大小正好符合我的需求,所以在我的情况下缩放是无用的 :( - Phate

0

我认为你应该像下面展示的那样,每次打开后都关闭输入流。

InputStream iStream = null;
try{
     URLConnection conn = (new URL(args[0])).openConnection();
     iStream = conn.getInputStream(); 
     itmap = BitmapFactory.decodeStream(iStream);

     done = true;
   }catch(IOException ex){
      failedTries++;
      try {
           Thread.sleep(100);
          } catch (InterruptedException e) {
          }
   } finally {
      if(iStream != null)
          iStream.close();
   }

注意:建议在使用完流后关闭它们。


0
尝试将您创建的任务设置在:
        DownloadBitmapTask task = new DownloadBitmapTask(context,image, 
                holder.cellProgress); 

当位图下载完成时,请将其返回到 null。您没有关闭/处理 AsyncTask 中分配的所有资源,因此 GC 无法释放它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接