Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize router automatic registration by generating bytecode #254

Merged
merged 1 commit into from
Dec 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,71 @@
public class LogisticsCenter {
private static Context mContext;
static ThreadPoolExecutor executor;
private static boolean registerByPlugin;

/**
* arouter-auto-register plugin will generate code inside this method
* call this method to register all Routers, Interceptors and Providers
* @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
* @since 2017-12-06
*/
private static void loadRouterMap() {
registerByPlugin = false;
//auto generate register code by gradle plugin: arouter-auto-register
// looks like below:
// registerRouteRoot(new ARouter..Root..modulejava());
// registerRouteRoot(new ARouter..Root..modulekotlin());
}

/**
* method for arouter-auto-register plugin to register Routers
* @param routeRoot IRouteRoot implementation class in the package: com.alibaba.android.arouter.core.routers
* @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
* @since 2017-12-06
*/
private static void registerRouteRoot(IRouteRoot routeRoot) {
markRegisteredByPlugin();
if (routeRoot != null) {
routeRoot.loadInto(Warehouse.groupsIndex);
}
}

/**
* method for arouter-auto-register plugin to register Interceptors
* @param interceptorGroup IInterceptorGroup implementation class in the package: com.alibaba.android.arouter.core.routers
* @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
* @since 2017-12-06
*/
private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
markRegisteredByPlugin();
if (interceptorGroup != null) {
interceptorGroup.loadInto(Warehouse.interceptorsIndex);
}
}

/**
* method for arouter-auto-register plugin to register Providers
* @param providerGroup IProviderGroup implementation class in the package: com.alibaba.android.arouter.core.routers
* @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
* @since 2017-12-06
*/
private static void registerProvider(IProviderGroup providerGroup) {
markRegisteredByPlugin();
if (providerGroup != null) {
providerGroup.loadInto(Warehouse.providersIndex);
}
}

/**
* mark already registered by arouter-auto-register plugin
* @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
* @since 2017-12-06
*/
private static void markRegisteredByPlugin() {
if (!registerByPlugin) {
registerByPlugin = true;
}
}

/**
* LogisticsCenter init, load all metas in memory. Demand initialization
Expand All @@ -62,36 +127,43 @@ public synchronized static void init(Context context, ThreadPoolExecutor tpe) th

try {
long startInit = System.currentTimeMillis();
Set<String> routerMap;

// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generate by arouter-compiler.
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();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;

// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generate by arouter-compiler.
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();
}

PackageUtils.updateVersion(context); // Save new version name when router map update finish.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

PackageUtils.updateVersion(context); // Save new version name when router map update finish.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();

logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();

for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions arouter-auto-register/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apply plugin: 'groovy'

dependencies {
compile gradleApi()
compile localGroovy()
}

repositories {
mavenCentral()
}
dependencies {
compile 'com.android.tools.build:gradle:2.2.0'
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.billy.android.register

import org.apache.commons.io.IOUtils
import org.objectweb.asm.*

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
/**
* generate register code into LogisticsCenter.class
* @author billy.qi email: qiyilike@163.com
*/
class RouterRegisterCodeGenerator {
RouterRegisterSetting extension

private RouterRegisterCodeGenerator(RouterRegisterSetting extension) {
this.extension = extension
}

static void insertInitCodeTo(RouterRegisterSetting registerSetting) {
if (registerSetting != null && !registerSetting.classList.isEmpty()) {
RouterRegisterCodeGenerator processor = new RouterRegisterCodeGenerator(registerSetting)
File file = RouterRegisterTransform.fileContainsInitClass
if (file.getName().endsWith('.jar'))
processor.insertInitCodeIntoJarFile(file)
}
}

/**
* generate code into jar file
* @param jarFile the jar file which contains LogisticsCenter.class
* @return
*/
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))

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)
if (RouterRegisterSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
println('codeInsertToClassName:' + entryName)
def bytes = referHackWhenInit(inputStream)
jarOutputStream.write(bytes)
} else {
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
inputStream.close()
jarOutputStream.closeEntry()
}
jarOutputStream.close()
file.close()

if (jarFile.exists()) {
jarFile.delete()
}
optJar.renameTo(jarFile)
}
return jarFile
}

//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)
//generate code into this method
if (name == RouterRegisterSetting.GENERATE_TO_METHOD_NAME) {
mv = new MyMethodVisitor(Opcodes.ASM5, mv)
}
return mv
}
}

class MyMethodVisitor extends MethodVisitor {

MyMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}

@Override
void visitInsn(int opcode) {
//generate code before return
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
extension.classList.each { name ->
//new a object for the specified class
mv.visitTypeInsn(Opcodes.NEW, name)
mv.visitInsn(Opcodes.DUP)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, name, "<init>", "()V", false)
// generate invoke register method into LogisticsCenter.loadRouterMap()
mv.visitMethodInsn(Opcodes.INVOKESTATIC
, RouterRegisterSetting.GENERATE_TO_CLASS_NAME
, extension.registerMethodName
, "(L${extension.interfaceName};)V"
, false)
}
}
super.visitInsn(opcode)
}
@Override
void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.billy.android.register

import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* Simple version of AutoRegister plugin for ARouter
* @author billy.qi email: qiyilike@163.com
* @since 17/12/06 15:35
*/
public class RouterRegisterPlugin 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) {
println 'project(' + project.name + ') apply arouter-auto-register plugin'
def android = project.extensions.getByType(AppExtension)
def transformImpl = new RouterRegisterTransform(project)

//init arouter-auto-register settings
ArrayList<RouterRegisterSetting> list = new ArrayList<>(3)
list.add(new RouterRegisterSetting('IRouteRoot', 'registerRouteRoot'))
list.add(new RouterRegisterSetting('IInterceptorGroup', 'registerInterceptor'))
list.add(new RouterRegisterSetting('IProviderGroup', 'registerProvider'))
RouterRegisterTransform.registerList = list
//register this plugin
android.registerTransform(transformImpl)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.billy.android.register
/**
* register setting
* @author billy.qi email: qiyilike@163.com
* @since 17/3/28 11:48
*/
class RouterRegisterSetting {
/**
* The register code is generated into this class
*/
static final String GENERATE_TO_CLASS_NAME = 'com/alibaba/android/arouter/core/LogisticsCenter'
/**
* you know. this is the class file(or entry in jar file) name
*/
static final String GENERATE_TO_CLASS_FILE_NAME = GENERATE_TO_CLASS_NAME + '.class'
/**
* The register code is generated into this method
*/
static final String GENERATE_TO_METHOD_NAME = 'loadRouterMap'
/**
* The package name of the class generated by the annotationProcessor
*/
static final String ROUTER_CLASS_PACKAGE_NAME = 'com/alibaba/android/arouter/routes/'
/**
* The package name of the interfaces
*/
private static final INTERFACE_PACKAGE_NAME = 'com/alibaba/android/arouter/facade/template/'
/**
* scan for classes which implements this interface
*/
String interfaceName = ''
/**
* register method name in class: {@link #GENERATE_TO_CLASS_NAME}
* for {@link #interfaceName}
*/
String registerMethodName = ''

/**
* jar file which contains class: {@link #GENERATE_TO_CLASS_NAME}
*/
File fileContainsInitClass
/**
* scan result for {@link #interfaceName}
* class names in this list
*/
ArrayList<String> classList = new ArrayList<>()

/**
* constructor for arouter-auto-register settings
* @param interfaceName interface to scan
* @param registerMethod method to generate code into
*/
RouterRegisterSetting(String interfaceName, String registerMethod){
this.interfaceName = INTERFACE_PACKAGE_NAME + interfaceName
this.registerMethodName = registerMethod
}

}
Loading