组件是如何通讯的?揭秘ARouter路由机制

面试官: 有没有使用过组件化,组件化通讯如何作到的,ARouter有用过吗

心理分析:组件化通常在架构常常被考到,组件化的内容比较多,跟咱们平时的开发mvc的单体应用不同,组件化是团队做战,须要设计复杂的组件通讯与交互android

求职者: 从组件化的由来,优点 弊端开始,最后引出组件化的劣势,组件通讯git

1 组件化

1.1 组件化初衷

  • APP版本不断的迭代,新功能的不断增长,业务也会变的愈来愈复杂,维护成本高。
  • 业务耦合度高,代码愈来愈臃肿,团队内部多人协做开发困难。
  • Android项目在编译代码的时候电脑会很是卡,又由于单一工程下代码耦合严重,每修改一处代码后都要从新编译打包测试,致使很是耗时。
  • 方便单元测试,改动单独一个业务模块,不须要着重于关注其余模块被影响。

1.2 什么是组件化

组件化就是将一个app分红多个Module,以下图,每一个Module都是一个组件(也能够是一个基础库供组件依赖),开发的过程当中咱们能够单独调试部分组件,组件间不须要互相依赖,但能够相互调用,最终发布的时候全部组件以lib的形式被主app工程依赖并打包成一个apk。github

1.3 组件化优点

  • 组件化就是将通用模块独立出来,统一管理,以提升复用,将页面拆分为粒度更小的组件,组件内部除了包含UI实现,还包含数据层和逻辑层。
  • 每一个工程均可以独立编译、加快编译速度,独立打包。
  • 每一个工程内部的修改,不会影响其余工程。
  • 业务库工程能够快速拆分出来,集成到其余App中。
  • 迭代频繁的业务模块采用组件方式,业务线研发能够互不干扰、提高协做效率,并控制产品质量,增强稳定性。
  • 并行开发,团队成员只关注本身的开发的小模块,下降耦合性,后期维护方便等。

2 组件化通讯

2.1 组件化通讯

​组件化互相不直接依赖,若是组件A想调用组件B的方法是不行的。不少开发者由于组件化之间通讯比较复杂 则放弃了组件化的使用面试

组件通讯有如下几种方式:api

1.本地广播
本地广播,也就是LoacalBroadcastRecevier。更可能是用在同一个应用内的不一样系统规定的组件进行通讯,好处在于:发送的广播只会在本身的APP内传播,不会泄漏给其余的APP,其余APP没法向本身的APP发送广播,不用被其余APP干扰。本地广播比如对讲通讯,成本低,效率高,但有个缺点就是二者通讯机制所有委托与系统负责,咱们没法干预传输途中的任何步骤,不可控制,通常在组件化通讯过程当中采用比例不高。
2.进程间的AIDL
​进程间的AIDL。这个粒度在于进程,而咱们组件化通讯过程每每是在线程中,何况AIDL通讯也是属于系统级通讯,底层以Binder机制,虽然说Android提供模板供咱们实现,但每每使用者很差理解,交互比较复杂,每每也不适用应用于组件化通讯过程当中。
3.匿名的内存共享
匿名的内存共享。好比用Sharedpreferences,在处于多线程场景下,每每会线程不安全,这种更可能是存储一一些变化不多的信息,好比说组件里的配置信息等等
4.Intent Bundle传递
Intent Bundle传递。包括显性和隐性传递,显性传递须要明确包名路径,组件与组件每每是须要互相依赖,这背离组件化中SOP(关注点分离原则),若是走隐性的话,不只包名路径不能重复,须要定义一套规则,只有一个包名路径出错,排查起来也稍显麻烦,这个方式每每在组件间内部传递会比较合适,组件外与其余组件打交道则使用场景很少。

2.2 目前主流作法之一就是引入第三者,好比图中的Base Module。

3 ARouter组件通讯框架

3.1 ARouter 简介

是ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。主要用做组件化通讯浏览器

GitHub:https://github.com/alibaba/ARouter安全

前言多线程

Intent intent = new Intent(mContext, XxxActivity.class);
 intent.putExtra("key","value");
 startActivity(intent);
 
 Intent intent = new Intent(mContext, XxxActivity.class);
 intent.putExtra("key","value");
 startActivityForResult(intent, 666);

上面一段代码,在Android开发中,最多见也是最经常使用的功能就是页面的跳转,咱们常常须要面对从浏览器或者其余App跳转到本身App中页面的需求,不过就算是简简单单的页面跳转,随着时间的推移,也会遇到一些问题:架构

  1. 集中式的URL管理:谈到集中式的管理,老是比较蛋疼,多人协同开发的时候,你们都去AndroidManifest.xml中定义各类IntentFilter,使用隐式Intent,最终发现AndroidManifest.xml中充斥着各类Schame,各类Path,须要常常解决Path重叠覆盖、过多的Activity被导出,引起安全风险等问题
  2. 可配置性较差:Manifest限制于xml格式,书写麻烦,配置复杂,能够自定义的东西也较少
  3. 跳转过程当中没法插手:直接经过Intent的方式跳转,跳转过程开发者没法干预,一些面向切面的事情难以实施,比方说登陆、埋点这种很是通用的逻辑,在每一个子页面中判断又很不合理,毕竟activity已经实例化了
  4. 跨模块没法显式依赖:在App小有规模的时候,咱们会对App作水平拆分,按照业务拆分红多个子模块,之间彻底解耦,经过打包流程控制App功能,这样方便应对大团队多人协做,互相逻辑不干扰,这时候只能依赖隐式Intent跳转,书写麻烦,成功与否难以控制。

为了解决以上问题,咱们须要一款可以解耦、简单、功能多、定制性较强、支持拦截逻辑的路由组件:咱们选择了Alibaba的ARouter,偷个懒,直接贴ARouter的中文介绍文档:mvc

3.2 ARouter 优点

从 ARouter Github 了解到它的优点:

支持直接解析标准URL进行跳转,并自动注入参数到目标页面中 支持多模块工程使用 支持添加多个拦截器,自定义拦截顺序 支持依赖注入,可单独做为依赖注入框架使用 支持InstantRun 支持MultiDex(Google方案) 映射关系按组分类、多级管理,按需初始化 支持用户指定全局降级与局部降级策略 页面、拦截器、服务等组件均自动注册到框架 支持多种方式配置转场动画 支持获取Fragment 彻底支持Kotlin以及混编 典型的应用:

从外部URL映射到内部页面,以及参数传递与解析 跨模块页面跳转,模块间解耦 拦截跳转过程,处理登录、埋点等逻辑

跨模块API调用,经过控制反转来作组件解耦

3、典型应用场景

  1. 从外部URL映射到内部页面,以及参数传递与解析
  2. 跨模块页面跳转,模块间解耦
  3. 拦截跳转过程,处理登录、埋点等逻辑
  4. 跨模块API调用,模块间解耦(注册ARouter服务的形式,经过接口互相调用)

4、基础功能

  1. 添加依赖和配置
  2. apply plugin: 'com.neenbedankt.android-apt'
buildscript {
 repositories {
 jcenter()
 }
 dependencies {
 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
 }
 }
 apt {
 arguments {
 moduleName project.getName();
 }
 }
 dependencies {
 apt 'com.alibaba:arouter-compiler:x.x.x'
 compile 'com.alibaba:arouter-api:x.x.x'
 ...
 }
添加注解
// 在支持路由的页面、服务上添加注解(必选)
 // 这是最小化配置,后面有详细配置
 @Route(path = "/test/1")
 public class YourActivity extend Activity {
 ...
 }
初始化SDK
ARouter.init(mApplication); // 尽量早,推荐在Application中初始化

发起路由操做 // 1. 应用内简单的跳转(经过URL跳转在'中阶使用'中) ARouter.getInstance().build("/test/1").navigation();

// 2. 跳转并携带参数
 ARouter.getInstance().build("/test/1")
 .withLong("key1", 666L)
 .withString("key3", "888")
 .navigation();

5、进阶用法

经过URL跳转

// 新建一个Activity用于监听Schame事件
 // 监听到Schame事件以后直接传递给ARouter便可
 // 也能够作一些自定义玩法,比方说改改URL之类的
 // http://www.example.com/test/1
 public class SchameFilterActivity extends Activity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 // 外面用户点击的URL
 Uri uri = getIntent().getData();
 // 直接传递给ARouter便可
 ARouter.getInstance().build(uri).navigation();
 finish();
 }
 }
 // AndroidManifest.xml 中 的参考配置
 <activity android:name=".activity.SchameFilterActivity">
 <!-- Schame -->
 <intent-filter>
 <data
 android:host="m.aliyun.com"
 android:scheme="arouter"/>
 <action android:name="android.intent.action.VIEW"/>
 <category android:name="android.intent.category.DEFAULT"/>
 <category android:name="android.intent.category.BROWSABLE"/>
 </intent-filter>
 <!-- App Links -->
 <intent-filter android:autoVerify="true">
 <action android:name="android.intent.action.VIEW"/>
 <category android:name="android.intent.category.DEFAULT"/>
 <category android:name="android.intent.category.BROWSABLE"/>
 <data
 android:host="m.aliyun.com"
 android:scheme="http"/>
 <data
 android:host="m.aliyun.com"
 android:scheme="https"/>
 </intent-filter>
 </activity>

使用ARouter协助解析参数类型

// URL中的参数会默认以String的形式保存在Bundle中
 // 若是但愿ARouter协助解析参数(按照不一样类型保存进Bundle中)
 // 只须要在须要解析的参数上添加 @Param 注解
 @Route(path = "/test/1")
 public class Test1Activity extends Activity {
 @Param // 声明以后,ARouter会从URL中解析对应名字的参数,并按照类型存入Bundle
 public String name;
 @Param
 private int age;
 @Param(name = "girl") // 能够经过name来映射URL中的不一样参数
 private boolean boy;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 name = getIntent().getStringExtra("name");
 age = getIntent().getIntExtra("age", -1);
 boy = getIntent().getBooleanExtra("girl", false); // 注意:使用映射以后,要从Girl中获取,而不是boy
 }
 }

开启ARouter参数自动注入(实验性功能,不建议使用,正在开发保护策略)

// 首先在Application中重写 attachBaseContext方法,并加入ARouter.attachBaseContext();
 @Override
 protected void attachBaseContext(Context base) {
 super.attachBaseContext(base);
 ARouter.attachBaseContext();
 }
 // 设置ARouter的时候,开启自动注入
 ARouter.enableAutoInject();
 // 至此,Activity中的属性,将会由ARouter自动注入,无需 getIntent().getStringExtra("xxx")等等

声明拦截器(拦截跳转过程,面向切面搞事情)

// 比较经典的应用就是在跳转过程当中处理登录事件,这样就不须要在目标页重复作登录检查
 // 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
 @Interceptor(priority = 666, name = "测试用拦截器")
 public class TestInterceptor implements IInterceptor {
 /**
 * The operation of this interceptor.
 *
 * @param postcard meta
 * @param callback cb
 */
 @Override
 public void process(Postcard postcard, InterceptorCallback callback) {
 ...
 callback.onContinue(postcard); // 处理完成,交还控制权
 // callback.onInterrupt(new RuntimeException("我以为有点异常")); // 以为有问题,中断路由流程
 // 以上两种至少须要调用其中一种,不然会超时跳过
 }
 /**
 * Do your init work in this method, it well be call when processor has been load.
 *
 * @param context ctx
 */
 @Override
 public void init(Context context) {
 }
 }

处理跳转结果

// 经过两个参数的navigation方法,能够获取单次跳转的结果
 ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
 @Override
 public void onFound(Postcard postcard) {
 ...
 }
 @Override
 public void onLost(Postcard postcard) {
 ...
 }
 });

自定义全局降级策略

// 实现DegradeService接口,并加上一个Path内容任意的注解便可
 @Route(path = "/xxx/xxx") // 必须标明注解
 public class DegradeServiceImpl implements DegradeService {
 /**
 * Router has lost.
 *
 * @param postcard meta
 */
 @Override
 public void onLost(Context context, Postcard postcard) {
 // do something.
 }
 /**
 * Do your init work in this method, it well be call when processor has been load.
 *
 * @param context ctx
 */
 @Override
 public void init(Context context) {
 }
 }

为目标页面声明更多信息

// 咱们常常须要在目标页面中配置一些属性,比方说"是否须要登录"之类的
 // 能够经过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,能够配置32个开关
 // 剩下的能够自行发挥,经过字节操做能够标识32个开关
 @Route(path = "/test/1", extras = Consts.XXXX)

使用ARouter管理服务(一) 暴露服务

/**
 * 声明接口
 */
 public interface IService extends IProvider {
 String hello(String name);
 }
 /**
 * 实现接口
 */
 @Route(path = "/service/1", name = "测试服务")
 public class ServiceImpl implements IService {
 @Override
 public String hello(String name) {
 return "hello, " + name;
 }
 /**
 * Do your init work in this method, it well be call when processor has been load.
 *
 * @param context ctx
 */
 @Override
 public void init(Context context) {
 }
 }

使用ARouter管理服务(二) 发现服务

1. 能够经过两种API来获取Service,分别是ByName、ByType
 IService service = ARouter.getInstance().navigation(IService.class); // ByType
 IService service = (IService) ARouter.getInstance().build("/service/1").navigation(); // ByName
 service.hello("zz");
 2. 注意:推荐使用ByName方式获取Service,ByType这种方式写起来比较方便,但若是存在多实现的状况时,SDK不保证能获取到你想要的实现

使用ARouter管理服务(三) 管理依赖

能够经过ARouter service包装您的业务逻辑或者sdk,在service的init方法中初始化您的sdk,不一样的sdk使用ARouter的service进行调用,
每个service在第一次使用的时候会被初始化,即调用init方法。
这样就能够告别各类乱七八糟的依赖关系的梳理,只要能调用到这个service,那么这个service中所包含的sdk等就已经被初始化过了,彻底不须要
关心各个sdk的初始化顺序。

6、更多功能

初始化中的其余设置

ARouter.openLog(); // 开启日志
ARouter.printStackTrace(); // 打印日志的时候打印线程堆栈

详细的API说明

// 构建标准的路由请求
 ARouter.getInstance().build("/home/main").navigation();
 // 构建标准的路由请求,并指定分组
 ARouter.getInstance().build("/home/main", "ap").navigation();
 // 构建标准的路由请求,经过Uri直接解析
 Uri uri;
 ARouter.getInstance().build(uri).navigation();
 // 构建标准的路由请求,startActivityForResult
 // navigation的第一个参数必须是Activity,第二个参数则是RequestCode
 ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
 // 直接传递Bundle
 Bundle params = new Bundle();
 ARouter.getInstance()
 .build("/home/main")
 .with(params)
 .navigation();
 // 指定Flag
 ARouter.getInstance()
 .build("/home/main")
 .withFlags();
 .navigation();
 // 以为接口不够多,能够直接拿出Bundle赋值
 ARouter.getInstance()
 .build("/home/main")
 .getExtra();
 // 使用绿色通道(跳过全部的拦截器)
 ARouter.getInstance().build("/home/main").greenChannal().navigation();

资料免费领取方式:如今关注我而且加入群聊
群号:1018342383

image.png

相关文章
相关标签/搜索