ARoute
前言
最近公司有一个总路由的架构需求 ,这里看到了ARoute,路由的思想感觉很有意思 ,简单翻阅了源码总结下来
使用
plugins { id 'kotlin-kapt' }
android {
...
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
implementation 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
MainActivity
//执行跳转逻辑
ARouter.getInstance().build(ARouteTable.HOME)
.withString("name", "小度小度")
.navigation()
HomeActivity
@Route(path = ARouteTable.HOME)
class HomeActivity : ComponentActivity() {
@Autowired
@JvmField
var name: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
}
}
原理分析
生成 (编译时)
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
//
routes.put("app", ARouter$$Group$$app.class);
}
}
public class ARouter$$Group$$app implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
// /app/home 就是我们添加的path , RouteMeta会将我们的参数 跳转的Activity构建出来
atlas.put("/app/home", RouteMeta.build(RouteType.ACTIVITY, HomeActivity.class, "/app/home", "app", new java.util.HashMap<String, Integer>(){{put("name", 8); }}, -1, -2147483648));
}
}
public class ARouter$$Providers$$app implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
}
}
经过编译之后 Aroute 通过APT ,Javapont 动态生成了以上三个文件
注入 (运行时)
我们先看在Application中的init函数
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
我们接着查看LogisticsCenter的init
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//load by plugin first
loadRouterMap();
//如果是通过plugin注册的,也就是动态生成了代码
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
...
//如果是新安装的apk 或者是debug模式
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
//根据包获取所有生成的Class文件
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 finishes.
} 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();
for (String className : routerMap) {
//如果是以 "com.alibaba.android.arouter.routes" + "ARouter" + "$$Root" 开头的
//1 com.alibaba.android.arouter.routes.ARouter$$Root$$app 这是我们的全路径
//这里可以注意一下 ARoute自己也有一个文件满足这个条件 也会执行
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
//反射调用函数 loadInto
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} //如果是Interceptors
else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
//反射调用loadInto
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
}//如果是Providers 也是反射调用
else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
logger.debug(TAG, 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 e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
- 这里其实我们这里就是执行了
routes.put("app", ARouter$$Group$$app.class);这段代码 ,将ARouter$$Group$$app.class放入app的表中
我们再看一看是获取class文件的
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
//根据上下文获取所有的资源路径
List<String> paths = getSourcePaths(context);
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
//如果是以.zip结尾的
if (path.endsWith(EXTRACTED_SUFFIX)) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
//解析成dexfile文件
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
//如果是以 com.alibaba.android.arouter.routes 开头的就加入classNames中
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();
}
}
});
}
parserCtl.await();
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
我们看如何获取所有的资源路径
public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
File sourceApk = new File(applicationInfo.sourceDir);
List<String> sourcePaths = new ArrayList<>();
//添加默认的apk地址
sourcePaths.add(applicationInfo.sourceDir);
//the prefix of extracted file, ie: test.classes
String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
// 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
// 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
if (!isVMMultidexCapable()) {
//如果启用了MultiDex 说明有多个包
//the total dex numbers
//从这个Preferences中获取dex的数量
int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
//for each dex file, ie: test.classes2.zip, test.classes3.zip...
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName);
if (extractedFile.isFile()) {
//一个一个添加到sourcePaths中
sourcePaths.add(extractedFile.getAbsolutePath());
//we ignore the verify zip part
} else {
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
}
}
if (ARouter.debuggable()) { // Search instant run support only debuggable
sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
}
return sourcePaths;
}
到目前位置我们就已经拿到了所有的className 。之后我们就可以用这些className反射创建调用内部的方法
获取(调用时)
接下来我们查看具体的调用
ARouter.getInstance().build(path)
.withString("name", "小度小度")
.navigation()
先看build
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);
}
return build(path, extractGroup(path), true);
}
}
protected Postcard build(String path, String group, Boolean afterReplace) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
if (!afterReplace) {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
}
//这里会把当前的path 与 group存起来
//path = /app/home group = app
return new Postcard(path, group);
}
}
上面的build生成了一个Postcard函数
withString其实就是往Postcard中的Bundle中存数据
紧接着 我们看navigation函数
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// Pretreatment failed, navigation canceled.
return null;
}
// 默认取自己的如果没有就是全局的context
postcard.setContext(null == context ? mContext : context);
try {
//这里完成明信片配置
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
if (null != callback) {
callback.onLost(postcard);
} else {
// No callback for this invoke, then we use the global degrade service.
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() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
//我们的Activity跳转就在这里
_navigation(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(postcard, requestCode, callback);
}
return null;
}
我们先看postcard的配置
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) {
//判断有没有这个组
if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
try {
//这里执行 生成类 Group的方法
addRouteGroupDynamic(postcard.getGroup(), null);
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
//重新执行一次 ,第二次RouteMeta不为空了
completion(postcard); // Reload
}
} else {
//第二次 对postcard进行赋值
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.
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
}
// Save params name which need auto inject.
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// Save raw uri
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must implement IProvider
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) {
logger.error(TAG, "Init provider failed!", e);
throw new HandlerException("Init provider failed!");
}
}
postcard.setProvider(instance);
//这里 Provider 或者 Fragment 会直接设置绿色通道 不走拦截器
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
我们看一看Group的解析
public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (Warehouse.groupsIndex.containsKey(groupName)){
//通过app 获取到ARouter$$Group$$app这个类 ,然后反射调用loadInto方法
Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
//删除
Warehouse.groupsIndex.remove(groupName);
}
// cover old group.
if (null != group) {
group.loadInto(Warehouse.routes);
}
}
public class ARouter$$Group$$app implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
//将path内容放入map中
atlas.put("/app/home", RouteMeta.build(RouteType.ACTIVITY, HomeActivity.class, "/app/home", "app", new java.util.HashMap<String, Integer>(){{put("name", 8); }}, -1, -2147483648));
}
}
到这里就 completion就完成了
接着我们开始_navigation
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
switch (postcard.getType()) {
//我们执行的就是Activity
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (0 != flags) {
intent.setFlags(flags);
}
// Non activity, need FLAG_ACTIVITY_NEW_TASK
if (!(currentContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// Navigation in main looper.
//上面从postcard中取出参数 然后在主线程中完成跳转
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
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 ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
到这里一个基础的Activity跳转就分析完成了
拓展
我们在开发中经常会自动注入参数的功能
@Autowired
@JvmField
var name: String? = null
ARouter.getInstance().inject(this)
实现原理
它会在编译期生成一个文件 ,内部从Intent中获取数据
public class HomeActivity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
//这就是Inject中传过来的参数
HomeActivity substitute = (HomeActivity)target;
//调用本类的getIntent()
substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
}
}
我们看一看调用
static void inject(Object thiz) {
AutowiredService autowiredService = ((AutowiredService)
//构建了一个path为 /arouter/service/autowired的路由 ,然后navigation
ARouter.getInstance().build("/arouter/service/autowired").navigation());
if (null != autowiredService) {
autowiredService.autowire(thiz);
}
}
在这之前,我们需要回到init中
我们还记得上面提到的 生成文件的全路径为 com.alibaba.android.arouter.routes.ARouter$$Root$$app
其实ARoute自己内部也有一个文件 com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi
我们看一看它的文件
public class ARouter$$Providers$$arouterapi implements IProviderGroup {
public ARouter$$Providers$$arouterapi() {
}
public void loadInto(Map<String, RouteMeta> providers) {
//会放入一个AutowiredServiceImpl 与 InterceptorServiceImpl放到我们的providers中
providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
}
}
//这是group文件
public class ARouter$$Group$$arouter implements IRouteGroup {
public ARouter$$Group$$arouter() {
}
public void loadInto(Map<String, RouteMeta> atlas) {
//这个地方熟悉不 /arouter/service/autowired 就是我们inject时填入的path 早已存在了
atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
}
}
public class ARouter$$Root$$arouterapi implements IRouteRoot {
public ARouter$$Root$$arouterapi() {
}
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("arouter", arouter.class);
}
}
这里应该大家就明白了 ,ARoute默认放入了它自己的文件在对应的目录下,当生成文件执行的时候 将这个默认文件也执行
这样就可以提前将AutowiredServiceImpl类型的参数放到RouteMeta中
下面我们接着分析,我们这里获取到了实现类 ,调用autowire方法
public void autowire(Object instance) {
doInject(instance, null);
}
private void doInject(Object instance, Class<?> parent) {
Class<?> clazz = null == parent ? instance.getClass() : parent;
//获取到我们上面生成的类
//这个 getSyringe 这里用了缓存,从缓存取出对象
ISyringe syringe = getSyringe(clazz);
if (null != syringe) {
//执行inject 也就是我们的生成类被执行
syringe.inject(instance);
}
Class<?> superClazz = clazz.getSuperclass();
// has parent and its not the class of framework.
if (null != superClazz && !superClazz.getName().startsWith("android")) {
doInject(instance, superClazz);
}
}
这里一切就清楚了
总结
总体的步骤分为以下几步
- 生成对应的
route文件 ,root对应group,也就是app对应/app/home …等一堆path init的时候 通过package取出所有的 dex文件 然后获取到className,最后通过反射 调用Class内部的方法 ,也就是生成类中的方法build的时候 ,获取当前path对应的参数 装入到postcard中navigation的时候, 拿到postcard解析出所有的参数 ,进行组装跳转
最后文章中有问题的地方,欢迎大家多多评论指正,提前祝所有观看文章的朋友们新年快乐 😉
参考链接: Arouter从使用到原理