美文网首页
AppJoint原理解析

AppJoint原理解析

作者: 展翅高飞鹏程万里 | 来源:发表于2020-03-22 16:56 被阅读0次

    写这篇文章的初心:因为项目中有使用到AppJoint框架来搭建组件化的项目,自己也一直很好奇AppJoint框架的源码&代码逻辑,所以才有了这篇文章的出现。

    直接上库的开源地址 有兴趣的小伙伴不妨尝试一下使用之来搭建组件化框架,绝对开拓你的思路,不多逼逼,下面直接进入我们的主题。
    AppJoint开源项目地址:https://github.com/PrototypeZ/AppJoint

    概述

    AppJoint是在构建组件化过程中,解决某些问题手段工具。可以用来解决:多Application合并和初始化问题,模块间相互调用的问题。

    AppJoint能够解决组件化遇到的问题

    • 如何初始化多个Application
    • 如何在不相互依赖的模块间相互访问

    解决问题一,如何初始化多个Application
    场景分析
    一般组件化工程包含一个主模块的Application和多个模块中的Application。那如何解决多个Application初始化问题。
    解决方式
    一般人认为:因为主模块依赖所有的模块,理所当然可以访问所有的公共类。那么只需要在主模块Application的各个生命周期中,按一定顺序调用各个模块Application对应的生命周期方法,那么就可以解决这个问题
    AppJoint角度
    没错少年,你这个想法是非常正确的。但是做法有点他太low。如果有一百个Application你要手动在主模块Application的各个生命周期中调用一百次对应的各个Application的生命周期方法?是不是感觉太麻烦,所以AppJoint就是为了解决这个问题,引入了AOP,自动帮我们完成代码的插入(原理后面会提到)。从而达到了减少开发者的工作量,加快工作效率的目的

    AppJoint类库文件中包含两个特殊的注解:
        @AppSpec
        @ModuleSpec(priority = ${value})//其中vlaue为指定初始化的优先级
        
        第一个注解AppSpec是用来标记壳Application,第二个注解ModuleSpec用来标记业务模块的Application。
        那么AppJoint类库就是根据这些标记,在壳Application中主动调用被ModuleSpec标记的Application中的生命周期方法,
        来达到初始化目的
    

    解决问题二,如何在不相互依赖的模块间相互访问
    场景分析:如果有两个模块A,B,而且两个模块之间是没有相互依赖的,那么A,B模块之间就不可以相互访问。
    解决方式
    如果模块A和B有个共同依赖模块C,假设有个接口a1存在模块C中,并且模块A中存在接口a1的实现类A1。此时如果我想让模块B使用模块A中的 类A1,可是因为模块B没有依赖模块A , 所以访问不到类A1 。这种情况下可以使用反射创建类A1对象,然后通过JAVA语言多态的特性,赋值给类型a1。那么此时模块B就可以间接访问类A1对象和方法了。此处有不懂的小伙伴可以亲自尝试
    AppJoint实现方式
    其实AppJoint也是大致使用了上面的技巧(反射),但是它还帮我们完成了一步,就是自动找出所有模块中需要反射的类,然后我们需要的时候通过反射生成给我们使用。从而达到了减少开发者的工作量,加快工作效率的目的

    使用下面的注解标记需要被反射的类
       @ServiceProvider(${"value"})//此处的Value用来标识当前实现类的唯一标识
    使用下面的方法生成需要的类对象
       Module1Service service = AppJoint.service(Module1Service.class);
    

    AppJoint库实现逻辑

    AppJoint原理.png

    AppJonit具体代码分析

    使用了Aop技术其实就是自动完成AppJoint.java源文件应该需要承担的代码职责。减少开发者工作量,提高开发效率。

    要想彻底了解下面的执行逻辑,需要懂得技术:

    1.jar文件的格式,可以自行百度百科中查看
    2.安卓编写Gradle的插件流程
    3.ASM基本的原理,熟悉相关api(ASM是AOP实现的其中一种方案)

    • 如何填充AppJoint中的字段moduleApplications?看看AppJointPlugin是如何根据@ModuleSpec注解找寻相关的类文件。
    逻辑执行图.png
    代码逻辑
    //file为java源文件,output为文件要保存的文件夹
    boolean findAnnotatedClasses(File file, File output) {
        if (!file.exists() || !file.name.endsWith(".class")) {
            return
        }
        def needsModification = false
        def inputStream = new FileInputStream(file)
        //开始解析java源文件
        ClassReader cr = new ClassReader(inputStream)
        cr.accept(new ClassVisitor(Opcodes.ASM5) {
            static class AnnotationMethodsVisitor extends AnnotationVisitor {
    
                AnnotationMethodsVisitor() {
                    super(Opcodes.ASM5)
                }
    
                @Override
                void visit(String name, Object value) {
                    mProject.logger.info("Annotation value: name=$name value=$value")
                    super.visit(name, value)
                }
            }
    
            @Override//解析类文件头部是否包含注解
            AnnotationVisitor visitAnnotation(String desc, boolean visible) {
                mProject.logger.info("visiting $desc")
                switch (desc) {
                    //解析@ModuleSpec注解
                    case "Lio/github/prototypez/appjoint/core/ModuleSpec;":
                      addModuleApplication(new AnnotationModuleSpec(cr.className))
                      return new AnnotationMethodsVisitor() {
                        @Override
                        void visit(String name, Object value) {
                          def moduleApplication = moduleApplications.find({
                            it.className == cr.className
                          })
                          if (moduleApplication) {
                            moduleApplication.order = Integer.valueOf(value)
                          }
                          super.visit(name, value)
                        }
                      }
                    case "Lio/github/prototypez/appjoint/core/AppSpec;":
                        appApplications[file] = output
                        needsModification = true
                        break
                        //解析@ServiceProvider
                    case "Lio/github/prototypez/appjoint/core/ServiceProvider;":
                      return new AnnotationMethodsVisitor() {
    
                        boolean valueSpecified;
    
                        @Override
                        void visit(String name, Object value) {
                          valueSpecified = true;
                          cr.interfaces.each {
                              routerAndImpl[new Tuple2(it, value)] = cr.className
                          }
                          super.visit(name, value)
                        }
    
                        @Override
                        void visitEnd() {
                          if (!valueSpecified) {
                            cr.interfaces.each {
                              routerAndImpl[new Tuple2(it, SERVICE_PROVIDER_DEFAULT_NAME)] =
                                  cr.className
                            }
                          }
                          super.visitEnd()
                        }
                      }
                }
                return super.visitAnnotation(desc, visible)
            }
        }, 0)
        inputStream.close()
        return needsModification
    }
    
    //往moduleApplications添加元素(被ModuleSpec标记的Application)
    private void addModuleApplication(AnnotationModuleSpec annotationOrder) {
          for (int i = 0; i < moduleApplications.size(); i++) {
              if (annotationOrder.className == moduleApplications.get(i).className) {
                  // the module application class ready to be added is already marked
                  return
              }
          }
          moduleApplications.add(annotationOrder)
      }
      
      
      //将相关代码添加到AppJoint.java的构造器中
      class AddCodeToConstructorVisitor extends MethodVisitor {
    
        AddCodeToConstructorVisitor(MethodVisitor mv) {
            super(Opcodes.ASM5, mv)
        }
    
        @Override
        void visitInsn(int opcode) {
            switch (opcode) {
                case Opcodes.IRETURN:
                case Opcodes.FRETURN:
                case Opcodes.ARETURN:
                case Opcodes.LRETURN:
                case Opcodes.DRETURN:
                case Opcodes.RETURN:
                    //在构造器结尾处,插入moduleApplications包含所有Module的Application
                    moduleApplications.sort { a, b -> a.order <=> b.order }
                    for (int i = 0; i < moduleApplications.size(); i++) {
                        mProject.logger.info("insertApplicationAdd order:${moduleApplications[i].order} className:${moduleApplications[i].className}")
                        //moduleApplications[i].className,Application对应的文件名。作用插入代码
                        insertApplicationAdd(moduleApplications[i].className)
                    }
                  routerAndImpl.each {
                    Tuple2<String, String> router, impl -> insertRoutersPut(router, impl)
                  }
                    break
            }
            super.visitInsn(opcode)
        }
    
        /**
         * 使用字节码的方式,往AppJoint.class文件中的构造器插入下面的字节码
         * 字节码大概的意思:获取AppJoint类中的mmoduleApplications字段,
         * 并使用add方法添加New出来的applicationName实例。
         */
        void insertApplicationAdd(String applicationName) {
            mv.visitVarInsn(Opcodes.ALOAD, 0)
            mv.visitFieldInsn(Opcodes.GETFIELD, "io/github/prototypez/appjoint/AppJoint", "moduleApplications", "Ljava/util/List;")
            mv.visitTypeInsn(Opcodes.NEW, applicationName)
            mv.visitInsn(Opcodes.DUP)
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, applicationName, "<init>", "()V", false)
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true)
            mv.visitInsn(Opcodes.POP)
        }
    
    • 如何填充AppJoint中的字段routersMap?看看AppJointPlugin是如何根据@ServiceProvider注解找寻相关的类文件。
      在AppJointTransform.groovy文件中有个routerAndImpl字段,专门用来保存被@ServiceProvider标注的类文件,具体字段的类型为:

    HashMap<Tuple2<String, String>, String>()

    第一个String保存的字段为抽象的接口名字。
    第二个字段String保存的字段为@ServiceProvider中的Value字段。
    第三个String保存的具体的接口抽象类的名字。

    逻辑执行图.png
    //大致的逻辑和解析@ModuleSpec注解差不多,具体可以参考上面的代码
    
    //最后就是将查找的类信息,往AppJoint.class文件中的构造方法插入相关代码,来完成AppJoint的字段routersMap的初始化
    /**
    * 解释一下代码中的操作含义:获取AppJoint中的routersMap字段,然后获取抽象接口的Class对象,获取具体抽象类接口的实现类的Class对象
    * 和最后的ServiceProvider的Values字符串,一同保存到routerMap中。
    */
    void insertRoutersPut(Tuple2<String, String> router, String impl) {
          mv.visitVarInsn(Opcodes.ALOAD, 0)
      mv.visitFieldInsn(Opcodes.GETFIELD, "io/github/prototypez/appjoint/AppJoint", "routersMap",
          "Lio/github/prototypez/appjoint/util/BinaryKeyMap;")
      mv.visitLdcInsn(Type.getObjectType(router.first))//返回Class对象
      mv.visitLdcInsn(router.second)//String类型
      mv.visitLdcInsn(Type.getObjectType(impl))//返回Class对象
      mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "io/github/prototypez/appjoint/util/BinaryKeyMap",
          "put", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", true)
      }
    
    • 如何完成AppJointTransform中的appApplications赋值?看看AppJointPlugin是如何根据@AppSpec注解找寻相关的类文件。
      用来保存@AppSpec注解字段的数据,声明为def appApplications = [:],Key-Value的形式。
      左边的Key类型为:对应的类文件,类型为File,右边的Value类型为:对应类文件要保存的文件夹,类型为File
      逻辑执行图.png
    // 开始解析壳app的Application文件
    appApplications.each { File classFile, File output ->
        inputStream = new FileInputStream(classFile)
        ClassReader reader = new ClassReader(inputStream)
        ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS)
        ClassVisitor visitor = new ApplicationClassVisitor(writer)//这里的ApplicationClassVisitor承担了代码插入的功能
    
        reader.accept(visitor, 0)
        output.bytes = writer.toByteArray()//将插入的字节赋值给最终要输出文件
        inputStream.close()
    }
    
    //添加字节码到Application类文件中
    class ApplicationClassVisitor extends ClassVisitor {
    
        boolean onCreateDefined
        boolean attachBaseContextDefined
        boolean onConfigurationChangedDefined
        boolean onLowMemoryDefined
        boolean onTerminateDefined
        boolean onTrimMemoryDefined
    
    
        ApplicationClassVisitor(ClassVisitor cv) {
            super(Opcodes.ASM5, cv)
        }
    
        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions)
            mProject.logger.info("visiting method: $access, $name, $desc, $signature, $exceptions")
            switch (name + desc) {
                case "onCreate()V":
                    onCreateDefined = true
                    return new AddCallAppJointMethodVisitor(methodVisitor, "onCreate", "()V", false, false)
                case "attachBaseContext(Landroid/content/Context;)V":
                    attachBaseContextDefined = true
                    return new AddCallAppJointMethodVisitor(methodVisitor, "attachBaseContext", "(Landroid/content/Context;)V", true, false)
                case "onConfigurationChanged(Landroid/content/res/Configuration;)V":
                    onConfigurationChangedDefined = true
                    return new AddCallAppJointMethodVisitor(methodVisitor, "onConfigurationChanged", "(Landroid/content/res/Configuration;)V", true, false)
                case "onLowMemory()V":
                    onLowMemoryDefined = true
                    return new AddCallAppJointMethodVisitor(methodVisitor, "onLowMemory", "()V", false, false)
                case "onTerminate()V":
                    onTerminateDefined = true
                    return new AddCallAppJointMethodVisitor(methodVisitor, "onTerminate", "()V", false, false)
                case "onTrimMemory(I)V":
                    onTrimMemoryDefined = true
                    return new AddCallAppJointMethodVisitor(methodVisitor, "onTrimMemory", "(I)V", false, true)
    
            }
            return methodVisitor
        }
    
        @Override
        void visitEnd() {
            if (!attachBaseContextDefined) {
                defineMethod(4, "attachBaseContext", "(Landroid/content/Context;)V", true, false)
            }
            if (!onCreateDefined) {
                defineMethod(1, "onCreate", "()V", false, false)
            }
            if (!onConfigurationChangedDefined) {
                defineMethod(1, "onConfigurationChanged", "(Landroid/content/res/Configuration;)V", true, false)
            }
            if (!onLowMemoryDefined) {
                defineMethod(1, "onLowMemory", "()V", false, false)
            }
            if (!onTerminateDefined) {
                defineMethod(1, "onTerminate", "()V", false, false)
            }
            if (!onTrimMemoryDefined) {
                defineMethod(1, "onTrimMemory", "(I)V", false, true)
            }
            super.visitEnd()
        }
    
        void defineMethod(int access, String name, String desc, boolean aLoad1, boolean iLoad1) {
            MethodVisitor methodVisitor = this.visitMethod(access, name, desc, null, null)
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0)
            if (aLoad1) {
                methodVisitor.visitVarInsn(Opcodes.ALOAD, 1)
            }
            if (iLoad1) {
                methodVisitor.visitVarInsn(Opcodes.ILOAD, 1)
            }
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "android/app/Application", name, desc, false)
            methodVisitor.visitInsn(Opcodes.RETURN)
            methodVisitor.visitEnd()
        }
    }
    
    
    //在对应得生命周期得方法中调用AppJoint.class对应的生命周期的方法
    class AddCallAppJointMethodVisitor extends MethodVisitor {
    
        String name
        String desc
        boolean aLoad1
        boolean iLoad1
    
        AddCallAppJointMethodVisitor(MethodVisitor mv, String name, String desc, boolean aLoad1, boolean iLoad1) {
            super(Opcodes.ASM5, mv)
            this.name = name
            this.desc = desc
            this.aLoad1 = aLoad1
            this.iLoad1 = iLoad1
        }
    
        void visitInsn(int opcode) {
            switch (opcode) {
                case Opcodes.IRETURN:
                case Opcodes.FRETURN:
                case Opcodes.ARETURN:
                case Opcodes.LRETURN:
                case Opcodes.DRETURN:
                case Opcodes.RETURN:
                    //调用AppJoint的get方法,获取对应的AppJoint对象。
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "io/github/prototypez/appjoint/AppJoint", "get", "()Lio/github/prototypez/appjoint/AppJoint;", false)
                    if (aLoad1) {
                        mv.visitVarInsn(Opcodes.ALOAD, 1)
                    }
                    if (iLoad1) {
                        mv.visitVarInsn(Opcodes.ILOAD, 1)
                    }
                    //访问AppJoint对应name方法。
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "io/github/prototypez/appjoint/AppJoint", name, desc, false)
                    break
            }
            super.visitInsn(opcode)
        }
    }
    

    AppJoint得源码就分析完了,看完源码之后,我们知道AOP得这种技术是多么得好用,强大。也看到了会操作字节码,看懂字节码在AOP编程中有多重要。建议大家平时可以多了解AOP在安卓得应用,AOP目前有多种实现框架,都可以列出来对比对比。
    提示:要实现跨模块调用可以用多种方式技巧,将接口下沉到公共模块,再使用反射技术,构造具体接口的实现类,从而达到跨模块调用。其实AppJoint也是实现了这个逻辑,不过他们为了减少开发者工作量,自动帮我们填充了必要的代码,完成了模块调用之间的关联。

    下面介绍几篇学习ASM的文章:

    asm学习笔记之生成方法
    https://blog.csdn.net/aesop_wubo/article/details/48948211

    推荐大家一本书:《深入理解Java虚拟机》里面有详细介绍GC机制,.class的结构,包括字节码等等

    相关文章

      网友评论

          本文标题:AppJoint原理解析

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