Created
May 6, 2020 13:58
-
-
Save jeremyrempel/47c0789316fb714bec56fe7cdfd42dcf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Memory Leaks | |
## Setup Leak Canary | |
- https://github.com/square/leakcanary | |
- Add to gradle. initialize on app startup, ensure debug is off | |
## 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 | |
```kotlin | |
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 | |
```kotlin | |
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* | |
```kotlin | |
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 | |
```kotlin | |
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 | |
```kotlin | |
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. | |
```kotlin | |
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. | |
```kotlin | |
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