安卓代码修复框架Patch(2)

2019-07-28  本文已影响0人  呵呵_9e25

安卓代码修复框架Patch

只支持程序重启修复,不支持热修复

markdown

上一篇文章自定义了gradle插件,但是这并没有啥用,因为我们要做代码修复,然而我们的插件没有做任何东西


当然我们必须知道我们的插件要做代码修复,具体要明确我们修复的目的,那我们开始列出我们的几个目标,然后一一实现它

插件目标


实现目标

class BLogger{
    private static String TAG

    static void init(BaseVariant variant){
        //构建变体的名称
        TAG=">PatchPlugin-${variant.name.capitalize()}"
    }

    static void i(String log){
        println("${TAG}:${log}")
    }
}

这里我们传递了一个BaseVariant类型参数进来,这样每个构建变体构建时都能区分开来

 @Override
    void apply(Project project) {
        //afterEvaluate是一般比较常见的一个配置参数的回调方式,只要project配置成功均会调用,
        project.afterEvaluate {
            project.android.applicationVariants.each {
                variant ->
                        //初始化日志打印器
                        BLogger.init(variant)
                        BLogger.i("Hello World 中国")
            }
        }
    }

我们在project.afterEvaluate项目配置之后,可以通过project.android.applicationVariants遍历获取到所有的构建变体,然后我们把我们开始配置我们的日志类,然后打印一下日志

我们点击上传一下这个插件 ,然后点击app的assemble任务看看构建日志

image.png
我们可以看到,他打印了两个变体的日志,因为我们现在没有自定义productFlavors { xiaomi{} },所以并没有出现小米的变体,但是还是会有默认的DebugRelease这两个构建变体

我们分析一下这个问题,第三库和安卓提供的代码库我们都不需要管,我们只想修复我们需要修复的代码,另外还有一个问题是,我们有一些业务比较稳定,也没有出现bug的可能性了,我们是不是应该区分一下,就不需要做代码注入了

基于上面的分析,我们先做一个准备,就是我们必须让开发者可以自己配置哪些业务代码是不需要被注入代码的,同时哪些类是需要注入的,这些都需要动态配置,需要怎么做呢?
我们可以参考android插件的配置,我们自定义一个扩展。
下面我们来实现一下 ,然后我们验证一下有没有用

 @Override
    void apply(Project project) {
       //第一步创建扩展
        project.extensions.create("bigman", BigmanExtension, project)

        //afterEvaluate是一般比较常见的一个配置参数的回调方式,只要project配置成功均会调用,
        project.afterEvaluate {
            //通过名字找到配置里面的bigman扩展
            extension = project.extensions.findByName("bigman") as BigmanExtension

            project.android.applicationVariants.each {
                variant ->
                    BLogger.init(variant)
                    extension.excludeClass.each {
                        name ->
                            BLogger.i("排除类名称:${name}")
                    }
            }
    }
apply plugin: 'com.android.application'
apply plugin: 'com.bigman.tech'
bigman{
    includePackage = ['com/example/administrator/patchdemo']
    excludeClass = ['com/example/administrator/patchdemo/patchlib/SignaChecker','me/wcy/cfix/sample/Test']
}

然后我们对比一下上面的代码

 extension.excludeClass.each {
                        name ->
                            BLogger.i("排除类名称:${name}")
                    }

这里如果我们配置了includePackage,那其实已经可以很好地排除第三方库,但是假如我们这个位置不做任何配置,那我们还需要做额外的事情,比如android/support/,R,BuildConfig这些不应该修改的类文件,怎么做,这个就比较简单我们这里会提供一个方法,这里后面会用到,代码先放出来

private static boolean shouldProcessClass(File classFile,BigmanExtension extension){
        if (!classFile.exists()||!classFile.name.endsWith(SdkConstants.DOT_CLASS)){
            return false
        }

        FileInputStream inputStream=new FileInputStream(classFile)
        ClassReader cr=new ClassReader(inputStream)
        String className=cr.className
        inputStream.close()
        return !className.startsWith("com/bigman/tech/lib")&&
                !className.contains("android/support/")&&
                !className.contains("/R\$")&&
                !className.endsWith("/R")&&
                !className.endsWith("/BuildConfig")&&
                PatchSetUtils.isIncluded(className,extension.includePackage)&&
                !PatchSetUtils.isExcluded(className,extension.excludeClass)
    }

其实上面的代码就能解决上面说的问题,好了目标2已经完成


//Gradle版本为1.5-2.x
   Task dexTaskLower = project.tasks.findByName("transformClassesWithDexFor${variant.name.capitalize()}")
    //Gradle版本为3.0+
  Task dexTaskHigner = project.tasks.findByName("transformClassesWithDexBuilderFor${variant.name.capitalize()}")
   //获取生成dex的任务
   Task dexTask
 if (dexTaskLower != null) {
        dexTask = dexTaskLower
  } else if (dexTaskHigner != null) {
        dexTask = dexTaskHigner
  } else {
    BLogger.i("Gradle 版本暂不支持")
   return
 }

这里任务的名称其实是固定的 我们兼容了1.5-3.0+版本,通过project.tasks.findByName就能获取到任务名称
这里其实我们可以抽出来写成一个方法,看起来简洁一点

我们说要在dex任务之前进行,但是具体要怎么做,大家应该是懵逼的

     //补丁任务将在dex任务获取完依赖之后再进行
     patchJarBeforeDexTask.dependsOn 
                         dexTask.taskDependencies.getDependencies(dexTask)
      //dex任务将在补丁任务执行完之后再执行
     dexTask.dependsOn patchJarBeforeDexTask

patchJarBeforeDexTask这里是我们将要进行代码注入的任务,这个任务我们等一下会讲到,大家知道它是一个任务就行
上面两句代码的意思就是我们这个代码注入任务patchJarBeforeDexTask是在dexTask任务获取完所有的依赖文件之后执行,这样能保证class文件的完整性,然后dexTask任务必须在我们代码注入任务patchJarBeforeDexTask完成之后执行
我们在这里改变了打包任务的执行次序,而且创建了一个patchJarBeforeDexTask的任务,当然这个任务使我们的核心


     String patchJarBeforeDex = "patchJarBeforeDex${variant.name.capitalize()}"
     project.task(patchJarBeforeDex) << {
                            Set<File> inputFiles = dexTask.inputs.files.files
                            inputFiles.each {
                                file ->
                                    //打印dex任务返回的输入文件
                                    BLogger.i("transformClassesTask input:${file.absolutePath}")
                            }
                        }

可以看到我们通过project.task(patchJarBeforeDex) << {}创建了一个任务,这个任务就是我们的核心任务,我们可以看到我们在任务里面获取到了dexTask的一些输入文件,其实它们就是我们的class文件,我们上传一下,然后看看效果,操作和上面的一样。

image.png
可以看到已经获取到了所有的class文件和jar文件,这些文件我将对它们进行过滤和代码插入
我们可以简要看看哪些类应该过滤,比如上图的R.class,R$**.class,android\support\这几个安卓内置的类咱们都不需要去植入代码,那么大家就能理解我们上面写的那几句代码的意思了
  !className.contains("android/support/")&&
                !className.contains("/R\$")&&
                !className.endsWith("/R")&&

这里就是过滤安卓内部代码的语句


其他代码看源码 不说了

      
      
     

















上一篇 下一篇

猜你喜欢

热点阅读