AppJoint原理解析

1,039 阅读5分钟
原文链接: www.jianshu.com
登录 注册写文章 首页下载APP

AppJoint原理解析

展翅高飞鹏程万里关注赞赏支持

AppJoint原理解析

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

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

概述

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

使用例子

//解决问题一,多Application问题
AppJoint类库文件中包含两个特殊的注解:
    @AppSpec
    @ModuleSpec(priority = ${value})//其中vlaue为指定初始化的优先级
    
    第一个注解AppSpec是用来标记壳Application,第二个注解ModuleSpec用来标记业务模块的Application。
    那么AppJoint类库就是根据这些标记,在壳Application中主动调用被ModuleSpec标记的Application中的生命周期方法,
    来达到初始化目的
//解决问题二,如何解决模块间相互调用的问题
AppJoint类库文件中包含一个特殊的注解
    @ServiceProvider(${"value"})//此处的Value用来标识当前实现类的唯一标识
    
    场景分析:比如用两个模块A,B。因为A,B模块是没有相互依赖的,那么A,B模块就不可以访问之间的API。
    但是我们又知道,通过下沉接口,然后通过使用反射获取真正的接口实现类对象,那么就可以在两者之间产生联系(相互调用)。
    AppJoint为了减少开发者的工作量,加快工作效率。自动完成了相关代码的编写。使到开发者只关注定义接口协议,和实现结构,供其他模块调用
    
   最后,我们可以在其他任何模块获得 Module1Service 接口的实例,调用里面的所有方法:(ModuleService为模块提供给外界调用的接口协议)
   Module1Service service = AppJoint.service(Module1Service.class);

AppJoint库实现逻辑

[图片上传失败...(image-203105-1584867416932)]

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学习笔记之生成方法
blog.csdn.net/aesop_wubo/…

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

推荐阅读更多精彩内容

评论0 赞1赞 赞赏