欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Android进阶课学习收获(29~30)

程序员文章站 2022-06-21 19:17:53
第29讲:MVP中presenter生命周期管理 我们经常在Android MVP架构中的Presenter层做一些耗时操作,比如请求网络数据等。然后根据请求后的结果刷新View。但是如果按返回结束Activity,而Presenter仍然在执行耗时操作,那么就有可能造成内存泄漏,严重时甚至会造成程序崩溃,因为Presenter中的View已经变为null。为了解决这个问题,我们需要将Activity的某些生命周期方法与Presenter保持一致。Lifecycle绑定Presente......

第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进阶课学习收获(29~30)

注意:上述代码存在一点问题,使用了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点优化介绍:

  1. 如何支持Presenter的生命周期,使其在Activity被销毁时也能取消相应的耗时请求;
  2. 合理使用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

相关标签: 学习 读书笔记