美文网首页Android开发经验谈Gradle
Gradle学习笔记(一) 自定义插件上传APK文件

Gradle学习笔记(一) 自定义插件上传APK文件

作者: houtengzhi | 来源:发表于2018-11-17 16:48 被阅读8次

    工作中打包好的APK文件经常需要签名才能安装,公司没有提供签名APK所需的私钥,每次签名都需要先上传到签名服务器上,再下载签名后的APK文件,这样手动操作很繁琐,遂萌生出写个一键签名的脚本,这里考虑用gradle脚本来实现,在APK打包好之后可以自动上传至服务器签名然后自动下载。

    签名服务器接口分析

    编写gradle插件需要groovy,与java是兼容的,这样我们可以直接用OkHttp框架更方便地实现各种网络请求。由于没有现成的签名服务API接口,只能自己通过fiddler抓包来一步步分析请求参数。

    首先是模拟登录

    在签名服务器登录页面输入用户名和密码,观察抓包的情况。可以看到请求头以及表单信息


    请求头信息 表单信息

    有了这些参数,接下来我们就可以手动模拟登录。

    FormBody formBody = new FormBody.Builder()
                    .add("return", "index.php")
                    .add("username", username)
                    .add("password", password)
                    .build();
    Request request = new Request.Builder()
                    .post(formBody)
                    .url(url)
                    .addHeader("User-Agent", USER_AGENT)
                    .build();
    Response response = mOkHttpClient.newCall(request).execute();
    

    模拟登录成功后就可以获取到包含登录信息的Cookie,后面的上传APK文件请求中需要携带这个Cookie。OkHttp3提供了cookieJar的方法来实现Cookie的缓存。

    OkHttpClient.Builder builder = new OkHttpClient.Builder()
    builder.connectTimeout(10, TimeUnit.SECONDS)
               .readTimeout(20, TimeUnit.SECONDS)
               .writeTimeout(20, TimeUnit.SECONDS)
               .cookieJar(new CookieJar() {
    
               //使用Map缓存Cookie
               private final Map<String, List<Cookie>> cookieStore = new HashMap<>()
    
                @Override
                void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                    cookieStore.put(url.host(), cookies)
                }
    
                @Override
                List<Cookie> loadForRequest(HttpUrl url) {
                    List<Cookie> cookies = cookieStore.get(url.host())
                    return cookies != null ? cookies : new ArrayList<Cookie>()
                }
            })
    mOkHttpClient = builder.build();
    
    模拟网页上传文件

    浏览器是用Multipart/form-data上传文件的,下图是抓包网页上传文件的请求头和请求体信息。请求头中包含Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFFpuNGUcr8mOaedw这一行,其中multipart/form-data是表单方式上传文件,----WebKitFormBoundaryFFpuNGUcr8mOaedw是随机生成的分隔符boundary,在下面详细的请求体数据中可以看到这个。

    请求体中包含了两处数据,一个是自定义名称APC_UPLOAD_PROGRESS以及它的值,再就是名称为APK文件二进制数据。

    用OkHttp模拟相同的请求信息,调用APIsetType(MultipartBody.FORM)设置请求头Content-Type,就不需要我们手动设置boundary了。此外OkHttp提供了addFormDataPart的API可以方便的构造表单数据。

    MediaType mediaType = MediaType.parse("application/vnd.android.package-archive");
            RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("APC_UPLOAD_PROGRESS", String.valueOf(key))
                    .addFormDataPart("upfile", file.getName(), RequestBody.create(mediaType, file))
                    .build();
            Request request = new Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .addHeader("User-Agent", USER_AGENT)
                    .addHeader("Connection", "keep-alive")
                    .build();
            Response response = mOkHttpClient.newCall(request).execute()
    
    下载文件

    后面根据返回信息生成的url就可以下载签名后的APK文件了。

    Request request = new Request.Builder()
                    .url(url)
                    .addHeader("User-Agent", USER_AGENT)
                    .addHeader("Accept-Encoding", "identity")   // 服务器下发的的资源不要做gzip, 否则没有content-length,报ProtocolException
                    .build();
    
            Response response = mOkHttpClient.newCall(request).execute()
    

    gradle插件

    关于gradle插件的编写已有很多不错的文章,大家可以参考下面罗列出来的。

    配置参数类

    外部配置参数

    class ConfigExt {
        String username
        String password
        String loginUrl
        String baseSignUrl
        String signType
        String suffix
        boolean debugged = false
        boolean autoSign = true
        boolean deleteSourceFile = true
    }
    

    嵌套配置参数,可以设置每个buildType是否签名

    class VariantConfigExt {
        boolean sign
    }
    
    创建SignPlugin

    这里根据variant生成不同的签名task,通过设置签名tasksignXXX和打包taskassembleXXX不同的依赖关系以实现自动或手动签名。

    class SignPlugin implements Plugin<Project> {
        private Project mProject = null
    
        @Override
        void apply(Project project) {
            this.mProject = project
            GLog.i("apply")
    
            project.extensions.create('remoteSign', ConfigExt)
    
            ConfigExt mConfig = project.remoteSign
    
            // 根据不同的buildType创建VariantConfigExt
            if (project.android.hasProperty("buildTypes")) {
                project.android.buildTypes.all { type ->
                    project.remoteSign.extensions.create("${type.name}", VariantConfigExt)
                }
                // release默认自动签名
                mConfig.release.sign = true
            }
    
            if (project.android.hasProperty("applicationVariants")) {
                project.android.applicationVariants.all { variant ->
    
                    Task assembleTask
    
                    DefaultTask signTask = createSignTask(variant)
                    GLog.debug = (mConfig != null && mConfig.debugged)
    
                    variant.outputs.each { output ->
    
                        assembleTask = output.assemble
                        GLog.d("    config: " + (mConfig == null ? "null" : mConfig.toString()))
                        GLog.d("    assembleTask: " + assembleTask.name)
                        signTask.assembleTaskName = assembleTask.name
    
                        signTask.dependsOn assembleTask
                        if (project.remoteSign != null && project.remoteSign.autoSign) {
                            def buildType = variant.buildType.name
                            boolean hasBuildTypePro = mConfig.hasProperty(buildType)
                            boolean sign = true
                            if (hasBuildTypePro) {
                                sign = mConfig.getProperty(buildType).properties.get("sign").asBoolean()
                                GLog.d(buildType + ": " + mConfig.getProperty(buildType).toString())
                            }
    
                            if (sign) {
                                assembleTask.doLast { tk ->
                                    if (!tk.state.failure) {
                                        signTask.execute()
                                    }
                                }
                            }
                        }
    
                    }
    
    
                }
            }
    
    
        }
    
        private DefaultTask createSignTask(Object variant) {
            String variantName = variant.name.capitalize()
            GLog.i("create sign${variantName} task")
            DefaultTask signTask = mProject.task("sign${variantName}", type: RemoteSignTask)
            signTask.group = 'signature'
            signTask.description = 'Upload apk to sign server'
            return signTask
        }
    
    创建DoSignTask

    如果在gradle中修改过输出的apk名称,在配置阶段(在SignPlugin的apply方法中)就查找文件名会是修改之前的名称,所以这里我们在task执行阶段再根据前面生成的assembleTask名称查找对应生成的apk文件名,

    class DoSignTask extends DefaultTask {
    String assembleTaskName
    ...
    ...
    private ApkInfo getApkInfo(Project project) {
            glogd("getApkInfo:")
            ApkInfo apkInfo = new ApkInfo()
    
            project.android.applicationVariants.all { variant ->
    
                variant.outputs.each { output ->
                    GLog.d("    assembleTask: ${output.assemble.name}")
                    if (output.assemble.name == assembleTaskName) {
                        File apkFile = output.outputFile
                        apkInfo.apkFile = apkFile
                        apkInfo.apkName = apkFile.name
                        apkInfo.parentDir = apkFile.parent
                        GLog.d("    output: ${apkFile.name}")
                    }
                }
            }
            return apkInfo
        }
    ...
    ...
    }
    
    发布插件到maven私服

    在项目根目录下新建一个uploadToMaven.gradle文件,还在开发调试阶段可以发布SNAPSHOT版本 (理解Maven中的SNAPSHOT版本和正式版本)。

    apply plugin: 'maven'
    
    def mavenReleaseUrl = 'http://xx.xx.x.xx/nexus/content/repositories/gradle-plugin/'
    def mavenSnapshotUrl = 'http://xx.xx.x.xx/nexus/content/repositories/android-snapshots/'
    
    uploadArchives {
        configuration = configurations.archives
        repositories {
            mavenDeployer {
                repository(url: uri(mavenReleaseUrl)) {
                    authentication(userName: MAVEN_USERNAME, password: MAVEN_PASSWORD)
                }
    
                snapshotRepository(url: uri(mavenSnapshotUrl)) {
                    authentication(userName: MAVEN_USERNAME, password: MAVEN_PASSWORD)
                }
            }
        }
    }
    

    在module的build.gradle文件中添加

    group='com.ahgx.gradleplugin'
    version='1.1.0'
    //version='1.0.0-SNAPSHOT'
    
    //apply from: '../uploadToLocalRepo.gradle'
    apply from: '../uploadToMaven.gradle'
    
    集成使用

    在项目根目录下的build.gradle文件中添加

    buildscript {
        repositories {
            maven {
                url uri('http://xx.xx.x.xx/nexus/content/groups/public/')
            }
        }
        dependencies {
            classpath 'com.ahgx.gradleplugin:sign-plugin:1.1.0'
        }
    }
    

    在mudule下的build.gradle文件中添加

    apply plugin: 'com.ahgx.sign-plugin'
    

    可配置参数有

    remoteSign {
        username 'xxxxx'                                  //用户名,必填
        password 'xxxxx'                                     //密码,必填
        loginUrl 'http://xx.xx.x.xx/mantis/login.php'         //登录地址,必填
        baseSignUrl 'http://xx.xx.x.xx/mantis'                //地址,必填
        signType 'app'                                       //签名类型(app, apk, sys),必填
    
        debugged false                                       //调试模式,默认false
        autoSign true                                       //打包APK之后自动签名,默认true
        suffix '-signed'                                    //签名后文件命名后缀,默认签名服务器自动添加
        deleteSourceFile true                              //是否删除原APK文件,默认true
    
        release {
            sign true    //是否自动签名,默认为true
        }
    
        debug {
            sign false   //是否自动签名,默认为false
        }
    

    最后在AndroidStudio右侧的gradle面板上可以看到signature分组下的签名task,双击执行即可手动签名。


    参考文章

    相关文章

      网友评论

        本文标题:Gradle学习笔记(一) 自定义插件上传APK文件

        本文链接:https://www.haomeiwen.com/subject/dcfsfqtx.html