一.简介
组件化中,每个Module之间的页面跳转是个问题。因为每个Module不可能知道其他Module的Activity路径。所以我们没有办法像普通的工程一样使用Intent方式跳转。那么组件化开发中每个Module之间如何完成页面跳转呢?
路由,对的我们可以使用路由完成这个工作。市面上常见的路由有阿里的ARouter。
二.ARouter集成
1.组件的Module的Gradle文件添加一下依赖
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
//ARouter编译的时候需要的module名字
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
...
implementation 'com.alibaba:arouter-api:1.5.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.1'
}
注意:kotlin配置
plugins {
...
id 'kotlin-kapt'
}
dependencies {
...
implementation 'com.alibaba:arouter-api:1.5.1'
kapt 'com.alibaba:arouter-compiler:1.5.1'
}
2.初始化(一般在Application中初始化)
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
...
ARouter.init(this);
...
}
...
}
3.目标页配置跳转注解
@Route(path = "/anim/AnimActivity")
public class AnimActivity extends AppCompatActivity implements View.OnClickListener {
....
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
....
}
....
}
4.跳转页面跳转
<1> 普通跳转
ARouter.getInstance().build("/anim/AnimActivity").navigation();
<2> 基本传值跳转
ARouter.getInstance().build("/anim/AnimActivity").withInt("IntKey", 12).withString("StringKey", "张三").navigation();
接收
Intent intent = getIntent();
int i = intent.getIntExtra("IntKey", -1);
String s = intent.getStringExtra("StringKey");
Log.d("AnimActivity", "i----:" + i);
Log.d("AnimActivity", "s----:" + s);
结果
D/AnimActivity: i----:12
D/AnimActivity: s----:张三
<3> Bundle传值
Bundle bundle = new Bundle();
bundle.putInt("IntKey", 20);
bundle.putString("StringKey", "李四");
bundle.putLong("LongKey", 123456);
bundle.putDouble("DoubleKey", 123.456);
ARouter.getInstance().build("/anim/AnimActivity").withBundle("BundleKey", bundle).navigation();
接收
Intent intent = getIntent();
Bundle bundle = intent.getBundleExtra("BundleKey");
int i = bundle.getInt("IntKey");
String s = bundle.getString("StringKey");
long l = bundle.getLong("LongKey");
double d = bundle.getDouble("DoubleKey");
Log.d("AnimActivity", "i----:" + i);
Log.d("AnimActivity", "s----:" + s);
Log.d("AnimActivity", "l----:" + l);
Log.d("AnimActivity", "d----:" + d);
结果
D/AnimActivity: i----:20
D/AnimActivity: s----:李四
D/AnimActivity: l----:123456
D/AnimActivity: d----:123.456
说明:
ARouter页面跳转时通过.withXXX方法给我们提供了类似于原生Intent的所有带参数的传值。接收的方式和原生Intent跳转一样。
5.ARouter的其他注解
刚刚配置目标Activity时使用了ARouter的@Route注解,使用path配置目标Activity的路径。其实ARouter为我们配置了很多的注解。
<1> @Route注解
Route注解是作用在类上面,里面会携带path路径。
<2> @Interceptor注解
它能拦截你的路由,什么时候让路由通过什么时候让路由不通过,项目中如果需要拦截,可以使用此注解。
<3> @Autowired注解
Autowired注解是定义在目标页的属性上,通常用来定义目标页接收的值,还可以定义上面说到的接收服务方。
<4> ...
6.总结
到此,ARouter的基本使用就完成了,注意是基本使用。因为ARouter的功能还有很多,比如ARouter实现了Gradle插件路由的自动加载功能(它主要是在编译期通过gradle插装把需要依赖arouter注解的类自动扫描到arouter的map管理器里面),比如配置混淆规则等等。
详情参考官网:https://github.com/alibaba/ARouter
三.ARouter源码解析
1.简介
在上述使用Arouter过程中,我们的Module依赖了arouter-api、arouter-compiler,两个Jar包。在 编译期 arouter-compiler通过扫描项目中用到的Route、Autowired、Interceptor等注解来生成对应的class文件。然后将这些Class文件放到Map中。跳转的时候根据Key(就是我们@Route时使用path确定的路径,比如path = "/anim/AnimActivity")。
其实,Activity,Service,等等四大组件的跳转,使用系统Intent的方式完全可以。比如
Intent intent=new Intent(MainActivity.this, RxBusActivity.class);
intent.putExtra("bean",student);
startActivity(intent);
但是,组件化中。不同的Module之间一般没有相互依赖。所以拿不到目标的Class<?>对象。所以我们在组件化中不可能使用Intent的方式跳转。下面我们从源码层面看一下ARouter是如何做到的。
2.源码解析之 初始化
ARouter.init(this);
调用ARouter类的init方法
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info("ARouter::", "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info("ARouter::", "ARouter init over.");
}
}
我们重点看下面一行代码
hasInit = _ARouter.init(application);
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info("ARouter::", "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
接下来,我们看下面一行代码
LogisticsCenter.init(mContext, executor);
public static synchronized void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
loadRouterMap();
//如果使用了Gradle配置路由列表 直接打印不继续执行
if (registerByPlugin) {
ARouter.logger.info("ARouter::", "Load router map by arouter-auto-register plugin.");
} else {
//没有使用Gradle配置路由列表 也就是使用我们上面的方法
Object routerMap;
//debuggable=false或者没有新的版本 SharedPreferences取出HashSet
if (!ARouter.debuggable() && !PackageUtils.isNewVersion(context)) {
ARouter.logger.info("ARouter::", "Load router map from cache.");
routerMap = new HashSet(context.getSharedPreferences("SP_AROUTER_CACHE", 0).getStringSet("ROUTER_MAP", new HashSet()));
} else {
//创建新的集合存放到SharedPreferences中
ARouter.logger.info("ARouter::", "Run with debug mode or new install, rebuild router map.");
routerMap = ClassUtils.getFileNameByPackageName(mContext, "com.alibaba.android.arouter.routes");
if (!((Set)routerMap).isEmpty()) {
context.getSharedPreferences("SP_AROUTER_CACHE", 0).edit().putStringSet("ROUTER_MAP", (Set)routerMap).apply();
}
PackageUtils.updateVersion(context);
}
ARouter.logger.info("ARouter::", "Find router map finished, map size = " + ((Set)routerMap).size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();
Iterator var5 = ((Set)routerMap).iterator();
while(var5.hasNext()) {
String className = (String)var5.next();
if
(className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Root")) {// 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中
((IRouteRoot)((IRouteRoot)Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if // 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中
(className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Interceptors")) {
((IInterceptorGroup)((IInterceptorGroup)Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if // 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中
(className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Providers")) {
((IProviderGroup)((IProviderGroup)Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
ARouter.logger.info("ARouter::", "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
if (Warehouse.groupsIndex.size() == 0) {
ARouter.logger.error("ARouter::", "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
ARouter.logger.debug("ARouter::", String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception var7) {
throw new HandlerException("ARouter::ARouter init logistics center exception! [" + var7.getMessage() + "]");
}
}
小结1
不是通过Gradle自动加载路由列表的情况下:
<1> 通过ClassUtils.getFileNameByPackageName获取到apk中前缀为com.alibaba.android.arouter.routes的类,然后压缩成zip文件,然后通过DexFile.loadDex转化成DexFile对象,然后过滤出com.alibaba.android.arouter.routes前缀的class并返回。
<2> 拿到了需要的class类后,放到sp里面,方便下次不去扫描apk拿class,更新版本号。
<2.1> 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中。
<2.2> 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中。
<2.3> 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中。
3.源码解析之 配置目标页面路径
@Route(path = "/anim/AnimActivity")
public class AnimActivity extends AppCompatActivity {
...
}
配置后,编译后,在具体Module的build目录下会自动生成ARouter相关的类。如图
自动生成的ARouter$$Group$$anim类
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$anim implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/anim/AnimActivity", RouteMeta.build(RouteType.ACTIVITY, AnimActivity.class, "/anim/animactivity", "anim", null, -1, -2147483648));
}
}
也就是说,ARouter自动把使用@Route注解中配置的path当成Key,然后把目标Activity当成Value存放在Map中。
小结2
ARouter会在apt阶段,生成的class类有Arouter$$Root$$模块名,模块名是在gradle中配置的arg("AROUTER_MODULE_NAME", "${project.getName()}")属性,把所有组的信息放到传进来的map中,这个组是通过我们在Route注解的path属性拆分的,比如定义/anim/AnimActivity,会认为组名是anim,Arouter$$Root$$组名放的是该组下,所有的路由表信息,包括route、provider注解通过RouteMeta包装对应的class类信息,provider注解会放在Arouter$$Providers$$模块名下面。
4.源码解析之 跳转页面
ARouter.getInstance().build("/anim/AnimActivity").withBundle("BundleKey", bundle).navigation();
public Object navigation() {
return this.navigation((Context)null);
}
最终调用
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
PretreatmentService pretreatmentService = (PretreatmentService)ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
return null;
} else {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException var8) {
logger.warning("ARouter::", var8.getMessage());
if (debuggable()) {
this.runInMainThread(new Runnable() {
public void run() {
Toast.makeText(_ARouter.mContext, "There's no route matched!\n Path = [" + postcard.getPath() + "]\n Group = [" + postcard.getGroup() + "]", 1).show();
}
});
}
if (null != callback) {
callback.onLost(postcard);
} else {
DegradeService degradeService = (DegradeService)ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
if (!postcard.isGreenChannel()) {
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
public void onContinue(Postcard postcardx) {
_ARouter.this._navigation(context, postcardx, requestCode, callback);
}
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
_ARouter.logger.info("ARouter::", "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
return null;
} else {
return this._navigation(context, postcard, requestCode, callback);
}
}
}
private Object _navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch(postcard.getType()) {
case ACTIVITY:
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
intent.setFlags(268435456);
}
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
this.runInMainThread(new Runnable() {
public void run() {
_ARouter.this.startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
return null;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment)instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment)instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception var11) {
logger.error("ARouter::", "Fetch fragment instance error, " + TextUtils.formatStackTrace(var11.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
}
小结3
最终,还是通过Intent的方式完成传值跳转。通过上述ARouter在编译期间的Map集合,获取相应的Class文件。最终完成跳转。