ARouter原理简单分析

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

这步有两种方式

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

这种解耦方式很实用,可以轻松实现不具备依赖的模块间获取数据,当然他们一定会有一个公用的模块,且这个模块会下沉到基础模块去。这种思路也体现在一些组件化框架中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值