项目搭建
1.需求分析
2.功能模块划分
3.基础工具类、框架、接口提取
4.创建路由接口
5.配置gradle完成模块单独测试
if (appb_lib) {
apply plugin: 'com.android.library'
}else {
apply plugin: 'com.android.application'
}
安装 Octotree浏览器插件查看项目结构
image
baseMoudle路由工具类 RouterTools
//添加router与activity对应关系
public void addRouter(String router, Class<? extends Activity> clazz) {
mRouterMap.put(router, clazz);
}
//根据router跳转界面
public void navigate(Context context, String router) {
System.out.println(mRouterMap.get(router));
context.startActivity(new Intent(context, mRouterMap.get(router)));
}
//初始化router与activity对应关系数据
public void init(Context context) {
//用于asm增强
loadRouterMap();
if (isUseAms) {
return;
}
//如果没有使用asm 则采用遍历apk中的所有class,找到实现IRouter的类反射调用
String packageResourcePath = context.getApplicationContext().getPackageResourcePath();
try {
DexFile dexFile = new DexFile(packageResourcePath);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String element = entries.nextElement();
Log.e("RouterTools", "init: " + element);
if (element.contains("com.example.router")) {
Log.e("RouterTools", "init: " + element);
Class<?> aClass = Class.forName(element);
int modifiers = aClass.getModifiers();
if (IRouter.class.isAssignableFrom(aClass) && !Modifier.isInterface(modifiers)) {
((IRouter) aClass.newInstance()).addRouter(mRouterMap);
}
}
}
} catch (Exception e) {
e.printStackTrace();
Log.e("RouterTools", "init: ", e);
}
for (Map.Entry<String, Class<? extends Activity>> stringClassEntry : mRouterMap.entrySet()) {
Log.e("RouterTools", "stringClassEntry: "
+ stringClassEntry.getKey() + "--" + stringClassEntry.getValue());
}
}
//用于asm增强函数
private static void loadRouterMap() {
isUseAms = false;
//在此处完成字节码插桩
//register("com.example.router.appc.MyRouter$$APPC")
//register("com.example.router.appb.MyRouter$$APPB")
}
//用于asm调用,如果使用了ams就不需要遍历apk
private static void register(String className) {
isUseAms = true;
Log.e("RouterTools", "register: " + className);
try {
Class<?> aClass = Class.forName(className);
int modifiers = aClass.getModifiers();
if (IRouter.class.isAssignableFrom(aClass) && !Modifier.isInterface(modifiers)) {
((IRouter) aClass.newInstance()).addRouter(mRouterMap);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
APT自动生成个个模块实现IRouter的类
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.example.annotation.MyRouter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions({"MODULE_NAME"})
public class MyRouterProcessor extends AbstractProcessor {
private Filer filer;
private Elements elementUtils;
private Messager printMessage;
private ArrayList<MyRouterBean> classNames = new ArrayList<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
printMessage = processingEnv.getMessager();
println("init-----------------------------------------------");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
classNames.clear();
println("process-----------------------------------------------");
/** 获取对应模块的 MODULE_NAME
javaCompileOptions {
annotationProcessorOptions {
arguments = [MODULE_NAME: project.getName()]
}
}
**/
String module_name = processingEnv.getOptions().get("MODULE_NAME");
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(MyRouter.class);
//遍历找到带MyRouter注解的Activity
for (Element element : elements) {
if (element instanceof TypeElement) {//类注解
TypeElement typeElement = (TypeElement) element;
PackageElement packageOf = elementUtils.getPackageOf(typeElement);
Name qualifiedName = typeElement.getQualifiedName();
println(packageOf.getQualifiedName().toString() + "--" + qualifiedName);
MyRouter annotation = typeElement.getAnnotation(MyRouter.class);
classNames.add(new MyRouterBean(qualifiedName.toString(), annotation.value()));
}
}
println("process---------------------测试--------------------------");
for (TypeElement typeElement : set) {
PackageElement packageOf = elementUtils.getPackageOf(typeElement);
Name qualifiedName = typeElement.getQualifiedName();
println(packageOf.getQualifiedName().toString() + "--" + qualifiedName);
}
try {
writeToFile(module_name);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
//生成Java代码
private void writeToFile(String module_name) throws IOException {
if (classNames.isEmpty()) {
return;
}
//ClassName会自动倒入包
ClassName string = ClassName.get("java.lang",
"String");
ClassName hashMap = ClassName.get("java.util",
"HashMap");
ClassName activity = ClassName.get("",
"? extends android.app.Activity");
ClassName clazz = ClassName.get("java.lang",
"Class");
//泛形参数
ParameterizedTypeName typeName = ParameterizedTypeName.get(clazz, activity);
ParameterizedTypeName type = ParameterizedTypeName.get(hashMap, string, typeName);
MethodSpec.Builder addRouter = MethodSpec.methodBuilder("addRouter")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addJavadoc("addRouter\r\n@routerMap 路由map")
.returns(TypeName.VOID)
.addParameter(
type
,"routerMap");
ClassName routerTools = ClassName.get("com.example.basemoudle",
"RouterTools");
for (MyRouterBean className : classNames) {
// addRouter.addStatement("$T.getInstance().addRouter(\"$L\",$L.class)",routerTools, className.getRouter(), className.getClassName());
addRouter.addStatement("routerMap.put(\"$L\",$L.class)", className.getRouter(), className.getClassName());
}
TypeSpec typeSpec = TypeSpec.classBuilder("MyRouter$$" + module_name.toUpperCase())
.addSuperinterface( ClassName.get("com.example.basemoudle","IRouter"))
.addModifiers(Modifier.PUBLIC)
.addMethod(addRouter.build())
// .addAnnotation(AnnotationSpec
// .builder(MyRouter.class)
// .addMember("value","value=$L","2")
// .build())
.build();
JavaFile javaFile = JavaFile.builder("com.example.router." + module_name.toLowerCase(), typeSpec)
.build();
javaFile.writeTo(filer);
}
private void println(String msg) {
printMessage.printMessage(Diagnostic.Kind.NOTE,
"MyProcessor:" + msg + "\r\n");
}
}
生成的代码如下:
//注解的类
@MyRouter("MainActivity")
class MainActivity : AppCompatActivity() {}
//app模块自动生成的代码
package com.example.router.app;
···
···
public class MyRouter$$APP implements IRouter {
/**
* addRouter
* @routerMap 路由map
*/
@Override
public void addRouter(HashMap<String, Class<? extends android.app.Activity>> routerMap) {
routerMap.put("MainActivity",com.zmp.javaproject.MainActivity.class);
}
}
app 初始化即可
//初始化
RouterTools.getInstance().init(this)
//使用路由
RouterTools.getInstance().navigate(this,"appb/BMainActivity")
plugin优化
此时的路由框架已经可以使用,但是apk运行期间初始化需要遍历dex,比较耗时
自定义插件
package com.example.plugin;
import com.android.build.gradle.AppExtension;
import com.android.build.gradle.AppPlugin;
import com.example.plugin.tools.Logger;
import com.example.plugin.transform.MyRouterTransform;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.jetbrains.annotations.NotNull;
/**
* Created at 9:25 2020/9/23
*
* @author zmp
* <p>
* des:
*/
public class MyPlugin implements Plugin<Project> {
@Override
public void apply(@NotNull Project project) {
Logger.make(project);
Logger.w("apply:init-----------------");
boolean hasAppPlugin = project.getPlugins().hasPlugin(AppPlugin.class);
Logger.w("apply:init-----------------" + hasAppPlugin);
if (hasAppPlugin) {
Logger.w("apply:init-----------------" + hasAppPlugin);
AppExtension android = project.getExtensions().getByType(AppExtension.class);
//register this plugin 注册MyRouterTransform
android.registerTransform(new MyRouterTransform(project));
}
}
}
自定义MyRouterTransform 完成插桩
/**
* Created at 9:41 2020/9/23
*
* @author zmp
* <p>
* des:
*/
public class MyRouterTransform extends MyBaseTransform {
public MyRouterTransform(Project project) {
super(project);
}
protected void transformJar(File input, File dest) {
if (ScanUtil.shouldProcessPreDexJar(input.getAbsolutePath())) {
ScanUtil.scanJar(input, dest);
} else {
super.transformJar(input, dest);
}
}
protected void transformSingleFile(String parentPath, File input, File dest) {
if (!parentPath.endsWith(File.separator)) {
parentPath += File.separator;
}
String replace = input.getAbsolutePath().replace(parentPath, "");
if (!File.separator.equals("/")) {
replace = replace.replaceAll("\\\\", "/");
}
try {
if (ScanUtil.shouldProcessClass(replace)) {
ScanUtil.scanClass(input, dest);
}
FileUtils.copyFile(input, dest);
} catch (Exception e) {
e.printStackTrace();
}
}
protected void insertInitCodeTo() {
File containsInitClass = ScanUtil.fileContainsInitClass;
Logger.e("containsInitClass:" + containsInitClass);
Logger.e("containsInitClass:" + Arrays.toString(ScanUtil.CLASS_NAMES.toArray()));
if (containsInitClass != null) {
insertInitCodeIntoJarFile(containsInitClass);
}
}
/**
* generate code into jar file
*
* @param jarFile the jar file which contains LogisticsCenter.class
* @return
*/
private void insertInitCodeIntoJarFile(File jarFile) {
try {
File optJar = new File(jarFile.getParent(), jarFile.getName() + ".opt");
if (optJar.exists()) {
optJar.delete();
}
JarFile file = new JarFile(jarFile);
Enumeration<JarEntry> 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 (ScanUtil.ASM_CLASS_NAME.equals(entryName)) {
byte[] 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);
} catch (Exception e) {
e.printStackTrace();
}
}
//refer hack class when object init
private byte[] referHackWhenInit(InputStream inputStream) {
Logger.e("referHackWhenInit");
byte[] bytes = null;
try {
ClassReader cr = new ClassReader(inputStream);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
bytes = cw.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
class MyClassVisitor extends ClassVisitor {
MyClassVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public 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
Logger.e("visitMethod:" + name);
if (name.equals(ScanUtil.GENERATE_TO_METHOD_NAME)) {
mv = new RouteMethodVisitor(api, mv, access, name, desc);
}
return mv;
}
}
static class RouteMethodVisitor extends AdviceAdapter {
protected RouteMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
@Override
protected void onMethodEnter() {
super.onMethodEnter();
}
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
//插入字节码
for (String className : ScanUtil.CLASS_NAMES) {
Logger.w("className:" + className);
push(className.replaceAll("/", "."));
invokeStatic(Type.getType(ScanUtil.ASM_CLASS_NAME_TYPE), new Method("register", "(Ljava/lang/String;)V"));
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + Integer.MAX_VALUE, maxLocals);
}
}
}
发布插件到 mavenLocal
apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'maven'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation gradleApi()
implementation 'com.android.tools.build:gradle:4.0.1'
}
apply plugin: 'maven-publish'
publishing {
publications {
mavenJava(MavenPublication) {
groupId 'com.example.android'
artifactId "zhanpple"
version 1.0
from components.java
// more goes in here
}
}
repositories {
mavenLocal()
}
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
image
使用插件 项目build.gradle中
repositories {
google()
jcenter()
mavenLocal()
}
dependencies {
...
...
classpath "com.example.android:zhanpple:1.0"
}
使用插件 app中
apply plugin: 'my.router.plugin'
apply plugin: 'my.router.plugin' 要与插件的gradle-plugins对应
image
编译app 对比字节码
image
网友评论