Arouter 源码学习 1

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Arouter 源码学习 1相关的知识,希望对你有一定的参考价值。

前言

ARouter开源地址

本系列是笔者学习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风险.ArouterGradle插件可以帮助我们避免这一情况,但是也存在局限性(这个问题后续文章在讨论).

我们最后看下
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的主要内容,如果未能解决你的问题,请参考以下文章

ARouter 在多 module 项目中实战

ARouter源码分析—— 缓存与优化

ARouter源码分析

源码解读Arouter是如何实现的

源码解读Arouter是如何实现的

ARouter源码分析—— Provider源码分析