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"))
}