Android进阶课学习收获(29~30)
第29讲:MVP中presenter生命周期管理
我们经常在Android MVP架构中的Presenter层做一些耗时操作,比如请求网络数据等。然后根据请求后的结果刷新View。但是如果按返回结束Activity,而Presenter仍然在执行耗时操作,那么就有可能造成内存泄漏,严重时甚至会造成程序崩溃,因为Presenter中的View已经变为null。为了解决这个问题,我们需要将Activity的某些生命周期方法与Presenter保持一致。
Lifecycle绑定Presenter生命周期
Lifecycle的使用很简单,Activity通过继承AppCompatActivity会自动继承来自父类ComponentActivity的方法getLifecycle。具体使用如图所示:
public class LoginActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
getLifecycle().addObserver(
@Override
public void onStateChanged(LifecycleOwner source,
Lifecycle.Event event){
Log.e("TAG","event is" + event.name());
}
);
}
}
onStateChanged方法会在Activity的生命周期发生变化时被触发,比如打开LoginActivity时就会显示ON_CREATE、ON_START、ON_RESUME。
LifeCycle还提供了注解的方式供使用,因此我们可以很容易地创建一个接口IPresenter类,在这个类中声明对各种Activity生命周期的回调,如图所示:
public interface IPresenter extends LifecycleObserver{
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
void onCreate(@NonNull LifecycleOwner owner);
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume(@NonNull LifecycleOwner owner);
...
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void onDestroy(@NonNull LifecycleOwner owner);
}
IPresenter接口通过注解的方式,将Activity的生命周期绑定到相应的方法上,我们只需要在BasePresenter中实现上述方法,并在方法中做数据绑定和取消的操作即可。如下图:
注意:上述代码存在一点问题,使用了Android中的Log来打印日志信息,严格来说,Presenter层应该禁止出现任何Android中的类,这里只是为了演示效果,才直接输出日志。
接下来,只需要再修改LoginActivity,将BasePresenter注册到LifeCycle中即可,如下:
public class LoginActivity extends AppCompatActivity{
private BasePresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
presenter = new BasePresenter();
getLifecycle().addObserver(presenter);
}
}
在LoginActivity方法中有login方法,此方法会执行BasePresenter中的login方法,如下所示:
public class LoginActivity extends AppCompatActivity{
...
public void login(View view){
presenter.login();
}
}
public class BasePresenter implements IPresenter{
private ThreadPoolExecutor threadPoolExecutor;
@Override
public void onCreate(){
threadPoolExecutor = new
ThreadPoolExecutor(1,1,1,TimeUnit.MINUTES, new
LinkedBlockingDeque<Runnable>(3));
}
public void login(){
threadPoolExecutor.execute(new Runnable(){
@Override
public void run(){
try{
for(int i = 0;i<20;i++){
Thread.sleep(1000);
}
}catch (InterruptException e){}
}
});
}
}
在BasePresenter的login方法中,我们模拟了一段耗时操作,如果在Activity onDestroy时,BasePresenter里还有没处理完的耗时操作,则会造成内存泄漏,解决办法就是在BasePresenter的onDestroy方法中停止正在执行的耗时操作,如下:
public class BasePresenter implements IPresenter{
private ThreadPoolExecutor threadPoolExecutor;
@Override
public void onDestroy(){
if(threadPoolExecutor != null){
threadPoolExecutor.shutdownNow();
}
}
}
合理使用Presenter生命周期
并不是所有的Activity的生命周期都需要通知Presenter。Presenter应遵循单一职责原则,主要用来刷新View。
总结
本节课主要对MVP架构中Presenter层的使用做了2点优化介绍:
- 如何支持Presenter的生命周期,使其在Activity被销毁时也能取消相应的耗时请求;
- 合理使用Presenter的声明周期,Activity中所有的方法都委托给Presenter来处理是不合理的,这样会造成Presenter层极其庞大,也难以维护,有时也会违反单一职责原则。
第30讲:如何设计一个比较合理的LogUtil类?
我们在项目中经常会打印各种Log信息,用来查看程序运行中的详细情况。因此设计一个合理的LogUtils也成为了一个好的程序员的必选条件之一。
设置Debug开关
我们需要设置一个开关来控制是否打印Log日志,只有在Debug版本才会打开此开关,如图所示:
public class LogUtils{
private static boolean sLogSwitch = BuildConfig.DEBUG;
public static d(String tag , String msg){
if(sLogSwitch){
Log.d(tag , msg);
}
}
}
但使用BuildConfig.DEBUG这个变量具有一定的局限性。比如现场突然发现一个异常现象,而我们需要现场抓取异常日志加以分析。因为是release版本,所以不会有任何log信息被打印。因此这个开关的设置最好具有一定的灵活性,比如可以再加一层System Property的设置,如图所示:
//获取系统属性
String log_switch = System.getProperty("persist.danny.log", "null");
Log.e("TAG", "before Log_switch is" + log_switch);
//设置系统属性
System.setProperty("persist.danny.log" , "true");
log_switch = System.getProperty("persist.danny.log", "null");
Log.e("TAG", "after Log_switch is" + log_switch);
使用System Property的好处是一旦设置之后,即便APP 重启,System Property中的变量依旧是设置之后的值,与Android中的SharedPreference非常相似。开发者只要定义好通过何种方式将这种属性打开即可,可以仿照Android系统设置中的”开发者选项“来实现,当用户快速连续点击某item时,才将此属性打开。
另外,我们还可以通过Proguard在打包阶段清楚某些Log日志、打印代码,规则如下:
-assumenosideeffects class android.util.Log {
public static *** d(...);
}
设置log日志本地保存
有时候我们需要将部分log日志以文件形式保存在手机磁盘中,因此我们还需要设置开关,控制日志是打印在控制台还是保存到文件中。
涉及文件的写操作,所以最好在子线程中完成日志的保存。因此在LogUtils中可以使用线程池控制子线程完成日志保存。
Config文件统一配置
如果LogUtils中的开关较多,在加上还有其他配置项,比如日志保存为文件的路径等。这种情况可以使用一个全局的Config来配置LogUtils中所有的配置项。
特殊格式装换
我们经常会处理一些特殊格式的数据,比如JSON、XML。为了打印这部分数据,还需要在LogUtils类中做一些格式转换的操作:
借助三方库打印log
XLog是比较常用的打印日志开源库,GitHub地址参考XLog GitHub。XLog基本囊括了我们上文介绍的所有功能:
-
全局配置或基于单条日志的配置;
-
支持打印任意对象以及可自定义的对象格式化器;
-
支持打印数组;
-
支持打印无限长的日志(没有 4K 字符的限制);
-
XML 和 JSON 格式化输出;
-
线程信息(线程名等,可自定义);
-
调用栈信息(可配置的调用栈深度,调用栈信息包括类名、方法名、文件名和行号);
-
支持日志拦截器;
-
保存日志文件(文件名和自动备份策略可灵活配置)。
XLog使用比较简单,先调用Init方法进行初始化,最好在Application中。
除了打印日志和方法,XLog还能打印线程以及调用栈信息。
总结:LogUtils应该对外提供相应的开关,用来设置是否需要打印日志,以及打印日志的通道,如果是日志保存到文件等耗时操作,还应该在子线程中完成。
本文地址:https://blog.csdn.net/qq_21924213/article/details/108663766