Android Support Multidexの原理分析
22452 ワード
Android Support Multidexの原理分析
前言
公式紹介:Configure Apps with Over 64 K Methods
ソース分析
まず公式の使用紹介を見てみましょう.
When writing instrumentation tests for multidex apps, no additional configuration is required. AndroidJUnitRunner supports multidex out of the box, as long as you use MultiDexApplication or override the attachBaseContext() method in your custom Application object and call MultiDex.install(this) to enable multidex.
androidを使う必要があるならsupport_Multidexというjarパッケージは、jarパッケージのMultiDexApplicationまたは
attachBaseContext()
メソッドを書き換えるだけでいいです.attachBaseContext
メソッドを書き換え、MultiDex.install(this)
を呼び出すことができる.次に、この方法の具体的な実装を見てみましょう.
public class MultiDexApplication
extends Application
{
protected void attachBaseContext(Context base)
{
super.attachBaseContext(base);
MultiDex.install(this); // MultiDex.install(this)
}
}
この方法では、まずAndroidデバイスがMultiDexをサポートしているかどうかを判断し、実際にAndroid 4.4以上のバージョンの仮想マシンはART技術を導入し、すでにMultiDexをサポートしており、Dalvikとの違いは詳しくはART and Dalvikを参照してください.
次に、現在のAndroidシステムのバージョンを判断します.
android_support_multidex.jar
が実際にサポートしている範囲はApi:4から20です.MultiDexExtractor.load(context, applicationInfo, dexDir, false);
メソッドの呼び出しを続行 public static void install(Context context)
{
Log.i("MultiDex", "install");
if (IS_VM_MULTIDEX_CAPABLE) // MultiDex, return
{
Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
return;
}
if (Build.VERSION.SDK_INT < 4) { // Android , Api 4(Android 1.6)
throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT +
" is unsupported. Min SDK version is " + 4 + ".");
}
try
{
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) { // Application null, null install 。
return;
}
synchronized (installedApk)
{
String apkPath = applicationInfo.sourceDir; // apk , ,
if (installedApk.contains(apkPath)) {
return;
}
installedApk.add(apkPath);
if (Build.VERSION.SDK_INT > 20) { // Android ,Api 20(Android 4.4W) Multidex
Log.w("MultiDex", "MultiDex is not guaranteed to work in SDK version " +
Build.VERSION.SDK_INT + ": SDK version higher than " +
20 + " should be backed by " +
"runtime with built-in multidex capabilty but it's not the " +
"case here: java.vm.version=\"" +
System.getProperty("java.vm.version") + "\"");
}
try
{
loader = context.getClassLoader(); // ClassLoader
}
catch (RuntimeException e)
{
ClassLoader loader;
Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.",
e); return;
}
ClassLoader loader;
if (loader == null)
{
Log.e("MultiDex",
"Context class loader is null. Must be running in test mode. Skip patching.");
return;
}
try
{
clearOldDexDir(context); // Dex
}
catch (Throwable t)
{
Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.",
t);
}
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); // Dex : /data/app/ /code_cache/secondary-dexes
List files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); // Dex
if (checkValidZipFiles(files)) // zip
{
installSecondaryDexes(loader, dexDir, files); // Dex , !
}
else
{
Log.w("MultiDex", "Files were not valid zip files. Forcing a reload.");
files = MultiDexExtractor.load(context, applicationInfo, dexDir, true); //zip ,
if (checkValidZipFiles(files)) {
installSecondaryDexes(loader, dexDir, files); // , ,
} else {
throw new RuntimeException("Zip files were not valid.");
}
}
}
Log.i("MultiDex", "install done");
}
catch (Exception e)
{
Log.e("MultiDex", "Multidex installation failure", e);
throw new RuntimeException("Multi dex installation failed (" + e.getMessage() + ").");
}
}
MultiDexExtractor.load(context, applicationInfo, dexDir, false);
メソッド:次に、このメソッドの具体的な実装方法を見てみましょう. static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload)
throws IOException
{
Log.i("MultiDex", "MultiDexExtractor.load(" + applicationInfo.sourceDir + ", " + forceReload + ")");
File sourceApk = new File(applicationInfo.sourceDir); // apk /data/app/ -1.apk
long currentCrc = getZipCrc(sourceApk); // crc
List<File> files;
if ((!forceReload) && (!isModified(context, sourceApk, currentCrc))) // dex
{
try
{
files = loadExistingExtractions(context, sourceApk, dexDir);
}
catch (IOException ioe)
{
List<File> files;
Log.w("MultiDex", "Failed to reload existing extracted secondary dex files, falling back to fresh extraction",
ioe);
List<File> files = performExtractions(sourceApk, dexDir); //
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
}
else
{
Log.i("MultiDex", "Detected that extraction must be performed.");
files = performExtractions(sourceApk, dexDir); //
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
Log.i("MultiDex", "load found " + files.size() + " secondary dex files");
return files;
}
loadExistingExtractions(context, sourceApk, dexDir); すでにロードされているDexファイルを含む圧縮ファイルを取得します.
private static List loadExistingExtractions(Context context, File sourceApk, File dexDir)
throws IOException
{
Log.i("MultiDex", "loading existing secondary dex files");
String extractedFilePrefix = sourceApk.getName() + ".classes"; // /data/app/ -1.classes
int totalDexNumber = getMultiDexPreferences(context).getInt("dex.number", 1);
List files = new ArrayList(totalDexNumber);
for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++)
{
String fileName = extractedFilePrefix + secondaryNumber + ".zip"; // /data/app/ -1.apk.classes2.zip ...
File extractedFile = new File(dexDir, fileName); // /data/app/ -1.apk.classesN.zip
if (extractedFile.isFile())
{
files.add(extractedFile); // , List
if (!verifyZipFile(extractedFile)) // zip
{
Log.i("MultiDex", "Invalid zip file: " + extractedFile);
throw new IOException("Invalid ZIP file.");
}
}
else
{
throw new IOException("Missing extracted secondary dex file '" +
extractedFile.getPath() + "'");
}
}
return files;
}
performExtractions(sourceApk, dexDir);
private static final String DEX_PREFIX = "classes";
private static final String DEX_SUFFIX = ".dex";
private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String EXTRACTED_SUFFIX = ".zip";
private static List performExtractions(File sourceApk, File dexDir)
throws IOException {
final String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; // /data/app/ .classes
// Ensure that whatever deletions happen in prepareDexDir only happen if the zip that
// contains a secondary dex file in there is not consistent with the latest apk. Otherwise,
// multi-process race conditions can cause a crash loop where one process deletes the zip
// while another had created it.
prepareDexDir(dexDir, extractedFilePrefix); // classes{}.dex
List files = new ArrayList();
final ZipFile apk = new ZipFile(sourceApk); // zipFile, apk: // /data/apk/ -1.apk
try {
int secondaryNumber = 2;
ZipEntry dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); // zip , classes2.dex ,classesN.dex....
while (dexFile != null) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName); // /data/app/ -1.apk.classes2.zip
files.add(extractedFile);
Log.i(TAG, "Extraction is needed for file " + extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false; //
while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { // 3
numAttempts++;
// Create a zip file (extractedFile) containing only the secondary dex file
// (dexFile) from the apk.
extract(apk, dexFile, extractedFile, extractedFilePrefix);
// Verify that the extracted file is indeed a zip file.
isExtractionSuccessful = verifyZipFile(extractedFile); // zip
// Log the sha1 of the extracted zip file
Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") +
" - length " + extractedFile.getAbsolutePath() + ": " +
extractedFile.length());
if (!isExtractionSuccessful) {
// Delete the extracted file
extractedFile.delete(); //
if (extractedFile.exists()) {
Log.w(TAG, "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++;
dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
}
} finally {
try {
apk.close();
} catch (IOException e) {
Log.w(TAG, "Failed to close resource", e);
}
}
return files;
}
最後に、現在のAndroidシステムのApiバージョンに従って対応するinstallメソッドを呼び出し、Dexファイルからロードします.
private static void installSecondaryDexes(ClassLoader loader, File dexDir, List files)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir); //files List ,// /data/app/ -1.apk.classes2.zip
} else if (Build.VERSION.SDK_INT >= 14) { // /data/app/ -1.apk.classesN.zip
V14.install(loader, files, dexDir);
} else {
V4.install(loader, files);
}
}
}
まとめ
上記の分析により、MultiDex全体の読み取りプロセスを簡単にまとめることができます.