组件化基础ARouter(一、启动Activity)
一、ARouter概述
ARouter是一个用于帮助Android App进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。ARouter的典型应用场景有:
从外部URL映射到内部页面,以及参数传递与解析;
跨模块页面跳转,模块间解耦;
拦截跳转过程,处理登陆、埋点等逻辑;
跨模块API调用,通过控制反转来做组件解耦;
本篇主要介绍ARouter的用法之一:跨模块跳转启动Activity。
二、ARouter基础用法
首先,通过一个简单的例子来介绍ARouter的基础用法:在MainActivity中有一个FirstButton,FirstButton点击后打开模块first_library中的FirstActivity,在FirstActivity中有一个SecondButton,SecondButton点击后打开模块second_library中的SecondActivity:
在不使用Arouter的情况下,简单的实现方式是:
(1)在app的build.gradle中依赖first_library,在first_library模块中依赖second_library:
// app>build.gradle dependencies { implementation project(':first_library') } 复制代码
// ModuleFirst>build.gradle dependencies { implementation project(':second_library') } 复制代码
(2)在MainActivity中实现跳转:
public class MainActivity extends Activity implements View.OnClickListener { @Override public void onClick(View v) { if (v.getId() == R.id.first_button) { Intent intent = new Intent(this, FirstActivity.class); startActivity(intent); } else if (v.getId() == R.id.second_button) { Intent intent = new Intent(this, SecondActivity.class); startActivity(intent); } } } 复制代码
但是当在大型项目中,存在大量的跳转逻辑,这种直接的模块依赖造成依赖关系耦合。
另外,假设first_library模块中的Activity_A1要打开second_library模块中的Activity_B1,second_library模块中的Activity_B1又要打开irst_library模块中的Activity_A2,这样first_library模块依赖second_library模块,而second_library模块又依赖first_library模块,会造成循环依赖:
Circular dependency between the following tasks: :first_library:generateDebugRFile \--- :second_library:generateDebugRFile \--- :first_library:generateDebugRFile (*) 复制代码
下面介绍ARouter如何在保证模块间解耦的情况下,实现跨模块页面跳转。
2.1 添加依赖和配置
apply plugin: 'kotlin-kapt' android { defaultConfig { javaCompileOptions { annotationProcessorOptions { // ARouter参数 arguments = [AROUTER_MODULE_NAME: project.getName()] } } } } dependencies { implementation 'com.alibaba:arouter-api:1.5.2' // 项目中如果使用了kotlin,则需要使用kapt关键字使用Annotation Processor // java代码使用annotationProcessor关键字即可 kapt 'com.alibaba:arouter-compiler:1.5.2' } 复制代码
2.2 添加注解
FirstActivity上添加@Route注解,path为"/first/activity":
@Route(path = "/first/activity") public class FirstActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); secondButton = findViewById(R.id.second_button); secondButton.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.second_button) { ARouter.getInstance().build("/second/activity").navigation(); } } } 复制代码
SecondActivity上添加@Route注解,path为"/second/activity":
@Route(path = "/second/activity") public class SecondActivity extends AppCompatActivity {} 复制代码
2.3 初始化SDK
if (BuildConfig.DEBUG) { // Debug包必须开启调试模式!否则会有各种问题(线上版本需要关闭,否则有安全风险) ARouter.openDebug(); ARouter.openLog(); } // 尽可能早,推荐在Application中初始化 ARouter.init(getApplication()); 复制代码
2.4 发起路由操作
public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override public void onClick(View v) { if (v.getId() == R.id.first_button) { ARouter.getInstance().build("/first/activity").navigation(); } } } 复制代码
三、ARouter源码分析
下面通过源码来分析ARouter是如何实现第二节中的功能的。第二节中ARouter是通过三步(添加注解、初始化SDK、发起路由)来实现的,相应地,本节分三小节:注解处理APT、初始化、发起路由来分析每步的主要工作。
3.1 注解处理APT
在2.2中添加注解@Route(path = "/second/activity")后,ARouter是使用2.1小节中声明的arouter-compiler来处理注解,自动生成代码,在此基础上实现路由跳转的功能。关于Annotation Processor的基知识可参考:Annotation Processor简单用法。
ARouter APT自动生成三个class文件(位于/first_library/build/generated/source/kapt/debug/com/alibaba/android/arouter/routes目录下):
这三个class分别实现了IRouteGroup、IRouteRoot、IProviderGroup,且类名都以ARouter$开头,都位于com.alibaba.android.arouter.routes包下:
public class ARouter$$Group$$second implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/second/activity", RouteMeta.build(RouteType.ACTIVITY, SecondActivity.class, "/second/activity", "second", null, -1, -2147483648)); } } public class ARouter$$Root$$second_library implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("second", ARouter$$Group$$second.class); } } public class ARouter$$Providers$$second_library implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } } 复制代码
后面再分析自动生成的代码有什么用途。
3.2 初始化SDK
在使用ARouter的路由功能前,需要先初始化SDK:
ARouter.init(getApplication()); 复制代码
下面分析初始化ARouter SDK的源码如下:
3.2.1 ARouter
/** * ARouter门面 */ public final class ARouter { private volatile static boolean hasInit = false; public static void init(Application application) { if (!hasInit) { // ARouter是门面模式,代码实现在_ARouter中,下面接着分析_ARouter hasInit = _ARouter.init(application); if (hasInit) { _ARouter.afterInit(); } } } } 复制代码
3.2.2 _ARouter
下面接着分析_ARouter源码:
final class _ARouter { private volatile static boolean hasInit = false; private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance(); private static Handler mHandler; private static Context mContext; private static InterceptorService interceptorService; protected static synchronized boolean init(Application application) { mContext = application; // 主要初始化逻辑都在LogisticsCenter中 LogisticsCenter.init(mContext, executor); hasInit = true; mHandler = new Handler(Looper.getMainLooper()); return true; } static void afterInit() { interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation(); } } 复制代码
3.2.3 LogisticsCenter.init()
public class LogisticsCenter { private static Context mContext; static ThreadPoolExecutor executor; private static boolean registerByPlugin; public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; executor = tpe; //load by plugin first loadRouterMap(); if (registerByPlugin) { logger.info(TAG, "Load router map by arouter-auto-register plugin."); } else { // 1.关键代码routeMap Set<String> routerMap; // It will rebuild router map every times when debuggable. // 2.debug模式或者PackageUtils判断本地路由为空或有新版本 if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) { // 3.获取ROUTE_ROOT_PAKCAGE(com.alibaba.android.arouter.routes)包名下的所有类 // arouter-compiler根据注解自动生成的类都放在com.alibaba.android.arouter.routes包下 routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); // 4.建立routeMap后保存到sp中,下次直接从sp中读取StringSet;逻辑见else分支; if (!routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); } // 5.更新本地路由的版本号 PackageUtils.updateVersion(context); // Save new version name when router map update finishes. } else { routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())); } // 6.获取routeMap后,根据路由类型注册到对应的分组里 for (String className : routerMap) { if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { // 7.加载root,类名以SUFFIX_ROOT(com.alibaba.android.arouter.routes.ARouter$$Root)开头 // 以<String,Class>添加到HashMap(Warehouse.groupsIndex)中 ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { // 8.加载interceptorMeta,类名以SUFFIX_INTERCEPTORS(com.alibaba.android.arouter.routes.ARouter$$Interceptors)开头 // 以<String,IInterceptorGroup>添加到UniqueKeyTreeMap(Warehouse.interceptorsIndex)中;以树形结构实现顺序拦截 ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { // 9.加载providerIndex,类名以SUFFIX_PROVIDERS(com.alibaba.android.arouter.routes.ARouter$$Providers)开头 // 以<String,IProviderGroup>添加到HashMap(Warehouse.groupsIndex)中 ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } } } } 复制代码
3.2.4 Warehouse
Warehouse源码比较简单:
class Warehouse { // Cache route and metas static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); static Map<String, RouteMeta> routes = new HashMap<>(); // Cache provider static Map<Class, IProvider> providers = new HashMap<>(); static Map<String, RouteMeta> providersIndex = new HashMap<>(); // Cache interceptor static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]"); static List<IInterceptor> interceptors = new ArrayList<>(); } 复制代码
结合3.1小节自动生成的代码来分析,Warehouse.groupsIndex中存放的key就是@Route(path = "/second/activity")注解中所指定的path,value就是class:
public class ARouter$$Group$$second implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/second/activity", RouteMeta.build(RouteType.ACTIVITY, SecondActivity.class, "/second/activity", "second", null, -1, -2147483648)); } } public class ARouter$$Providers$$second_library implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } } 复制代码
3.2.5 总结
Arouter的初始化ARouter.init(getApplication()),过程如下:
3.3 发起路由操作
在2.3小节已经给出了发起路由操作的简单用法:
ARouter.getInstance().build("/first/activity").navigation(); 复制代码
3.3.1 单例
接下来就从入口处分析发起路由的源码实现:
public final class ARouter { // 单例模式 public static ARouter getInstance() { if (!hasInit) { throw new InitException("ARouter::Init::Invoke init(context) first!"); } else { if (instance == null) { synchronized (ARouter.class) { if (instance == null) { instance = new ARouter(); } } } return instance; } } public Postcard build(String path) { // _ARouter也是单例模式 return _ARouter.getInstance().build(path); } } 复制代码
同3.2小节,ARouter是门面模式,相应地ARouter.getInstance()的单例模式的实际调用也是在_ARouter.getInstance()中, build(String path)、navigation()等代码实际实现都在_ARouter中,后面不再单独说明。
3.3.2 ARouter.build()
继续分析_ARouter.getInstance().build()方法,方法返回Postcard对象,该对象表示一次路由操作所需的全部信息:
final class _ARouter { protected Postcard build(String path) { // 1.首先获取PathReplaceService,判断是否重写跳转URL,默认为空 // 进阶用法可以自定义类实现PathReplaceService来实现重写跳转URL,见github README PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { path = pService.forString(path); } // 2.构造Postcard对象 return build(path, extractGroup(path), true); } /** * 取出path中的组路径: /后的第一个即group */ private String extractGroup(String path) { String defaultGroup = path.substring(1, path.indexOf("/", 1)); return defaultGroup; } /** * 构造Postcard对象 */ protected Postcard build(String path, String group, Boolean afterReplace) { // 1.同build(String path)中的说明,判断是否重写跳转URL,默认没有重写的实现,afterReplace为true if (!afterReplace) { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { path = pService.forString(path); } } // 2.构造Postcard对象 return new Postcard(path, group); } } 复制代码
3.3.3 Postcard
Postcard表示明信片,代表一次路由操作的所需信息,如下所示,信息比较多,我们暂时先只关注其父类RouteMeta的group和path属性:
public final class Postcard extends RouteMeta { // Base private Uri uri; private Object tag; // A tag prepare for some thing wrong. inner params, DO NOT USE! private Bundle mBundle; // Data to transform private int flags = 0; // Flags of route private int timeout = 300; // Navigation timeout, TimeUnit.Second private IProvider provider; // It will be set value, if this postcard was provider. private boolean greenChannel; private SerializationService serializationService; private Context context; // May application or activity, check instance type before use it. private String action; // Animation private Bundle optionsCompat; // The transition animation of activity private int enterAnim = -1; private int exitAnim = -1; } public class RouteMeta { private RouteType type; // Type of route private Element rawType; // Raw type of route private Class<?> destination; // Destination private String path; // Path of route 路径 private String group; // Group of route 组 private int priority = -1; // The smaller the number, the higher the priority private int extra; // Extra data private Map<String, Integer> paramsType; // Param type private String name; private Map<String, Autowired> injectConfig; // Cache inject config. } 复制代码
3.3.4 Postcard.navigation()
ARouter.getInstance().build("/first/activity")返回Postcard对象,接下来继续分析Postcard.navigation():
public final class Postcard extends RouteMeta { public Object navigation() { return navigation(null); } public Object navigation(Context context, NavigationCallback callback) { // 实际实现在ARouter中 return ARouter.getInstance().navigation(context, this, -1, callback); } } 复制代码
3.3.5 _ARouter.navigation()
接下来继续分析_ARouter:
final class _ARouter { /** * 执行路由流程,主要工作包括:预处理、完善路由信息、拦截、继续执行路由流程 */ protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { // 1.自定义预处理PretreatmentService;没有自定义预处理或者预处理完成后继续向下传递 PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) { return null; } // 设置Application Context postcard.setContext(null == context ? mContext : context); try { // 2.LogisticsCenter完善路由信息;详见3.3.6分析 // 在我们的例子中postcard现在只有path和group信息,LogisticsCenter会完善要打开的Activity类、routeType等路由信息 LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { // LogisticsCenter根据path和group信息完善路由信息时如果未找到,则回调onLost if (null != callback) { callback.onLost(postcard); } else { DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class); if (null != degradeService) { degradeService.onLost(context, postcard); } } return null; } if (null != callback) { callback.onFound(postcard); } // 3.Postcard是否是绿色通道,是则继续执行_navigation; // 不是则执行interceptorService判断是否有拦截流程,本次暂不分析拦截流程; if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR. interceptorService.doInterceptions(postcard, new InterceptorCallback() { @Override public void onContinue(Postcard postcard) { _navigation(postcard, requestCode, callback); } @Override public void onInterrupt(Throwable exception) { if (null != callback) { callback.onInterrupt(postcard); } } }); } else { // 4.继续执行_navigation流程 return _navigation(postcard, requestCode, callback); } return null; } /** * 根据完善的Postcard,执行对应的路由逻辑 */ private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) { final Context currentContext = postcard.getContext(); // 1.根据不同的routeType执行不同逻辑;我们的例子中routeType是ACTIVITY switch (postcard.getType()) { case ACTIVITY: // 2.从Postcard取出信息构造Intent;我们的例子中postcard.getDestination()是要打开的Activity类 final Intent intent = new Intent(currentContext, postcard.getDestination()); intent.putExtras(postcard.getExtras()); // Set flags. int flags = postcard.getFlags(); if (0 != flags) { intent.setFlags(flags); } if (!(currentContext instanceof Activity)) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } String action = postcard.getAction(); if (!TextUtils.isEmpty(action)) { intent.setAction(action); } runInMainThread(new Runnable() { @Override public void run() { // 3.启动Activity 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; } } 复制代码
3.3.6 LogisticsCenter.completion()
补充分析上述流程中LogisticsCenter.completion()的主要工作:
public class LogisticsCenter { public synchronized static void completion(Postcard postcard) { // 1.从Warehouse.routes中查找Postcard的path所对应的RouteMeta RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); if (null == routeMeta) { // routeMet为空,则从groupsIndex查找;没查找到则不存在,查找到则动态添加 if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) { 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. addRouteGroupDynamic(postcard.getGroup(), null); completion(postcard); // Reload } } else { // 2.从Warehouse.routes中查找到Postcard所对应的RouteMeta后,完善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); postcard.greenChannel(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors default: break; } } } } 复制代码
另外,LogisticsCenter是如何知道path="/first/activity"的Postcard在补全信息时,其对应的RouteType是Activity,对应的类是FirstActivity.class呢,看3.1小节中注解自动生成的代码,就可以看出来,APT处理过程中就会生成其对应信息,然后在3.2.3中LogisticsCenter.init()会将这些信息记录下来:
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */ public class ARouter$$Group$$first implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/first/activity", RouteMeta.build(RouteType.ACTIVITY, FirstActivity.class, "/first/activity", "first", null, -1, -2147483648)); } } 复制代码
3.3.7 总结
作者:BC
链接:https://juejin.cn/post/7048527567346728990