What happens with view references in unfinished As

2019-08-15 22:12发布

问题:

Note this question was inspired by a comment to https://stackoverflow.com/a/33370816/519334 .

I have a holder class that belongs to a GridView-item with a reference to an ImageView

public static class Holder {
    ImageView mRowImage;
    String mImageUrl;

    // neccessary to cancel unfinished download
    BitmapLoaderAsyncTask mDownloader;
}

The corresponding GridItemAdapter uses an AsyncTask to get the image for the GridItem

public class GridItemAdapter extends BaseAdapter {
    @Override
    public View getView(int position, ...) {
        ...
        if (view == null) {
            holder = ...
            ...
        } else {
            holder = (Holder) view.getTag();
        }
        ...
        // cancel unfinished mDownloader
        if (holder.mDownloader != null) {
            holder.mDownloader.cancel(false);
            holder.mDownloader = null;
        }
        holder.mImageUrl = mImageUrls.get(position);
        holder.mDownloader = new BitmapLoaderAsyncTask()
        holder.mDownloader.execute(holder); 
    }
}

static class BitmapLoaderAsyncTask extends AsyncTask<Holder, Void, Bitmap> {
    Holder mHolder;
    protected Bitmap doInBackground(Holder... holders) {
        mHolder = holders[0];
        ...
    }
    protected void onPostExecute(...) {
        mHolder.mDownloader = null; 
        if (!isCancelled()) {
            this.mHolder.mRowImage.setImageBitmap(image);
        }
        this.mHolder = null;
    }
}

A comment suggested that there might be a problem with this code after orientation change.

Szenario

  • Grid is in Landscape - mode
  • GridItemAdapter starts BitmapLoaderAsyncTask#1 for loading Image1.jpg
    • async task has mHolder.mRowImage
  • Grid orientation changes from Landscape to Portrait mode
  • BitmapLoaderAsyncTask#1 finishes and calls onPostExecute(..)
  • (!!!) In onPostExecute(..) the image mHolder.mRowImage is updated. Since mHolder.mRowImage does not exist any more because orientation change so there should be a crash.

I have code similar to the described scenario and up to now I havn-t had the (!!!) crash yet.

My Question

  • Is this just coincedence that there was no (!!!) crash yet?
  • Is there a simple solution to check in onPostExecute(..) that mHolder.mRowImage is not valid any more?
  • Or is there something in Android that protects the AsyncTask?

回答1:

Since mHolder.mRowImage does not exist any more because orientation change so there should be a crash.

That is not correct, all Threads are GC roots, and your AsyncTask is holding strong reference to View object (it's inside your Holder class) and your View has strong reference to Activity/Fragment/etc. So your Activity/Fragment/etc won't be properly garbage collected as long as your AsyncTask is running. It won't cause any crash (because View do exists) but memory leak will occur and result will be delivered to old Activity/Fragment/etc.

However if you make sure that AsyncTask is cancelled properly everything will be ok. But if you want to be 100% sure you should use WeakReference to hold you Holder class in BitmapLoaderAsyncTask

@edit

The way you do task cancelling now is incorrect. After orientation change all the view will be inflated once again (in new Activity/Fragment/etc), thus view.getTag will always result null.