ARouter原理简单分析
- 组件化或者模块化开发模式,已逐渐成为主流的形式,使用这些模式可以让我们程序更容易的扩展、更方便的维护,ARouter的出现就是让组件间、模块间是实现完全的独立
- ARouter是一个很经典的开源项目,官方定义是: Android 平台中对页面、服务提供路由功能的中间件
本次我们试着剖析下他们的核心原理:实现跨module间的方法调用
一、提出问题,ARouter 基本使用使用例子
//基础公共库定义了IHelloService接口
public interface IHelloService extends IProvider {
void sayHello();
}
// B Moudle定义了这个接口实现类:
@Route(path = "/service/hello")
public class HelloServiceImpl implements IHelloService{
@Override
public void sayHello() {
xxxx
}
}
// A Moudle要获取HelloService接口实现类的实例对象:
//两种方式
ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
ARouter.getInstance().build("/xxx/hello").navigation().sayHello("mike");
A module与B module没有依赖关系,那A module要拿到B module的HelloServiceImpl实例对象,怎么处理?
二、分析问题:如何实现跨module间的方法调用?
原理是借助APIT中间类,作为模块A,和模块B的桥梁,实现A,B直接的通信。下面具体描述;
1.在编译期生成中间类
这个步骤是借助APT和JavaPoet来实现。生成的类如下:关键信息其实就是一个Key-Value 的数据。
key 代表 url-path, value 代表具体类是实例
2. 生成路由表
第一步生成的中间都放在同一个包路径下, 因此生成路由表的操作,其实就是,遍历这些生成类,然后反射创建对象,并调用loadInto方法,将数据保存到仓库WareHouse
这步有两种方式
-
- 初始化(运行时)耗时遍历base.apk,扫描到这个两个类,就直接反射创建。
-
- gradle插件(auto-register):编译期利用ASM操作transforms文件夹下的字节码)实现
//方式一:
public static void init(Context context, ThreadPoolExecutor executor) {
final Set<String> fileNames = new HashSet<>();
ApplicationInfo applicationInfo = context.getApplicationInfo();
final String path = applicationInfo.sourceDir;
//因为5.0以上直接就是一个apk的路径,所以不考虑多个路径
//耗时大概1s左右,这个是Arouter耗时的关键
DexFile dexFile = null;
try {
dexFile = new DexFile(path);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String element = entries.nextElement();
//去找含有这个com.docwei.arouter.routes路径的文件名
if (element.contains(Consts.PACKAGE_OF_GENERATE_FILE)) {
fileNames.add(element);
}
}
} catch (IOException e) {
e.printStackTrace();
}
for (String fileName : fileNames) {
//反射去创建这个类对象,然后保存到仓库
if (fileName.startsWith(Consts.PACKAGE_OF_GENERATE_FILE + "." + "ARouter$$Root")) {
((IRouterRoot) (Class.forName(fileName).getConstructor().newInstance()))
.loadInto(WareHouse.sGroups);
}
if (fileName.startsWith(Consts.PACKAGE_OF_GENERATE_FILE + "." + "ARouter$$Provider")) {
((IProviderGroup) (Class.forName(fileName).getConstructor().newInstance()))
.loadInto(WareHouse.sProviders);
}
}
//方式二:
//初始化流程
public class LogisticsCenter {
static boolean sAutoRegister;
static Context sContext;
public static void init(Context context, ThreadPoolExecutor executor) {
loadRouteMap();
if (sAutoRegister) {
return;
}else{
//走方式一
xxx
}
}
public static void loadRouteMap() {
sAutoRegister = false;
//这个方法将被ASM修改,添加对应的代码
//register("com.docwei.arouter.routes.ARouter$$Root$$app);
}
public static void register(String name) {
if (!TextUtils.isEmpty(name)) {
Object obj = Class.forName(name).getConstructor().newInstance();
if (obj instanceof IRouterRoot) {
((IRouterRoot) obj).loadInto(WareHouse.sGroups);
}
if (obj instanceof IProviderGroup) {
((IProviderGroup) (Class.forName(name).getConstructor().newInstance()))
.loadInto(WareHouse.sProviders);
}
sAutoRegister = true;
}
}
方式二 会在这个编译期的时候定位到loadRouteMap 方法,添加代码:
public static void loadRouteMap() {
sAutoRegister = false;
register("com.docwei.arouter.routes.ARouter$$Root$$app");
register("com.docwei.arouter.routes.ARouter$$Provider$$app");
}
3. 最后根据用户传入的path或者接口类class找实例对象
这这步就比较简单,根据 路由路径 key 从路由表中找到具体类,实例化,调用方法即可:
//通过接口名获取接口实例对象
public Object navgation(Class service) {
RouteMeta routeMeta = WareHouse.sProviders.get(service.getName());
if (routeMeta == null) {
return null;
}
PostCard postCard = new PostCard(routeMeta.getPath(), routeMeta.getGroup(), routeMeta.destination, routeMeta.type);
LogisticsCenter.completePostCard(postCard);
return postCard.getProvider();
}
//通过path去获取实例
public static void completePostCard(PostCard postCard) {
RouteMeta routeMeta = WareHouse.sRoutes.get(postCard.getPath());
if (routeMeta == null) {
Class<? extends IRouterGroup> iRouterGroup = WareHouse.sGroups.get(postCard.getGroup());
if (iRouterGroup == null) {
Log.e("myRouter", "completePostCard: " + "path map page not found");
return;
}
IRouterGroup routerGroup = iRouterGroup.getConstructor().newInstance();
routerGroup.loadInto(WareHouse.sRoutes);
completePostCard(postCard);
} else {
postCard.destination = routeMeta.destination;
postCard.type = routeMeta.getType();
//获取对象实例
if (postCard.getType() == BizType.IPROVIDER) {
IProvider iProvider = WareHouse.sProviderObjects.get(postCard.destination);
if (iProvider == null) {
iProvider = (IProvider) postCard.getDestination().getConstructor()
.newInstance();
postCard.setProvider(iProvider)
iProvider.init(sContext);
}
}
}
}
三、拦截器
这个跟OKHttp的拦截器 还点类似,把所以的拦截器根据优先级,都注册到一个拦截器列表。 然后调用navigation的时候,一个个拦截器执行下去,如果 需要直接返回,则中断这个过程
先看下用户定义的拦截器
@Interceptor(priority = 9)
public class MyInterceptor implements IInterceptor { xxx }
public interface IInterceptor extends IProvider {
//在页面跳转时,依次走完所有的拦截器process方法,如果中间有拦截器拦截了操作,那就中断页面跳转
void process(PostCard postCard,IInterceptorCallback iInterceptorCallback);
}
初始化ARouter后,会反射创建所有的拦截器对象来获取多个拦截器实例对象。
//所有的拦截器都加入了仓库
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : WareHouse.sInterceptors.entrySet()) {
IInterceptor interceptor = entry.getValue().getConstructor().newInstance();
interceptor.init(context);
WareHouse.sInterceptorObjects.add(interceptor);
}
四、@AutoWird 原理
AutoWird 注解自动给字段赋值
先看下用户使用autowird注解
public class SecondActivity extends AppCompatActivity {
@AutoWird
public String name;
@AutoWird
public long price;
@AutoWird
public MyTestSerializableBean mSerializableBean;
@AutoWird
public MyTestParcelBean mMyTestParcelBean;
@AutoWird
public int score;
@AutoWird
public double goal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
ARouter.getInstance().inject(this);
TextView textView = findViewById(R.id.tv);
textView.setText(name + "---" + mMyTestParcelBean.desk
+ "---" + mSerializableBean.book + "---" +
price + "---" + score + "---" + goal);
}
}
关键方法是: ARouter.getInstance().inject(this);
其实会自动调用这个中间类:
//做的操作就是:由这个类去完成
public class ARouter$$SecondActivity$$AutoWird implements IAutoWird {
@Override
public void inject(Object target) {
SecondActivity substitute= (SecondActivity) target;;
Intent intent=substitute.getIntent();
substitute.mSerializableBean = (MyTestSerializableBean) intent.getSerializableExtra("mSerializableBean");
substitute.price = intent.getLongExtra("price",0);
substitute.goal = intent.getDoubleExtra("goal",0);
substitute.name = intent.getStringExtra("name");
substitute.score = intent.getIntExtra("score",0);
substitute.mMyTestParcelBean = intent.getParcelableExtra("mMyTestParcelBean");
}
}
编译期,根据注解生成这个类,初始化ARouter后创建这个ARouter A u t o W i r d AutoWird AutoWirdapp对象,等到用户调用ARouter.getInstance().inject(this); 那就再反射创建这个ARouter S e c o n d A c t i v i t y SecondActivity SecondActivityAutoWird对象,调用其inject方法。
五、总结
ARouter的代码是一个很经典的开源项目,其中的思路其实在很多项目上都是通用的,比较 bufferKnife, databinding, 原理都是通过APT生成中间类,来做一些通用的功能,来加速开发。
而且其中 也包含了 控制反转+依赖注入 服务端Spring的思想。
引用别人的大白话:
IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合,更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的使程序的整个体系结构变得非常灵活。在运行期,在外部容器动态的将依赖对象注入组件,当外部容器启动后,外部容器就会初始化。创建并管理bean对象,以及销毁他,这种应用本身不负责依赖对象的创建和维护,依赖对象的创建和维护是由外部容器负责的称为控制反转。
IOC容器就是 WareHouse类的几个Map
依赖注入 :build("/test/second")
这种解耦方式很实用,可以轻松实现不具备依赖的模块间获取数据,当然他们一定会有一个公用的模块,且这个模块会下沉到基础模块去。这种思路也体现在一些组件化框架中。