Arouter 源码学习 1
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Arouter 源码学习 1相关的知识,希望对你有一定的参考价值。
前言
本系列是笔者学习ARouter
开篇系列.本文不是教程系列,而是大致分析Arouter
路由分发逻辑.
以下是一些预备知识:
Arouter
是一个基于apt
框架,
对于@Route
,@Interceptor
,@Autowired
等注解的类会在Example/app/build/generated/source/kapt
生成对应的类,如下图:
注意除了Autowired
注解生成在我们自己的包下,其他类全部都在com.alibaba.android.arouter.routes
.(这么做是为了方便构建路由表时方便查找对应的映射信息)
源码分析
初始化
本小节是解析Arouter
初始化代码,可能你需要MultiDex
基础知识.
上面我们说过Arouter
会生成对应类的进而完成路由的分发,和注入.
举个例子:
我们有一个路由信息如下:
@Route(path = "/test/activity")
class LibOneActivity : Activity() {
}
我们在另一个Activity进行跳转
ARouter
.getInstance()
.build("/test/activity")
.navigation();
现在的问题是,Arouter
怎么知道/test/activity
对应LibOneActivity
?
首先我们需要知道@Route(path = "/test/activity")
会生成如下一个类:
package com.alibaba.android.arouter.routes;
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity", RouteMeta.build(RouteType.ACTIVITY, LibOneActivity.class, "/test/activity", "test", new java.util.HashMap<String, Integer>(){{put("person", 11); put("name", 8); put("age", 3); }}, -1, -2147483648));
}
}
Arouter
会利用生成的类构造一个路由表,从而完成键值对映射,对于怎么具体利用上面的类且看下文.
Arouter
初始化代码
//这里我们顺带分析`MultiDex`下Arouter的处理逻辑
public class App extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
ARouter.init(this); // 尽可能早,推荐在Application中初始化
}
}
//ARouter.java
class ARouter{
public static void init(Application application) {
//略...
hasInit = _ARouter.init(application);
//略...
}
}
//_ARouter.java
class _ARouter{
protected static synchronized boolean init(Application application) {
mContext = application;
//我们重点看这个,executor是一个线程池类ThreadPoolExecutor
//LogisticsCenter内部会完成一个路由表的构建
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
}
//LogisticsCenter.java
class LogisticsCenter{
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
//billy.qi modified at 2017-12-06
//load by plugin first
//通过Arouter提供gradle插件完成对路由表的构建.这里会采用字节插装的实现
//这个插件不是必选的,我们姑且把这个函数视为不存在即可.
//如果不通过插件初始化路由会在初始化时及其耗时
loadRouterMap();
//registerByPlugin判断是否启用插件帮助我们构建路由表
if (registerByPlugin) {
} else {
Set<String> routerMap;
//对于debug版本每次都重新构建路由表
//对于apk升级版本同样进行路由升级
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
//ROUTE_ROOT_PAKCAGE为字符串,内容为com.alibaba.android.arouter.routes
//getFileNameByPackageName查找当前apk下所有ROUTE_ROOT_PAKCAGE包下的类
//现在你知道为什么Arouter为什么要将所有生成的类放入包下了吧
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();
}
//Arouter每次更新路由表内部提升一个版本
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>()));
}
//拿取routerMap取得的对象,按照类名进行分组放入Warehouse对应的map结构中
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
//反射构造放入Warehouse中
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
//反射构造放入Warehouse中
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
//反射构造放入Warehouse中
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
我们看下Warehouse
类:
//Warehouse.java
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<>();
}
可见 ARouter.init(this);
作用如下:
取出com.alibaba.android.arouter.routes
包下面的所有类,根据分类放入Warehouse
中.
我们再细看一下getFileNameByPackageName
函数如何驱动对应包下所有的类.
ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
public class ClassUtils {
//获得packageName下面所有的类
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
//结果容器
final Set<String> classNames = new HashSet<>();
//getSourcePaths获得所有dex文件夹路径,如果android版本已经天然支持multiDex,那么仅返回apk路径
List<String> paths = getSourcePaths(context);
//用于让主线程等候所有dex查找完成
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
for (final String path : paths) {
//在子线程中进行io操作读取所有dex下的类
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
//构造一个DexFile
if (path.endsWith(EXTRACTED_SUFFIX)) {
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
//DexFile的entries方法可以获得一个迭代器,获取当前dex中所有的类
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
//符合当前包名就放入结果集合
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();
}
}
});
}
//加载完成后返回结果
//注意这里其实有可能会ANR的,所以推荐能使用Arouter的Plugin就使用,不过存在局限性.
parserCtl.await();
return classNames;
}
}
这里你会发现Arouter
虽然使用起来傻瓜式,其实隐含了ANR风险.Arouter
的Gradle
插件可以帮助我们避免这一情况,但是也存在局限性(这个问题后续文章在讨论).
我们最后看下
public static List<String> getSourcePaths(Context context)
函数
//得到上下文对应所有dex的集合
public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
//当前apk路径
File sourceApk = new File(applicationInfo.sourceDir);
List<String> sourcePaths = new ArrayList<>();
//将当前apk放入结果中
sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
//如果当前系统需要可能需要分包,那么Arouter假设你使用Google官方的库,
//如果你使用其他的分包方案下面的代码会存在错误
//文件的前缀
String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
// 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
// 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
if (!isVMMultidexCapable()) {
//Google的MultiDex会持久化分包结果存储到sharedPreference,比如有多个dex包
//getMultiDexPreferences读取MultiDex持久化数据,返回dex有多少个包
int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
//MultiDex库会将所有dex放入code_cache/secondary-dexes中
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...
//读取dex文件,这里同样是根据MultiDex库的规范进行操作.
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName);
if (extractedFile.isFile()) {
sourcePaths.add(extractedFile.getAbsolutePath());
//we ignore the verify zip part
} else {
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
}
}
//这里是Arouter用于支持instant run,不过现在已经没有
if (ARouter.debuggable()) { // Search instant run support only debuggable
sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
}
return sourcePaths;
}
注入分析
案例如下:
@Route(path = "/yourservicegroupname/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
class MainActivity : Activity() {
@Autowired(name = "/yourservicegroupname/hello")
@JvmField
var helloService2: HelloService? = null
override fun onCreate(savedInstanceState: Bundle?) {
//注入一个服务
ARouter.getInstance().inject(this);
val sayHello = helloService2?.sayHello("你好")
}
}
//生成的其中一个类会放入Warehouse.groupsIndex中
public class ARouter$$Group$$yourservicegroupname implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/yourservicegroupname/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
}
}
@Autowired
类似Dagger
的@Inject
,用于依赖注入.本小节我们分析下Arouter
的代码.
存在@Autowired
的代码会在编译时生成对于对应类用于注入.
package com.example.example;
public class MainActivity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
//serializationService对象我们可以先跳过
serializationService = ARouter.getInstance().navigation(SerializationService.class);
MainActivity substitute = (MainActivity)target;
//执行注入
substitute.helloService2 = (HelloService)ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
}
}
在分析如何调MainActivity$$ARouter$$Autowired.inject
之前,我们看下(HelloService)ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
代码分析
(HelloService)ARouter
.getInstance()//获取单例
.build("/yourservicegroupname/hello")
.navigation();
class ARouter{
private volatile static ARouter instance = null;
//DCL单例模式
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) {
return _ARouter.getInstance(以上是关于Arouter 源码学习 1的主要内容,如果未能解决你的问题,请参考以下文章