Android内存的性质在方向更改和垃圾回收时泄漏

时间:2014-11-14 14:29:55

标签: android android-activity memory-leaks android-asynctask orientation-changes

我有一个活动加载并显示一个稍大的位图。首先是危险的业务,但我们使用下面的标准方法使用BitmapFactory加载它:

private static Bitmap decodeSampledBitmapFromFile(String filepath, int reqWidth,int orientation) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filepath,options);
    if(orientation==0){
    options.inSampleSize = calculateInSampleSizeWidth(options, reqWidth);
    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap d = BitmapFactory.decodeFile(filepath,options);
    return Bitmap.createScaledBitmap(d, reqWidth, d.getHeight()*reqWidth/d.getWidth(), true);
    }
    else{
        options.inSampleSize = calculateInSampleSizeHeight(options,reqWidth);
        options.inJustDecodeBounds = false;
        Bitmap d = BitmapFactory.decodeFile(filepath,options);
        return Bitmap.createScaledBitmap(d, d.getWidth()*reqWidth/d.getHeight(), reqWidth, true);
    }
}
private static int calculateInSampleSizeWidth( BitmapFactory.Options options, int reqWidth) {
    int inSampleSize = 1;   
    if (options.outWidth > reqWidth) {
        final int halfWidth = options.outWidth / 2;
        while ( (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

将此Bitmap加载到ImageView中是在如下所示的AsyncTask中完成的:

private static class LoadBitmapTask extends AsyncTask<String, Void, Bitmap>{
    private ViewSampleActivity _activity;
    public LoadBitmapTask(ViewSampleActivity activity){
        this._activity = activity;
    }
    public void detach(){
        this._activity = null;
    }
    @Override
    protected Bitmap doInBackground(String... params) {
        try{
            String filename = params[0];
            Display display = _activity.getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int orientation = getCameraPhotoOrientation(_activity,Uri.fromFile(new File(filename)),filename);
            Bitmap d = null;

            d = decodeSampledBitmapFromFile(filename, (int)(((double)size.x)*0.85),orientation);
            d = rotateBitmapAppropriately(_activity,Uri.fromFile(new File(filename)),filename,d);
            return d;
        }
        catch(Exception e){
            return null;
        }
    }

    @Override
    protected void onPostExecute(Bitmap result){
        try{
            _activity.sampleImageView.setImageBitmap(result);
            _activity.sampleImageView.setVisibility(View.VISIBLE);
            _activity.sampleImageView.getLayoutParams().height = result.getHeight();
            _activity.sampleImageView.getLayoutParams().width = result.getWidth();
            _activity.viewSampleLayout.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
            _activity.hasPicture = true;
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

为了避免内存泄漏,我重载了活动的onStop方法,以便从AsyncTask中分离活动。

@Override
public void onStop(){
    if(this.loadBitmapTask!=null){
        loadBitmapTask.detach();
    }
    super.onStop();
}

现在,我看到的奇怪行为是活动在第一次运行时加载得很好。方向更改后,活动将正确重新加载。但是,在五次左右的方向更改后,应用程序将因内存不足错误而崩溃。 Bitmap似乎是明显的罪魁祸首。我没有保存任何ui元素或位图或任何东西的静态副本,所以我想知道这些内存不足错误是否是由于内存泄漏造成的。

有问题的活动是活动层次结构的最后一个。层次结构的根,即主活动,有一个带有WeakReference的android Handler对象,还有一个管理蓝牙网络线程的持久片段。层次结构中的第二个活动是一个简单的ListActivity。

相关活动的父活动是否会对此活动的Java垃圾收集产生影响?

1 个答案:

答案 0 :(得分:1)

Bitmap d方法中decodeSampledBitmapFromFile()会被垃圾收集吗?我并非100%清楚这些概念,但似乎从d创建缩放副本而不回收d本身是可疑的。

我还建议尝试重新连接到AsyncTask而不是让它死掉(并产生更多?!)。如果您正在使用FragmentActivity,则可以使用非配置实例。在活动中存储对任务的引用,并将其另存为非配置实例。然后尝试重新附加onCreate您的活动:

private LoadBitmapTask mTask;

@Override
public Object onRetainCustomNonConfigurationInstance() {
    if(mTask != null){
       mTask.detach();
    }
    return mTask;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    mTask = (LoadBitmapTask) getLastCustomNonConfigurationInstance();

    if (mTask != null) {
        mTask.attach(this);
    }
}

通过这种方式,您可以在中断的地方接听&#34;使用AsyncTask而不必产生更多。