Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jeremyrempel/47c0789316fb714bec56fe7cdfd42dcf to your computer and use it in GitHub Desktop.
Save jeremyrempel/47c0789316fb714bec56fe7cdfd42dcf to your computer and use it in GitHub Desktop.
# 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