Long Luong
Long Luong

Reputation: 810

Switching activities while running AsyncTask can leak memory?

I am trying to work with AsyncTask using inner class. But I face a problem with Leak Memory. So I decided to write some test code in order to figure out where issue could be. In this code below i have tried to run a task that counts from 0 to 100. Than I left the activity while the task was running. I got an _InterruptedException_ and Activity leaked(using Leak Canary), then my app was freezed until it crashed. I could not understand why, because the task was canceled, bevor I left the activity.

Here is my little sample code:

public class MainActivity extends AppCompatActivity {
TextView textView;
BackgroundTask _task;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    textView = (TextView) findViewById(R.id.textView);

   _task = new BackgroundTask(textView);
    _task.execute();
}

@Override
protected void onPause() {
    _task.cancel(true);
    super.onPause();
}

@Override
protected void onResume() {
    if (_task.isCancelled()){
        _task = new BackgroundTask(textView);
        _task.execute();
    }
    super.onResume();
}

private class BackgroundTask extends AsyncTask<Void, Integer, String> {
    private WeakReference<TextView> _textView;

    public BackgroundTask(TextView textView) {
        this._textView = new WeakReference<TextView>(textView);
    }

    @Override
    protected String doInBackground(Void... params) {
        for (int i = 0; i <= 100; i++)
            try {
                Thread.sleep(1000);
                publishProgress(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        return "DONE";
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        TextView textView = _textView.get();
        if (textView != null) {
            textView.setText(values[0] + " .");
        }
        Log.d("==> ",values[0]+" ");
    }

    @Override
    protected void onPostExecute(String result) {
        TextView textView = _textView.get();
          if (textView != null) {
             textView.setText(result);
    }
        //MainActivity.this.isFinishing();
    }
  }

 }

Here is my log:

D/==>: 0 
D/==>: 1 
D/==>: 2 
W/System.err: java.lang.InterruptedException
W/System.err:     at java.lang.Thread.sleep(Native Method)
W/System.err:     at java.lang.Thread.sleep(Thread.java:1031)
W/System.err:     at java.lang.Thread.sleep(Thread.java:985)
W/System.err:     at com.example.longluong.test_app.MainActivity$BackgroundTask.doInBackground(MainActivity.java:58)
W/System.err:     at com.example.longluong.test_app.MainActivity$BackgroundTask.doInBackground(MainActivity.java:47)
W/System.err:     at android.os.AsyncTask$2.call(AsyncTask.java:295)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
W/System.err:     at java.lang.Thread.run(Thread.java:818)
I/art: Waiting for a blocking GC Explicit
I/art: Starting a blocking GC Explicit
I/art: Explicit concurrent mark sweep GC freed 343(27KB) AllocSpace objects, 0(0B) LOS objects, 82% free, 1267KB/7MB, paused 488us total 18.818ms
I/art: hprof: heap dump "/storage/emulated/0/Download/leakcanary-com.example.longluong.test_app/0a1c4ebe-238f-4156-a317-ed8d454de769_pending.hprof" starting...
I/art: hprof: heap dump completed (12MB) in 7.001s
D/LeakCanary: In com.example.longluong.test_app:1.0:1.
D/LeakCanary: * com.example.longluong.test_app.MainActivity has leaked:
D/LeakCanary: * GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1')
D/LeakCanary: * references com.example.longluong.test_app.MainActivity$BackgroundTask.this$0
D/LeakCanary: * leaks com.example.longluong.test_app.MainActivity instance
D/LeakCanary: * Retaining: 6.7 KB.
D/LeakCanary: * Reference Key: 4db87806-d761-42c8-a874-9682d7477106
D/LeakCanary: * Device: LENOVO Lenovo Lenovo TB2-X30F TB2-X30F
D/LeakCanary: * Android Version: 6.0.1 API: 23 LeakCanary: 1.5 00f37f5
D/LeakCanary: * Durations: watch=5055ms, gc=125ms, heap dump=7205ms, analysis=26304ms
D/LeakCanary: * Details:
D/LeakCanary: * Instance of java.lang.Thread
D/LeakCanary: |   static NANOS_PER_MILLI = 1000000
D/LeakCanary: |   static defaultUncaughtHandler = com.android.internal.os.RuntimeInit$UncaughtHandler@583012736 (0x22c01180)
D/LeakCanary: |   static count = 902
D/LeakCanary: |   static MAX_PRIORITY = 10
D/LeakCanary: |   static $staticOverhead = byte[48]@1873454545 (0x6faaa5d1)
D/LeakCanary: |   static NORM_PRIORITY = 5
D/LeakCanary: |   static MIN_PRIORITY = 1
D/LeakCanary: |   contextClassLoader = dalvik.system.PathClassLoader@583019904 (0x22c02d80)
D/LeakCanary: |   daemon = false
D/LeakCanary: |   group = java.lang.ThreadGroup@1871611928 (0x6f8e8818)
D/LeakCanary: |   hasBeenStarted = true
D/LeakCanary: |   id = 900
D/LeakCanary: |   inheritableValues = null
D/LeakCanary: |   interruptActions = java.util.ArrayList@584600480 (0x22d84ba0)
D/LeakCanary: |   localValues = java.lang.ThreadLocal$Values@584600512 (0x22d84bc0)
D/LeakCanary: |   lock = java.lang.Object@583012608 (0x22c01100)
D/LeakCanary: |   name = java.lang.String@584594240 (0x22d83340)
D/LeakCanary: |   nativePeer = -1218480552
D/LeakCanary: |   parkBlocker = null
D/LeakCanary: |   parkState = 1
D/LeakCanary: |   priority = 5
D/LeakCanary: |   stackSize = 0
D/LeakCanary: |   target = java.util.concurrent.ThreadPoolExecutor$Worker@583008752 (0x22c001f0)
D/LeakCanary: |   uncaughtHandler = null
D/LeakCanary: |   shadow$_klass_ = java.lang.Thread
D/LeakCanary: |   shadow$_monitor_ = 0
D/LeakCanary: * Instance of com.example.longluong.test_app.MainActivity$BackgroundTask
D/LeakCanary: |   static $staticOverhead = byte[16]@583275521 (0x22c41401)
D/LeakCanary: |   static serialVersionUID = 0
D/LeakCanary: |   static $change = null
D/LeakCanary: |   _textView = java.lang.ref.WeakReference@584490912 (0x22d69fa0)
D/LeakCanary: |   this$0 = com.example.longluong.test_app.MainActivity@583586560 (0x22c8d300)
D/LeakCanary: |   mCancelled = java.util.concurrent.atomic.AtomicBoolean@584499616 (0x22d6c1a0)
D/LeakCanary: |   mFuture = android.os.AsyncTask$3@583016640 (0x22c020c0)
D/LeakCanary: |   mStatus = android.os.AsyncTask$Status@1871859248 (0x6f924e30)
D/LeakCanary: |   mTaskInvoked = java.util.concurrent.atomic.AtomicBoolean@584499632 (0x22d6c1b0)
D/LeakCanary: |   mWorker = android.os.AsyncTask$2@583012640 (0x22c01120)
D/LeakCanary: |   shadow$_klass_ = com.example.longluong.test_app.MainActivity$BackgroundTask
D/LeakCanary: |   shadow$_monitor_ = 0
D/LeakCanary: * Instance of com.example.longluong.test_app.MainActivity
D/LeakCanary: |   static $staticOverhead = byte[16]@584134657 (0x22d13001)
D/LeakCanary: |   static serialVersionUID = 0
D/LeakCanary: |   static $change = null
D/LeakCanary: |   _task = com.example.longluong.test_app.MainActivity$BackgroundTask@583008704 (0x22c001c0)
D/LeakCanary: |   textView = android.support.v7.widget.AppCompatTextView@584087552 (0x22d07800)
D/LeakCanary: |   mDelegate = android.support.v7.app.AppCompatDelegateImplV23@583064160 (0x22c0da60)
D/LeakCanary: |   mEatKeyUpEvent = false
D/LeakCanary: |   mResources = null
D/LeakCanary: |   mThemeId = 2131230877
D/LeakCanary: |   mCreated = true
D/LeakCanary: |   mFragments = android.support.v4.app.FragmentController@584499648 (0x22d6c1c0)
D/LeakCanary: |   mHandler = android.support.v4.app.FragmentActivity$1@584490944 (0x22d69fc0)
D/LeakCanary: |   mMediaController = null
D/LeakCanary: |   mNextCandidateRequestIndex = 0
D/LeakCanary: |   mOptionsMenuInvalidated = false
D/LeakCanary: |   mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@584490976 (0x22d69fe0)
D/LeakCanary: |   mReallyStopped = true
D/LeakCanary: |   mRequestedPermissionsFromFragment = false
D/LeakCanary: |   mResumed = false
D/LeakCanary: |   mRetaining = false
D/LeakCanary: |   mStopped = true
D/LeakCanary: |   mStartedActivityFromFragment = false
D/LeakCanary: |   mStartedIntentSenderFromFragment = false
D/LeakCanary: |   mActionBar = null
D/LeakCanary: |   mActionModeTypeStarting = 0
D/LeakCanary: |   mActivityInfo = android.content.pm.ActivityInfo@584475392 (0x22d66300)
D/LeakCanary: |   mActivityTransitionState = android.app.ActivityTransitionState@584326848 (0x22d41ec0)
D/LeakCanary: |   mApplication = com.example.longluong.test_app.LeakyApp@584548416 (0x22d78040)
D/LeakCanary: |   mCalled = true
D/LeakCanary: |   mChangeCanvasToTranslucent = false
D/LeakCanary: |   mChangingConfigurations = false
D/LeakCanary: |   mComponent = android.content.ComponentName@584499664 (0x22d6c1d0)
D/LeakCanary: |   mConfigChangeFlags = 0
D/LeakCanary: |   mCurrentConfig = android.content.res.Configuration@584503552 (0x22d6d100)
D/LeakCanary: |   mDecor = null
D/LeakCanary: |   mDefaultKeyMode = 0
D/LeakCanary: |   mDefaultKeySsb = null
D/LeakCanary: |   mDestroyed = true
D/LeakCanary: |   mDoReportFullyDrawn = false
D/LeakCanary: |   mEmbeddedID = null
D/LeakCanary: |   mEnableDefaultActionBarUp = false
D/LeakCanary: |   mEnterTransitionListener = android.app.SharedElementCallback$1@1871616672 (0x6f8e9aa0)
D/LeakCanary: |   mExitTransitionListener = android.app.SharedElementCallback$1@1871616672 (0x6f8e9aa0)
D/LeakCanary: |   mFinished = true
D/LeakCanary: |   mFragments = android.app.FragmentController@584499680 (0x22d6c1e0)
D/LeakCanary: |   mHandler = android.os.Handler@584548448 (0x22d78060)
D/LeakCanary: |   mHasCurrentPermissionsRequest = false
D/LeakCanary: |   mIdent = 115814648
D/LeakCanary: |   mInstanceTracker = android.os.StrictMode$InstanceTracker@584499696 (0x22d6c1f0)
D/LeakCanary: |   mInstrumentation = android.app.Instrumentation@584284208 (0x22d37830)
D/LeakCanary: |   mIntent = android.content.Intent@584326912 (0x22d41f00)
D/LeakCanary: |   mLastNonConfigurationInstances = null
D/LeakCanary: |   mMainThread = android.app.ActivityThread@583020800 (0x22c03100)
D/LeakCanary: |   mManagedCursors = java.util.ArrayList@584548480 (0x22d78080)
D/LeakCanary: |   mManagedDialogs = null
D/LeakCanary: |   mMenuInflater = null
D/LeakCanary: |   mParent = null
D/LeakCanary: |   mReferrer = null
D/LeakCanary: |   mResultCode = 0
D/LeakCanary: |   mResultData = null
D/LeakCanary: |   mResumed = false
D/LeakCanary: |   mSearchEvent = null
D/LeakCanary: |   mSearchManager = null
D/LeakCanary: |   mStartedActivity = false
D/LeakCanary: |   mStopped = true
D/LeakCanary: |   mTemporaryPause = false
D/LeakCanary: |   mTitle = java.lang.String@584548512 (0x22d780a0)
D/LeakCanary: |   mTitleColor = 0
D/LeakCanary: |   mTitleReady = true
D/LeakCanary: |   mToken = android.os.BinderProxy@584548544 (0x22d780c0)
D/LeakCanary: |   mTranslucentCallback = null
D/LeakCanary: |   mUiThread = java.lang.Thread@1933361752 (0x733cc258)
D/LeakCanary: |   mVisibleBehind = false
D/LeakCanary: |   mVisibleFromClient = true
D/LeakCanary: |   mVisibleFromServer = true
D/LeakCanary: |   mVoiceInteractor = null
D/LeakCanary: |   mWindow = com.android.internal.policy.PhoneWindow@583319712 (0x22c4c0a0)
D/LeakCanary: |   mWindowAdded = true
D/LeakCanary: |   mWindowManager = android.view.WindowManagerImpl@584548576 (0x22d780e0)
D/LeakCanary: |   mInflater = com.android.internal.policy.PhoneLayoutInflater@584483648 (0x22d68340)
D/LeakCanary: |   mOverrideConfiguration = null
D/LeakCanary: |   mResources = android.content.res.Resources@584284288 (0x22d37880)
D/LeakCanary: |   mTheme = android.content.res.Resources$Theme@584548608 (0x22d78100)
D/LeakCanary: |   mThemeResource = 2131230877
D/LeakCanary: |   mBase = android.app.ContextImpl@584475520 (0x22d66380)
D/LeakCanary: |   shadow$_klass_ = com.example.longluong.test_app.MainActivity
D/LeakCanary: |   shadow$_monitor_ = 1266075474
D/LeakCanary: * Excluded Refs:
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mNextServedView
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedView
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedInputConnection
D/LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mCurRootView
D/LeakCanary: | Field: android.os.UserManager.mContext
D/LeakCanary: | Field: android.net.ConnectivityManager.sInstance
D/LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
D/LeakCanary: | Thread:FinalizerWatchdogDaemon (always)
D/LeakCanary: | Thread:main (always)
D/LeakCanary: | Thread:LeakCanary-Heap-Dump (always)
D/LeakCanary: | Class:java.lang.ref.WeakReference (always)
D/LeakCanary: | Class:java.lang.ref.SoftReference (always)
D/LeakCanary: | Class:java.lang.ref.PhantomReference (always)
D/LeakCanary: | Class:java.lang.ref.Finalizer (always)
D/LeakCanary: | Class:java.lang.ref.FinalizerReference (always)

Upvotes: 1

Views: 484

Answers (1)

Haris Qurashi
Haris Qurashi

Reputation: 2124

After InterruptedException your for loop is still looping, when you are publishing your progress your thread is cancelled so you need to check whether your thread is cancelled or not.

When your application crashes you still have a WeakReference of your TextView that's why you have a memory leak. You need to clear your WeakReference in onPostExecute and onCancelled.

Change your:

doInBackground to

@Override
protected String doInBackground(Void... params) {
    for (int i = 0; i <= 100; i++){
        if(isCancelled())
            break;
        try {
            Thread.sleep(1000);
            // After 1 second may be thread is cancelled
            if(!isCancelled()) 
                publishProgress(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
            break;
        }
    }
    return "DONE";
}

onProgressUpdate to

@Override
protected void onProgressUpdate(Integer... values) {
    if(_textView != null){
        TextView textView = _textView.get();
        if (textView != null) {
            textView.setText(values[0] + " .");
        }
        Log.d("==> ",values[0]+" ");
    }
}

onPostExecute to

@Override
protected void onPostExecute(String result) {
    // safe check
    if(_textView != null){
        TextView textView = _textView.get();
        if (textView != null) {
            textView.setText(result);
        _textView.clear();
    }
    _textView = null;
}

And add onCancelled

@Override
protected void onCancelled() {
    // safe check
    super.onCancelled();
    if(_textView != null)
        _textView.clear();
    _textView = null;
}

Upvotes: 1

Related Questions