自定义Gradle插件修复AndroidStudio的bug

个人博客地址 原文地址https://moonlightshadow.cn/article/829120680799764480
背景
之前发布过一篇文章,通过脚本方式修复ASrun之后无法启动app的文章。后来觉得这样子好像不太优雅。又想到以前写过Gradle插件,就有开始了修复之旅。
什么Bug
在as某个版本开始,点击run按钮后,看到编译app安装且成功了,但是app没没有自动启动。我一度以外是我写的程序闪退了。这个bug已经反馈大概有半年了吧,今天是2021年4月6日,Google丝毫没有要解决的意思。现在4.1.2版本还是存在这个问题。
开始修复
之前分析过,as其实已经执行了安装命令
adb install xxxx.apk
但是为什么没有启动,或者说启动了app其实一闪而过。这个没时间去看plugin的源码。调试这个plugin源码太麻烦了,之前就简单调试过,不过不是找安装的任务。
既然他不启动,我们就帮他启动
app编译完成之后,继续执行我们的(安装)和(启动)命令不就好了?
这个想法如何通过插件实现呢?
Plugin开发
- 首先,我们创建一个Module,把里面的文件删除,保留src和build.gradle
- 创建main文件夹,main中创建groovy文件夹,然后创建自己的包。
- 创建resource文件夹和groovy同级。创建META-INF.gradle-plugins文件夹
- 创建frida.debug.properties文件。
真个目录结构大概这样。 
build.gradle配置api依赖
apply plugin: 'java-library'
apply plugin: 'groovy'//使用groovy插件
//添加这两行
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation gradleApi()//引入gradleApi
implementation localGroovy()
implementation 'com.android.tools.build:gradle:3.6.3'
}
sourceCompatibility = "8"
targetCompatibility = "8"
apply plugin: 'maven'
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../localMaven'))
//固定模式repository(url:"file://D://repository/")
pom.groupId = "frida.debug"
pom.artifactId = "run"
pom.version = "1.0.0"
}
}
group = "frida.debug"
version = "1.0.0"
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'//指定groovy目录
}
resources {
srcDir 'src/main/resources'//指定resources目录
}
}
}
//生成源文件
task sourceJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
//文档打包成jar
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
//拷贝javadoc文件
task copyDoc(type: Copy) {
from "${buildDir}/docs/"
into "docs"
}
//上传到JCenter所需要的源码文件
artifacts {
archives javadocJar
archives sourceJar
}
javadoc {
options {
//如果你的项目里面有中文注释的话,必须将格式设置为UTF-8,不然会出现乱码
encoding "UTF-8"
charSet 'UTF-8'
author true
version true
links "http://docs.oracle.com/javase/7/docs/api"
}
}
创建我们的插件入口 FixRunPlugin
package com.debug.run
import org.gradle.api.Plugin
import org.gradle.api.Project
class FixRunPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
//读取app.gradle中我们定义只runInfo
project.extensions.create(InstallPluginExtension.appInfo, InstallPluginExtension.class)
project.tasks.create('task', RunTask.class)
project.android.applicationVariants.all {
def variantName = it.name.capitalize()
//创建分组下任务名字,当前variantName
RunTask task = project.tasks.create("${variantName}Package", RunTask.class)
task.targetProject = project
task.variant = it
//依赖assemble,需要先编译出所有的该variant的包
println('task name =====> ' + it.assemble)
//我们定义的task执行时机,是依赖某个task完成之后
//这里的it,就是assembleXXX,当我们点击run按钮,触发的就是assembleDebug
task.dependsOn it.assemble
}
}
}
配置入口 在META-INF中配置
implementation-class=com.debug.run.FixRunPlugin
创建我们接受的参数 InstallPluginExtension
package com.debug.run;
/**
* 目前还没找到api直接在task中获取当前project的application id和启动页面全路径和连接的设备的id
* 现在曲线救国
*/
public class InstallPluginExtension {
public static final String appInfo = "runInfo";
//需要填写我们的application id
public String appId;
//app的启动页面的全路径
public String appLauncher;
}
创建修复用的任务Task
package com.debug.run
import com.android.build.gradle.api.ApplicationVariant
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
/**
* 这个就是我们依赖run任务之后做的任务的具体实现
*/
class RunTask extends DefaultTask {
@Input
ApplicationVariant variant;
@Input
Project targetProject
RunTask() {
//创建一个分组,a_debug,为什么是a,排序a在最前面容易找到,分组对应的任务是RunTask
group = "a_debug"
}
@TaskAction
doAction() {
//打包完成
print 'start task-------------------------'
//获取参数
def appId = project.extensions.runInfo.appId
def appLauncher = project.extensions.runInfo.appLauncher
variant.outputs.all { output ->
if (output.outputFile == null && !output.outputFile.name.endsWith('.apk')) {
//如果不是apk文件
contiue
}
def apkPath = output.outputFile.getAbsolutePath()
println('apk file = ' + apkPath)
//上面可以得到apk文件地址。
def installCommand = "adb install -r " + apkPath
Process installProcess = installCommand.execute()
println "Success execute Command: ${installCommand.toString().readLines()}"
installProcess.in.eachLine { processing ->
println processing
println('install completed')
def startCommand = "adb shell am start -n " + appId + "/" + appLauncher
Process startProcess = startCommand.execute()
println "Success execute Command: ${startCommand.toString().readLines()}"
startProcess.in.eachLine { pln ->
println pln
println('done~')
}
}
}
}
}
以上就是任务全部代码,任务详细代码就不解释了,上面有注释。大家都看得懂,插件是java写的。
发布插件
代码写完之后,我们打开AS右边的Gradle标签。 找到我们的module名称plugin_run。展开。 双击uploadArchives 我这个配置是发布到项目的根目录的localMaven。当然你也可以发布到远程Maven的。 插件信息如下
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../localMaven'))
//固定模式repository(url:"file://D://repository/")
pom.groupId = "frida.debug"
pom.artifactId = "run"
pom.version = "1.0.0"
//插件全称frida.debug:run:1.0.0
}
}
如何使用
在根目录build.gradle的dependencies
classpath 'frida.debug:run:1.0.0'
在app的build.gradle中引入插件
同步gradle。在标签中打开app=Tasks找到group:a_debug。展开。
双击这个任务,就可以编译且安装正常启动App了。
当你完成一次之后,就是这样的。 后面你只需要点击run按钮,或者快捷键shift f10就可以重复触发这个定制的任务了。这个就是无bug的run了。 连超越用过都说好哦。