APP 启动优化java
UI 绘制优化android
内存优化git
图片压缩github
长图优化数组
电量优化缓存
Dex 加解密性能优化
..等等, 这里只是拿几个常见的举例说明。
框架名称 | 所属公司 | 是否开源 | 修复方式 |
---|---|---|---|
Dexposed | alibaba | 开源 | 实时修复 |
Andfix | alibaba | 开源 | 实时修复 |
Hotfix | alibaba | 暂未开源 | 实时修复 |
Qzone 超级补丁 | QQ 空间 | 暂未开源 | 冷启动修复 |
QFix | 手 Q 团队 | 开源 | 冷启动修复 |
Robust | 美团 | 开源 | 实时修复 |
Nuwa | 大众点评 | 开源 | 冷启动修复 |
RocooFix | 百度金融 | 开源 | 冷启动修复 |
Aceso | 美丽说蘑菇街 | 开源 | 实时修复 |
Amigo | 饿了么 | 开源 | 冷启动修复 |
Tinker | 微信 | 开源 | 冷启动修复 |
Sophix | alibaba | 未开源 | 实时修复 + 冷启动修复 |
APP 从新启动后,让 ClassLoader 去加载新的类。
class 暂未被加载到系统中,收到推送利用插桩原理让 ClassLoader 优先加载修复好的 dex 。
65536 限制
com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
复制代码
当应用程序报 65536 错误的根本缘由是,应用的方法数量超过了最大数 65536 个,由于 DVM Bytecode 的限制, DVM 指令集的方法调用指令 invoke-kind 索引为 16 bits, 最多能引用 65535 个方法
LinearAlloc 限制
INSTALL_FAILED_DEXOPT
复制代码
在安装应用时可能会提示 上面的错误,产生的缘由是 LinearAlloc 限制。 DVM 中的 LinearAlloc 是一个固定的缓存区,当方法数超出缓存区的大小时会报错。
解决
为了解决 65536 限制和 LinearAlloc 限制,从而产生了 Dex 分包机制。 Dex 分包方案主要作的时在打包时将应用代码分红多个 Dex,将应用启动时必须用到的类和这些类的直接引用类放到主 Dex 中,其它代码放到次 Dex 中。当应用启动时先加载主 Dex,等到应用启动后再动态地加载次Dex,从而缓解了主 Dex 的 65536 限制和 LinearAlloc 限制
gradle 配置
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.ykun.hotfix"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// 开启分包
multiDexEnabled true
// 设置分包配置文件
multiDexKeepFile file('multidex.keep') } dexOptions {
javaMaxHeapSize "4g"
preDexLibraries = false
additionalParameters = [ // 配置multidex参数
'--multi-dex', // 多dex分包
'--set-max-idx-number=50000', // 每一个包内方法数上限
'--main-dex-list=' + '/multidex.keep', // 打包到主classes.dex的文件列表
'--minimal-main-dex'
]
}
}
复制代码
配置 multidex.keep 将指定的 class 放入 class.dex 中
格式:
//参考
com/ykun/hotfix/BaseActivity.class
com/ykun/hotfix/BaseApplication.class
com/ykun/hotfix/MainActivity.class
复制代码
效果
源码:
/**遍历须要找到须要加载的 class */
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
复制代码
插桩原理:
经过源码得知 findClass 是经过遍历 dexElements 来找到 class, 若是咱们反射获得 DexPathList 的私有数组 dexElements,咱们外部改变这个数组内部顺序索引,将修复好的 dex 放入 [0] 的位置,那么是否是可以优先使用修复好的 dex 勒? 很明显,是成立的。下面开始撸代码吧。
接收来至服务器发来的补丁包,若是修复包已经存在则删除,copy 到私有目录防止用户不当心删除。
/**这里模拟已经下载好的 dex 补丁包*/
private void downloadPatch() {
//1 从服务器下载dex文件 好比v1.1修复包文件(classes2.dex)
File sourceFile = new File(Environment.getExternalStorageDirectory(), "classes2.dex");
// 目标路径:私有目录
//getDir("odex", Context.MODE_PRIVATE) data/user/0/包名/app_odex
File targetFile = new File(getDir("hotfix",
Context.MODE_PRIVATE).getAbsolutePath() + File.separator + "classes2.dex");
if (targetFile.exists()) {
targetFile.delete();
}
try {
// 复制dex到私有目录
FileUtils.copyFile(sourceFile, targetFile);
Toast.makeText(this, "Bug 修复成功!", Toast.LENGTH_SHORT).show();
FixDexUtils.loadFixedDex(this);
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
建立修复包的类加载器 DexClassLoader (经过源码得知是继承的 BaseDexClassLoader)
/** * 建立类加载器 * * @param context * @param fileDir */
private static void createDexClassLoader(Context context, File fileDir) {
String optimizedDirectory = fileDir.getAbsolutePath() + File.separator + "opt_dex";
File fOpt = new File(optimizedDirectory);
if (!fOpt.exists()) {
fOpt.mkdirs();
}
DexClassLoader classLoader;
for (File dex : loadedDex) {
//初始化类加载器
classLoader = new DexClassLoader(dex.getAbsolutePath(), optimizedDirectory, null,
context.getClassLoader());
//热修复
hotFix(classLoader, context);
}
}
复制代码
获取系统的 PathClassLoader
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
复制代码
获取修复包的 dexElements
Object pathList = ReflectUtils.reflect(myClassLoader).field("pathList").get();
Object myDexElements = ReflectUtils.reflect(pathList).field("dexElements").get();
复制代码
获取系统的 dexElements
Object sysPathList = ReflectUtils.reflect(pathClassLoader).field("pathList").get();
Object sysDexElements = ReflectUtils.reflect(sysPathList).field("dexElements").get();
复制代码
将系统的 dexElements 和 修复包的 dexElements merge 成新的 dexElements
// 合并,这里利用插桩原理进行合并数组,将修复好的 class2.dex 放入第一位,优先加入就好了
Object dexElements = ArrayUtils.combineArray(myDexElements, sysDexElements);
复制代码
从新赋值给 DexPathList 的 dexElements 属性
//从新赋值
ReflectUtils.reflect(sysPathList).field("dexElements", dexElements);
复制代码
热修复 = “黑科技”?
热修复不一样于国内 APP 进程保活这种 “黑科技”,让 app 常驻后台,既耗电又占用内存,浪费不少手机资源。还有 APP 的推送服务,无节操地对用户进行信息轰炸。还有更无节操的全家桶 app。致使 Android手机卡顿不堪,这些所谓的 “黑科技” 都是为了手机厂商的利益而损害用户的体验。
而热修复是可以让开发者和用户共赢的。不只厂商能快速迭代更新 app,使功能尽快上线,并且热更新过程用户无感知,节省大量更新时间,提升用户体验。更重要的能保证 app 的功能稳定,bug 能及时修复。
IOS 封杀了热修复功能,Android 的热修复也会被 pass 掉吗?
google 和 apple 公司在中国的 diwei 不同
Android 和 IOS 的开放性不一样
热修复将来发展前景是很乐观的。