一 ARouter的使用步骤
第一步 添加依賴
因为ARouter在各个模块都会用到,因此可以在ModuleBase 里面添加依赖。
api 'com.alibaba:arouter-api:1.4.0'
第二步 引入注解处理器
ARouter 使用了编译时注解,这里需要在各个子模块引入处理器。
annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
为编译期间生成路径映射。同时也需要在各子模块的build中加入
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
这段配置主要是给编译注解处理器传递一个参数。
第三步 初始化ARouter:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initRouter(this);
}
public static void initRouter(Application application) {
if (BuildConfig.DEBUG) {
// 打印日志
ARouter.openLog();
// 开启调试模式
ARouter.openDebug();
}
ARouter.init(application);
}
第四步 添加注解:
@Route(path = "/test/testActivity")
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public static void startLoginActivity() {
ARouter.getInstance().build("/test/testActivity2").navigation();
}
}
以上就是基本使用过程,更多详情大家可以查阅相关api。 我们下面进入ARouter的源码,分析其原理。
二 ARouter初始化
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
//初始化,内部就是获取到动态生成的类
hasInit = _ARouter.init(application);
if (hasInit) {
//初始化之后
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
下面我们看下init 方法
2.1 初始化Arouter.init
protected static synchronized boolean init(Application application) {
mContext = application;
//executor 是个线程池
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
return true;
}
这里调用了LogisticsCenter的init 方法。
LogisticsCenter 主要由两个功能
- 搜集所有的组件信息并且注册到项目中(把组件信息读到内存中),方便后续的调用。所谓组件可以理解Path 注解生成的类。
- 统一封装Postcard逻辑。
/**
* LogisticsCenter init, load all metas in memory. Demand initialization
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
//
loadRouterMap();
if (registerByPlugin) {
//使用插件初始化了,就不需要继续往下面走了
logger.info(TAG,
"Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generate by arouter-compiler.
//或者指定包路径下面的所有类
routerMap = ClassUtils.getFileNameByPackageName(mContext,
ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
//缓存数据
context.getSharedPreferences(AROUTER_SP_CACHE_KEY,
Context.MODE_PRIVATE).edit().putStringSet(
AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context);
// Save new version name when router map update finish.
} else {
logger.info(TAG, "Load router map from cache.");
//从缓存里面获取
routerMap = new HashSet<>
(context.getSharedPreferences(
AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
.getStringSet(AROUTER_SP_KEY_MAP,
new HashSet<String>()));
}
logger.info(TAG, "Find router map finished, map size = " +
routerMap.size() + ", cost " +
(System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();
//分类加载进入仓库里面,Warehouse
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME
+ SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor()
.newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup(Class.forName(className)
.getConstructor().newInstance()))
.loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className)
.getConstructor().newInstance()))
.loadInto(Warehouse.providersIndex);
}
}
}
xxxxx 删除部分代码
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
首先调用了loadRouterMap
private static void loadRouterMap() {
registerByPlugin = false;
//auto generate register code by gradle plugin: arouter-auto-register
// looks like below:
// registerRouteRoot(new ARouter..Root..modulejava());
// registerRouteRoot(new ARouter..Root..modulekotlin());
}
因为初始化的时候需要变遍历dex文件找到我们需要要的类,这个阶段是很费时间的,因此采用在插件,在编译的时候遍历class文件,然后修改字节码。最终会在loadRouterMap这里插入初始化的代码。插入的代码就是方法中注释的代码。
插入的方法registerRouteRoot 会将registerByPlugin 设置为true。ARouter 通过gradle 插件修改字节码的实现我们放到后面介绍。
这里分析registerRouteRoot 为false的情况。
registerRouteRoot 为false 的时候会进入else 分支,这里主要是通过ClassUtils 来获取所有的在编译期间根据注解生成的类。
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
*
* @param context U know
* @param packageName 包名
* @return 所有class的集合
*/
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
//获取所有的dex 文件,dex内部包含编译后的源码
List<String> paths = getSourcePaths(context);
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
//遍历所有的dex文件
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
//获取dex 里面的文件。
//这里可以将dex看成是一个压缩文件,里面有很多的class 文件。
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
//获取类的名字
String className = dexEntries.nextElement();
//该类处于com.alibaba.android.arouter.routes 包下面。
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();
}
}
});
}
//等待所有的dex文件被遍历完。
parserCtl.await();
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
dex是Android平台上(Dalvik虚拟机)的可执行文件, 相当于Windows平台中的exe文件, 每个Apk安装包中都有dex文件, 里面包含了该app的所有源码, 通过反编译工具可以获取到相应的java源码。
早期的是时候一个apk里面只有一个dex 文件后来andorid 支持分包之后,一个apk可以包含多个dex 文件。getSourcePaths 获取所有的dex的路径,感兴趣的可以自己研究一下。
因为整个遍历的过程是很耗费时间的因此在获取到所有处于com.alibaba.android.arouter.routes 包下面的类之后,会将这些类的名字保存在sp 文件里面,当这次读取的时候就直接从sp 文件读取。加快速度。
之后会遍历所有的类,根据类对应的注解的不同,通过反射创建一个对象然后将之加入到不同的仓库内。这里主要是Route 与Interceptor 的注解。
最终创建的对象都保存在了Warehouse 里面。
2.1 初始化_ARouter.afterInit
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
内部会最终调用到_Arouter.build
/**
* Build postcard by path and default group
*/
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
//重定向服务
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
//extractGroup 是通过path截获group
//例如path="/app/main" app就是group
return build(path, extractGroup(path));
}
}
/**
* Build postcard by path and group
*/
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return new Postcard(path, group);
}
}
如果我们的应应用中没有实现PathReplaceService这个接口,则pService=null。
可以看到此时刚创建PostCard的时候 ,PostCard仅仅只有path,group字段,其余的字段都还没有赋值呢。
build 创建一个PostCard 之后紧接着调用其navigation 方法。
最终会调用到_ARouterd的navigation ,
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
xxx
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(xxx)
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
这里主要是调用LogisticsCenter.completion 方法。
/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should complete by this method.
*/
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// Load route and cache it into memory, then delete from metas.
//第一次调用走这个分支。
try {
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
//创建IRouteGroup并添加到Warehouse.routes
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
//递归调用,此时会走下面那个分支。
completion(postcard); // Reload
}
} else {
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
xxx
}
switch (routeMeta.getType()) {
case PROVIDER:
//进入这里
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // There's no instance of this provider
IProvider provider;
try {
//通过反射创建一个对象
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
第一次进入的时候routeMeta为null 然后通过发射创建一个IRouteGroup并将之保存在Warehouse.routes里面。 IRouteGroup 实际类是编译期间根据生成注解生成的类,里面包含被注解的类的名字。
例如
之后递归调用completion 方法,递归进入之后routeMeta就不是null 会进入else 分支。
这里实际就是获取到被注解的类的构造方法通过反射创建一个对象。
自此completion 方法就分析完了,这里主要是获取到被注解的类的构造方法并创建一个对象保存在WareHouse 里面。
当completion 知心完之后会调用_navigation 方法。
_navigation 就不在展开介绍了,对于Proiver 类型而言其会返回postcard.getProvider()的返回值。
总结:
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
返回的对象就是通过Route 注解的路径为"/arouter/service/interceptor"的类一个对象。
"/arouter/service/interceptor" 是ARouter 定义的一个特殊的路径,
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService
InterceptorServiceImpl 主要用来创建用户自定义的拦击器。
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
try {
//反射创建拦截器
IInterceptor iInterceptor =
interceptorClass.getConstructor().newInstance();
//初始化这个拦截器
iInterceptor.init(context);
//缓存这个拦截器
Warehouse.interceptors.add(iInterceptor);
} catch (Exception ex) {
throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
}
}
有感兴趣的可以查看一下InterceptorServiceImpl。
ARouter 的初始化到此就介绍完了,总结一下就是遍历所有的dex 文件获取到所有在编译期间生成的类并保存在爱WareHouse 里面。之后找到注解为@Route(path = "/arouter/service/interceptor")的类也就是InterceptorServiceImpl类然后创建一个对象,调用这个类的init方法,该方法内部会创建用户注册的Interceptor。
二 页面跳转
ARouter.getInstance().build("/test/test1").navigation();
这个方法的流程的大部分在初始的过程中都介绍了,
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
xxx
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
if (!postcard.isGreenChannel()) {
//会进入这个分支
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
//执行完用户自定义的拦截器之后进入这里
_navigation(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
//
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
页面跳转的时候postcard.isGreenChannel()返回的是false,因此会进入执行用户注册的拦截器,当用户注册的拦截器执行完之后调用_navigation 方法。
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// Navigation in main looper.
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
} else {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
break;
xxxx
default:
return null;
}
return null;
}
private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
此处会进入ACTIVITY 分支,到了这里就是大家都熟悉的页面跳转也就死通过Intent 来跳转页面。