一、apk签名(必须)
1.apk签名(数字签名)
apk签名:开发者(开发商)通过数字证书(也可以叫签名文件),给apk应用打上带有个人(公司)信息的标记,以表示自己是该应用的开发者或拥有者。签名后的apk解压后能看到META-INF文件夹,并且里面会有3个签名相关的文件(MANIFEST.MF,CERT.SF,CERT.RSA)
数字签名: 哈希算法(sha1, md5) + 私钥加密
查看apk的拥有者
keytool -printcert -file CERT.RSA
关于Android数字签名相关事项:
所有的应用都必须进行签名,android系统不会安装没有签名的应用;
使用不同数字证书签名的两个apk,无法实现覆盖安装。所以,应用在版本升级打包时必须使用同一个数字证书(keystore文件)进行签名; 这一点,也保证了用户手机上已安装的应用,不会被原作者外的第三者开发的假冒应用安装替换,因为第三者不可能持有原作者的数字证书(签名文件)
应用上线需要release签名(或者叫正式签名:指使用个人或公司的签名文件签名),不能用debug签名(开发调试签名:指使用sdk自带的签名文件签名)
数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常使用,一般开启99年就好了
apk签名可以使用自签名的数字证书(自己创建的keystore签名文件),不需要使用CA机构认证的数字证书(需要给CA机构付费);
2.生成签名文件的两种方式
通过IDE(AndroidStudio, Eclipse)生成
通过命令行keytool命令生成
keytool -genkeypair -validity [签名文件的有效期,单位为天] -keystore [keystore签名文件(数字证书)] -storepass [store密码] -alias [密钥对的别名] -keypass [密钥密码]
列表出所有的密钥对:
keytool -list -v -keystore itheima.keystore
参考: https://www.chinassl.net/?f=faq&a=view&r=457
3.AndroidStudio中导出正式签名包
android {
// 定义签名用到的数字证书
signingConfigs {
config {
keyAlias 'itheima'
keyPassword '123456'
storeFile file('E:/_GZ05/_code/01.ProGuardDemo_AS/itheima.jks')
storePassword '123456'
}
}
...
buildTypes {
// debug签名, 直接运行使用
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
// 打正式包
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config // 使用上面配置的签名文件进行签名
}
}
}
二、Android代码混淆(非必须)
1. 混淆简介
- Android代码混淆是一种应用源代码保护技术,用来防止别人对apk进行逆向分析;
- 从Android2.3开始,Google在SDK中加入了一个叫ProGuard的工具,使用它来进行代码混淆。
- proguard是一个压缩、优化和混淆java字节码文件的免费工具,其作用:
删除代码中的注释;
删除代码中没有用到的类、字段、方法和属性;
会把代码中的包名、类名、方法名变量名等修改为abcd...这种没有意义的名字,使得反编译出来的代码难以阅读,从而达到防止apk被破解和逆向分析的目的;
经过ProGuard混淆后apk安装包会变小
2. 如何使用Proguard进行代码混淆?
buildTypes {
release {
// 修改此参数为true,表示要进行混淆
minifyEnabled true
// 表示要使用sdk中的proguard-android.txt和当前项目中的proguard-rules.pro指定混淆规则
// 通常只需修改后者就可以了
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
// 指定jdk为1.7
compileOptions {
targetCompatibility 1.7
sourceCompatibility 1.7
}
}
在app下的proguard-rules.pro文件中配置混淆规则3. 混淆规则配置[重点]
【注意】:有些java类是不能进行混淆,需要配置混淆规则。
1.所有的第三方包jar/库, 都不进行混淆。 对于每一个第三方的jar或库都需要配置如下两行,其中#号为注释,
#com.umeng.analytics:是要配置的第三方库的包名,配置成包名前缀com.umeng也可以
-dontwarn com.umeng.analytics.** # 不要提示警告
-keep class com.umeng.analytics.** { *; } # 对指定的类不进行混淆
2.注意:如果你使用的proguard的版本比较低, 对于每一个jar或第三方依赖, 还需要配置如下一行,不加可能会出现“类1 can't find referenced class 类2”这样的错。
-libraryjars libs/umeng-analytics-v5.6.1.jar
只要用到反射的类都不能进行混淆
3.如果使用了Gson解析json字符串,如果需要如下配置
# 保留要解析的javabean中的泛型信息
-keepattributes Signature
# bean包及其子包下, 所有要通过Gson反射解析的javabean都不能进行混淆
-keep class com.xxx.bean.**
-keep class com.xxx.bean.** { *;}
4.如果使用了EventBus, 用来接收事件的以onEvent (onEventMainThread)开头的方法不能进行混淆:# 如果用到了WebView中js与java代码的相互调用,js要调用的java类中的方法不能混淆
-keep class com.xxx.WebViewActivity {
public *;
}
# 回调方法声明在内部类JsInterface中的配置方法
-keep class com.xxx.WebViewActivity2$JsInterface {
public *;
}
参考第三方库的github官方文档进行混淆规则的配置, 例如下面的这些库官网上都有混淆配置说明。
https://github.com/bumptech/glide
4.混淆后生成的相关文件
dump.txt
说明 APK 中所有类文件的内部结构。
mapping.txt
提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt
列出未进行混淆的类和成员。
usage.txt
列出从 APK 移除的代码。
使用mapping.txt文件对出错日志还原:
retrace.bat -verbose mapping.txt stacktrace.txt > out.txt
其中mapping.txt文件较为重要(app/build/output/mapping/mapping.txt),该文件需要存档,以便用来还原和查看混淆后的出错日志;
在使用友盟的bug自动捕获功能时,需要把mapping.txt上传到友盟后台。
三、Android资源混淆(非必须)
通过微信资源混淆工具实现(不是必须: 微信6.0,QQ6.0,网易新闻客户端)
AndResGuard
- 对res下的资源文件进行混淆, 将原本冗长的资源路径变短,例如将res/drawable/wechat变为r/d/a。
- AndResGuard不涉及编译过程,只需输入一个apk(签名未签名都可以,处理后会直接将原签名删除),可得到一个实现资源混淆后的apk;
使用第三方工具AndResGuard-master(微信资源混淆)实现资源混淆;
四、多渠道打包(非必须)
1.gradle打包,2.其他工具打包(美团多渠道)
目的:统计的时候,检测各个市场的数据
五、apk加固(必须)
三方平台: 梆梆安全, 360加固保, 爱加密, 阿里聚安全, 娜迦, 腾讯加固(乐固)
操作步骤: 生成一个混淆签名的正式包 --> 上传apk --> 下载加固后的apk --> 重新签名
- 梆梆安全: https://www.bangcle.com
- 360加固保: http://jiagu.360.cn/
- 爱加密: http://www.ijiami.cn/ 加固 效果差一点,还能看得到里面的类,但是没有方法体实现。
- 阿里聚安全: https://jaq.alibaba.com
- 娜迦: http://www.nagain.com/
- 腾讯加固: http://wiki.open.qq.com/wiki/%E5%BA%94%E7%94%A8%E5%8A%A0%E5%9B%BA
六、apk瘦身(非必须)
减小apk安装包的大小
必要性
同样的功能,apk越小越好,用户下载动机更大。
了解apk的组成
- classes.dex: 是java源码编译后生成的java字节码文件
- resources.arsc:编译后的二进制资源文件
- AndroidManifest.xml
- assets: 目录可以存放一些配置文件或资源文件
- lib: 存放jar包,子目录armeabi存放的是一些so文件
- META-INF: 目录下存放的是签名信息,用来保证apk包的完整性和系统的安全
- res: apk图片布局等资源文件
辅助分析工具
(1)nimbledroid[了解]: 能够得知app内存使用,网络使用,磁盘输入/输出,文件大小等一些NimbleDroid认为至关重要的数据; * 官网:https://nimbledroid.com/(无需翻墙,访问比较慢) * 注册 * 登录 * 上传apk * 生成分析结果图;
(2)Android Studio 2.3 apk逆向工具--APK Analyzer[推荐] 参考:http://blog.youkuaiyun.com/nicolelili1/article/details/52669823
瘦身方案
1.开启minifyEnabled(开启混淆功能:会删除没用到的类或方法)
2.开启shrinkResources(去除无用资源:会使用一个像素的图片替换没有用到的图片)
buildTypes {
release {
minifyEnabled true
shrinkResources true // 要与minifyEnable=true同时使用, 否则会出错
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
signingConfig signingConfigs.config
}
}
//添加如下配置就ok了
lintOptions {
checkReleaseBuilds false
abortOnError false // 出错时继续执行,不退出
}
3.删除无用的语言资源(国际化文件)
defaultConfig {
...
resConfigs "zh" // 默认会保留英文语言包 + 这里配置的zh中文语言包;
}
4.对于非透明的大图,jpg将会比png的大小有显著的优势,在启动页,活动页等之类的大图展示区采用jpg将是明智的选择;
- 使用【格式工厂工具】进行转换:png -> jpg
- jpg: 压缩率较高,但是没有透明度信息,适用于启动界面的背景图片
- png: 有透明度信息,压缩率比jpg较小
- bmp: 几乎没有压缩
官方网站: https://tinypng.com/ (无需翻墙)TinyPNG 使用一种智能有损压缩技术(通过降低图片中的颜色数量,来减少存储图片所需要的数据)来降低PNG图片的大小。这样的压缩对图片的效果影响是很小的,但是可以大大降低图片的大小,并且还能保持 PNG 的 alpha 透明度因为 TinyPNG 将 PNG 图片压缩成8位的 PNG(而不是24位),所以它的压缩比例非常高,至少都有 50% 以上的压缩比例,有些甚至可以达到70%,并且压缩之后的图片和原图人眼基本看不出区别。
6.使用webp图片格式
从Android 4.0+开始原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的webp,使用的时候需要注意
了解webp格式: http://isux.tencent.com/introduction-of-webp.html(WebP探寻之路)
- Android中图片优化之webp使用: http://blog.youkuaiyun.com/reboot123/article/details/46552437
android4.0以下使用第三方兼容webp图片:http://blog.youkuaiyun.com/jiwangkailai02/article/details/17015451
使用【格式工厂】进行转换;
8.使用shape文件替换图片
9.使用尽量优化的图片;
10.使用第三方包时把用到的代码添加到项目中来,避免引用整一个第三方库。
11.So的使用, 不用兼容所有的cpu架框
七、增量更新(非必须)
1. 背景
- window系统的升级过程
- android系统的升级过程
- android studio的升级过程
- 国内某些应用升级过程
- 自从Android 4.1 开始, Google Play 引入了应用程序的增量更新功能,App使用该升级方式,可节省约2/3的流量。
2.什么是增量更新
升级的时候只需要从服务器下载和本地版本不同的部分,然后在本地在合成新的apk包,也就是只需要升级增加的部分3.增量更新实现流程
服务器生成和管理差异包客户端下载差异包,并且与旧包全并形成新的安装包客户端再安装新的安装包
4.增量更新实现工具--bsdiff
bsdiff: 开源的二进制比较工具作用: apk文件的差分、合成网站: http://www.daemonology.net/bsdiff/
5.增量更新优缺点
节省流量,加快更新速度需要生成和管理很多的差异包
7.原理
服务端:用已有的包和最新的包生成差分包,
客户端: 下载生成的查分包,最后合并校验,跟新成功
注意:程序不是很大,比如只有2、3M,那么完全没有必要使用增量更新,增量更新适用于apk包比较大的情况,比如游戏客户端
八、热修复(非必须)
1. 概念
一种快速、低成本修复产品软件版本缺陷的方式;不用重新发包, 在用户不知情的情况下修复bug
2. 热修复框架及对比
阿里巴巴:AndFix和Dexposed
微信开源:Tinker
大众点评:Nuwa
美团:Robust
阿里的开源项目AndFix和Dexposed,目前dexposed的兼容性较差,只有2.3,4.0~4.4兼容,其他Android版本不兼容或未测试;而andfix则兼容2.3~7.0。微信tinker: github tinkerAndFix: https://github.com/alibaba/AndFix[阿里]Nuwa: https://github.com/jasonross/Nuwa [个人]HotFix: https://github.com/dodola/HotFix [个人]Dexposed: https://github.com/alibaba/dexposed [阿里]
http://www.cnblogs.com/alibaichuan/p/5863616.html
https://blog.youkuaiyun.com/itachi85/article/details/79522200
阿里巴巴的AndFix(即时) 采用了底层替换方案
和微信的Tinker(不即时)
Instant Run方案 底层替换方案
1. 阿里AndFix
创建一个新项目AndroidFixDemo, 添加脚本依赖
compile 'com.alipay.euler:andfix:0.3.1@aar'
添加权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
添加添加补丁的代码:实现在应用启动时(Application的onCreate方法)加载补丁,实现热修复:(注意: 后面生成的补丁名字叫out.apatch, 并且要放在sdcard根目录下)
PatchManager patchManager = new PatchManager(this);
patchManager.init("1.0");
// 加载补丁
patchManager.loadPatch();
// add patch at runtime
try {
// 注意: 补丁要放到sdkcard根目录下,并且名字为out.apatch
String patchFileString = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/out.apatch";
// 添加新补丁
patchManager.addPatch(patchFileString);
Log.d("AndFix", "添加新补丁:" + patchFileString);
} catch (Exception e) {
Log.e("AndFix", "", e);
}
对比两个apk生成补丁out.apatch(注意apk需要进行签名,否则会修复失败): 解压项目AndFix-master.zip/tools里的apkpatch-1.0.3.zip, 生成两个apk(原apk和修复后的apk),再通过apkpatch工具对比两个apk, 在文件夹patch目录下生成补丁, 然后把后缀名为.apatch的文件修改为out.apatch(补丁名需为:out.apatch)
语法
apkpatch -f <新apk> -t <旧apk> -o <输出全路径> -k <签名文件> -p <密码> -a <别名> -e <密码>
# 默认签名
apkpatch -f app-debug-v2.apk -t app-debug-v1.apk -o C:\Users\Administrator\Desktop\apkpatch-1.0.3\patch -k itheima.jks -p christ -a christ-e christ
安装运行旧版本;
把补丁out.apatch放到sdcard根目录下:
# 通过adb命令把out.apatch补丁push到sdcard根目录
adb push out.apatch /sdcard/
关闭旧版本(要退出当前进程),再打开应用,应用会加载补丁,查看是否热修复成功。2.微信Tinker(bugly集成)
bugly: bug捕获, 集成Tinker的热修复
集成指南:https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20170322165254
1.project下build.gradle添加:
// tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明确版本号,例如1.0.4
classpath "com.tencent.bugly:tinker-support:latest.release"
2.module下的build.gradle添加:
android {
defaultConfig {
...
ndk {
//设置支持的SO库架构
abiFilters 'armeabi' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
}
}
dependencies {
...
// 多dex配置
compile "com.android.support:multidex:1.0.1"
// 其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.3.2
compile 'com.tencent.bugly:crashreport_upgrade:latest.release'
compile 'com.tencent.bugly:nativecrashreport:latest.release'
}
3.在模块下创建tinker-support.gradle,该文件的配置如下:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "app-0208-15-10-00"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
// 指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"
// 是否启用覆盖tinkerPatch配置功能,默认值false
// 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 编译补丁包时,必需指定基线版本的apk,默认值为空
// 如果为空,则表示不是进行补丁包的编译
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
// 对应tinker插件applyMapping
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
// 对应tinker插件applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
// 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
tinkerId = "base-1.0.1"
// 构建多渠道补丁时使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否开启反射Application模式
enableProxyApplication = false
}
/**
* 一般来说,我们无需对下面的参数做任何的修改
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
//oldApk ="${bakPath}/${appName}/app-release.apk"
ignoreWarning = false
useSign = true
dex {
dexMode = "jar"
pattern = ["classes*.dex"]
loader = []
}
lib {
pattern = ["lib/*/*.so"]
}
res {
pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = []
largeModSize = 100
}
packageConfig {
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
//tinkerId = "1.0.1-base"
//applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
//applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
}
}
4.并在app中的build.gradle文件最后添加:
apply from: 'tinker-support.gradle'
5.创建SampleApplicationLike,并指定appid:
public class SampleApplicationLike extends DefaultApplicationLike {
public static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplicationLike(Application application, int tinkerFlags,
boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onCreate() {
super.onCreate();
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
// 调试时,将第三个参数改为true
Bugly.init(getApplication(), "a858252095", false);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
// you must install multiDex whatever tinker is installed!
MultiDex.install(base);
// 安装tinker
// TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
6.创建SampleApplication,注意替换如下的包名:(如果项目中要使用自定义的Application需要继承此SampleApplication)public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "包名.SampleApplicationLike",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
7.在清单文件中配置SampleApplication
<application
...
android:name=".SampleApplication">
8.配置权限
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <--!危险权限需要动态申请 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <--!危险权限需要动态申请 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <--!危险权限需要动态申请 -->
9.配置签名文件, 生成经过正式签名的apk
signingConfigs {
config {
keyAlias 'christ'
keyPassword '123456'
storeFile file('C:/christ01.jks')
storePassword '123456'
}
}
release {
// 使用上面配置的签名文件
signingConfig signingConfigs.config
}
debug {
// 使用上面配置的签名文件
signingConfig signingConfigs.config
}
完整接入流程
- 打基准包安装并上报联网(注:填写唯一的tinkerId)
- 对基准包的bug修复(可以是Java代码变更,资源的变更)
- 修改基准包路径、修改补丁包tinkerId、mapping文件路径(如果开启了混淆需要配置)、resId文件路径
- 执行buildTinkerPatchRelease打Release版本补丁包
- 选择app/build/outputs/patch目录下的补丁包并上传(注:不要选择tinkerPatch目录下的补丁包,不然上传会有问题)
- 编辑下发补丁规则,点击立即下发
- 杀死进程并重启基准包,请求补丁策略(SDK会自动下载补丁并合成)
- 再次重启基准包,检验补丁应用结果
- 查看页面,查看激活数据的变化