江西住房和城乡建设网站,报告模板,百度云登录入口,做冲压件加工有什么好网站FyListen 在 MVP 中的内存优化表现
本文只是分享个人开源框架的内存优化测试#xff0c;你可以直接跳到最后#xff0c;参考内存泄漏的分析过程#xff01;
项目地址#xff1a; https://github.com/StudyNoteOfTu/fylisten2-alpha1
由于使用到 AOP#xff0c;所以直接…FyListen 在 MVP 中的内存优化表现
本文只是分享个人开源框架的内存优化测试你可以直接跳到最后参考内存泄漏的分析过程
项目地址 https://github.com/StudyNoteOfTu/fylisten2-alpha1
由于使用到 AOP所以直接导入依赖是是用不了的。需要将该模块和主模块进行合并并做AspectJ的配置。
FyListen是否能够应用到项目中需要测试。本文测试的结果为【可以使用而且符合开闭原则】
本次实验分析我选择分析它在MVP架构中的内存优化表现我将通过以下场景过程进行模拟 Activity 实现了 LifecyclePublisher 接口并将 onDestroy() 生命周期暴露出去 Presenter 层使用 RxJava 来控制 Model层和View层 Presenter 层的 Rxjava 利用 FyListen 对传入的 View层进行生命周期监听 compose()操作符发现生命周期结束立马执行 dispose()停止上游的异步任务最快速度释放资源 View的任务是从网络上下载图片并保存到本地 Model层1retrofit从网络下载图片模拟卡顿10sModel层2引用Context进行本地文件写入 RxJava 极大地化简了 Model 层代码 如果让Model层自行做异步再通过接口回调则需要在Model层的接口中加上一个类似 public interface IDownloadModel{public interface OnDownloadListener{void onFinish(String path);}
}由于使用RxJava自动切换到子线程执行所以完全可以让 Model 层的方法进行同步的返回只需要由 Presenter 层将最后的结果回调给 View 层如果View层没有销毁的话/dispose()没有被调用
同时为了尽可能地简化代码以表现出 FyListen 生命周期监听的优越性我们不做
不在View层对Presenter进行解绑 - 减少了 View 层 onDestroy() 中的代码量不在Presenter层对FyListen做发布者释放 和 unregisterPublisher() 方法的设计初衷保持一致这个方法只用来发布者主动要求不再发布不代表发布者生命周期结束
1. 代码结构 Model层 FileStoreModelImpl - 本地文件写入工具负责将下载图片的byte[]写入本地文件 - 模拟本地io public class FileStoreModelImpl implements IFileStoreModel {FileOutputStream fos;/*** 取消文件存储或者读取的工作*/Overridepublic void cancel() {try {if (fos ! null) {fos.close();}} catch (IOException e) {e.printStackTrace();}}/*** 开始文件存储*/Overridepublic String storeFile(Context context, String filename, String filepath, byte[] filebytes) {try {//由于读写暂时没用到context我们使用是GCROOT的局部变量来对context作一个强引用Context c context;File file new File(filepathFile.separatorfilename);if (file.exists()){file.delete();}File parentFile file.getParentFile();if (!parentFile.exists()){parentFile.mkdirs();}//本地文件写入file.createNewFile();fos new FileOutputStream(file);fos.write(filebytes);fos.flush();fos.close();} catch (IOException e) {e.printStackTrace();}return filepathFile.separatorfilename;}
} PictureDownloadImpl - 网络图片下载工具 - 模拟耗时的网络请求 public class PictureDownloadImpl implements IPictureDownloadModel {//将 Observable交出去让presenter来调度该异步任务Overridepublic ObservableResponseBody download(String url) {//使用retrofit进行下载Retrofit retrofit new Retrofit.Builder().baseUrl(https://img-blog.csdnimg.cn/).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build();IPictureDownloadService downloadServiceProxy retrofit.create(IPictureDownloadService.class);//模拟网络卡顿等待时间长达1000秒try {Thread.sleep(1000_000);} catch (InterruptedException e) {e.printStackTrace();}return downloadServiceProxy.download(url);}//由于将异步任务交出有 Presenter层来管理所以observable任务的停止有rxjava来控制这里不作实现Overridepublic void cancel() {}
}Presenter层 BasePresenter - 绑定的同时开启生命周期监听当LifecyclePublisher生命周期来到 onDestroy()的时候进行解绑 public class BasePresenterT implements Listener {//所有presenter都需要传入IView 所以我们抽出Base层 并且用弱引用来绑定IView//解决内存泄漏问题我们暂时采用弱引用方式// 还是得等到内存不足的时候才让gc来收protected WeakReferenceT mViewRef;//根除内存泄漏 --- 绑定//进行绑定public void attachView(T view){mViewRef new WeakReferenceT(view);//进行监听监听目标退出时自动回调并进行解绑if (view instanceof LifecyclePublisher){//如果是个生命周期发布者就监听它//如果项目中并没有使用LifecyclePublisher即view没有实现LifecyclePublisher就不会进到这里FyListen.registerListenerAnyway((LifecyclePublisher)view,this);}}//进行解绑public void detachView(){//手动地打断弱引用而非让gc来mViewRef.clear();}/*** 监听到onDestroy()的时候自动解绑* param p 生命周期发布者*/Overridepublic void onDestroy(LifecyclePublisher p) {detachView();}Overridepublic void onError(LifecyclePublisher p, String error) {//一般不会使用到这是LifecyclePublisher绑定出问题时候的回调}
}PictureDownloadPresenter - 具体表现层负责控制Model层进行图片下载与保存并将结果回调给View层。 Model层分别为下载器和文件存储工具将任务细化解耦便于后期维护使用 RxJava 按顺序执行下载和存储这两个步骤如果任务成功完成将回调如果任务中断则直接结束方法 public class PictureDownloadPresenterT extends IPictureDownloadView extends BasePresenterT {//下载器IPictureDownloadModel pictureDownloadModel new PictureDownloadImpl();//文件工具-存储byte[]或者stream到本地再次之前请自行处理权限申请IFileStoreModel fileModel new FileStoreModelImpl();//presenter发起电影下载以及文件存储工作任务分工明确使用两个model//rxjava将两个先后执行的异步任务整合起来得到最终结果后回到主线程回调UIpublic void downloadPicture(String url) {if (mViewRef.get() ! null) {mViewRef.get().beginDownload();if (pictureDownloadModel ! null fileModel ! null) {//开启监听以及线程切换//1.执行下载任务//2.下载任务执行之后保存文件//3.主线程回调任务结果// compose(CommonTransformer.listen(mViewRef.get())):// 传入的mViewRef.get()如果是LifecyclePublisher就会进行生命周期监听注册//当view的onDestroy()被监听到时会自动调用dispose()停止rxjava的工作//如果不是就自行根据原项目结构进行内存泄漏处理Disposable subscribe Observable.create((ObservableOnSubscribeString) emitter - emitter.onNext(url)).flatMap(new FunctionString, ObservableString() {Overridepublic ObservableString apply(String s) throws Exception {return pictureDownloadModel.download(s).map(new FunctionResponseBody, String() {Overridepublic String apply(ResponseBody responseBody) throws Exception {//获取返回结果中的文件通过filemodel进行存储分工明确byte[] bytes responseBody.bytes();//这些model都同步执行任务提交异步交给rxjava来完成String filePath Environment.getExternalStorageDirectory().getPath();String fileName test.jpg;return fileModel.storeFile((Context) mViewRef.get(), fileName, filePath, bytes);}});}}).compose(CommonTransformer.listen(mViewRef.get())).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new ConsumerString() {Overridepublic void accept(String s) throws Exception {if (mViewRef.get() ! null) {//主线程回调下载结果返回文件下载路径mViewRef.get().pictureDownloaded(s);}}});}}}}RxJava使用 compose()操作符来插入生命周期监听 public class CommonTransformerT implements Listener, ObservableTransformerT,T {CompositeDisposable compositeDisposable new CompositeDisposable();//rxjava监听到生命周期发布者发起了 onDestroy//就会回调到这里将rxjava的任务取消Overridepublic void onDestroy(LifecyclePublisher p){Log.e(TAG,publisher ondestroyed);if (!compositeDisposable.isDisposed()){compositeDisposable.dispose();}}Overridepublic void onError(LifecyclePublisher p, String error) {Log.e(TAG,publisher error);}Overridepublic ObservableSourceT apply(ObservableT upstream) {return upstream.doOnSubscribe(new ConsumerDisposable() {Overridepublic void accept(Disposable disposable) throws Exception {compositeDisposable.add(disposable);}});}public static T CommonTransformerT listen(Object o){CommonTransformerT transformer new CommonTransformer();if (o instanceof LifecyclePublisher){//如果object是lifecyclePublisher就注册否则什么也不做Log.e(TAG,listen to lifecycle publisher);FyListen.registerListenerAnyway((LifecyclePublisher) o,transformer);}return transformer;}
}View层
常见MVP架构通常会使用BaseActivity来进行Presenter的绑定和解绑由于 FyListen 符合设计思想对拓展开放对修改关闭。这表现在
只需要在原项目的BaseActivity中实现LifecyclePublisher这个空接口在onDestry()上填上LifecycleTrace(Status.ON_DESTROY) 注解
对原本项目几乎没有任何修改 BaseActivity - MVP架构下的Activity基类 public abstract class BaseLifecyclePublisherActivityV, T extends BasePresenterV extends AppCompatActivity implements LifecyclePublisher {//表示层的引用public T mPresenter;Overrideprotected void onCreate(Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//由于监听者使用的是 registerListenerAnyway()// 所以甚至不需要发布者主动进行注册// 更加符合了对修改关闭对拓展开放的设计原则
// FyListen.registerPublisher(this);mPresenter createPresenter();mPresenter.attachView((V)this);}protected abstract T createPresenter();//对外暴露onDestroy()的生命周期回调LifecycleTrace(Status.ON_DESTROY)Overrideprotected void onDestroy() {super.onDestroy();//没有FyListen的时候未解决内存泄漏//你需要手动在这里解绑//mPresenter.detachView();}}MainActivity - 下载图片并展示的活动 一个View对应一个Presenter一个Presenter可以根据业务数据要求找到不同的Model层进行数据获取并将获取到的数据处理完后回调View层更新UI的接口。通过RxJava我们不再需要在View层写类似 runOnUIThread()的代码。 public class MainActivity extends BaseLifecyclePublisherActivityIPictureDownloadView, PictureDownloadPresenterIPictureDownloadView implements IPictureDownloadView,LifecyclePublisher {Button btnDownload;ImageView ivPicture;//图片地址String picUrl eb102bfc59ae457798bbf13c381a4af1.jpeg;Overrideprotected void onCreate(Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initViews();}//绑定表示层//如果未来表示层逻辑需要替换在这里替换需要的表示层即可Overrideprotected PictureDownloadPresenterIPictureDownloadView createPresenter() {return new PictureDownloadPresenter();}private void initViews() {btnDownload findViewById(R.id.btn_download);btnDownload.setOnClickListener(v-{mPresenter.downloadPicture(picUrl);});ivPicture findViewById(R.id.imageview);}//----------UI 回调---------------Overridepublic void pictureDownloaded(String filepath) {//假设我们下载了一个图片我们把它展现出来Glide.with(this).load(filepath).into(ivPicture);}Overridepublic void beginDownload() {//通知用户开始下载了Toast.makeText(this, 开始下载图片并存储到本地, Toast.LENGTH_SHORT).show();}
}2. 测试开始 - 测试成功
我们从LauncherActivity进入到MainActivity后点击按钮进行下载图片如果一直等待图片将会展示出来这是正常运行的情况 为了测试在图片下载过程中点击返回键Activity是否能够正常被释放我将图片下载的延时设置为100s我们来做以下操作
点击下载图片点击返回键查看Profiler中内存情况 点击下载图片时候的内存情况点击返回后的内存情况主动GC后的内存情况
1. 点击下载图片时候的内存情况
由于MainActivity在前台运行所以自然没有被释放 2. 在图片还在下载过程中模拟1000秒等待点击返回
我们观察到虽然点了返回但是MainActivity仍然没有被释放 我们注意到如果一个对象只被弱引用所持有的也不会立即被释放需要等到GC到来的时候才被释放。我们来看一下 FyListen 是否成功将其他引用全部断开 可以看到现在剩下唯一的GCROOT只有 WeakHashMap 对它持有引用而且是弱引用。我们可以等待GC的到来也可以通过下面这个按钮让GC提前到来我在这里卡了很久一直在想为什么WeakHashMap没有释放对MainActivity的引用后来才想起来这只是个弱引用只是GC还没来而已 通知GC清理战场后图片还没下载好但是我们惊喜的发现Activity被释放了 本人又进行了额外强度大一点的测试连续开关MainActivity多次导致内存中有很多的MainActivity实例通知GC时全都被回收过程同上不贴图了。
3. 为什么会这样呢
WeakHashMap的原理我后续会另外整理一份文档和大家分享。我们只需要知道WeakHashMap对key是个弱引用key被回收后value会在 get()、put()等方法中被主动置空清除。
我们主要来分析 FyListen 作为核心角色在这里面发挥的作用具体使用参考FyListen 的接口文档
1. 代码优化
FyListen 可以被用在原本就构建好的MVP工程你只需要做两件事
让BaseActivity或者Activity实现 LifecyclePublisher 接口在Presenter层实现一个监听者这里的可能性很多我上面是在一个RxJava的项目中的表现但这并不影响没有使用 RxJava 框架的项目你只需要做到实现一个Listener接口的实例让它监听View层的LifecyclePublisher实例 例如本文分析时通过Listener实例通过dispose()让RxJava的异步任务停止从而释放引用你也可以在Presenter层任意一个合适的地方对View层注册监听。如果你的项目比较特殊View和Presenter是多对多FyListen的回调中也明确了是谁发来的生命周期通知。 你不再需要在 BaseActivity 的 onDestroy() 中对 Presenter层进行解绑所有解绑工作都放在了 Presenter层的Listener实例的 onDestroy(LifecyclePublisher p)生命周期回调之中
2. 优化原理
首先FyListen的监听者可以主动要求实现了 LifecyclePublisher接口的发布者进行生命周期发布注册这使得你不用在 BaseActivity 的onCreate() 中加入registerPublisher()这串代码进行发布者注册。对修改关闭设计
//监听者在注册监听时只需要有发布者的引用就可以让发布者被动的去注册生命周期发布。
//如果这个发布者此时已经被释放了将不会注册成功并回调监听者的 onError()方法
FyListen.registerListenerAnyway((LifecyclePublisher) o,listener);其次FyListen由于监听到了LifecyclePublisher的onDestroy()生命周期会通知所有仍然存活着的监听者。监听者们可以通 onDestroy(LifecyclePublisher p)的回调判断是否需要退出如果要退出执行一些任务关闭的方法
例如本例中监听到onDestroy主动调用dispose()关闭 RxJava 的工作
public class CommonTransformerT implements Listener, ObservableTransformerT,T {//作为Listener发布者通知 ON_DESTROY 的时候回调到这个方法Overridepublic void onDestroy(LifecyclePublisher p){//通过 dispose() 来关闭rxjava的工作if (!compositeDisposable.isDisposed()){compositeDisposable.dispose();}}
}除了关闭执行的任务还需要在FyListen管理着中将View层的引用断开但是这个步骤由WeakHashMap帮我们完成了因为本身就是弱引用。
需要注意的是本项目中认为Model层是长生命周期View层是短生命周期所以不需要处理View对Model层的引用准确来说是不需要处理 FyListen的map对Model层的强引用Model层的引用会在View层释放之后的某一个时间被回收。如果有性能要求你也可以在监听者的onDestroy()中进行解绑监听提前释放引用。如果你的项目中View层是长生命周期而Model层工具是短声明周期请注意调用 unregisterListener(LifecyclePublisher target, Listener listener)将Model层解放出来使之可以被回收