Skip to content

Instantly share code, notes, and snippets.

@jeremyrempel
Created May 6, 2020 13:59
Show Gist options
  • Save jeremyrempel/bd3fdebf31f88b378ea831aedb760052 to your computer and use it in GitHub Desktop.
Save jeremyrempel/bd3fdebf31f88b378ea831aedb760052 to your computer and use it in GitHub Desktop.

Memory Leaks

Setup Leak Canary

Context Leaks via Threads

  • When not static will retain reference to outer class beyond lifecycle of container (Activity, Fragment). In below example MyTask will retain a reference to Activity
  • Storing app state in activity is the path to the dark side

Fixes:

  • Make MyTask static or seperate class
  • If context is required: Use application context or provide activity callback with weakref
  • Link MyTask and Activity lifecycles
  • Move concurrency code outside Activity
class MyLeakyActivity : AppCompatActivity() {

    fun onCreate(savedState: Bundle) {
        // MyTask will retain a reference to Activity
        MyTask().execute()
    }

    fun updateUi(data: MyData) {}

    class MyTask extends AsyncTask {
        fun execute() {
            // doLongOp()
        }
        fun onPostExecute(data: MyData) {
            // activity may be zombie
            updateUi(data)
        }
    }
}

RXJava Lambda Example 1

If the lambda accesses any variables from the outer scope it will retain a reference to caller class by creating a inner class.

Fixes:

  • Link Observable and Activity lifecycles
  • Move concurrency code outside Activity
class MyLeakyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)

        val someCapturedState = "hello world 2"

        Observable.create<String> {
            // doLongOp()
            it.onNext("hello world 1")
            it.onNext(someCapturedState)
        }
        .subscribeOn(Schedulers.computation())
        .subscribe()
    }
}

RXJava Example 1 Decompiled to Java

public final class MyLeakyActivity extends AppCompatActivity {

   public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
      super.onCreate(savedInstanceState, persistentState);
      final String someCapturedState = "hello world 2";
      Observable.create((ObservableOnSubscribe)(new ObservableOnSubscribe() {
         public final void subscribe(@NotNull ObservableEmitter it) {
            Intrinsics.checkParameterIsNotNull(it, "it");
            it.onNext("hello world 1");
            it.onNext(someCapturedState);
         }
      })).subscribeOn(Schedulers.computation()).subscribe();
   }
}

Fragment Reference to Activity

Fragment lifecycle is different than Activity. If fragment outlives Activity such as a configuration change when Fragment will be reused will leak reference to parent.

Solutions

  • Utilize callbacks listener pattern, attach/detach listener on appropriate callbacks
  • Any logic that requires access to Activity view, logic should exist within Activity and observe the appropriate lifecycle
  • Use static inner class as opposed to anonoymous class
  • Retain reference to activity using WeakReference
class LeakyFragment : Fragment {    
    private lateinit view: View
    override fun onActivityCreated(savedInstanceState: Bundle) {
        view = getActivity.findViewById(R.id.search_view)
    }
}

Presenter+RXJava

Presenter initiating a observable but not being destroyed with activity. Will continue to execute.

Solutions:

  • Link the Activity lifecycle to the observable in LeakyPresenter if the operation can be interrupted (such as a query operation)
  • If LeakyPresenter is doing an update operation. Use WeakReference in LeakyPresenter to View to ensure operation is not interrupted. Presenter can be retained across configuration changed with getLastCustomNonConfigurationInstance
class LeakyActivity : AppCompatActivity, LeakyView {
    val presenter = LeakyPresenter(this)

    override fun onCreate() {
        presenter.loadData()
    }

    override fun onDataLoaded(data: MyData) {
        // update ui
    }
}

class LeakyPresenter(view: LeakyView) {

    fun loadData() {
        val disposable = repo.getData().subscribe(::onDataLoaded)
    }

    fun onDataLoaded(data: MyData) {
        view.onDataLoaded(data)
    }
}

Listeners

When registering a listener to a resource such as location or content provider failure to unregister can result in a leak. If ContentObservers are being used along with a threading library such as RXJava ensure to subscribe and unsubscribe in a thread safe manner.

class LeakyResource(val context: Context) {
    fun listenForChanges() {
        val contentObserver: ContentObserver = getContent()
        contentObserver.registerContentObserver()
    }
}

Singleton

When referencing global state such as Singletons special care needs to be taken to ensure no leaks. In the below the current activity callback is being passed a reference to Singleton. Possible solutions are utilizing a weak reference or using an Observable callback on Singleton.

class LeakyActivity() : MyCallback {
  Singleton.addCallback(this)
}

Tools

  • MAT. Before loading hprof file in mat use convertor tool in Android SDK: hprof-conv android.hprof mat.hprof
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment