- https://github.com/square/leakcanary
- Add to gradle. initialize on app startup, ensure debug is off
- 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)
}
}
}
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 lifecycle is different than Activity. If fragment outlives Activity such as a configuration change when Fragment will be reused will leak reference to parent.
- 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 initiating a observable but not being destroyed with activity. Will continue to execute.
- 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)
}
}
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()
}
}
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)
}
- MAT. Before loading hprof file in mat use convertor tool in Android SDK: hprof-conv android.hprof mat.hprof