当前位置:   article > 正文

Android工程师进阶第九课 Android构建、MVP架构和日志管理_gradle.kts 打日志

gradle.kts 打日志

第28讲:Android Gradle 构建问题解析

想必我们做 Android App 开发的应该对 gradle 都不太陌生,因为 Android Studio 的帮助,Android 工程师使用 gradle 的门槛不算太高,基本的配置都大同小异,只要在 Android Studio 默认生成的 build.gradle 中稍加修改,都能满足项目要求。但是深入细致的了解 gradle 的基本知识,还是能够帮助我们更优雅地实现项目的配置工作。有些场景 gradle 甚至能帮助我们完成一些业务上的需要。这节课我们就来了解一下 gradle 那些需要掌握的基本知识。

gradle Task

Task(任务)可以理解为 gradle 的执行单元,gradle 通过执行一个个 Task 来完成整个项目构建工作。

自定义 Task

我们可以在 build.gradle 中使用关键字 task 来自定义一个 Task。比如创建 build.gradle 文件,并添加 task,如图所示:

Drawing 0.png

上图中定义了一个简单的 Task A,然后在终端中使用以下命令行执行此 Task,即可看到打印结果,如图所示:

Drawing 1.png

从结果中可以看出,打印日志是在 gradle 的配置(Configure)阶段执行的。gradle 的构建生命周期包含 3 部分:初始化阶段、配置阶段、执行阶段。在 task A 中添加 doFirst 闭包,如下所示:

Drawing 2.png

再次执行 gradle A,打印结果如下所示:

Drawing 3.png

gradle 在运行期会执行所有 task 的配置语句,然后执行指定的 Task。

Task 之间可以存在依赖关系

gradle 中的 Task 可以通过 dependsOn 来指定它依赖另一个 Task,如下所示:

Drawing 4.png

在 build.gradle 中,我新加了一个 Task B,并通过 dependsOn 关键字指定 task B 依赖于 task A。 在命令行中执行“gradle B”,结果如下所示:

Drawing 5.png

可以看出虽然我们只是执行了 task B,但是因为依赖关系的存在,task A 也会被执行。

gradle 会在配置 Configure 阶段,确定依赖关系。对于 Android 项目来说即为执行各个 module 下的 build.gradle 文件,这样各个 build.gradle 文件中的 task 的依赖关系就被确认下来了,而这个依赖关系的确定就是在 Configuration 阶段。

gradle 自定义方法

我们可以在 build.gradle 中使用 def 关键字,自定义方法,比如以下代码中自定义了 getDate 方法,并在 task 中使用此方法。

Drawing 6.png

通过 gradle 命令执行上述 task,结果如下:

Drawing 7.png

系统预置 task

自定义 task 时,还可以使用系统提供的各种显示 task 来完成相应的任务。具体就是使用关键字 type 来指定使用的是哪一个 task。

比如我在当前目录下新建 2 个文件夹:src 和 dst。目录如下:

Drawing 8.png

然后在 src 中创建文件 Demo.java,代码如下

Drawing 9.png

最后当前路径结构如下:

Drawing 10.png

修改 build.gradle,新添加一个 task copy 如下:

Drawing 11.png

然后在命令行中执行“gradle copy”,运行结束后,重新查看当前目录结构如下:

Drawing 12.png

可以看出 Demo.java 被拷贝了一份到 dst 目录中。

除了 Copy 之外,还有很多其他显示的 task 可用,比如我们可以通过自定义 task 实现编译 Demo.java 并将编译后的 .class 输出到某一特定路径,具体实现如下所示:

Drawing 13.png

解释说明:

  1. 通过 type: JavaCompile 指定是编译 Java 类的 task;

  2. source 指定需要编译类的文件路径;

  3. include 指定需要编译哪一个 Java 类;

  4. destinationDir 指定编译之后,生成 .class 文件的保存路径。

最后命令行中执行“gradle compile”,查看目录结构如下:

Drawing 14.png

gradle project

在 Android 中每个 module 就对应着一个 project,gradle 在编译时期会为每一个 project 创建一个 Project 对象用来构建项目。这一过程是在初始化阶段,通过解析 settings.gradle 中的配置来创建相应的 Project。

Drawing 15.png

上图 settings.gradle 中导入了 3 个 project,但是实际上还会有一个根 project,使用 ./gradlew project 查看,如下所示:

Drawing 16.png

我们可以在根 project 中统筹管理所有的子 project,具体在 LagouGradle 路径下的 build.gradle 中进行设置,如下所示:

Drawing 17.png

这样写的好处是项目中所有 module 的配置都统一写在一个地方,统筹管理。比如经常会在主项目的 build.gradle 中添加包过滤,解决依赖冲突,如下所示:

Drawing 18.png

buildSrc 统筹依赖管理

随着项目越来越大,工程中的 module 越来越多,依赖的三方库也越来越多。一般情况下我们会在一个集中的地方统一管理这些三方库的版本。比如像谷歌官方推荐的使用 ext 变量,在根 module 的 build.gradle 中,使用 ext 集中声明各种三方库的版本,如下所示:

Drawing 19.png

然后在子 module 中,引用这些版本信息。

Drawing 20.png

但是这种写法有点小瑕疵:不支持 AS 的自动补充功能,也无法使用代码自动跟踪,因此可以考虑使用 buildSrc。

buildSrc 是 Android 项目中一个比较特殊的 project,在 buildSrc 中可以编写 Groovy 语言,但是现在谷歌越来也推荐使用 Kotlin 来编写编译语句。

先在根路径下创建目录 buildSrc,结构如下:

Drawing 21.png

注意:这个工程的只能有一个,并且名字必须为 buildSrc。

创建好之后,在 buildSrc 中创建 build.gradle.kts 文件,并添加 Kotlin 插件。

Drawing 22.png

编译工程有可能会报错,如下所示:

Drawing 23.png

只要添加 repositories { jcenter() } 仓库即可。

接下来在 buildSrc 中创建 src/main/java 目录,并在此目录下创建 Dependencies.kt(名字可随意取)。在 Dependencies.kt 中创建两个 object,分别用来管理工程中的版本信息以及依赖库。

Drawing 24.png

我们可以在 Versions 中添加各种项目中可能会引用到的版本。

Drawing 25.png

然后在 Deps 中引用 Versions 中的变量。

Drawing 26.png

最后我们就可以在各个 module 中的 build.gradle 中直接使用 Deps 中的变量用来声明依赖,比如在 app module 的 build.gradle 中添加如下依赖。

Drawing 27.png

上图中分别是使用 buildSrc 前后的对比,并且在使用 Deps 的过程中,studio 会给出自动提示,如下:

image.gif

![在这里插入图片描述](https://img-blog.csdnimg.cn/976ced6e0877447490c7ab56f366cb71.gif#pic_center)

总结

这节课主要介绍了 gradle 构建中的 task 和 project。

task 与大部分的开发者开发是最为紧密的,它是 gradle 构建的基本单元。我们每次编译工程时,Android studio 会在控制台打印出执行的 task 名称,类似下图中的格式。

Drawing 29.png

我们也可以自定义 task 实现相同的构建需求。

project 对应的项目中的 module,每个 module 中包含一个 build.gradle,每个 build.gradle 都会被 gradle 编译成 Project 字节码。我们在 build.gradle 中所写的所有逻辑,实际上最终都会被映射成此 Project 字节码内的实现逻辑。


第29讲:MVP 中 preenter 生命周期的管理

我们经常在 Android MVP 架构中的 Presenter 层做一些耗时操作,比如请求网络数据等。然后根据请求后的结果刷新 View。但是如果按返回结束 Activity,而 Presenter 依然在执行耗时操作,那么就有可能造成内存泄漏,严重时甚至会造成程序崩溃,因为 Presenter 中的 View 已经变为 null。为了解决这个问题,我们需要将 Activity 的某些生命周期方法与 Presenter 保持一致。

Lifecycle 绑定 Presenter 生命周期

LifeCycle 的使用很简单,Activity 通过继承 AppCompatActivity 会自动继承来自父类 ComponentActivity 的方法 getLifeCycle。具体使用如图所示:

Drawing 0.png

onStateChanged 方法会在 Activity 的生命周期发生变化时被触发,比如打开 LoginActivity 时就会显示如下日志:

Drawing 1.png

在 LoginActivity 中按下返回键,则打印如下日志:

Drawing 2.png

LifeCycle 还提供了注解的方式供使用,因此我们可以很容易地创建一个接口 IPresenter 类,在这个接口中声明对各种 Activity 生命周期的回调,如图所示:

Drawing 3.png

上图中 IPresenter 接口通过注解的方式,将 Activity 的生命周期绑定到相应的方法上,我们只要在 BasePresenter 中实现上述方法,并在方法中做数据绑定与取消的操作即可,具体如图所示:

Drawing 4.png

注意:上图中的代码存在一点问题,使用了 Android 中的 Log 来打印日志信息。严格来说在 Presenter 层应该禁止出现任何 Android 中的类,这里为了快速演示效果,所以直接使用 Log 打印日志。

接下来只要再修改 LoginActivity,将 BasePresenter 注册到 LifeCycle 中即可,如图所示:

Drawing 5.png

重新打开 LoginActivity,显示日志结果如图所示:

Drawing 6.png

关闭 LoginActivity,显示日志如图所示:

Drawing 7.png

可以看出当 Activity 执行 onDestroy 时,BasePresenter 的 onDestroy 方法也会被执行。

在 LoginActivity 方法中有 login 方法,此方法会执行 BasePresenter 中的 login 方法,如图所示:

Drawing 8.png

在 BasePresenter 的 login 方法中,模拟执行了一段耗时操作。如果在 Activity onDestroy 时,BasePresenter 还没有处理完耗时操作,则会造成内存泄漏。解决办法就是在 BasePresenter 的 onDestroy 方法中停止正在执行的耗时操作,如图所示:

Drawing 9.png

合理使用 Presenter 生命周期

并不是所有的 Activity 的生命周期都需要通知 Presenter。举一个例子,新增的需求是根据 GPS 定位,展示用户的位置。但是为了节省电量,有可能会在灭屏之后,解绑定 GPS 定位的接收事件。

如果使用 MVP 架构,需要有一个 TrackingActivity 实现 MVP 的接口 TrackingView,并在生命周期方法中调用 presenter 的相应方法,如图所示:

SzZhF94eX4vy46bm.png

TrackingPresenter 是 presenter 层的实现,内部实现了 GPS 定位的监听事件,并分别在 resume 和 stop 方法中绑定和解绑定 GPS。如图所示:

Drawing 11.png

上述写法是常规的 MVP 写法,但是存在 2 个问题:

  1. GpsTracker 实际的控制周期是跟 Activity 有关的,因为亮屏和灭屏事件是在 Activity 中接收的,中间多了一层 Presenter 层其实是多余的。

  2. 从重构的角度看,TrackingPresenter 其实违反了职责单一原则(Single Responsibility),因为 Presenter 层的主要作用是用来刷新 View,但是上述代码中 TrackingPresenter 还负责对 GpsTracker 进行管理。

这种情况下,我们可以将 GpsTracker 初始化在 Activity 中,将 GpsTracker 的绑定与解绑定都在 Activity 中管理。最后将 GpsTracker 传给 TrackingPresenter 执行业务上的逻辑,具体实现如下:

TrackingActivity

Drawing 12.png

TrackingActivity 中对 tracker 进行管理,并且根据 Presenter 层的逻辑处理,回调 showCurrentPosition 方法。

TrackingPresenter

Drawing 13.png

TrackingPresenter 只负责对 GPS 事件的监听,并根据结果刷新 View。

这样 View 层和 Presenter 层的职责单一原则就完成了,在完成实际需求的前提下,也丝毫不影响 Presenter 层的单元测试。当然,并没有绝对正确或者错误的架构,说到底代码具体要怎样写,功能具体应该怎样实现,最终还是要看实际业务场景。

总结

这节课我主要对 MVP 架构中 Presenter 层的使用做了 2 点优化介绍:

  1. 如何支持 Presenter 的生命周期,使其在 Activity 被销毁时也能取消相应的耗时请求。

  2. 合理使用 Presenter 的生命周期,Activity 中所有的方法都委托给 Presenter 来处理是不合理的,这样会造成 Presenter 层极其庞大,也难以维护,有时也会违反职责单一原则。


第30讲:如何设计一个比较合理的 LogUtil 类?

我们在项目中经常会打印各种 Log 信息,用来查看程序运行中的详细情况。因此打印日志俨然已经成了程序开发工作中的一部分,而设计一个合理的 LogUtils 也成了一个好的程序员的必选条件之一。

设置 Debug 开关

有时候为了调试方便,我们甚至会将用户的账号、密码、余额等信息也打印到控制台,但是如果这部分 Log 信息也出现在线上版本中,那用户的私密信息,或者程序相关核心实现都会被暴露;除此之外,打印日志的代码并不属于业务需求的必要代码,复杂的 Log 信息还会造成一定的性能损耗,所以这部分代码都不应该出现在线上版本的 App 中。因此我们需要设置一个开关来控制是否打印 Log 日志,只有在 Debug 版本才会打开此开关,如图所示:

Drawing 0.png

通常情况下我们会使用 BuildConfig.DEBUG 来作为是否要打印日志的开关。但是使用这个变量具有一定的局限性。比如现场突然发现一个异常现象,而我们需要现场抓取异常的日志信息加以分析。因为是 release 版本,所有不会有任何 log 信息被打印。因此这个开关的设置最好具有一定的灵活性,比如可以再加一层 System Property 的设置,如图所示:

Drawing 1.png

上述代码打印结果如下所示:

Drawing 2.png

使用 System Property 的好处是一旦设置之后,即使重启 App,System Property 中的变量依旧是设置之后的值,与 Android 中的 SharedPreference 非常相似。开发者只要定义好通过何种方式将这种属性打开即可,建议仿照 Android 系统设置中的“开发者选项”来实现,当用户快速连续点击某 item 时,才将此属性打开。

另外,我们还可以通过 ProGuard 在打包阶段清除某些 Log 日志、打印代码,具体规则如下:

Drawing 3.png

设置 log 日志本地保存

有时候我们需要将部分 log 日志以文件的形式保存在手机磁盘中,因此我们还需要设置开关,控制日志是打印在控制台还是保存到文件中。如下所示:

Drawing 4.png

因为涉及文件的写操作,所以最好是在子线程中完成日志的保存。因此在 LogUtils 中可以使用线程池控制子线程完成日志保存,如下所示:

Drawing 5.png

Config 文件统一配置

如果 LogUtils 中的开关较多,再加上还有其他配置项,比如日志保存为文件的路径等。这种情况可以使用一个全局的 Config 来配置 LogUtils 中所有的配置项,如下所示:

Drawing 6.png

特殊格式转换

我们经常会处理一些特殊格式的数据,比如 JSON、XML。为了打印这部分数据,还需要在 LogUtils 类中做一些格式转换的操作:

Drawing 7.png

借助于三方库打印 log

如果感觉自己封装一个 LogUtils 类比较麻烦,或者没有好的实现思路,那就不妨尝试使用行内内已经成熟的 log 日志库。

XLog

XLog 是比较常用的打印日志开源库,GitHub 地址参考 XLog github。XLog 基本囊括了我们上文介绍的所有功能:

  • 全局配置或基于单条日志的配置;

  • 支持打印任意对象以及可自定义的对象格式化器;

  • 支持打印数组;

  • 支持打印无限长的日志(没有 4K 字符的限制);

  • XML 和 JSON 格式化输出;

  • 线程信息(线程名等,可自定义);

  • 调用栈信息(可配置的调用栈深度,调用栈信息包括类名、方法名、文件名和行号);

  • 支持日志拦截器;

  • 保存日志文件(文件名和自动备份策略可灵活配置)。

XLog 使用比较简单,先调用 init 方法进行初始化,最好是在 Application 中。

Drawing 8.png

然后就可以直接调用 Xlog 的静态方法打印相应日志即可:

Drawing 9.png

也可以在打印日志时,添加局部的配置信息:

Drawing 10.png

打印结果类似下图所示:

Drawing 11.png

可以看出,除了打印日志的类和方法,XLog 还能打印线程信息以及调用栈信息。

总结

这节课主要介绍了项目开发中对 LogUtils 类的配置,因为项目中会在很多地方打印各种日志信息,所以为了方便统一管理,我们应该将所有日志打印的工作都集中到一个 Utils 类中。LogUtils 应该对外部提供相应的开关,用来设置是否需要打印日志,以及打印日志的通道,如果是将日志保存在文件中等耗时操作,还应该考虑在子线程中完成。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/503708
推荐阅读
相关标签
  

闽ICP备14008679号