I've distilled things down to the Activity shown below with the associated build.config and activity_main.xml.
The problem is LeakCanary is reporting a leak of the Activity if you press BACK before the counter completes, whereas letting it run to the end doesn't.
I've an idea what's going on. I know there must be an anonymous inner class created in the Activity by the Observer which survives the onPause() call because if you watch the console you can see it continuing the count. I know I've unsubscribed at this point, but the thread in the process is still running as an inner class of the Activity, which is the hallmark of a leak. This isn't just an artificial corner case for me either, in my real app the count is supposed to continue after an orientation change, so I persist what's necessary in a retained fragment and pick up the count accordingly, with the same leak. I just didn't need to show it here to simplify the example.
MainAcivity.java:
package com.otamate.rxactivityleaker;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import com.squareup.leakcanary.LeakCanary;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class MainActivity extends AppCompatActivity {
private Observable<Long> mObservable;
private Subscriber<Long> mSubscriber;
public TextView mTextView;
public final static int MAX_PROGRESS = 10;
public final static int EMIT_DELAY_MS = 1000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LeakCanary.install(getApplication());
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textView);
mSubscriber = createSubscriber();
mObservable = createObservable();
mObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}
private Observable createObservable() {
return Observable.create (
new Observable.OnSubscribe<Long>() {
@Override
public void call(Subscriber<? super Long> sub) {
for (long i = 1; i < MAX_PROGRESS + 1; i++) {
Log.d("Observable", "Progress: " + i);
sub.onNext(i);
SystemClock.sleep(EMIT_DELAY_MS);
}
sub.onCompleted();
}
}
);
}
private Subscriber createSubscriber() {
return new Subscriber<Long>() {
@Override
public void onNext(Long val) {
Log.d("Subscriber", "Loop " + val);
mTextView.setText("Progress: " + val);
}
@Override
public void onCompleted() {
Log.d("Subscriber", "Completed");
mTextView.setText("Done!");
}
@Override
public void onError(Throwable e) {
Log.d("Subscriber", "Error: " + e);
mTextView.setText("Error!");
}
};
}
@Override
public void onPause() {
super.onPause();
if (mSubscriber != null) {
Log.d("MainActivity", "onPause() Unsubscribed");
mSubscriber.unsubscribe();
}
}
}
Here's activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.otamate.rxactivityleaker.MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Idle" />
</RelativeLayout>
build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.otamate.rxactivityleaker"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'io.reactivex:rxandroid:0.25.0'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
}
Observable.create
makes it easy for you to shoot yourself in the foot .
Your problem is here:
private Observable createObservable() {
return Observable.create (
new Observable.OnSubscribe<Long>() {
@Override
public void call(Subscriber<? super Long> sub) {
for (long i = 1; i < MAX_PROGRESS + 1; i++) {
Log.d("Observable", "Progress: " + i);
sub.onNext(i);
SystemClock.sleep(EMIT_DELAY_MS);
}
sub.onCompleted();
}
}
);
}
When creating your Observable you are NOT checking if the Subscriber
unsubscribed before calling onNext/onCompleted
. You are also not handling back-pressure.
Check out AbstractOnSubscribe it can help you create your Observable
easier.
EDIT: AbstractOnSubscribe
was removed in 1.1 but maybe SyncOnSubscribe will work.
Try this
Subscription subscription;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LeakCanary.install(getApplication());
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textView);
mSubscriber = createSubscriber();
mObservable = createObservable();
subscription = mObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mSubscriber);
}
Than in onPause()
@Override
public void onPause() {
super.onPause();
if (subscription != null) {
subscription.unsubscribe();
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.