美文网首页
Android组件化之路由

Android组件化之路由

作者: 流星弑永恒 | 来源:发表于2020-09-28 14:41 被阅读0次

项目搭建

1.需求分析

2.功能模块划分

3.基础工具类、框架、接口提取

4.创建路由接口

5.配置gradle完成模块单独测试

if (appb_lib) {
   apply plugin: 'com.android.library'
}else {
   apply plugin: 'com.android.application'
}

Demo地址:https://github.com/zhanpple/MyProject

安装 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

参考代码 https://github.com/alibaba/ARouter

相关文章

网友评论

      本文标题:Android组件化之路由

      本文链接:https://www.haomeiwen.com/subject/nbvduktx.html