Multidex記錄三:原始碼解析
記錄Multidex原始碼解析
為什麼要用記錄呢,因為我從開始接觸Android時我們的專案就在65535的邊緣。不久Google就出了multidex的解決方案。我們也已經接入multidex好多年,但我自己還沒有接入,知其然而不知其所以然。出了問題只能自己找原始碼來分析。前兩篇文章 Multidex記錄一:介紹和使用 和 Multidex記錄二:缺陷&解決 分別講述了怎麼接入和接入時遇到的問題,本博文只是對multidex原始碼學習過程中的分析和理解的記錄。
關於Multidex
的相關知識點前兩章已經講的差不多了,這篇文章只分析Multidex
的安裝。
流程圖
原始碼分析
我們先來看看MultiDex
I/MultiDex: VM with version 1.6.0 does not have multidex support
Installing application
MultiDexExtractor.load(/data/app/com.xxx.xxx-1.apk, false, )
I/MultiDex: Blocking on lock /data/data/com.xxx.xxx/code_cache/secondary-dexes/MultiDex.lock
/data/data/com.xxx.xxx/code_cache/secondary-dexes/MultiDex. lock locked
Detected that extraction must be performed.
I/MultiDex: Extraction is needed for file /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes2.zip
Extracting /data/data/com.xxx.xxx/code_cache/secondary-dexes/tmp-com.xxx.xxx-1.apk.classes1415547735.zip
I/MultiDex: Renaming to / data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes2.zip
Extraction succeeded - length /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes2.zip: 4238720 - crc: 2971858359
Extraction is needed for file /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes3.zip
Extracting /data/data/com.xxx.xxx/code_cache/secondary-dexes/tmp-com.xxx.xxx-1.apk.classes-1615165740.zip
I/MultiDex: Renaming to /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes3.zip
Extraction succeeded - length /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes3.zip: 3106018 - crc: 3138243730
Extraction is needed for file /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes4.zip
Extracting /data/data/com.xxx.xxx/code_cache/secondary-dexes/tmp-com.xxx.xxx-1.apk.classes-469912688.zip
I/MultiDex: Renaming to /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes4.zip
Extraction succeeded - length /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx-1.apk.classes4.zip: 2163715 - crc: 1148318293
load found 3 secondary dex files
I/MultiDex: install done
第二次啟動時的日誌:
I/MultiDex: VM with version 1.6.0 does not have multidex support
Installing application
MultiDexExtractor.load(/data/app/com.xxx.xxx-1.apk, false, )
Blocking on lock /data/data/com.xxx.xxx/code_cache/secondary-dexes/MultiDex.lock
/data/data/com.xxx.xxx/code_cache/secondary-dexes/MultiDex.lock locked
I/MultiDex: loading existing secondary dex files
load found 3 secondary dex files
install done
初始化資訊
public final class MultiDex {
static final String TAG = "MultiDex";
//老版本dex檔案存放路徑
private static final String OLD_SECONDARY_FOLDER_NAME = "secondary-dexes";
//dex檔案存放路徑 code_cache/secondary-dexes
private static final String SECONDARY_FOLDER_NAME;
//Multidex最高支援的版本,大於20Android系統已支援
private static final int MAX_SUPPORTED_SDK_VERSION = 20;
//Multidex最低支援的版本
private static final int MIN_SDK_VERSION = 4;
//vm的版本資訊
private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;
private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;
//apk路徑
private static final Set<String> installedApk;
//是否支援Multidex
private static final boolean IS_VM_MULTIDEX_CAPABLE;
static {
//SECONDARY_FOLDER_NAME=code_cache/secondary-dexes
SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
installedApk = new HashSet();
//VM是否已經支援自動Multidex
IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(System.getProperty("java.vm.version"));
}
Multidex安裝
public static void install(Context context) {
//VM是否已經支援自動Multidex
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
} else if (VERSION.SDK_INT < 4) {
//Multidex最低支援的版本
throw new RuntimeException("Multi dex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
} else {
try {
/***部分程式碼省略***/
//多執行緒鎖
synchronized(installedApk) {
//apkPath = data/data/com.xxx.xxx/
String apkPath = applicationInfo.sourceDir;
if (installedApk.contains(apkPath)) {
return;
}
installedApk.add(apkPath);
/***部分程式碼省略***/
//清除 /data/data/com.xxx.xxx/files/secondary-dexes 目錄下的檔案
try {
clearOldDexDir(context);
} catch (Throwable var8) {
Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", var8);
}
//data/data/com.xxx.xxx/code_cache/secondary-dexes
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
//解壓apk,獲得dex的zip檔案列表
List<File> files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
//校驗zip檔案
if (checkValidZipFiles(files)) {
//安裝dex檔案
installSecondaryDexes(loader, dexDir, files);
} else {
//校驗失敗,重新執行解壓(解壓失敗直接丟擲異常)和安裝
Log.w("MultiDex", "Files were not valid zip files. Forcing a reload.");
files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
if (!checkValidZipFiles(files)) {
throw new RuntimeException("Zip files were not valid.");
}
installSecondaryDexes(loader, dexDir, files);
}
}
}
/***部分程式碼省略***/
}
}
Multidex獲取dex檔案
載入dex檔案
/**
* @param context
* @param applicationInfo
* @param dexDir /data/data/com.xxx.xxx/code_cache/secondary-dexes/
* @param forceReload 是否強制重新載入
* @return 包含dex的zip檔案列表
* @throws IOException
* if an error occurs when writing the file.
*/
static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException {
//data/data/com.xxx.xxx/
File sourceApk = new File(applicationInfo.sourceDir);
//apk的迴圈冗餘校驗碼
long currentCrc = getZipCrc(sourceApk);
List files;
//是否強制執行reload和是否已經解壓過apk
if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
try {
//是否已存在zip檔案
files = loadExistingExtractions(context, sourceApk, dexDir);
} catch (IOException var9) {
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
} else {
//獲取apk中的classes2.dex並且壓縮為zip檔案
files = performExtractions(sourceApk, dexDir);
//儲存當前apk的資訊,作為下次有效快取的明證
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
return files;
}
/**
* @param context
* @param timeStamp apk的最後一次修改時間
* @param crc apk的迴圈冗餘校驗碼
* @param totalDexNumber apk中一共有幾個dex
*/
private static void putStoredApkInfo(Context context, long timeStamp, long crc, int totalDexNumber) {
SharedPreferences prefs = getMultiDexPreferences(context);
Editor edit = prefs.edit();
edit.putLong("timestamp", timeStamp);
edit.putLong("crc", crc);
edit.putInt("dex.number", totalDexNumber);
apply(edit);
}
獲取已經存在的dex的壓縮包
/**
* @param context
* @param dexDir /data/data/com.xxx.xxx/code_cache/secondary-dexes/
* @param sourceApk data/app/com.xxx.xxx-1.apk
* @return 包含dex的zip檔案列表
* @throws IOException
* if an error occurs when writing the file.
*/
private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir) throws IOException {
//extractedFilePrefix = com.xxx.xxx.apk.classes
String extractedFilePrefix = sourceApk.getName() + ".classes";
//totalDexNumber=apk中dex的數量
int totalDexNumber = getMultiDexPreferences(context).getInt("dex.number", 1);
List<File> files = new ArrayList(totalDexNumber);
//主dex已經載入過了,載入class2.dex,class3.dex......
for(int secondaryNumber = 2; secondaryNumber <= totalDexNumber; ++secondaryNumber) {
//fileName = com.xxx.xxx.apk.classes2.zip
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
//extractedFile = data/data/com.xxx.xxx/code_cache/secondary-dexes/com.wuba.bangjob-1.apk.classes2.zip
File extractedFile = new File(dexDir, fileName);
if (!extractedFile.isFile()) {
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
files.add(extractedFile);
//校驗zip檔案是否完整
if (!verifyZipFile(extractedFile)) {
Log.i("MultiDex", "Invalid zip file: " + extractedFile);
throw new IOException("Invalid ZIP file.");
}
}
return files;
}
生成dex的壓縮zip檔案
/**
* @param sourceApk data/app/com.xxx.xxx-1.apk
* @param dexDir /data/data/com.xxx.xxx/code_cache/secondary-dexes/
* @return 包含dex的zip檔案列表
* @throws IOException
* if an error occurs when writing the file.
*/
private static List<File> performExtractions(File sourceApk, File dexDir) throws IOException {
//extractedFilePrefix = com.xxx.xxx.apk.classes
String extractedFilePrefix = sourceApk.getName() + ".classes";
//刪除data/data/com.xxx.xxx/code_cache/secondary-dexes/目錄下的檔案
prepareDexDir(dexDir, extractedFilePrefix);
List<File> files = new ArrayList();
ZipFile apk = new ZipFile(sourceApk);
try {
//從class2.dex開始
int secondaryNumber = 2;
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
//fileName = com.xxx.xxx.apk.classes2.zip
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
File extractedFile = new File(dexDir, fileName);
files.add(extractedFile);
//最多三次嘗試生成dex的zip檔案
int numAttempts = 0;
//標識是否生成dex的zip檔案
boolean isExtractionSuccessful = false;
while(numAttempts < 3 && !isExtractionSuccessful) {
++numAttempts;
extract(apk, dexFile, extractedFile, extractedFilePrefix);
//校驗壓縮檔案是否完整,否則刪除重來
isExtractionSuccessful = verifyZipFile(extractedFile);
if (!isExtractionSuccessful) {
extractedFile.delete();
if (extractedFile.exists()) {
Log.w("MultiDex", "Failed to delete corrupted secondary dex '" + extractedFile.getPath() + "'");
}
}
}
if (!isExtractionSuccessful) {
throw new IOException("Could not create zip file " + extractedFile.getAbsolutePath() + " for secondary dex (" + secondaryNumber + ")");
}
++secondaryNumber;
}
}/***部分程式碼省略***/
return files;
}
將classes2.dex放入zip檔案中
/**
* @param apk apk的壓縮包
* @param dexFile apk中的classes2.dex檔案
* @param extractTo /data/data/com.xxx.xxx/code_cache/secondary-dexes/com.xxx.xxx.apk.classes2.zip
* @param extractedFilePrefix com.wuba.bangjob-1.apk.classes
* @throws IOException
* if an error occurs when writing the file.
*/
private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException {
InputStream in = apk.getInputStream(dexFile);
ZipOutputStream out = null;
///data/data/com.wuba.bangjob/code_cache/secondary-dexes/com.xxx.xxx.apk.classes1415547735.zip
File tmp = File.createTempFile(extractedFilePrefix, ".zip", extractTo.getParentFile());
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
try {
//將classes2.dex進行壓縮內容名稱為classes.dex
ZipEntry classesDex = new ZipEntry("classes.dex");
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex);
byte[] buffer = new byte[16384];
for(int length = in.read(buffer); length != -1; length = in.read(buffer)) {
out.write(buffer, 0, length);
}
out.closeEntry();
} finally {
out.close();
}
//重新命名檔案為com.xxx.xxx.apk.classes2.zip
if (!tmp.renameTo(extractTo)) {
throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + extractTo.getAbsolutePath() + "\"");
}
}/***部分程式碼省略***/
}
dex檔案的裝載
/**
* @param loader
* @param dexDir /data/data/xxx.xxx.xxx/code_cache/secondary-dexes/
* @param files dex的壓縮zip檔案
*/
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
if (VERSION.SDK_INT >= 19) {//KitKat(19)
MultiDex.V19.install(loader, files, dexDir);
} else if (VERSION.SDK_INT >= 14) {//IceCreamSandwich(14,15),JellyBean(16,17,18)
MultiDex.V14.install(loader, files, dexDir);
} else {
MultiDex.V4.install(loader, files);
}
}
}
private static final class V19 {
private V19() {
}
/**
* @param loader PathClassLoader
* @param additionalClassPathEntries dex壓縮包路徑
* @param optimizedDirectory opt之後的dex檔案目錄
*/
private static
相關推薦
Multidex記錄三:原始碼解析
記錄Multidex原始碼解析
為什麼要用記錄呢,因為我從開始接觸Android時我們的專案就在65535的邊緣。不久Google就出了multidex的解決方案。我們也已經接入multidex好多年,但我自己還沒有接入,知其然而不知其所以然。出了問題只能自
自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程
本文是《自定義spring boot starter三部曲》系列的終篇,前文中我們開發了一個starter並做了驗證,發現關鍵點在於spring.factories的自動載入能力,讓應用只要依賴starter的jar包即可,今天我們來分析Spring和Spring boot原始碼,瞭解s
DelayQueue阻塞佇列第二章:原始碼解析
DelayQueue阻塞佇列系列文章
介紹
DelayQueue是java併發包中提供的延遲阻塞佇列,業務場景一般是下單後多長時間過期,定時執行程式等
1-DelayQueue的組成結構
/**
* DelayQueue佇列繼承了AbstractQueue,
python UI自動化實戰記錄三:pageobject-基類
指令碼思路:使用pageobject模式,寫一個basepage基類,所有頁面的通用方法封裝到基類中。
專案中的測試頁面page1和page2都繼承自basepage基類。可使用基類定義的方法。基類裡會將webdriver和page合二為一,既將webdriver的操作改寫成page的方法。
Istio技術與實踐01: 原始碼解析之Pilot多雲平臺服務發現機制
服務模型
首先,Istio作為一個(微)服務治理的平臺,和其他的微服務模型一樣也提供了Service,ServiceInstance這樣抽象服務模型。如Service的定義中所表達的,一個服務有一個全域名,可以有一個或多個偵聽埠。
type Service struct
Istio技術與實踐02:原始碼解析之Istio on Kubernetes 統一服務發現
前言
文章Istio技術與實踐01: 原始碼解析之Pilot多雲平臺服務發現機制結合Pilot的程式碼實現介紹了Istio的抽象服務模型和基於該模型的資料結構定義,瞭解到Istio上只是定義的服務發現的介面,並未實現服務發現的功能,而是通過Adapter機制以一種可擴充套件
Alink漫談(十八) :原始碼解析 之 多列字串編碼MultiStringIndexer
# Alink漫談(十八) :原始碼解析 之 多列字串編碼MultiStringIndexer
[ToC]
## 0x00 摘要
Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習演算法平臺,是業界首個同時支援批式演算法、流式演算法的機器學習平臺。本文將帶領大家來分析Alink中
Alink漫談(十九) :原始碼解析 之 分位點離散化Quantile
# Alink漫談(十九) :原始碼解析 之 分位點離散化Quantile
[Toc]
## 0x00 摘要
Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習演算法平臺,是業界首個同時支援批式演算法、流式演算法的機器學習平臺。本文將帶領大家來分析Alink中 Quantile 的
Java集合框架之三:HashMap原始碼解析 Java集合框架之三:HashMap原始碼解析
Java集合框架之三:HashMap原始碼解析
版權宣告:本文為博主原創文章,轉載請註明出處,歡迎交流學習!
HashMap在我們的工作中應用的非常廣泛,在工作面試中也經常會被問到,對於這樣一個重要的集合
Spring原始碼解析(三):父子容器的概念
相信大家現在在使用spring專案開發時可能不只是單單使用spring一個框架進行開發, 可能會用到現在主流的ssm,spring和springmvc一起使用。
而在一起使用的時候我就發現了一個問題,在web.xml配置spring容器初始化的時候存在一個問題。
一般我們在配置sprin
OKHttp 3.10原始碼解析(三):快取機制
本篇我們來講解OKhttp的快取處理,在網路請求中合理地利用本地快取能有效減少網路開銷,提高響應速度。HTTP報頭也定義了很多控制快取策略的域,我們先來認識一下HTTP的快取策略。
一.HTTP快取策略
HTTP快取有多種規則,根據是否需要向伺服器發起請求來分類,我們將其分為兩大類:強制
Redis原始碼解析:27叢集(三)主從複製、故障轉移
一:主從複製
在叢集中,為了保證叢集的健壯性,通常設定一部分叢集節點為主節點,另一部分叢集節點為這些主節點的從節點。一般情況下,需要保證每個主節點至少有一個從節點。
叢集初始化時,每個叢集節點都是以獨立的主節點角色而存在的,通過向叢集節點
ElasticSearch原始碼解析(三):索引建立
我們先來看看索引建立的事例程式碼:
Directory directory = FSDirectory.getDirectory("/tmp/testindex"); // Use standard analyzer
Analyzer analyzer = new
Spark2.3.2原始碼解析: 10. 排程系統 Task任務提交 (三) TaskScheduler : Executor 任務提交
架構圖:
程式碼提交時序圖
Standalone模式提交執行流程圖:
首先寫一個WordCount程式碼(這個程式碼,為了觀察多個stage操作,我寫了兩個reducebykey
ARouter解析三:URL跳轉本地頁面原始碼分析
在前面文章中對ARouter中頁面跳轉和原始碼進行了分析,今天我們來學習下通過URL跳轉本地頁面的使用和跳轉原始碼分析。在看這篇文章之前建議小夥伴們先看下ARouter解析一:基本使用及頁面註冊原始碼解析,先對ARouter有個整體的瞭解。通過URL跳轉也免不了前面的準備工作,比如ARouter例項的獲取,初
Spring系列(三):Spring IoC原始碼解析
一、Spring容器類繼承圖
二、容器前期準備
IoC原始碼解析入口:
/**
* @desc: ioc原理解析 啟動
* @author: toby
* @date: 2019/7/22 22:20
*/
public class PrincipleMain {
public sta
Tomcat原始碼分析三:Tomcat啟動載入過程(一)的原始碼解析
Tomcat啟動載入過程(一)的原始碼解析
今天,我將分享用原始碼的方式講解Tomcat啟動的載入過程,關於Tomcat的架構請參閱《Tomcat原始碼分析二:先看看Tomcat的整體架構》一文。
先看看應用情況
在《Servlet與Tomcat執行示例》一文中,我詳細的記錄了Tomcat是如何啟動一個Ser
SpringBoot 原始碼解析 (三)----- Spring Boot 精髓:啟動時初始化資料
在我們用 springboot 搭建專案的時候,有時候會碰到在專案啟動時初始化一些操作的需求 ,針對這種需求 spring boot為我們提供了以下幾種方案供我們選擇:
ApplicationRunner 與 CommandLineRu
mybatis原始碼配置檔案解析之三:解析typeAliases標籤
在前邊的部落格在分析了mybatis解析settings標籤,《mybatis原始碼配置檔案解析之二:解析settings標籤》。下面來看解析typeAliases標籤的過程。
一、概述
在mybatis核心配置檔案(mybatis-config.xml)中有關typeAliases的配置如下,
<t
Spring Boot系列(三):Spring Boot整合Mybatis原始碼解析
一、Mybatis回顧
1、MyBatis介紹
Mybatis是一個半ORM框架,它使用簡單的 XML 或註解用於配置和原始對映,將介面和Java的POJOs(普通的Java 物件)對映成資料庫中的記錄。
2、Mybatis整體架構
二、Spring Boot整合Mybatis +