作者:linversion
前言
最近的一個新項目使用了Clean Architecture+模塊化+MVVM架構(gòu),將首頁每個tab對應(yīng)的功能都放到單獨的模塊且不相互依賴,這時就有了模塊間頁面跳轉(zhuǎn)的問題,經(jīng)過一番研究選擇了滴滴的DRouter,因為其出色的性能、靈活的組件拆分,更重要的是生成路由表時支持插件增量編譯、多線程掃描,運行時異步加載路由表,支持回調(diào)式ActivityResult,比ARouter好太多。本著用一個新框架,只會用還不夠的原則,我決定去了解一下框架的原理,并給自己制定了以下幾個問題:
1、框架的設(shè)計分層是什么樣的?
2、它是如何生成路由表的?
3、它是如何加載路由表的?
4、相比于ARouter如何提高了性能?
閱讀官方文檔
相比于直接一頭扎進(jìn)源碼,先閱讀官方的文檔總是沒錯的,官方給了一篇介紹的文章,寫得非常好,基本回答了我以上的所有問題。
首先在介紹DRouter的亮點部分得到了問題2、3、4的答案。
路由表在編譯期通過插件動態(tài)生成。插件會啟動多線程同時異步處理所有的組件;增量掃描功能可以幫助開發(fā)者在第二次編譯時,只對修改過的代碼進(jìn)行處理,極大地縮短路由表生成的時間。
在編譯器使用gradle插件配合transform掃描所有的類,生成路由表,并且支持增量掃描,回答了問題2。
另外框架初始化的時候啟動子線程去加載路由表,不阻塞主線程的執(zhí)行,盡其所能提高效率。
回答了問題3。
加載路由表、實例化路由、以及跨進(jìn)程命令到達(dá)服務(wù)端后的分發(fā)這些常規(guī)應(yīng)該使用反射的場景,使用預(yù)占位或動態(tài)生成代碼來替換成java的new創(chuàng)建和顯式方式執(zhí)行,最大限度的去避免反射執(zhí)行,提高性能。
回答了問題4,通過減少使用反射提升了性能。
在原理和架構(gòu)章節(jié)處給了一張架構(gòu)的設(shè)計圖:
整體架構(gòu)分三層,自下而上是數(shù)據(jù)流層、組件層、開放接口層。
數(shù)據(jù)流層是DRouter最重要的核心模塊,這里承載著插件生成的路由表、路由元素、動態(tài)注冊、以及跨進(jìn)程功能相關(guān)的序列化數(shù)據(jù)流。所有的路由流轉(zhuǎn)都會從這里取得對應(yīng)的數(shù)據(jù),進(jìn)而流向正確的目標(biāo)。
RouterPlugin和MetaLoader負(fù)責(zé)生成路由表,路由元素指的是RouterMeta,存放scheme/host/path等信息。
組件層,核心的路由分發(fā)、攔截器、生命周期、異步暫存和監(jiān)控、ServiceLoader、多維過濾、Fragment路由,以及跨進(jìn)程命令打包等。
開放接口層則是使用時接觸到的一些類,API設(shè)計得也很簡單易用,DRouter類和Request類分別只有75和121行代碼。
問題1得到解答,到此處也對整個框架有了一個整體的認(rèn)識。
閱讀源碼
1.初始化流程
調(diào)用DRouter.init(app)后的時序圖如下:
默認(rèn)是在子線程實現(xiàn)路由表加載,不影響主線程。
public static void checkAndLoad(final String app, boolean async) {
if (!loadRecord.contains(app)) {
// 雙重校驗鎖
synchronized (RouterStore.class) {
if (!loadRecord.contains(app)) {
loadRecord.add(app);
if (!async) {
Log.d(RouterLogger.CORE_TAG, "DRouter start load router table sync");
load(app);
} else {
new Thread("drouter-table-thread") {
@Override
public void run() {
Log.d(RouterLogger.CORE_TAG, "DRouter start load router table in drouter-table-thread");
load(app);
}
}.start();
}
}
}
}
}
最終走到了RouterLoader的load方法來加載路由表到一個map中,仔細(xì)看它的引入路徑是com.didi.drouter.loader.host.RouterLoader
,是不存在于源碼中的,因為它是編譯的時候生成的,位置位于app/build/intermediates/transforms/DRouter/dev/debug/../com/didi/drouter/loader/host/RouterLoader。
public class RouterLoader extends MetaLoader {
@Override
public void load(Map var1) {
var1.put("@@$$/browse/BrowseActivity", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/browse/BrowseActivity", "com.example.demo.browse.BrowseActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
}
public RouterLoader() {
}
}
public abstract class MetaLoader {
public abstract void load(Map<?, ?> data);
// for regex router
protected void put(String uri, RouterMeta meta, Map<String, Map<String, RouterMeta>> data) {
Map<String, RouterMeta> map = data.get(RouterStore.REGEX_ROUTER);
if (map == null) {
map = new ConcurrentHashMap<>();
data.put(RouterStore.REGEX_ROUTER, map);
}
map.put(uri, meta);
}
// for service
protected void put(Class<?> clz, RouterMeta meta, Map<Class<?>, Set<RouterMeta>> data) {
Set<RouterMeta> set = data.get(clz);
if (set == null) {
set = Collections.newSetFromMap(new ConcurrentHashMap<RouterMeta, Boolean>());
data.put(clz, set);
}
set.add(meta);
}
}
不難猜出其是在編譯期加了一個transform,生成RouterLoader類時加入了load方法的具體實現(xiàn),具體來說是javaassit API+Gradle Transform,所以去看看drouter-plugin在編譯期做了什么。
2.編譯期transform
直接看時序圖。
創(chuàng)建了一個RouterPlugin,并且注冊了一個Gradle Transform。
class RouterPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
...
project.android.registerTransform(new TransformProxy(project))
}
}
class TransformProxy extends Transform {
@Override
void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
String pluginVersion = ProxyUtil.getPluginVersion(invocation)
if (pluginVersion != null) {
...
if (pluginJar.exists()) {
URLClassLoader newLoader = new URLClassLoader([pluginJar.toURI().toURL()] as URL[], getClass().classLoader)
Class<?> transformClass = newLoader.loadClass("com.didi.drouter.plugin.RouterTransform")
ClassLoader threadLoader = Thread.currentThread().getContextClassLoader()
// 1.設(shè)置URLClassLoader
Thread.currentThread().setContextClassLoader(newLoader)
Constructor constructor = transformClass.getConstructor(Project.class)
// 2.反射創(chuàng)建一個RouterTransform
Transform transform = (Transform) constructor.newInstance(project)
transform.transform(invocation)
Thread.currentThread().setContextClassLoader(threadLoader)
return
} else {
ProxyUtil.Logger.e("Error: there is no drouter-plugin jar")
}
}
}
}
注釋2處反射創(chuàng)建一個com.didi.drouter.plugin.RouterTransform對象,并執(zhí)行其transform方法,此處真正處理transform邏輯,它的位置位于drouter-plugin模塊。
class RouterTransform extends Transform {
@Override
void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
...
// 1.創(chuàng)建一個DRouterTable目錄
File dest = invocation.outputProvider.getContentLocation("DRouterTable", TransformManager.CONTENT_CLASS,
ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY)
// 2.執(zhí)行RouterTask
(new RouterTask(project, compilePath, cachePathSet, useCache, dest, tmpDir, setting, isWindow)).run()
FileUtils.writeLines(cacheFile, cachePathSet)
Logger.v("Link: https://github.com/didi/DRouter")
Logger.v("DRouterTask done, time used: " + (System.currentTimeMillis() - timeStart) / 1000f + "s")
}
}
注釋2處new了一個RouterTask對象,并執(zhí)行其run方法,之后的log輸出就是平時編譯能看到的信息,表示transform的耗時。
public class RouterTask {
void run() {
StoreUtil.clear();
JarUtils.printVersion(project, compileClassPath);
pool = new ClassPool();
// 1.創(chuàng)建ClassClassify
classClassify = new ClassClassify(pool, setting);
startExecute();
}
private void startExecute() {
try {
...
// 2.執(zhí)行ClassClassify的generatorRouter
classClassify.generatorRouter(routerDir);
Logger.d("generator router table used: " + (System.currentTimeMillis() - timeStart) + "ms");
Logger.v("scan class size: " + count.get() + " | router class size: " + cachePathSet.size());
} catch (Exception e) {
JarUtils.check(e);
throw new GradleException("Could not generate d_router table\n" + e.getMessage(), e);
} finally {
executor.shutdown();
FileUtils.deleteQuietly(wTmpDir);
}
}
}
重點在于ClassClassify這個類,其generatorRouter方法便是最終處理生成路由表的邏輯。
public class ClassClassify {
private List<AbsRouterCollect> classifies = new ArrayList<>();
public ClassClassify(ClassPool pool, RouterSetting.Parse setting) {
classifies.add(new RouterCollect(pool, setting));
classifies.add(new ServiceCollect(pool, setting));
classifies.add(new InterceptorCollect(pool, setting));
}
public void generatorRouter(File routerDir) throws Exception {
for (int i = 0; i < classifies.size(); i++) {
AbsRouterCollect cf = classifies.get(i);
cf.generate(routerDir);
}
}
}
構(gòu)造函數(shù)處添加了RouterCollect
/ServiceCollect
/InterceptorCollect
,最終執(zhí)行的是他們的generate方法,分別處理路由表、service、攔截器,我們只看路由表的。
class RouterCollect extends AbsRouterCollect {
@Override
public void generate(File routerDir) throws Exception {
// 1.創(chuàng)建RouterLoader類
CtClass ctClass = pool.makeClass(getPackageName() + ".RouterLoader");
CtClass superClass = pool.get("com.didi.drouter.store.MetaLoader");
ctClass.setSuperclass(superClass);
StringBuilder builder = new StringBuilder();
builder.append("public void load(java.util.Map data) {\n");
for (CtClass routerCc : routerClass.values()) {
try {
// 處理注解、class類型等邏輯
...
StringBuilder metaBuilder = new StringBuilder();
metaBuilder.append("com.didi.drouter.store.RouterMeta.build(");
metaBuilder.append(type);
metaBuilder.append(").assembleRouter(");
metaBuilder.append("\"").append(schemeValue).append("\"");
metaBuilder.append(",");
metaBuilder.append("\"").append(hostValue).append("\"");
metaBuilder.append(",");
metaBuilder.append("\"").append(pathValue).append("\"");
metaBuilder.append(",");
if ("com.didi.drouter.store.RouterMeta.ACTIVITY".equals(type)) {
if (!setting.isUseActivityRouterClass()) {
metaBuilder.append("\"").append(routerCc.getName()).append("\"");
} else {
metaBuilder.append(routerCc.getName()).append(".class");
}
} else {
metaBuilder.append(routerCc.getName()).append(".class");
}
metaBuilder.append(", ");
...
metaBuilder.append(proxyCc != null ? "new " + proxyCc.getName() + "()" : "null");
metaBuilder.append(", ");
metaBuilder.append(interceptorClass != null ? interceptorClass.toString() : "null");
metaBuilder.append(", ");
metaBuilder.append(interceptorName != null ? interceptorName.toString() : "null");
metaBuilder.append(", ");
metaBuilder.append(thread);
metaBuilder.append(", ");
metaBuilder.append(priority);
metaBuilder.append(", ");
metaBuilder.append(hold);
metaBuilder.append(")");
...
if (isAnyRegex) {
// 2. 插入路由表
items.add(" put(\"" + uri + "\", " + metaBuilder + ", data); \n");
//builder.append(" put(\"").append(uri).append("\", ").append(metaBuilder).append(", data); \n");
} else {
items.add(" data.put(\"" + uri + "\", " + metaBuilder + "); \n");
//builder.append(" data.put(\"").append(uri).append("\", ").append(metaBuilder).append("); \n");
}
} catch (Exception e) {
e.printStackTrace();
}
Collections.sort(items);
for (String item : items) {
builder.append(item);
}
builder.append("}");
Logger.d("\nclass RouterLoader" + "\n" + builder.toString());
// 3.生成代碼
generatorClass(routerDir, ctClass, builder.toString());
}
}
}
此處邏輯比較多,但總體是清晰的,處理完注解和類型的判斷,獲取路由的信息,構(gòu)造將要插入的代碼,最后統(tǒng)一在父類AbsRouterCollect的generatorClass處理load方法的生成,此時編譯器的工作就完成了。
ARouter也提供了arouter-register插件,同是在編譯期生成路由表,不同的是在生成代碼時,ARouter使用的是ASM,DRouter使用Javassist,查了一下資料,ASM性能比Javassist更好,但更難上手,需要懂字節(jié)碼知識,Javassist在復(fù)雜的字節(jié)碼級操作上提供了更高級別的抽象層,因此實現(xiàn)起來更容易、更快,只需要懂很少的字節(jié)碼知識,它使用反射機制。
3.運行期加載路由表
重新貼一下加載路由表的load方法。
public class RouterLoader extends MetaLoader {
@Override
public void load(Map var1) {
var1.put("@@$$/browse/BrowseActivity", RouterMeta.build(RouterMeta.ACTIVITY).assembleRouter("", "", "/browse/BrowseActivity", "com.example.demo.browse.BrowseActivity", (IRouterProxy)null, (Class[])null, (String[])null, 0, 0, false));
}
public RouterLoader() {
}
}
看下RouteMeta的build方法。
public static RouterMeta build(int routerType) {
return new RouterMeta(routerType);
}
可見是直接new的一個路由類,這與ARouter直接通過反射創(chuàng)建路由類不同,性能更好。
4.總結(jié)
本文分析了DRouter路由部分的原理,其在編譯器使用Gradle Transform和Javassist生成路由表,運行時new路由類,異步初始化加載路由表,實現(xiàn)了高性能。