自定义Gradle插件实现打包上传到蒲公英
上一篇文章提到了关于如何写Gradle自定义插件Plugin,本篇文件介绍下如何写gradle插件实现打包上传到pgy。
要实现打包上传到蒲公英必须要弄清楚以下问题:
1、什么时候打包完成?监听到打包完成才能执行下一步的上传操作
Gradle的打包也是一组组的Task任务,每个任务都有一个dependsOn()方法,我们只要在Assemble打包任务执行完了以后,再执行就可以了,即让我们的上传任务dependsOn打包任务就可以了。
2、怎么上传?
蒲公英api文档:http://www.pgyer.com/doc/api#uploadApp
3、build.gradle文件内配置的参数怎么获取?
通过org.gradle.api.Project这类获取。Project是支持扩展的,我们可以自定义一个Extension的数据类型,作为Project的一个扩展。
在Project里面有个回调方法afterEvaluate(),它会在构建完成后执行。我们需在这个方法里,获取自定义的Extension就可以了,即执行:project.getExtensions().create(PLUGIN_EXTENSION_NAME, PGYExtension.class)
先看下工程目录:
具体实现:
1、包含配置参数的模型:
package com.shengqf.plugin;
import org.gradle.api.Project;
public class PGYExtension {
public String uKey;
public String apiKey;
public String installType; //1:公开,2:密码安装,3:邀请安装。默认为1公开
public String password; 安装密码
public String updateDescription; //版本更新描述,请传空字符串,或不传
public PGYExtension() {
}
public PGYExtension(String uKey, String apiKey, String installType, String password, String updateDescription) {
this.uKey = uKey;
this.apiKey = apiKey;
this.installType = installType;
this.password = password;
this.updateDescription = updateDescription;
}
public static PGYExtension getConfig(Project project) {
PGYExtension extension = project.getExtensions().findByType(PGYExtension.class);
if (extension == null) {
extension = new PGYExtension();
}
return extension;
}
}
2、上传apk的工具类:
package com.shengqf.plugin;
import com.android.build.gradle.api.BaseVariant;
import com.android.build.gradle.api.BaseVariantOutput;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
import java.io.File;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class PGYUploadTask extends DefaultTask {
private BaseVariant mVariant;
private Project mProject;
public void init(BaseVariant variant, Project project) {
this.mVariant = variant;
this.mProject = project;
setDescription("upload apk to pgy");
setGroup(UploadPlugin.PLUGIN_EXTENSION_NAME);
}
static class PGYRequest {
public String uKey;
public String apiKey;
public String installType; //1:公开,2:密码安装,3:邀请安装。默认为1公开
public String password; 安装密码
public String updateDescription; //版本更新描述,请传空字符串,或不传
}
@TaskAction
public void uploadToPGY() {
for (BaseVariantOutput output : mVariant.getOutputs()) {
File file = output.getOutputFile();
if (file == null || !file.exists()) {
throw new GradleException("apk file is not exist!");
}
PGYExtension extension = PGYExtension.getConfig(mProject);
PGYRequest request = new PGYRequest();
request.apiKey = extension.apiKey;
request.uKey = extension.uKey;
request.installType = extension.installType;
request.password = extension.password;
request.updateDescription = extension.updateDescription;
upload(request.uKey, request.apiKey, request.installType,
request.password, request.updateDescription, file);
}
}
/**
* 蒲公英上传apk的API文档:http://www.pgyer.com/doc/api#uploadApp
*/
private void upload(String ukey, String apiKey, String installType,
String password, String updateDescription, File apkFile) {
//builder
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
//add part
bodyBuilder.addFormDataPart("uKey", ukey);
bodyBuilder.addFormDataPart("_api_key", apiKey);
bodyBuilder.addFormDataPart("installType", installType);
bodyBuilder.addFormDataPart("password", password);
bodyBuilder.addFormDataPart("updateDescription", updateDescription);
//add file
bodyBuilder.addFormDataPart("file", apkFile.getName(), RequestBody
.create(MediaType.parse("*/*"), apkFile));
//request
Request request = new Request.Builder()
.url("http://upload.pgyer.com/apiv1/app/upload")
.post(bodyBuilder.build())
.build();
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
addInterceptor(clientBuilder);
try {
Response response = clientBuilder.build().newCall(request).execute();
String result = response.body().string();
System.out.println("upload result: " + result);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception ignored) {
}
}
//打印上传进度
private void addInterceptor(OkHttpClient.Builder clientBuilder) {
...
}
}
3、自定义插件:
package com.shengqf.plugin;
import com.android.build.gradle.AppExtension;
import com.android.build.gradle.api.ApplicationVariant;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectSet;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskContainer;
public class UploadPlugin implements Plugin<Project> {
public static final String PLUGIN_EXTENSION_NAME = "upload";
@Override
public void apply(Project project) {
PGYExtension extension = project.getExtensions().create(PLUGIN_EXTENSION_NAME, PGYExtension.class);
project.afterEvaluate(new Action<Project>() {
@Override
public void execute(Project project) {
DomainObjectSet<ApplicationVariant> appVariants = getAppVariants(project);
if (appVariants == null) {
return;
}
for (ApplicationVariant variant : appVariants) {
String buildTypeName = variant.getBuildType().getName();
System.out.println("buildTypeName === " + buildTypeName);
if (buildTypeName.equalsIgnoreCase("release")
|| buildTypeName.equalsIgnoreCase("debug")) {
String variantName = variant.getName().substring(0, 1).toUpperCase() + variant.getName().substring(1);
String taskName = "upload" + variantName;
TaskContainer taskContainer = project.getTasks();
PGYUploadTask pgyUploadTask = taskContainer.create(taskName, PGYUploadTask.class);
pgyUploadTask.init(variant, project);
variant.getAssembleProvider().get().dependsOn(project.getTasks().findByName("clean"));
pgyUploadTask.dependsOn(variant.getAssembleProvider().get());
}
}
}
});
}
private DomainObjectSet<ApplicationVariant> getAppVariants(Project project) {
AppExtension appExtension = (AppExtension) project.getExtensions().findByName("android");
if (appExtension == null) {
return null;
}
return appExtension.getApplicationVariants();
}
}
4、app引入自定义插件,并配置蒲公英的参数:
注意,配置参数的名称upload必须和通过project获取Extension的create方法的第一个参数名一致,即:
这样,就可以打包上传到蒲公英自动化了,右侧grale面板 ,可以看到app的Tasks下多了upload的任务(面板里的upload任务名称由创建Extension的create方法的第一个参数名决定):
点击uploadDebug即可打debug包自动上传到蒲公英
点击uploadRelease即可打release包自动上传到蒲公英
补充:
1、因为用了okhttp,所以buildSrc这个module的build.gradle文件需要引入okhttp的依赖:
apply plugin: 'java-library'
apply plugin: 'groovy'
apply plugin: 'maven'
repositories {
google()
mavenCentral()
}
dependencies {
implementation 'com.android.tools.build:gradle:3.5.2'
implementation("com.squareup.okhttp3:okhttp:3.8.1")
implementation gradleApi() // gradle api
implementation localGroovy() // groovy api
}
2、上文中上传任务打印上传进度方法addInterceptor:
private long tempBytes;
//打印上传进度
private void addInterceptor(OkHttpClient.Builder clientBuilder) {
clientBuilder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.method(original.method(), new ProgressRequestBody(original.body(), new ProgressRequestListener() {
@Override
public void onRequestProgress(long bytesWritten, long contentLength, boolean done) {
if (!done) {
if (bytesWritten - tempBytes > 1024 * 512) {
tempBytes = bytesWritten;
System.out.println("-------upload progress-------" + bytesWritten * 100 / contentLength + "%");
}
} else {
System.out.println("-------upload progress-------100%");
}
}
}))
.build();
return chain.proceed(request);
}
});
}
涉及到的类:
package com.shengqf.plugin;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;
public class ProgressRequestBody extends RequestBody {
private RequestBody requestBody;
private ProgressRequestListener progressListener;
private BufferedSink bufferedSink;
public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return requestBody.contentType();
}
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
if (bufferedSink == null) {
bufferedSink = Okio.buffer(sink(sink));
}
requestBody.writeTo(bufferedSink);
bufferedSink.flush();
}
private Sink sink(Sink sink) {
return new ForwardingSink(sink) {
long bytesWritten = 0L;
long contentLength = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
if (contentLength == 0) {
contentLength = contentLength();
}
bytesWritten += byteCount;
if(progressListener != null){
progressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
}
}
};
}
}
涉及到的接口:
package com.shengqf.plugin;
public interface ProgressRequestListener {
void onRequestProgress(long bytesWritten, long contentLength, boolean done);
}
在别的项目中使用,只需将buildSrc模块拷贝到项目中(注意,settings.gradle不用加buildSrc的配置),然后在项目的app模块的build.gradle文件引入自定义插件、配置蒲公英参数即可。