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 的開放性不一樣
熱修復將來發展前景是很樂觀的。