arouter-gradle-plugin version : 1.0.2
AutoRegister : https://github.com/luckybilly/AutoRegister
前言
====
在本系列的第一篇中講過(guò)凌蔬,ARouter 可以通過(guò)掃描 dex 文件中 class 的全類(lèi)名,來(lái)加載 compiler 生成的路由類(lèi)籍嘹。但這種方式影響性能燃异,并且效率也不高猫牡。所以在 ARouter v1.3.0 之后的版本中布疼,加入了自動(dòng)注冊(cè)的方式進(jìn)行路由表的加載居暖,自動(dòng)注冊(cè)可以縮短初始化時(shí)間,解決應(yīng)用加固導(dǎo)致無(wú)法直接訪(fǎng)問(wèn) dex 文件從而初始化失敗的問(wèn)題肛著。
那么自動(dòng)注冊(cè)到底是什么東東圆兵,為什么有這么強(qiáng)大的能力呢跺讯?
那么接下來(lái),我們就來(lái)分析分析衙傀。
預(yù)先需要了解的知識(shí)點(diǎn):
自定義 gradle plugin
gradle transform api
使用 asm 實(shí)現(xiàn)字節(jié)碼插樁
arouter-register
================
arouter-register 的入口就在 PluginLaunch
public class PluginLaunch implements Plugin<Project> {
@Override
public void apply(Project project) {
def isApp = project.plugins.hasPlugin(AppPlugin)
//only application module needs this plugin to generate register code
if (isApp) {
Logger.make(project)
Logger.i('Project enable arouter-register plugin')
def android = project.extensions.getByType(AppExtension)
def transformImpl = new RegisterTransform(project)
//init arouter-auto-register settings
ArrayList<ScanSetting> list = new ArrayList<>(3)
list.add(new ScanSetting('IRouteRoot'))
list.add(new ScanSetting('IInterceptorGroup'))
list.add(new ScanSetting('IProviderGroup'))
RegisterTransform.registerList = list
//register this plugin
android.registerTransform(transformImpl)
}
}
}
從上面的代碼可知:
只在 application module (一般都是 app module)生成自動(dòng)注冊(cè)的代碼抬吟;
初始化了自動(dòng)注冊(cè)的設(shè)置萨咕,這樣自動(dòng)注冊(cè)就知道需要注冊(cè) IRouteRoot IInterceptorGroup IProviderGroup 這三者统抬;
注冊(cè) RegisterTransform ,字節(jié)碼插樁將在 RegisterTransform 中完成危队;
可以看出聪建,重點(diǎn)就在 RegisterTransform 里面。那我們重點(diǎn)就關(guān)注下 RegisterTransform 的代碼茫陆,這里就貼出 transform 方法的源碼了金麸。(關(guān)于 Transform 的 InputTypes 和 Scopes 知識(shí)點(diǎn)在這就不講了,如有需要了解的同學(xué)可以看 Android 熱修復(fù)使用Gradle Plugin1.5改造Nuwa插件)
class RegisterTransform extends Transform {
@Override
void transform(Context context, Collection<TransformInput> inputs
, Collection<TransformInput> referencedInputs
, TransformOutputProvider outputProvider
, boolean isIncremental) throws IOException, TransformException, InterruptedException {
Logger.i('Start scan register info in jar file.')
long startTime = System.currentTimeMillis()
boolean leftSlash = File.separator == '/'
inputs.each { TransformInput input ->
// 掃描所有的 jar 文件
input.jarInputs.each { JarInput jarInput ->
String destName = jarInput.name
// rename jar files
def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath)
if (destName.endsWith(".jar")) {
destName = destName.substring(0, destName.length() - 4)
}
// 輸入的 jar 文件
File src = jarInput.file
// 輸出的 jar 文件
File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)
// 掃描 jar 文件簿盅,查找實(shí)現(xiàn) IRouteRoot IInterceptorGroup IProviderGroup 接口的類(lèi)挥下,并且找到 LogisticsCenter 在哪個(gè) jar 文件中
// 不掃描 com.android.support 開(kāi)頭的 jar
if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
// ScanUtil.scanJar 的代碼就不詳細(xì)展開(kāi)了,感興趣的同學(xué)可以自己去看下
ScanUtil.scanJar(src, dest)
}
FileUtils.copyFile(src, dest)
}
// 掃描所有的 class 文件桨醋,查找實(shí)現(xiàn) IRouteRoot IInterceptorGroup IProviderGroup 接口的類(lèi)
// 和掃描 jar 做差不多類(lèi)似的工作棚瘟。不同的點(diǎn)就是不用再去找 LogisticsCenter 類(lèi)
input.directoryInputs.each { DirectoryInput directoryInput ->
File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
String root = directoryInput.file.absolutePath
if (!root.endsWith(File.separator))
root += File.separator
directoryInput.file.eachFileRecurse { File file ->
def path = file.absolutePath.replace(root, '')
if (!leftSlash) {
path = path.replaceAll("\\\\", "/")
}
// 只處理 com/alibaba/android/arouter/routes/ 開(kāi)頭的 class
if(file.isFile() && ScanUtil.shouldProcessClass(path)){
ScanUtil.scanClass(file)
}
}
// copy to dest
FileUtils.copyDirectory(directoryInput.file, dest)
}
}
Logger.i('Scan finish, current cost time ' + (System.currentTimeMillis() - startTime) + "ms")
// 這里開(kāi)始字節(jié)碼插樁操作
if (fileContainsInitClass) {
// 遍歷之前找的 IRouteRoot IInterceptorGroup IProviderGroup
registerList.each { ext ->
Logger.i('Insert register code to file ' + fileContainsInitClass.absolutePath)
if (ext.classList.isEmpty()) {
Logger.e("No class implements found for interface:" + ext.interfaceName)
} else {
ext.classList.each {
Logger.i(it)
}
// 對(duì) LogisticsCenter.class 做字節(jié)碼插樁
RegisterCodeGenerator.insertInitCodeTo(ext)
}
}
}
Logger.i("Generate code finish, current cost time: " + (System.currentTimeMillis() - startTime) + "ms")
}
}
上面代碼的邏輯很清晰,按照之前設(shè)置好的 IRouteRoot IInterceptorGroup IProviderGroup 這三個(gè)接口喜最,然后掃描整個(gè)項(xiàng)目的代碼偎蘸,分別找到這三者各自的實(shí)現(xiàn)類(lèi),然后加入到集合中瞬内。最后在 LogisticsCenter 中實(shí)現(xiàn)字節(jié)碼插樁迷雪。
我們來(lái)詳細(xì)看下 RegisterCodeGenerator.insertInitCodeTo(ext) 的代碼
static void insertInitCodeTo(ScanSetting registerSetting) {
if (registerSetting != null && !registerSetting.classList.isEmpty()) {
RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
// RegisterTransform.fileContainsInitClass 就是包含了 LogisticsCenter.class 的那個(gè) jar 文件
File file = RegisterTransform.fileContainsInitClass
if (file.getName().endsWith('.jar'))
// 開(kāi)始處理
processor.insertInitCodeIntoJarFile(file)
}
}
插入的操作在 insertInitCodeIntoJarFile 中實(shí)現(xiàn)。
private File insertInitCodeIntoJarFile(File jarFile) {
if (jarFile) {
def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")
if (optJar.exists())
optJar.delete()
def file = new JarFile(jarFile)
Enumeration enumeration = file.entries()
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))
// 遍歷 jar 文件中的 class
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
ZipEntry zipEntry = new ZipEntry(entryName)
InputStream inputStream = file.getInputStream(jarEntry)
jarOutputStream.putNextEntry(zipEntry)
// 如果是 LogisticsCenter.class 的話(huà)
if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
Logger.i('Insert init code to class >> ' + entryName)
// 插樁操作
def bytes = referHackWhenInit(inputStream)
jarOutputStream.write(bytes)
} else {
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
inputStream.close()
jarOutputStream.closeEntry()
}
jarOutputStream.close()
file.close()
// 把字節(jié)碼插樁的 jar 替換掉原來(lái)舊的 jar 文件
if (jarFile.exists()) {
jarFile.delete()
}
optJar.renameTo(jarFile)
}
return jarFile
}
字節(jié)碼插樁的代碼還在 referHackWhenInit 方法中虫蝶。
//refer hack class when object init
private byte[] referHackWhenInit(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
return cw.toByteArray()
}
class MyClassVisitor extends ClassVisitor {
MyClassVisitor(int api, ClassVisitor cv) {
super(api, cv)
}
void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces)
}
@Override
MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions)
// 對(duì) loadRouterMap 這個(gè)方法進(jìn)行代碼插入
if (name == ScanSetting.GENERATE_TO_METHOD_NAME) {
mv = new RouteMethodVisitor(Opcodes.ASM5, mv)
}
return mv
}
}
class RouteMethodVisitor extends MethodVisitor {
RouteMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}
@Override
void visitInsn(int opcode) {
// 插入的代碼在 return 之前
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
extension.classList.each { name ->
name = name.replaceAll("/", ".")
mv.visitLdcInsn(name)//這里的name就是之前掃描出來(lái)的 IRouteRoot IInterceptorGroup IProviderGroup 實(shí)現(xiàn)類(lèi)名
// 生成 LogisticsCenter.register(name) 代碼
mv.visitMethodInsn(Opcodes.INVOKESTATIC
, ScanSetting.GENERATE_TO_CLASS_NAME
, ScanSetting.REGISTER_METHOD_NAME
, "(Ljava/lang/String;)V"
, false)
}
}
super.visitInsn(opcode)
}
@Override
void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals)
}
}
最終章咧,生成的代碼會(huì)像下面所示:
private static void loadRouterMap() {
registerByPlugin = false;
//auto generate register code by gradle plugin: arouter-auto-register
// looks like below:
register("com.alibaba.android.arouter.routes.ARouter$$Root$$app");
register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$app");
register("com.alibaba.android.arouter.routes.ARouter$$Group$$arouter");
}
那么順便來(lái)跟蹤一下 register 方法的代碼,看看里面是如何完成路由表注冊(cè)的能真。
private static void register(String className) {
if (!TextUtils.isEmpty(className)) {
try {
Class<?> clazz = Class.forName(className);
Object obj = clazz.getConstructor().newInstance();
if (obj instanceof IRouteRoot) {
registerRouteRoot((IRouteRoot) obj);
} else if (obj instanceof IProviderGroup) {
registerProvider((IProviderGroup) obj);
} else if (obj instanceof IInterceptorGroup) {
registerInterceptor((IInterceptorGroup) obj);
} else {
logger.info(TAG, "register failed, class name: " + className
+ " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
}
} catch (Exception e) {
logger.error(TAG,"register class error:" + className);
}
}
}
// 注冊(cè) IRouteRoot 類(lèi)型
private static void registerRouteRoot(IRouteRoot routeRoot) {
markRegisteredByPlugin();
if (routeRoot != null) {
routeRoot.loadInto(Warehouse.groupsIndex);
}
}
// 注冊(cè) IInterceptorGroup 類(lèi)型
private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
markRegisteredByPlugin();
if (interceptorGroup != null) {
interceptorGroup.loadInto(Warehouse.interceptorsIndex);
}
}
// 注冊(cè) IProviderGroup 類(lèi)型
private static void registerProvider(IProviderGroup providerGroup) {
markRegisteredByPlugin();
if (providerGroup != null) {
providerGroup.loadInto(Warehouse.providersIndex);
}
}
// 標(biāo)記通過(guò)gradle plugin完成自動(dòng)注冊(cè)
private static void markRegisteredByPlugin() {
if (!registerByPlugin) {
registerByPlugin = true;
}
}
這樣相比之下赁严,自動(dòng)注冊(cè)的方式確實(shí)比掃描 dex 文件更高效,掃描 dex 文件是在 app 運(yùn)行時(shí)操作的舟陆,這樣會(huì)影響 app 的性能误澳,對(duì)用戶(hù)造成不好的體驗(yàn)。而自動(dòng)注冊(cè)是在 build 的時(shí)候完成字節(jié)碼插樁的秦躯,對(duì)運(yùn)行時(shí)不產(chǎn)生影響忆谓。
學(xué)了今天這招,以后 compiler 生成的代碼需要注冊(cè)的步驟都可以通過(guò)自動(dòng)注冊(cè)來(lái)完成了踱承,贊一個(gè)??
番外
====
之前看到自動(dòng)注冊(cè)這么神奇倡缠,所以想看下插入字節(jié)碼之后 LogisticsCenter 代碼的效果哨免,所以反編譯了一下 ARouter demo apk,可以看到 LogisticsCenter.smali 的 loadRouterMap 方法:
.method private static loadRouterMap()V
.locals 1
.line 64
const/4 v0, 0x0
sput-boolean v0, Lcom/alibaba/android/arouter/core/LogisticsCenter;->registerByPlugin:Z
.line 69
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Root$$app"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Interceptors$$app"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
const-string v0, "com.alibaba.android.arouter.routes.ARouter$$Providers$$app"
invoke-static {v0}, Lcom/alibaba/android/arouter/core/LogisticsCenter;->register(Ljava/lang/String;)V
return-void
.end method
確實(shí)符合我們的預(yù)期啊昙沦,真好琢唾!
References
==========