1.概述

最近越来越不想写代码了,特别是一些重复性的代码,比如由于每次启动一个 Activity,我们都会很习惯的在 Activity 中写下:

public static void launch(Activity activity) {
Intent intent = new Intent();
intent.setClass(activity, xxxActivity.class);
activity.startActivity();
}

已经有两年Android开发经验的我掐指一算,好像有很多方法可以直接帮咱们生成这样类似的代码:

  • 通过 Android Studio 中自定义Activity的Template,这样就可以在生成一个Activity class的时候,会自动帮咱们填充launch方法。大幅提高Android开发效率之Android项目模板化
  • 通过 Android Studio 的快捷键方式 Live Templates,来定制 launch 代码块:你不一定知道的Android Studio中强大的快捷代码块
  • 可爱的编译时注解。

本文的重点放在了编译时注解的框架实现,毕竟是要和时代接轨的啦!

百牛信息技术bainiu.ltd整理发布于博客园

2.需求剖析

我的需求很简单,就完成通过注解来代替Intent代码来启动 Activity,让注解来帮我们生成 launch 方法,而且还要能够在组件化开发中使用。
需求很明确,所以直接开始构思:
所有的 launch 方法其实很类似,不同的就是跳转的终点 xxxActivity ,那么我们就想办法来构造一个对应的关系:通过配置注解 LaunchAnn 来给每个 Activity 分配一个String别名,我通过找到这个别名,我就知道了这个对应的真实 Activity。这样在需要启动xxxActivity的地方,我通过调用别名来启动这个 xxxActivity 就可以了,这样启动就不会依赖 xxxActivity,而仅仅是一个别名字符串而已。

3.开发流程

创建三个 module:

  • iocAnnotation:java lib,里面只放了编译时注解
  • iocCompiler: 只能是 java lib,不能是 Android lib(生成aar的),因为我们使用的 AbstractProcessor 在Android环境下是找不到的。
  • iocApi:Android lib,是给用户直接使用的,里面会用到一点点反射。

开发注解框架可以参考:Android 如何编写基于编译时注解的项目。注意的点:

  • 为啥要三个 module:由于 iocCompiler 只是在编译的时候才使用的,所以在运行的时候是用不到的,这样我们就可以不用打包到正式apk中,所以在gradle文件中引入方式是:annotationProcessor。
  • iocCompiler 对 AbstractProcessor 的注释有两种:1是直接在实现类中通过google提供的注解AutoService完成,2是自己在 resources 下 META-INF/services/javax.anotation.processing.processor中进行声明,如果有多个processor,直接逗号隔开。

定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface LaunchAnn {
String value();
}

注解使用:

@LaunchAnn("RuntimeActivity")
public class RuntimeActivity extends Activity { }

打开 activity:

public void onClick(View view) {
Launch.launch("RuntimeActivity", this);
}

iocCompiler 中的 CakeProcessor 中(AbstractProcessor实现类),生成了一个固定的LaunchUtil工具类。通过遍历有哪些类使用了LaunchAnn注解,然后把 LaunchAnn 的 value 值(这里的“RuntimeActivity”)作为 key,类对应的全路径(包名+类名)作为 value 存入到 LaunchUtil 工具类的 map 中,这样我就可以直接通过注解 LaunchAnn 的 value 值可以获取到对应类的全路径。
iocApi 的作用就是提供给用户调用的接口(onClick中的内容调用),LaunchUtil 中包含了注解对应的map信息,所以关键点就是能够解析LaunchUtil中的内容。
由于 iocApi 和 iocCompiler 没有半毛钱联系,所以怎么能够获取到 LaunchUtil 这个类?这点还是让我花费了一些时间的,最后通过在 iocApi 中定义一个 LaunchInjector 接口,实现通过key值获取Class的getPackageName的接口。当然我们的 LaunchUtil 工具也要实现与LaunchInject这个接口,这样通过反射LaunchUtil的,直接形成了一个 LaunchInjector 的对象,通过 getPackageName 方法就可以获取到需要跳转的 Activity 对应的 Class。
我感觉介绍的差不多了,不懂就自己看代码了。

4.适配组件化方案

其实最开始仅仅是想做一个懒人开发者,这样我就不用每次都写 launch 了,但是开发到最后才发现,前面还有好多要去学习的地方。
由于本身自己在学组件化开发,所以想把写的注解运用到组件化当中去。有4个 module:modulebase、moduleA、moduleB、app。命名中可以看出 modulebase 是基础包,moduleA 和 moduleB 都是功能模块包,最后总包 app module。
由于每个模块都是用了Launch,所以我们可以发现在编译注解的时候,每个 lib 下面都会有一个 LaunchUtil,在合并的时候就会冲突(提示类重复),所以我们需要针对不同的模块,定制不同的类名,我这边是根据模块绑定了,例如:LaunchUtil$$moduleA.java。这样多个 LauncUtil 类就可以共存了。
现在由于有多个 LauncUtil,我们怎么把所有的 LaunchUtil 里面的map信息进行汇总,得到一个总的 map 信息呢?这个东西我想了很久很久,过程中经历了两种实现,其中第一种最后证明不行,最后选择了第二种。为啥还要说第一种呢,因为我感觉还是有必要记录一下的:

  • 第一种,为什么不可以对注解生成的 LaunchUtil 再进行加入注解,然后再把所有的 LaunchUtil 在编译时获取其中的map,最后生成一个新的LaunchUtilMerge类呢?我最开始的想法就是这样的,在生成的 LaunchUtil 中也实现了一个注解:MergeLaunch 注解,然后再生成一个 MergeProcessor 来查找 MergeLaunch 对应的 LaunchUtil 类,获取每个类中的 map,得到一个总的 map 放入到 LaunchUtilMerge 中去。
    想象这很美好,但在开发过程中发现,由于编译的流程是:先编译 moduleA、moduleB 变成 aar 包,然后 app 依赖 aar 包后再进行编译,而 aar 中的类都是 .class 形式存在的,那么 moduleA.aar 中的 LaunchUtil 在 app module 看来就是一个 LauncUtil.class,即使你有 MergeLaunch 编译时注解,你也不可能找到 moduleA.arr 中的 LaunchUtil,因为他是一个 .class 文件,编译时注解只对 .java 文件有效。在组件化的时候,编译注解是对 java 类而言的,所以在 jar 包、aar 包中存在编译时注解,也是获取不到的。导致的后果就是,LaunchUtilMerge 最后也只找到了app module 中的 LaunchUtil,这样得到的 map 信息明显是不全的。分析到最后,我也是放弃了,很坚决的放弃!
  • 第二种,在第一种无解的情况下,手贱把 apk 反编译看了看,其实发现打包后每个模块对应的 LaunchUtil 其实都是在 dex 中的,所以有没有一种方法,通过类名来找对应的 LaunchUtil 呢?为了好处理在生成 LaunchUtil 的时候,我都把他们放在了一个文件夹下面:”seu.com.util“,这样我只要找到这个文件夹下面的所有类就可以了,现在的问题就变成了通过指定的包名来找对应的所有类。
    网上搜了搜,还真有在 Android 环境下,通过 DexFile 这个类来查找 dex 中对应的类名对应的全路径:Android中获取指定包名下的所有类,哈哈哈,这样我就找到了所有LaunchUtil对应的全路径名称,最后通过反射找到的所有类,获取每个类中的map,合并成一个汇总的 map,其中包括了所有 LaunchuAnn 注册过的 Activity 对应的类信息,在 iocApi 下的 Launch 类中。这样你就可以通过传入一个字符串的方式来启动 Activit y啦。

    5.总结

    前面的编译注解对老司机来说其实都是比较简单的,开发流程表比较固定。对于适配组件化开发的过程,其实还是一个比较新鲜的事情。因为看到了阿里的ARouter是适合组件化开发的,所以自己也是有点蠢蠢欲动的赶脚。最后有一个可以让人参考的 demo,希望大家多多交流和学习。对于还有更好的方法来获取所有的 LaunchUtil 的,欢迎交流。
    存在的不足:

  • 只是查找了一个dex中的所有类,一些apk有好多 dex 文件,那么要确保所有的 LaunchUtil 都被找到,需要查找所有的 dex,这一块是暂时还没有处理的。后续对 class 拆分成多个dex有了研究之后再回来研究这一块。
  • 项目还是比较适合个人学习提升的:https://github.com/dndxxiangyu/AndroidLearn

作者:五香鱼cc
链接:http://www.jianshu.com/p/af2b83af02e1
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Android适合组件化开发的路由框架:Launch的更多相关文章

  1. Android 业务组件化开发实践

    组件化并不是新话题,其实很早很早以前我们开始为项目解耦的时候就讨论过的.但那时候我们说的是功能组件化.比如很多公司都常见的,网络请求模块.登录注册模块单独拿出来,交给一个团队开发,而在用的时候只需要接 ...

  2. Android业务组件化之子模块SubModule的拆分以及它们之间的路由Router实现

    前言: 前面分析了APP的现状以及业务组件化的一些探讨(Android业务组件化之现状分析与探讨),以及通信的桥梁Scheme的使用(Android业务组件化之URL Scheme使用),今天重点来聊 ...

  3. Android组件化开发的简单应用

    组件化开发的主要步骤: 一.新建Modules 1.新建Project,作为应用的主Module. 2.新建Module:"Common",类型选择"Android Li ...

  4. Android项目模块化/组件化开发(非原创)

    文章大纲 一.项目模块化初步介绍二.项目模块化的两种模式与比较三.大型项目模块化的演进四.项目模块化总结五.参考文章   一.项目模块化初步介绍 1. 前言 在Android开发中,随着项目的不断扩展 ...

  5. Android项目实战(四十八):架构之组件化开发

    什么要组件化开发? 看一下普通项目的结构 , 一个项目下有多个Module(左侧图黑体目录),但是只有一个application,0个或多个library(在每个medel下的build.gradle ...

  6. Android业务组件化之现状分析与探讨

    前言: 从个人经历来说的话,从事APP开发这么多年来,所接触的APP的体积变得越来越大,业务的也变得越来越复杂,总来来说只有一句话:这是一个APP臃肿的时代!所以为了告别APP臃肿的时代,让我们进入一 ...

  7. Android业务组件化之URL Scheme使用

    前言: 最近公司业务发展迅速,单一的项目工程不再适合公司发展需要,所以开始推进公司APP业务组件化,很荣幸自己能够牵头做这件事,经过研究实现组件化的通信方案通过URL Scheme,所以想着现在还是在 ...

  8. vue.js组件化开发实践

    前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎么实现,技术选型自然 ...

  9. Android业务组件化之Gradle和Sonatype Nexus搭建私有maven仓库

    前言: 公司的业务组件化推进的已经差不多三四个月的时间了,各个业务组件之间的解耦工作已经基本完成,各个业务组件以module的形式存在项目中,然后项目依赖本地的module,多少有点不太利于项目的并行 ...

随机推荐

  1. codevs 2669 简单的试炼

    2.codevs   2669 简单的试炼 题目描述 Description 已知一个数S,求X和Y,使得2^X+3^Y=S. 输入描述 Input Description (多组数据) 每行一个整数 ...

  2. 使用SmartQQ实现的智能回复(Web QQ协议)

    采用SmartQQ SDK进行开发,官网:https://github.com/ScienJus/smartqq 此项目只是集成使用的方法,在com.jsoft.robot.SmartQQUse.Re ...

  3. MongoDB下配置用户权限

    MongoDB默认设置为无权限訪问限制 注:研究成果基于Windows平台 在部署mongodb成功后.进入控制台: 输入命令:mongod  use admin,你会发现该DB下包括了一个syste ...

  4. 安卓执行机制JNI、Dalvik、ART之间的比較 。android L 改动执行机制。

    Android L默认採用ART执行环境.全然兼容64位移动处理器.Google称这将比此前的Dalvik模式性能提高两倍,可是会占用很多其它的内存空间.Android有三种执行模式:JNI.Dalv ...

  5. People seldom do what they believe in. They do what is convenient, then repent.

    People seldom do what they believe in. They do what is convenient, then repent. 人们很少真正实践他们的理想.他们只做比较 ...

  6. C#如何实现挂机锁

    首先在主窗体中设置一个子窗体的实例,然后当点击挂机之后,隐藏当前窗体,同时显示子窗体.   把子窗体的背景窗体设置如下属性(主要是背景随便改成一个图片,然后FormBorderStyle改成None, ...

  7. react 实现pure render的时候,bind(this)隐患

    react 实现pure render的时候,bind(this)隐患 export default class Parent extends Component { ... render() { c ...

  8. saltstack源码安装

    环境 centos6.3,python2.7.5. 1.install libzmq-master $ git clone git://github.com/zeromq/libzmq.git $ c ...

  9. 数组index

    1. 数组index与数组名的位置关系     a[b] = *(a + b) = *(b + a) = b[a] int a[5] = {1, 2, 3, 4, 5}; printf("% ...

  10. js对象的属性问题

    ES6之前js的对象的属性只能是字符串, <html> <head> <script type="text/javascript"> var a ...