ARouter实现跨module数据传递

3,028 阅读5分钟

ARouter是一个很经典的开源项目,本次我们剖析它的主体功能之一:实现跨module数据传递。

一、跨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().build("/service/hello").navgation();
   ARouter.getInstance().navigation(IHelloService.class);

A module与B module没有依赖关系,那A module要拿到B module的HelloServiceImpl实例对象,怎么处理?

二、分析跨module数据传递 (分析问题)

如同上一篇,跨模块的页面跳转,获取要跳转页面的class一样,源码阶段,A、B module不存在耦合,靠编译期生成中间类,这个类作为A 、B 通信的桥梁,实现A、B module通信。

你需要处理的子问题:
1.生成这个中间类,寻找指定的注解Route,注解生成这个类。
2.它承担的任务:创建HelloServiceImpl实例对象,并通过key_value保存起来。

     ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
     ARouter.getInstance().build("/xxx/hello").navigation().sayHello("mike");

那这个实例对象的key可以是HelloService.class.getName(),可以是"/xxx/hello",value当然就是HelloServiceImpl实例对象了。

ARouter的主体就是这样,简单而又粗暴,碍于ARouter作为一个库,肯定有库的规范在,ARouter类对开发者暴露,实际处理逻辑的类是_ARouter;LogisticsCenter(物流中心)承担处理对象任务,比如反射创建空参构造的实例对象;WareHouse作为仓库保存数据。符合单一职责原则。

三、实现跨module数据传递 (解决问题)

1.  注解生成的类如下:

2.  找到这两个(ARouter?Provider?app 和 ARouter?Root?app)新生成的类,然后反射创建对象,并调用loadInto方法,将数据保存到仓库WareHouse。

怎么找

方式一:初始化(运行时)耗时遍历base.apk,扫描到这个两个类,就直接反射创建。
方式二: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;
        }
    }

方式二在编译期去操作字节码,找到LogisticsCenter这个class所在的jar,找到注解生成的类ARouter?Root?app、ARouter?Provider?app的全路径,然后定位到loadRouteMap,插入如下代码:

  public static void loadRouteMap() {
        sAutoRegister = false;
        register("com.docwei.arouter.routes.ARouter$$Root$$app");
        register("com.docwei.arouter.routes.ARouter$$Provider$$app");
    }

最后在初始化的调用这个loadRouteMap方法。
无论是注解生成新类,还是利用ASM修改字节码的方法体内容,在ARouter源码里面都有,如果你嫌源码复杂,可以看最后精简的ARouterDemo。

3. 最后根据用户传入的path或者接口类class找实例对象。

//通过接口名获取接口实例对象
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);
               
            }
        }
    }
}

}

四、拦截器与AutoWird实现原理

拦截器

先看下用户定义的拦截器

@Interceptor(priority = 9)
public class MyInterceptor implements IInterceptor { xxx }
public interface IInterceptor extends IProvider {
    void process(PostCard postCard,IInterceptorCallback iInterceptorCallback);
}

看到没有,继承的是IProvider接口,源码对多个拦截器的处理,是在初始化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);
}

在页面跳转时,依次走完所有的拦截器process方法,如果中间有拦截器拦截了操作,那就中断页面跳转。 以下代码经过等效改写。(子线程里面走完所有的拦截器process方法,但是源码还用到了CountDownLatch,感觉多此一举,因为子线程的run方法本身就是顺序执行的,拦截器也都按照优先级遍历执行。)

@Override
public void doInterceptor(final PostCard postcard, final IInterceptorCallback callback) {
final int size = WareHouse.sInterceptorObjects.size();
if (size > 0) {
    _ARouter.sExecutor.execute(new Runnable() {
        @Override
        public void run() {
            try {
                mCountDownLatch = new CountDownLatch(size);
                executeInterceptor(0, postcard, callback, size);
                mCountDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

 }
}

private void executeInterceptor(final int index, final PostCard postcard, final IInterceptorCallback callback, final int size) {
if (index < size) {
    WareHouse.sInterceptorObjects.get(index).process(postcard, new IInterceptorCallback() {
        @Override
        public void continuing(PostCard postCard) {
            executeInterceptor(index + 1, postcard, callback, size);
            mCountDownLatch.countDown();
            if (index + 1 == size) {
                callback.continuing(postCard);
            }
        }
        @Override
        public void interrupted(Throwable throwable) {
            callback.interrupted(throwable);
            int n = index;
            while (n + 1 <= size) {
                mCountDownLatch.countDown();
                n++;
            }
        }
    });
 }
}

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");
  }
}

这个IAutoWird是什么?

 public interface IAutoWird {
    void inject(Object target);
 }

注解生成这个类,初始化ARouter后创建这个ARouter?AutoWird?app对象,等到用户调用ARouter.getInstance().inject(this); 那就再反射创建这个ARouter?SecondActivity?AutoWird对象,调用其inject方法。

五、你学到什么了?

无论拦截器、autowird、页面跳转、获取实例对象,他们的实现原理都一样,一套原理贯穿整个ARouter的所有功能。这个原理也被很多组件化框架吸收套用,确实值得一看。

but 讲真,看完你学不到了啥,可能你啥都没学到,因为没走过源码的逻辑,你不知道作者在写的时候解决了哪些坑,需要哪些技能点。

如果觉得源码复杂,那就看看精简过后的吧。 诚意奉上精简的ARouterDemo