Android:由于AsyncTask导致内存泄漏

4

我被一个无法解决的内存泄漏问题困扰着。我使用MemoryAnalizer确定了它发生的位置,但我徒劳地试图摆脱它。以下是代码:

public class MyActivity extends Activity implements SurfaceHolder.Callback {
 ...

Camera.PictureCallback mPictureCallbackJpeg = new Camera.PictureCallback() {
    public void onPictureTaken(byte[] data, Camera c) {
        try  {
            // log the action
            Log.e(getClass().getSimpleName(), "PICTURE CALLBACK JPEG: data.length = " + data);

            // Show the ProgressDialog on this thread 
            pd = ProgressDialog.show(MyActivity.this, "", "Préparation", true, false); 

            // Start a new thread that will manage the capture 
            new ManageCaptureTask().execute(data, c); 
        }
        catch(Exception e){
            AlertDialog.Builder dialog = new AlertDialog.Builder(MyActivity.this);
            ...
            dialog.create().show();
        }
    }

    class ManageCaptureTask extends AsyncTask<Object, Void, Boolean> { 
        protected Boolean doInBackground(Object... args) {
            Boolean isSuccess = false;

            // initialize the bitmap before the capture
            ((myApp) getApplication()).setBitmapX(null);
            try{

                // Check if it is a real device or an emulator
                TelephonyManager telmgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                String deviceID = telmgr.getDeviceId();
                boolean isEmulator = "000000000000000".equalsIgnoreCase(deviceID);

                // get the bitmap
                if (isEmulator) {
                    ((myApp) getApplication()).setBitmapX(BitmapFactory.decodeFile(imageFileName));
                } else {
                    ((myApp) getApplication()).setBitmapX(BitmapFactory.decodeByteArray((byte[]) args[0], 0, ((byte[])args[0]).length));
                }

                ((myApp) getApplication()).setImageForDB(ImageTools.resizeBmp(((myApp) getApplication()).getBmp()));
                // convert the bitmap into a grayscale image and display it in the preview
                ((myApp) getApplication()).setImage(makeGrayScale());
                isSuccess = true;
            }
            catch (Exception connEx){
                errorMessageFromBkgndThread = getString(R.string.errcapture);
            }
            return isSuccess; 
        } 

        protected void onPostExecute(Boolean result) { 
            // Pass the result data back to the main activity 
            if (MyActivity.this.pd != null) { 
                MyActivity.this.pd.dismiss(); 
            } 
            if (result){
                ((ImageView) findViewById(R.id.apercu)).setImageBitmap(((myApp) getApplication()).getBmp());    
                ((myApp) getApplication()).setBitmapX(null);
            }
            else{
                // there was an error
                ErrAlert();
            }
        } 
    }     
};
private void ErrAlert(){
    // notify the user about the error
    AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    ...
    dialog.create().show();
}

当按下按钮时,活动将被终止,如下所示:

Button use = (Button) findViewById(R.id.use);
use.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MyActivity.this, NextActivity.class);
        intent.putExtra("dbID", "-1");
        intent.putExtra("category", category);
        ((myApp) getApplication()).setBitmapX(null);
        MyActivity.this.startActivity(intent);
        MyActivity.this.finish();
        }
    });

内存分析器指出内存泄漏位置:

((myApp) getApplication()).setBitmapX(BitmapFactory.decodeByteArray((byte[]) args[0], 0, ((byte[])args[0]).length));

非常感谢您的建议,提前感谢。


半猜测:在完成 Bitmap 对象使用后,是否应该调用 recycle() 函数? 此外,在您提供的上下文中,PickerActivity.this 的调用似乎没有意义。并且您应该缓存 (myApp) getApplication() 函数的调用,因为它经常被使用。 - Christopher Orr
Christopher,感谢您的回复,实际上PickerActivity是MyActivity(我编辑了帖子);位图在应用程序级别定义,这就是为什么我不会回收它们,因为我需要它们在后续活动中使用。调试应用程序和MemoryAnalyzer向我展示ManageCaptureTask即使在MyActivity终止时仍然存在,这就是我不知道如何做的:终止异步任务。我编辑了帖子,添加了onClick事件,在该事件中终止了MyActivity并启动了下一个活动。 - Manu
我看到你在整个应用程序中都在使用 Bitmap,但是我注意到你在某个时候将其设置为 null,所以想知道是否应该在那个时候对其进行回收。 - Christopher Orr
2个回答

10

当onPostExecute被调用后,您的线程是否被垃圾回收了呢?或者它仍然在内存中?

一个Async Task在activity被销毁时不会被取消或销毁。如果您的线程相对较轻,并且在短时间内完成,只需让它继续运行并在onPostExecute()方法中添加MyActivity.this.isFinishing()代码块。

由于Task是活动(即MyActivity.this)的私有类,因此Task保存了对该Activity的隐式引用。这意味着直到Task退出,该Activity才能被垃圾回收。


谢谢Janusz,我知道"Your Task stores a implicit reference to your Activity MyActivity.this",但是我不知道如何处理它。我会尝试你的建议,在onPostExecuteMethod中添加一个MyActivity.this.isFinishing()条件,并且会给你反馈。再次感谢! - Manu
3
@Manu,你如何添加isFinishing()语句?能否发布几行示例代码? - jerrytouille

0
您可以尝试下面的代码片段。
protected void onPostExecute(Boolean result) {
    if(YourActivity.this.isFinished()){
        //to smomething here
    }
}

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