Android热修复技术(二) Groovy语法及打Patch包

一、前言

上篇我们讲到了patch包的打包是通过dex命令来生成classes.dex之类的dex文件,但是实际项目开发中我们不可能每次都把对应的包、类一一拷贝出来然后自己手动去敲dx命令去打包,所我们的目的是编写任务和插件去自动打patch包,在这个过程中我们首先需要学习的是Gradle Task和Plugin的定制

二、Groovy语法

我们平时使用的Android studio开发就是基于Gradle来构建的,而Gradle是基于Grovvy语言的构建工具,它使用DSL语法,(Domain Specified Language 是领域专用语言,通俗的说叫行话,能达到共识的一种语言)将开发过程中需要的编译、构建、测试、打包以及部署工作,变得非常简单方便和重复使用,所以在学习gradle之前,咱们先大概了解下groovy的基本语法

在Groovy中编写Java代码也是可以的,因为Groovy是一门JVM语言,最终也会被编译成JVM字节码,交给虚拟机去执行

1.变量声明

访问修饰符

def:相当于局部变量
ext:相当于全局变量

声明变量

//groovy是一种规则非常松的语言,语句后面不用写分号
//而且类型也可以不用写
int x = 1
def x = 1
2.字符串
def s1 = 'the x is $x'  =>  单引号,输出会原样得到 the x is $x
def s2 = "the x is $x"  =>  双引号,输出的时候会对$符号进行转义 the x is 1
def s3 = '''
this is the first line
this is the second line
this is the third line
'''  
 => 三个引号'''xxx'''中的字符串支持随意换行,对$符不能进行转义
3.范围

范围是指值序列的速记,范围由序列中的第一个和最后一个表示,下面是范围写法的例子

  • 1…10 =>[1,10]
  • 1…<10 =>[1,10)
  • ‘a’…‘x’ =>[a,x]
  • 10…1 =>[10,1]
def test(){
   for(i in 1..5){
   		println i
   }
}
4.集合和映射

集合,相当于Java中List对象

//创建空列表
def list = []

//增加一个元素
list.add("Gradle")

//左移添加,返回该列表
list << "Groovy"

//增加多个元素
list.addAll(["Groovy","Gradle"])

//删除一个元素
list.remove("Groovy")

映射,相当于Java中的Map对象

//创建空映射
def map = [:]

//增加值
map = ['name':'shanshan','age':18]
map.put('lastName','xue')

//遍历映射元素
map.each{
	println "$it.key : $it.value"
}
map.eachWithIndex{ it, i ->
	println "$i : $it"
}

//判断映射是否包含某键
assert map.containsKey('name')
5.函数和闭包

函数,也可以写成和Java中一样的方法,不过groovy可以写的更简单些

//形参类型可以不写
String doSomething(arg1, arg2){
	println "method doSomething(arg1, arg2)"
}
//不指定返回类型,必须加def  最后一句不用写return 默认返回最后一句
def doSomething(){
	println "method doSomething"
}

闭包(Closure),是一种数据结构,属于一段代码,使用{}包括

//无参数
def closure1 = {
	println "hello world"
}
closure1()  //执行闭包,输出 hello world

--------------------------------------------------------------

//接收一个参数
def closure2 = {
	String str -> println str  //箭头前面是参数定义,后面是执行代码,str为外部传入的参数
}
如果只有一个参数可以用it代替,也可以写作:
def closure2 = {
	println it
}
closure2("hello world")  //执行闭包,输出 hello world

--------------------------------------------------------------

//接收多个参数
def closure3 = {
	String str, int n -> println "$str : $n"
}
//也可以省去参数类型,写作:
def closure3 = {
	str, n -> println "$str : $n"
}
closure3("hello world",1)  //执行闭包,输出 hello world : 1

--------------------------------------------------------------

//使用变量
def var = "hello world"
def closure4 = {
	println var
}
closure4()  //执行闭包 输出 hello world

--------------------------------------------------------------

//改变上下文
def closure5{
	println name  //这时name并不存在
}
MyClass m = new MyClass()
closure5.setDelegate(m)
class MyClass{
	def name = "hello world"
}
closuere5() //执行闭包,输出 hello world

闭包的优势

比如我们在写一个方法的时候,中间的某项操作可能需要调用者来实现,那么我们一般情况下是通过接口来实现,如果有了闭包,就可以直接使用闭包来实现,比如

interface Callback{
	void doCall(int a);
}
def doSomething(Callback callback){
	println "start"
	callback.doCall(10)  //需要通过接口来实现
	println "over"
}
//使用闭包
def doSomething(Closure c){
	println "start"
	c(10)  //直接通过闭包来实现,相当于传入了一段代码,也可以指定参数
	println "over"
}
//调用doSomething方法,省去了()
doSomething{ int a->
	println "a is ${a}"
}
6.类和对象

Groovy和其他面向对象语言一样,也存在类和对象的概念,和java中类的定义是一样的。
跟java的区别就在于Groovy会为每个字段生成getter和setter,我们可以访问字段本身访问setter和getter,比如

class MyClass{
	private String name
}
def myClass = new MyClass()
myClass.name = "this is name"  //因为groovy动态的为name创建了setter和getter,所以可以直接访问
println myClass.name
7.delegate机制
class Child{
	private String name
}
class Parent{
   private String name //parent也有一个name属性
	Child child = new Child();
	
	void config(Closure c){
		c.delegate = clild
		c.setResolveStrategy Closure.DELEGATE_FIRST //默认情况是OWNER_FIRST,即它会先查找闭包的owner(这里指parent)
		c()
	}
}

def parent = new Parent()
parent.config{
	name = "this is name"
}
println parent.name  //打印结果为null
println parent.child.name //打印结果为this is name

三、Gradle 工程和任务的认识

Gradle是一个框架,它负责定义流程和规则
每一个待编译的工程都叫一个Project
每一个Project在构建的时候都包含一系列Task

当我们新建一个项目,在没有编写任何gradle代码之前,我们可以在Terminal终端输入gradle tasks命令(需要配置环境变量)查看当前工程中可使用的任务有哪些

在android studio中,我们有Gradle Wrapper,它为Window、OSX、Linus平台都提供了相应的脚本文件,这些脚本使你可在不同平台的不同Gradle版本上正确执行编译打包脚本,不需要配置环境变量,这时候需要看所有任务需要执行的命令是./gradlew tasks

这里写图片描述

从图中可以看到,Android studio自动为我们添加了一些默认的任务,通过Gradle这些任务我们可以很方便的编译打包工程,而那些任务又是通过添加插件轻松实现的,都是通过build.gradle中apply plugin: 'com.android.application'这句话来实现的

一、工程和任务的关系

在Gradle的世界中最重要的两个概念就是:工程(Project)和任务(Task),
每一个Gradle项目都会包含一个和多个工程,一个工程可以是一个Module,也可以是一个第三方库

每一个工程又由一个或多个任务组成,一个任务代表了一个工作的最小单元,它可以是一次类的编译,打一个包等,Project为Task提供了执行上下文

这里写图片描述

二、工程属性

Project properties是Gradle专门为Project定义的属性,对于一个project来说,它有很多默认属性,比如:

  • project: Project本身
  • name: Project的名字
  • path: Project的绝对路径
  • description: Project的描述信息
  • buildDir: Project构建结果存放目录
  • version: Project的版本号

除了默认属性,我们也可以给project添加自定义属性,添加方法为

ext{
	versionCode = 1
	versionName = 1.0.0
}

使用方法

defaultConfig{
	versionCode project.property("versionCode")
	versionName project.property("versionName")
}

另外有一个小tip:就是我们可以把一些不想让别人看到的配置放在gradle.properties这个文件里面,比如

USER_NAME = 'user_name'
PASS_WORD = 'password'

//在build.gradle里面可以直接使用 "${USER_NAME}" 拿到

除了在build.gradle中定义属性,我们也可以通过命令 -P 完成定义和赋值,这种方式具有最高优先级

./gradlew -PversionCode=2 -PversionName=2.0.0 

另外一种方式,我们可以通过JVM系统参数定义Property 使用命令-D (需要以“org.gradle.project”为前缀)

./gradlew -Dorg.gradle.project.versionCode=3   
-Dorg.gradle.project.versionName=3.0.0

最后一种方式,通过环境变量设置Property (需要以“ORG_ GRADLE_ PROJECT_ ”为前缀)

export ORG_ GRADLE_ PROJECT_versionCode=4  
export ORG_ GRADLE_ PROJECT_versionName=4.0.0
三、任务的定义和使用

首先我们定义一个很简单的任务hello,有几种方式

//第一种
task hello{
	doLast{
		println 'hello World!'
	}
}
//上面的任务还可以使用以下写法,不过gradle 5.0已经过时了,可以作为了解
task hello << { //语法糖,相当于doLast
  	println 'hello World!'
}

//第二种  以字符串形式定义的
task('hello'){
	doLast{
		println 'hello World!'
	}
}

//第三种 以字符串类型定义的 并定义类型
task ('copy', type: Copy){
	from (file('srcDir')
	into (buildDir)
}
//或者不使用字符串定义 定义类型
task copy(type: Copy){
	from (file('srcDir')
	into (buildDir)
}

//第四种 使用create创建
tasks.create(name: 'hello'){
	doLast{
		println 'hello World!'
	}
}
//使用create创建并指定类型
tasks.create(name: 'hello',type: Copy){
	doLast{
		from (file('srcDir')
		into (buildDir)
	}
}

上面的任务可以在终端中输入 ./gradlew hello就能输出 hello World! ,这样就很简单的定义了一个任务

task有两个生命周期,配置阶段执行阶段

在配置阶段,Gradle将读取所有的build.gradle文件的所有内容来配置Project和Task等,比如设置Project和Task的Property,处理Task之间的依赖关系等等

task Task1{
	def name = "hello"  //这段代码是在配置阶段执行的,配置阶段也就是说在task没有执行的时候这段代码就会被扫描到,如果访问了访问不到的字段,就会直接报错
	
	doFirst{ //这段代码在task执行阶段执行,执行阶段是指通过./gradle Task1执行的时候这段代码才会真正执行
		println "doFirst $name"
	}
	
	doLast{  //这段代码在task执行阶段执行
		println "doLast $name"
	}
}
四、自定义Task

在Gradle中,我们有三种方法可以自定义Task

  • 在build.gradle文件中直接定义
class HelloWorldTask extends DefaultTask{
   @Optional  //标识可选
	String message = "default message"
	
	@TaskAction  //标识Task要执行的动作
	def hello(){
		println $message
	}
}

//使用方式
task hello(type: HelloWorldTask)

task hello2(type: HelloWorldTasl){
	messge = "this is a new message"  //给task参数赋值
}

//对于给参数赋值有几种方式,上面在定义task的时候赋值是一种,还有
方法二
hello2{
	message = "xxx"  //Gradle会为每个task创建一个同名的方法,该方法接受一个闭包
}
或
hello2.message="xxx"  //Gradle会为每个task创建一个同名的property,所以可以将Task当做property来使用

方法三  通过Task的configure()方法完成Property的设置
hello2.configure{
    message = "xxx"
}

  • 在当前工程的buildSrc目录下定义
    在第一种方式中,在build.gradle中直接定义Task,这样Task的定义和使用混合在一起,在需要定义Task不多时,可以采用这种方法,但是在项目中存在大量自定义Task时,这种方法就非常不合适了,一种改进方法是在另外的一个gradle文件中定义Task,然后使用apply from:到build.gradle中。

    这里我们使用另一种方法,在buildSrc目录下定义,Gradle执行时,会自动查找该目录下所定义的Task类型,并首先编译该目录下的groovy代码以供build.gradle文件使用。具体做法是在当前工程buildSrc/src/main/groovy/com/huli 目录下创建HelloWorldTask.groovy文件,将Task的定义拷贝过来

  package com.huli  //需要加上包名
  
  class HelloWorldTask extends DefaultTask{
	   @Optional  //标识可选
		String message = "default message"
		
		@TaskAction  //标识Task要执行的动作
		def hello(){
			println $message
		}
  }
  
  //使用方式区别在于 引用Task时,需要指定全名称
  task hello(type: com.huli.HelloWorldTask)
    
  • 在单独的项目中定义Task
    虽然第二种方式Task的定义和build.gradle分离开了,但是它依然只能应用在当前工程中,如果我们希望所定义的Task能够用在另外的项目中,那么第二种方式就不可行了,此时我们需要将Task的定义放在单独的工程中,然后在要使用该Task的工程中通过声明依赖的方式引入这些Task
    这种方式跟后面的自定义Plugin的第三种方式类似,这里不详细介绍,可以看后面。
五、任务的依赖关系

我们可以定义一个任务依赖于某个其他的任务

task build{
	doLast{
		println "i am build task"
	}
}

task release(dependsOn: build){  //通过dependsOn来指定依赖于某个任务
	doLast{
		println "i am release task"
	}
}
//如果上面两个任务是本来就有的而不是自己新写的,那么可以通过下面的方式来指定依赖
release.dependsOn build

在终端运行 ./gradlew release将会得到两行结果

i am build task
i am release task

六、对现有任务添加动作行为

比如要对上面定义的hello添加动作行为

//第一种方法
//在doFirst中添加
hello.doFirst{
	println "hello doFirst"
}

//在doLast中添加
hello.doLast{
	println "hello doLast1"
}

//第二种方法
hello{
	doLast{
		println "hello doLast2"
	}
}

在终端执行得到结果为:

hello doFirst
hello World!
hello doLast1
hello doLast2

七、给任务设置属性并访问
task myTask{
   //ext是固定写法
	ext.myProperty = "myValue"
	//可设置其他属性
	...
}
task printTaskProperties{
	doLast{
		println myTask.myProperty
	}
}
八、增量式构建

如果我们将Gradle的Task看作一个黑盒子,那么我们便可以抽象出输入和输出的概念,一个Task对输入进行操作,然后产生输出。比如,在使用java插件编译源代码时,即输入为Java源文件,输出则为class文件。如果多次执行一个Task的输入和输出是一样的,那么我们便可以认为这样的Task是没有必要重复执行的。此时,反复执行相同的Task是冗余的,并且是耗时的。

为了解决这样的问题,Gradle引入增量式构建的概念。在增量式构建中,我们为每一个Task定义输入(inputs)和输出(outputs),如果在执行一个Task时,他的输入和输出与前一次执行时没有发生变化,那么Gradle便会认为该Task是最新的(UP-TO-DATE),因此Gradle将不予执行。一个Task的inputs和outputs可以是一个或多个文件,可以是文件夹,还可以是Project的某个Property,甚至可以是某个闭包所定义的条件。

每个Task都拥有inuts和outputs属性,他们的类型分别为TaskInputs和TaskOutputs,在下面的例子中,我们展示了这么一种场景:名为combineFileContent的Task从sourceDir目录中读取所有的文件,然后将每个文件的内容合并到destination.txt中。让我们先来看看没有定义Task输入和输出的情况:

task combineFileContentNonIncremental {
   def sources = fileTree('sourceDir')

   def destination = file('destination.txt')

   doLast {
      destination.withPrintWriter { writer ->
         sources.each {source ->
            writer.println source.text
         }
      }
   }
}

多次执行./gradlew combineFileContentNonIncremental时,整个Task都会反复执行,即便在第一次执行已经得到了所需的结果,如果该task是一个非常繁重的task,那么多次重复执行势必造成没必要的时间浪费

这时,我们可以将sources声明为该Task的inputs,而将destination声明为outputs,重新创建task如下:

task combineFileContentIncremental {
   def sources = fileTree('sourceDir')
   def destination = file('destination.txt')

   //相比之下,只多了这两行代码
   inputs.dir sources
   outputs.file destination

   doLast {
      destination.withPrintWriter { writer ->
         sources.each {source ->
            writer.println source.text
         }
      }
   }
}

当首次执行combineFileContentIncremental时,Gradle会完整的执行该Task,但是紧接着再执行一次,命令就会显示

:combineFileContentIncremental UP-TO-DATE

BUILD SUCCESSFUL

Total time: 2.104 secs

可以看到,combineFileContentIncremental被标记为UP-TO-DATE,表示该Task是最新的,Gradle将不予执行,在实际应用中,你将遇到很多这样的情况,因为Gradle的很多插件都引入了增量式的构建机制

如果我们修改了inputs(即sourceDir文件夹)中的任何一个文件或删掉了destination.txt,当调用该任务时,Gradle又会重新执行,因为此时的Task已经不再是最新的了

九、Gradle目录结构分析(番外话题,作为了解)

在默认情况下,Gradle采用了与Maven相同的Java项目目录结构(src/main/java src/test/java之类的),在采用Maven目录结构的同时,还融入了自己的一些概念,即source set,Gradle为我们创建了2个source set,一个名为main,一个名为test

请注意,这里的source set的名字main与目录结构中的main文件夹并无必然的联系,只是在默认情况下,Gradle为了source set概念到文件系统目录结构映射方便,才采用了相同的名字,对于test也是如此。我们完全可以在build.gradle文件中重新配置这些source set所对应的目录结构,同时,我们还可以创建新的source set

从本质上讲,Gradle的每个source set都包含有一个名字,并包含有一个名为java和另一个名为resources的Property,他们分别用于表示该source set所包含的java源文件集合和资源文件集合,在实际应用时,我们可以将他们设置成任何目录值,比如

sourceSets {
   main {
      java {
         srcDir 'java-sources'
      }
      resources {
         srcDir 'resources'
      }
   }
}

另外我们还可以创建新的sourceSet,比如:

sourceSet{
	api
}

默认情况下,该api对应的Java源文件目录被Gradle设置为src/api/java,而资源文件目录则被设置成了src/api/resources,我们也可以像上面的main一样重新对api的目录结构进行配置

Gradle默认会自动为每一个创建的source set创建相应的task,创建规律为:对于名为A的sourceSet,Gradle将为其创建compile<A>Java、process<A>Resources和<A>Classes这3个Task,对于上面的api而言,Gradle会为其创建compileApiJava、processApiResources和apiClasses Task

你可能会注意到,对于main而言,Gradle并没有相应的compileMainJava,原因在于:由于main是Gradle默认创建的sourceSet,并且又是极其重要的,便省略掉了其中的Main,而是直接使用了compileJava作为main的编译Task,对于test来说,Gradle依然采用了compileTestJava

通常情况是,我们自己创建的名为api的source set会被其他source set所依赖,比如main中的类需要实现api中的某个接口等,此时,我们需要做两件事,第一,我们需要在编译main之前对api进行编译,即编译main中的Java源文件的Task应该依赖于api中的Task

Classes.dependsOn apiClasses

第二,在编译main时,我们需要将api编译生成的class文件放在main的classpath下

sourceSets{
	main{
		compileClasspath = compileClassPath + files(api.output.classesDir)
	}
	
	test{
		runtimeClasspath = runtimeClasspath + files(api.output.classesDir)
	}
}

四、项目构建过程中任务分析

对于我们一个Android项目来说,在你点了Android Studio上面的运行按钮的时候,等编译完成项目就能安装到手机上去了,但你知道这中间发生什么事情了吗?下面我们就需要分析这个过程大概都做了什么

我们主module app的build.gradle一般都是以 apply plugin: 'com.android.application' 开始的,这是一个Android studio自带的插件,该插件内部包含许多的Android相关的task,我们一般主要使用的是assemblecleanbuild,而其中assemble就是我们用于打包apk所用的task,而这个task的依赖关系特别复杂,下面这个图表示了assembleDebug的依赖关系

这里写图片描述

看上面的图着实很让人难以看懂其中的顺序,所以下面给出了相应task的执行顺序

:app:preBuild    
:app:preDebugBuild      
:app:checkDebugManifest  
:app:prepareDebugDependencies   
:app:compileDebugAidl
:app:compileDebugRenderscript
:app:generateDebugBuildConfig  //generated/source文件夹下生成buildConfig文件夹,根据不同的flavor和buildType生成对应文件夹
:app:generateDebugAssets
:app:mergeDebugAssets
:app:generateDebugResValues  //gennerated/res文件夹下,生成resValues文件夹,根据不同的flavor和buildType生成对应文件夹
:app:generateDebugResources
:app:mergeDebugResources
:app:processDebugManifest
:app:processDebugResources
:app:generateDebugSources  //在gennerated文件夹下生成对应的R.java文件
:app:compileDebugJavaWithJavac  //开始使用javac将.java编译成.class文件 intermediates下生成classes文件夹,所以我们代码注入一般放在这个步骤之后,因为代码注入是基于.class文件来做的
:app:compileDebugNdk
:app:compileDebugSources
:app:transformClassesWithDexForDebug
:app:mergeDebugJniLibFolders
:app:transformNative_libsWithMergeJniLibsForDebug
:app:processDebugJavaRes
:app:transformResourcesWithMergeJavaResForDebug
:app:validateDebugSigning   
:app:packageDebug
:app:zipalignDebug
:app:assembleDebug 

以上task都是属于app的module中的,若有多个module,gradle会为每个module执行一次该task链

有两个Task我们需要留意:
:app:compileDebugJavaWithJavac ————>编译.java文件到.class
:app: transformClassesWithDexForDebug ————>将.class文件打成dex包

根据以上依赖关系,如果我们有比如打patch包,或者代码注入等的需求(这些需求都需要在.java文件编译成.class文件后、打包成dex之前进行操作),就可以定义task然后进行依赖的插入,或者使用doFirstdoLast等相关操作来实现打包过程中,插入自己的业务需求

五、自定义Plugin

在Plugin中,我们可以向Project中加入新的Task,定义configurations和property等。在Gradle中创建自定义插件,有三种方式(跟Task定义的三种方式类似):

  1. 在build.gradle脚本中直接使用

  2. 在buildSrc中使用
    buildSrc是Gradle提供的在项目中配置自定义插件的默认目录

  3. 在独立Module中使用

每一个自定义的Plugin都需要实现Plugin接口,上面三种方式只是plugin放置的地方不一样(放置位置跟上面task的一样),其实Plugin的定义是一样的,我们重点讲第三种,下面是步骤分析

创建AndroidLibrary 取名为hotfix-patch,删除除build.gradle之外的其他文件

修改插件的build.gradle
apply plugin: 'groovy'  //使用groovy插件
apply plugin: 'maven'  //要上传maven仓库,所以也要用maven插件

repositories {
    mavenCentral()
    google()
    jcenter()
}

dependencies {
    //gradle sdk
    compile gradleApi()
    //groovy sdk
    compile localGroovy()
}


uploadArchives {
    repositories {
        mavenDeployer {
            //本地maven地址
            repository(url: uri('../repo-hotfix-patch'))
            pom.groupId = 'com.huli.plugin'
            pom.artifactId = "hotfix-patch"  //插件名
            pom.version = '1.0.0'
        }
    }
}

我们gradle的包管理都是存储在maven仓库中的,我们这里定义一个本地的maven仓库,我们代码完成后需要先打包代码到本地的maven仓库,然后在主项目中依赖我们打好的包

按照以下目录创建项目

这里写图片描述

此处有坑,注意下面的META-INF和gradle-plugins是父子目录关系,不是一个目录名称,Android Studio可能会给你优化到一个文件夹,导致你插件无法apply,请分开成父子目录写

首先src目录下创建groovy和resources目录
  • groovy包用来存储gradle代码
  • resources目录存放的是gradle插件名称,在外界apply需要根据定义的文件名进行apply,这里我命名hotfix.patch,那么在外界就使用apply plugin: ‘hotfix.patch’

hotfix.patch.properties文件内容如下,指定插件的实现是哪个类

implementation-class=com.huli.plugin.HotfixPatchPlugin
其次解释groovy包下的代码

下面是两个类,一个是HotfixExtension类,可以用来进行参数的传递,代码为

package com.huli.plugin

class HotfixExtension {
    String name //打patch包的task所依赖的task
}

然后是插件的代码

package com.huli.plugin

public class HotfixPatchPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.extensions.create('hotfixExt', HotfixExtension)
        
        println "this is a custom plugin"
        
        //在插件中定义task
        project.task('hello'){
        		doLast{
        			println "hello " + project.hotfixExt.name
        		}
        }
    }
}

在plugin中我们只需要实现apply方法,方法传入的是引用该plugin的project,project中存储着各种参数

把插件打包到本地仓库

点击下面按钮就能将插件打包上传到本地maven,之后会在刚才创建的maven目录下生成对应的jar

这里写图片描述

生成的目录如图,每次对插件代码的修改都需要再次上传

这里写图片描述

应用我们的插件

在根目录的build.gradle下配置

buildscript {
    repositories {
        maven { url uri('repo-hotfix-patch') }
    }
    dependencies {
        classpath 'com.huli.plugin:hotfix-patch:1.0.0'
    }
}

在app的build.gradle中引用插件

apply plugin: 'hotfix.patch'
hotfixExt {
    name = "xiaoming"
}

在gradle中执行命令 ./gradlew hello就能成功输出
hello xiaoming

六、patch打包任务分析

根据以上知识点,我们为了将这个打包任务抽成第一个第三方library,方便多个项目使用,我们需要使用第三种方式自定义plugin来实现

1. 新建Android Library工程 配置build.gradle
2. 创建名为HotfixPatchPlugin的插件,实现Plugin接口
3. 创建HotfixExtension实体类,用于传递参数
4. 创建PatchTask
5. 重写apply(Project project)方法
6. 使用自定义的Plugin
7. 配置需要打入patch包的类

这里写图片描述

8. 通过命令进行patch打包

./gradlew patchMe -PpatchName=shanshan

最后在根目录下生成对应的包含dex的jar包

这里写图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值