对生命周期进行监听,是解决内存泄漏非常重要的手段之一。
万物皆有灵:只要你认为存在生命周期的对象,都能使用 FyListen 进行生命周期监听,除了 Activity、Fragment、Application、Service等本身就存在生命周期的对象,你也可以认为View、视频播放器、资源下载器等也有生命周期,如视频播放器的生命周期可以视为:初始化->资源加载->播放中->暂停中->暂不播放(可被复用)->关闭结束。凡是可以定义出生命周期的,你都能使用 FyListen 进行生命周期监听,对代码结构进行优化、处理内存泄漏。
另外补充的是,FyListen 是线程安全的。在多线程环境下,观察者模式的“中间商”管理的资源认为是临界区资源,需要在线程安全的环境下操作,避免监听缺漏、空指针异常等各种问题。
本文从 1)接口、工具类基本介绍;2)实战应用;3)接口详细说明;4)接口方法重名处理办法 进行展开:
依赖导入:
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
implementation 'com.github.StudyNoteOfTu:fylisten2-alpha1:v1.0.0-alpha1'
}
LifecyclePublisher
接口:
可以被原本就被设计为有生命周期的组件实现:
-
public class DemoActivity extends Activity implements LifecyclePublisher{...}
-
public class DemoView extends View implements LifecyclePublisher{...}
-
public class DemoFragment extends Fragment implements LifecyclePublisher{...}
-
public class DemoApplication extends Application implements LifecyclePublisher{...}
也可以被你认为有生命周期的组件实现:
-
public class MediaPlayer implements LifecyclePublisher{...}
Listener
接口:
监听实现了 LifecyclePublisher 接口的类的生命周期,onError()回调设计为必须要实现,当被监听者注册出错等异常情况,可以在 onError() 接口中告诉开发者错误信息。其他的诸如 onAttach()、onCreate()、onDestroy() 等生命周期回调,可以选择你需要的进行实现:
匿名监听者
-
FyListen.registerListenerSafely(target,new Listener(){ @Override public void onCreate(LifecyclePublisher p){} @Override public void onResume(LifecyclePublisher p){} @Overide public void onError(LifecyclePublisher p,String error){} })
具体实现类监听者
-
public class DemoActivity extends Activity implements LifecyclePublisher{ @LifecycleTrace(Status.ON_DESTROY) @Override protected void onDestroy(){} @LifecycleTrace(Status.ON_RESUME) @Override protected void onResume(){} @Override protected void onCreate(Bundle savedInstanceState){ new Thread(()->{ Downloader downloader = new Downloader(); //耗时操作,开始去下载资源,并在下载完成后回调更新UI,由于Callback为匿名内部类,持有Activity的引用,可能会造成内存泄漏,所以需要生命周期监听,在activity需要被释放的时候,关闭后台任务 downloader.beginDownload(new Callback(){...}); //为下载器添加监听,在onDestroy()时停止下载,释放对Activity的引用 FyListen.registerListenerAnyway(this,downloader); }).start(); } private static class Downloader implements Listener{ public void beginDownload(Callback callback){ //开启线程下载资源 //下载完成时: callback.onFinish(...); } public void shutdown(){ //关闭下载资源的线程,取消下载,结束beginDownload方法,释放callback的引用, //也就是释放了对Activity的引用 } @Override public void onDestroy(LifecyclePublisher p){ //关闭下载器 shutdown(); } @Override public void onError(String err){ //关闭下载器 shutdown(); } } }
LifecycleTrace
注解:
被 LifecycleTrace 注解的方法被认为是生命周期的回调,只要这个方法被调用,就会通知监听者当前的生命周期。当然前提是实现了 LifecyclePublisher 接口,否则即使加了 LifecycleTrace 注解,也不会被任何人监听到。
它可以被加在本身就有生命周期定义的类上,如Activity:
-
public class DemoActivity extends Activity implements LifecyclePublisher{ @LifecycleTrace(Status.ON_DESTROY) @Override protected void onDestroy(){} }
也可以被加在你认为有生命周期的类上,比如你实现了一个MediaPlayer工具,它有四个阶段:1)初始化;2)加载资源;3)播放素材;4)暂停素材;4)工作结束:
-
public class MediaPlayer implements LifecyclePublisher{ @LifecycleTrace(Status.ON_ATTACH)//我选择用ON_ATTACH来定义其正在初始化 private void onInit(Context context){ //初始化工具 } private void loadSource(Source source){ //加载资源 loadFinish(); } @LifecycleTrace(Status.ON_CREATE)//我选择用 ON_CREATE 来定义其正在加载资源完成 private void loadFinish(){ //加载完成 } @LifecycleTrace(Status.ON_RESUME)//我选择用 ON_RESUME 来定义正在播放 public void play(){ //播放 } @LifecycleTrace(Status.ON_PAUSE)//我选择用 ON_PAUSE 来定义暂停中 public void pause(){ //暂停 } @LifecycleTrace(Status.ON_STOP)//我选择用 ON_STOP 来定义停止工作 public void stop(){ //没有工作,此时当前对象可以放到复用池等待复用 } @LifecycleTrace(Status.ON_DESTROY)//我选择用 ON_DESTROY 来定义资源释放 private void finish(){ //释放资源 } }
FyListen
工具使用入口类:
发布者可以通过FyListen进行注册发布,表示自己可以被别人监听由 LifecycleTrace 注解的生命周期
public class LifecyclePublisherImpl implements LifecyclePublisher{
public LifecyclePublisherImpl(){
FyListen.registerPublisher(this);
}
@LifecycleTrace(Status.ON_DESTROY)
private void finished(){
//...
}
}
使用场景:我使用 RxJava 进行异步数据请求,由于使用了匿名内部类,持有了外部类,例如Activity / Fragment 的引用,如果我再 Activity/Fragment 退出的时候不对异步操作进行关闭,匿名内部类仍然存活,就长时间持有外部类的引用,由此造成内存泄漏。我们通过下面的方法,RxJava+FyListen2,来处理内存泄漏:
如果不使用生命周期监听器,而本身onDestroy()中的业务代码就很多,未来就会不好维护(比如可能要找好久才发现:“哦,我忘记在这个Activity的onDestroy()里面加上 compositeDisposable.dispose()了”)
public class MainActivity extends AppCompatActivity implements LifcyclePublisher{
CompositeDisposable compositeDisposable;
@LifecycleTrace(Status.ON_DESTROY)
@Override
protected void onDestroy(){
super.onDestroy();
//如果不使用生命周期监听器,则要在这里手动添加:
//compositeDisposable.dispose();
}
@Override
proetected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Observable.create(emitter->{//匿名内部类
//模拟请求数据
Thread.sleep(15000);//耗时15s
emitter.onNext("user_info");
}).compose(CommonTransformer.listen(this))
.compose(new SchedulerTransformer<>())//线程切换
.subscribe(new Consumer<Object>(){
@Override
public void accept(Object o) throws Exception{
//拿到请求数据
//更新UI
}
})
}
//监听生命周期,进行dispose()
private static class CommonTransformer<T> implements Listener,ObservableTransformer<T,T>{
final CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override
public void onDestroy(LifecyclePublisher p){
if (!compositeDisposable.isDisposed()){
compositeDisposable.dispose();
}
}
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
compositeDisposable.add(disposable);
}
});
}
public static <T> CommonTransformer<T> listen(LifecyclePublisher lifecyclePublisher){
CommonTransformer<T> transformer = new CommonTransformer<>();
//第一个参数为要监听生命周期的目标,第二个参数为监听者
FyListen.registerListenerAnyway(lifecyclePublisher,transformer);
return transformer;
}
}
//线程切换的 transformer
private static class SchedulerTransformer<T> implements ObservableTransformer<T,T>{
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
}
}
- 只能注解在方法上,注解的方法将被 FyListen 认为是生命周期的回调。
- 传入的参数为:注解的方法被你认为是生命周期的哪一步。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LifecycleTrace {
@Status int value();
}
- 所有生命周期的状态标志:
public @interface Status {
public static final int INITIALED = -1;
public static final int ON_ATTACH = 0;
public static final int ON_CREATE =1 ;
public static final int ON_CREATE_VIEW=2;
public static final int ON_START=3;
public static final int ON_RESUME=4;
public static final int ON_PAUSE=5;
public static final int ON_STOP=6;
public static final int ON_DESTROY_VIEW=7;
public static final int ON_DESTROY=8;
public static final int ON_DETACH=9;
}
所有你认为有生命周期的类,都可以实现这个空接口。只有实现了该接口,才能被 FyListen 视为生命周期回调发布者。
public interface LifecyclePublisher {
}
监听者的统一规范,你可以监听到发布者公开的全部生命周期,也可以只监听其中几个生命周期。(由 Java8 接口新特性,不强制要求实现接口中所有方法)
public interface Listener {
default void onAttach(LifecyclePublisher p){}
default void onCreate(LifecyclePublisher p){}
default void onCreateView(LifecyclePublisher p){}
default void onStart(LifecyclePublisher p){}
default void onResume(LifecyclePublisher p){}
default void onPause(LifecyclePublisher p){}
default void onStop(LifecyclePublisher p){}
default void onDestroy(LifecyclePublisher p){}
default void onDestroyView(LifecyclePublisher p){}
default void onDetach(LifecyclePublisher p){}
//发布者不存在或者死亡,必须重写
void onError(LifecyclePublisher p,String error);
}
注册了生命周期发布能力的类,可以在对象存活期间,对监听者发布自己的生命周期。你可以在对象的任何生命时机进行注册。刚注册时,发布者状态为 Status.INITIALED
,表示还未发布任何生命周期。
-
通过
FyListen.registerPublisher(LifecyclePublisher o)
主动进行注册:Activity 作为生命周期发布者进行注册:
public MainActivity extends Activity implements LifecyclePublisher{ @Override protected void onCreate(Bundle savedInstanceState){ FyListen.registerPublisher(this); } }
public MediaPlayer implements LifecyclePublisher{ //我将MediaPlayer在初始化的时候就进行了发布者注册 public MediaPlayer(){ FyListen.registerPublisher(this); } }
-
由监听者通过
FyListen.registerListenerAnyway(LifecyclePublisher target,Listener listener)
强制对未注册的生命周期发布者进行注册,并将监听者自己绑定在其订阅者列表中。这样做的目的是,为确保防止内存泄漏,使用FyListen.registerListenerAnyway()
监听注册时,强制要求发布者也要注册,避免程序员由于疏忽,忘记将Activivity进行生命周期发布注册。//RxJava的一个工具类,可能你会把它放在项目的 utils.rxutils 文件夹下。它用于监听生命周期,进行dispose() private class CommonTransformer<T> implements Listener,ObservableTransformer<T,T>{ final CompositeDisposable compositeDisposable = new CompositeDisposable(); @Override public void onDestroy(LifecyclePublisher p){ if (!compositeDisposable.isDisposed()){ compositeDisposable.dispose(); } } @Override public ObservableSource<T> apply(Observable<T> upstream) { return upstream.doOnSubscribe(new Consumer<Disposable>() { @Override public void accept(Disposable disposable) throws Exception { compositeDisposable.add(disposable); } }); } public static <T> CommonTransformer<T> listen(LifecyclePublisher lifecyclePublisher){ CommonTransformer<T> transformer = new CommonTransformer<>(); //第一个参数为要监听生命周期的目标,第二个参数为监听者 //为确保防止内存泄漏,使用registerListenerAnyway进行注册,避免程序员由于疏忽,忘记将Activivity进行生命周期发布注册 FyListen.registerListenerAnyway(lifecyclePublisher,transformer); return transformer; } }
-
通过修改
LifecycleTraceAspect
中的needRegisterByDefault=true
让发布者自主进行生命周期发布注册。public class LifecycleTraceAspect { //该功能默认关闭 public static final boolean needRegisterByDefault = false; //... }
这里需要注意的是:
- 实现了 LifecyclePublisher 接口的类才能够发布生命周期消息
- 即使实现了接口,如果发布者没有任何一个方法被注解
LifecycleTrace
标记为生命周期方法,它从始至终不会因为(3)的设置而进行发布注册。
你可以通过FyListen.unregisterPublisher()
来取消注册,并通知其下所有监听者"我不在对外发布我的生命周期",而后主动放弃所有listener的引用。
public static boolean unregisterPublisher(LifecyclePublisher o) {}
实现了 Listener 接口的类,才可以进行生命周期监听注册,成为一个生命周期的监听者,一个监听者可以监听多个生命周期发布者。
-
使用
FyListen.registerListenerSafely()
进行监听注册。这是个安全模式,如果想监听的生命周期发布者没有进行发布注册,你将监听注册失败,并在onError()
回调中收到错误信息。该方法有两个重载形式:1)添加监听的时候,就获知发布者当前的生命周期状态,即获知发布者最近发布了自己的哪一个生命周期(通过回调获知):
public static void registerListenerSafely(LifecyclePublisher target, Listener listener,boolean wantCurrentStatusCallback) {}
2)添加监听的时候,不需要获知发布者当前的生命周期状态:
public static void registerListenerSafely(LifecyclePublisher target,Listener listener){ FyListen.registerListenerSafely(target,listener,false); }
-
使用
FyListen.registerListenerAnyway()
进行监听注册。如果发布者还没有进行生命周期注册发布,这个方法将会强制将该发布者进行发布注册。如果发布者已经注册过,将和FyListen.registerListenerSafely()
的效果一致。如果注册失败,会在onError
回调中收到错误信息。该方法也有两个重载形式:添加监听的时候,就获知发布者当前的生命周期状态,与safely方式的区别是,如果发布者是强制注册的,获知的生命周期状态将是
INITIALEd
,因为它之前没有发布过任何生命周期通知。public static void registerListenerAnyway(LifecyclePublisher taget,Listener listener){ FyListen.registerListenerAnyway(taget, listener,false); } public static void registerListenerAnyway(LifecyclePublisher target,Listener listener,boolean wantCurrentStatusCallback){}
监听者可以监听多个发布者,也需要取消对其中某一些发布者生命周期的监听,通过FyListen.unregisterListener(LifecyclePublisher target,Listener listener)
来取消对某一个发布者生命周期的监听:
public static void unregisterListener(LifecyclePublisher target,Listener listener){}
监听者本身可能也是一个短声明周期对象,而 FyListen 强引用着自己,为了避免自身的内存泄漏,需要在自己退出之前,通过FyListen.unregisterListenerAll(Listener listener)
取消所有的监听。
public static void unregisterListenerAll(@NonNull Listener listener){}
如果你忽略了监听解绑,也没关系,只要你监听的全都不是长生命/长时间不会被回收的对象,在它们全都被释放的时候,监听者的引用也会被释放。
FyListen中还提供了一个发布者使用的方法sendLifecycleCallback()
,可以通过代码主动调用,来发布生命周期消息。这个方法很危险,不建议外部随意使用,需要注意的是:
- 这是个静态方法,没有要求调用者一定是发布者本身
- 只有当传入的发布者引用是注册过的,才会发布生命周期消息
- 它是危险的!因为发布生命周期消息会更新 FyListen 对发布者当前生命周期的记录!所以需要使用者慎重使用。
- 好在:发布者如果进入到了新的生命周期,主动发布生命周期通知,还是会更新当前生命周期状态。虽然发布的消息可以是假的,但是并不影响到发布者本身,只影响到监听者们。
这是个多态带来的问题。不过,使用 FyListen 时,由于 Listener 的方法都需要传递 LifecyclePublisher 作为参数,即使实现类有同名方法,也由于方法重载的特性,对你的使用不造成影响。
如果你需要解决这种问题,你可以通过桥接**,通过内部类来处理方法重名问题:
public class Test{
interface A{
boolean f();
}
interface B{
void f();
}
//由于方法无法A和B的f()方法在Son中无法重载,无法编译通过
//public static class Son implement A,B{}
//使用桥接:
//1.外部类初始化的同时也初始化内部类
//2.内部类被初始化时获得对外部类的引用
//3.内部类的重名方法调用到外部类的桥接方法
//4.桥接用的内部类不应当独立于外部类,所以使用非静态内部类
//5.如果要写成静态内部类,最好将其设为private保持“只属于外部类"
//
//特性:内部类持有外部类时,可以访问到外部类的private变量,也就是Inner和Outter都可以操作 Outter中的资源!从而实现重名不可重载方法的处理。
public static class Outer implements A{
public final Inner inner;
public Outer(){
inner = new Inner(Outer.this);
}
public boolean f(){
System.out.println("f() called by A ref");
}
//如果SonInner可以单独存在,可以定为静态内部类
//如果SonInner必须依存外部类,就需要定为非静态内部类
private class Inner implements B{
private final Outer outer;
private Inner(Outer outer){
this.outer= outer;
}
public void f(){
System.out.println("f() called by B ref")
}
}
}
}
使用桥接:
- 外部类初始化的同时也初始化内部类
- 内部类被初始化时获得对外部类的引用
- 内部类的重名方法调用到外部类的桥接方法
- 桥接用的内部类不应当独立于外部类,所以使用非静态内部类
- 如果要写成静态内部类,最好将其设为private保持“只属于外部类”
特性:内部类持有外部类时,可以访问到外部类的private变量,也就是Inner和Outer都可以操作 Outer中的资源!从而实现重名不可重载方法的处理。
既然提到了这个,就说一下如何实现多继承,和上面的实现思路是一样的:
public static abstract class Father1{
abstract void f();
}
public static abstract class Father2{
abstract void f();
}
public static class Outer extends Father1{
private int resource = 0;
public final Inner inner;
public Outer() {
this.inner = new Inner(Outer.this);
}
@Override
void f() {
resource++;
}
private static class Inner extends Father2{
private final Outer outer;
private Inner(Outer outer) {
this.outer = outer;
}
@Override
void f() {
outer.resource++;
}
}
}