在Android Studio中实现360加固自动化,进一步提升生产力!

981 阅读3分钟

1.概述

Android Studio目前已经成为Android APP开发的首选IDE,其使用Gradle作为构建系统,Gradle使用groovy语言作为DSL可以扩展出随心所欲的功能,让APK构建能够更加灵活,很好的控制每一个环节。360加固作为国内较为出名的加固服务,能够较为有效的保护APK代码,使破解难度增高不少。加固已经是国内APK上市场的必须步骤。还依稀记得12年刚开发apk的时候,由于缺乏对软件代码保护的认识,外加技术不熟练,连混淆都不会,刚上市场不久的软件就被“打包党”盯上,轻易被别人制作出一模一样的软件。必要的代码保护可以说是必须的。

完全人工的方式去360加固网站或使用其PC客户端加固APK耗时费力,都是些机械的步骤,消磨大量时间。当查阅360加固使用文档会发现360加固支持命令行加固。加固保桌面助手支持命令行模式,即在命令行输入相关命令可执行加固应用、导入签名信息、导入多渠道配置信息等操作。这些固定的步骤是否可以简化?答案是显然的。

2.实现构想

将加固命令行集成到build.gradle脚本中,并定制为一个任务,在生成release APK的时候触发这个任务,主动去加固并下载最终加固好的APK。完成任务!

3.具体实现

3.1 安装360加固宝客户端

官网直接下载客户端安装即可。比如我安装到了I:\360jiagu,其中I:\360jiagu\jiagu\jiagu.jar是我们需要执行的jar,为我们提供了所需的各种加固使用命令。

3.2 主模块定制build.gradle

首先加固是基于已经签名的apk,因此需要我们把签名的一些信息集成进来,注意:为了安全如果需要上到公共库这种写法并不可取!

// 替换为你实际使用的真实签名信息
ext.myStoreFile = file("xxx.keystore")
ext.myStorePassword = 'xxx'
ext.myKeyAlias = 'KeyAlias'
ext.myKeyPassword = 'xxx'

定制加固任务,让其在assembleRelease任务之后执行,保证在生成了签名apk之后才进行加固。

    task reinforceAppTask(dependsOn: 'assembleRelease') {

        doLast {
            println "360jiagu begin"
            def currFile = new File(".")
            // 这里需要定制为实际的app路径
            def appFilePath = currFile.getCanonicalPath() +
                    File.separator + "app" + File.separator + "build" + File.separator + "outputs"+ File.separator + "apk" + File.separator +
                    "xxx-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk";

            println "appFilePath=" + appFilePath

            if(!new File(appFilePath).exists()){
                println "apk not exist"
                return
            }
            // 注意这里的jiagu.jar路径,需要指定为实际安装的路径
            def cmdBase = 'java.exe -jar' + ' I:\\360jiagu\\jiagu\\jiagu.jar'

            def cmdLogin = cmdBase + ' -login 360加固账号 360加固密码'
            def cmdImportsign = cmdBase + ' -importsign ' +
                    myStoreFile.getCanonicalPath() + ' ' + myStorePassword + ' ' + myKeyAlias + ' ' + myKeyPassword
            def cmdShowsign = cmdBase + ' -showsign'
            def cmdConfig = cmdBase + ' -config -x86'
            def cmdShowconfig = cmdBase + ' -showconfig'
            def cmdVersion = cmdBase + ' -version'
            def cmdJiagu = cmdBase + ' -jiagu ' + appFilePath + ' ' + currFile.getCanonicalPath() + ' -autosign'

            execute360JiaguCmd(cmdLogin)
            execute360JiaguCmd(cmdImportsign)
            execute360JiaguCmd(cmdShowsign)
            execute360JiaguCmd(cmdConfig)
            execute360JiaguCmd(cmdShowconfig)
            execute360JiaguCmd(cmdVersion)
            execute360JiaguCmd(cmdJiagu)

            println "360jiagu end"
        }
    }
def execute360JiaguCmd(cmd){
    // 注意后面的配置,替换为对应本地安装
    def p = cmd.execute(null, new File("I:\\360jiagu\\jiagu\\java\\bin"))
    println p.text
    p.waitFor()  // 用以等待外部进程调用结束
    println p.exitValue()
}

以上使用的加固命令一目了然,导入签名,显示签名,支持x86架构,显示加固宝版本…具体查阅360加固命令行文档。Android studio 命令行中执行gradle rAT,任务执行完后就会在项目根路径下找到加固好的apk。

3.3 完整脚本实例

这是我某APK内使用的完整build.gradle脚本,敏感信息以用xx代替。

apply plugin: 'com.android.application'

ext.myStoreFile = file("xx.keystore")
ext.myStorePassword = 'xx'
ext.myKeyAlias = 'xx'
ext.myKeyPassword = 'xx'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"

    aaptOptions.cruncherEnabled = false
    aaptOptions.useNewCruncher = false

    defaultConfig {
        applicationId "xx"
        versionCode 240
        versionName "v2.4.0"
        minSdkVersion 14
        targetSdkVersion 25
    }

    signingConfigs {
        releaseSign {
            storeFile myStoreFile
            storePassword myStorePassword
            keyAlias myKeyAlias
            keyPassword myKeyPassword
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles 'proguard.cfg'
        }
    }

    repositories {
        flatDir {
            dirs 'libs'
        }
    }

    android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                //修改apk文件名
                def fileName = "xx-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk"
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }

    task reinforceAppTask(dependsOn: 'assembleRelease') {

        doLast {
            println "360jiagu begin"
            def currFile = new File(".")
            def appFilePath = currFile.getCanonicalPath() +
                    File.separator + "app" + File.separator + "build" + File.separator + "outputs"+ File.separator + "apk" + File.separator +
                    "xx-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk";

            println "appFilePath=" + appFilePath

            if(!new File(appFilePath).exists()){
                println "apk not exist"
                return
            }

            def cmdBase = 'java.exe -jar' + ' I:\\360jiagu\\jiagu\\jiagu.jar'

            def cmdLogin = cmdBase + ' -login xx xx'
            def cmdImportsign = cmdBase + ' -importsign ' +
                    myStoreFile.getCanonicalPath() + ' ' + myStorePassword + ' ' + myKeyAlias + ' ' + myKeyPassword
            def cmdShowsign = cmdBase + ' -showsign'
            def cmdConfig = cmdBase + ' -config -x86'
            def cmdShowconfig = cmdBase + ' -showconfig'
            def cmdVersion = cmdBase + ' -version'
            def cmdJiagu = cmdBase + ' -jiagu ' + appFilePath + ' ' + currFile.getCanonicalPath() + ' -autosign'

            execute360JiaguCmd(cmdLogin)
            execute360JiaguCmd(cmdImportsign)
            execute360JiaguCmd(cmdShowsign)
            execute360JiaguCmd(cmdConfig)
            execute360JiaguCmd(cmdShowconfig)
            execute360JiaguCmd(cmdVersion)
            execute360JiaguCmd(cmdJiagu)

            println "360jiagu end"
        }
    }

    tasks.whenTaskAdded { theTask ->
        if (theTask.name.equals("assembleRelease")) {
            theTask.dependsOn "cleanOutputsDir"
        }
    }

    task cleanOutputsDir {
        def outputsPath = getBuildDir().getAbsolutePath() + File.separator + "outputs" + File.separator
        println "delete outputsPath=" + outputsPath
        new File(outputsPath).deleteDir()
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:support-v4:25.3.1'
    compile 'org.greenrobot:greendao:2.2.1'
    compile(name: 'dragSortListviewLibrary-release-0.6.1', ext: 'aar')
    compile(name: 'numberPickerLibrary-release-1.0', ext: 'aar')
    compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'com.kyleduo.switchbutton:library:1.4.1'
    compile 'com.squareup:android-times-square:1.6.5@aar'
    compile 'com.android.support:appcompat-v7:25.3.1'

    compile "com.github.hotchemi:permissionsdispatcher:2.4.0"
    annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:2.4.0"

    // 阿里百川反馈组件用
    compile(name: 'alicloud-android-feedback-3.1.1', ext: 'aar')
}

def execute360JiaguCmd(cmd){
    def p = cmd.execute(null, new File("I:\\360jiagu\\jiagu\\java\\bin"))
    println p.text
    p.waitFor()  // 用以等待外部进程调用结束
    println p.exitValue()
}

def releaseTime() {
    return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
}