공식 MultiDex 소스 분석
주요 원리: 응용
DexClassLoader
에 dex 파일을 동적으로 추가프로세스 분석
기본 프로세스
1. 검사(Vm에서 21+와 같은 하도급을 지원했는지, 최저 SDK 버전은 4이고 하도급을 했는지 확인)
2、오래된 dex 하도급의 디렉터리에 있는 파일을 정리한다
data/data/packageName/file/secondary-dexes
3. Dex 패키지 읽기, 디렉터리 저장data/data/packageName/code_cache/secondary-dexes
/data/data/pkgName/code_cache/secondary-dexes/base.apk.classesN.zip
아래 5.Dex 압축 패키지 파일 설치 로드,
DexPathList#makeDexElements
방법으로 dex의 로드를 진행하고 되돌아오는 Element
수조로 원래ClassLoader
아래의 Elements
를 확장하여 로드를 실현한다.public static void install(Context context) {
if (IS_VM_MULTIDEX_CAPABLE) {
Log.i(TAG, "VM has multidex support, MultiDex support library is disabled.");
return;
}
if (Build.VERSION.SDK_INT < MIN_SDK_VERSION) {
throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + MIN_SDK_VERSION + ".");
}
try {
ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
// Looks like running on a test Context, so just return without patching.
return;
}
synchronized (installedApk) {
String apkPath = applicationInfo.sourceDir;
if (installedApk.contains(apkPath)) { //
return;
}
installedApk.add(apkPath);
if (Build.VERSION.SDK_INT > MAX_SUPPORTED_SDK_VERSION) {
// : 20 dex
}
/*
*/
ClassLoader loader;
try {
loader = context.getClassLoader();
} catch (RuntimeException e) {
// MockContext
return;
}
if (loader == null) {
// Robolectric tests
return;
}
try {
clearOldDexDir(context); // ( data/data/pkg-name/) secondary-dexes
} catch (Throwable t) {
}
// data/data/packageName/code_cache/secondary-dexes
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
List files = MultiDexExtractor.load(context, applicationInfo, dexDir, false); // zip
if (checkValidZipFiles(files)) { // zip
installSecondaryDexes(loader, dexDir, files);
} else {
// ,
}
}
} catch (Exception e) {
}
}
설치 방법
DexClassLoader
구성할 때 지정한 디렉터리에 있는 zip, dex,jar 등 파일을 읽고 DexFile
로 불러오며 Element
그룹을 구성한다. 구성원pathList
에 기록된 다음에 클래스의 불러오는 것은 모두 이것DexFile
에서 찾으려고 시도하지만 dex가 패키지를 나누면'새로운 dex의 파일 경로'를 스스로 알려줘야 한다DexClassLoader
.여기에SDK19+를 예로 들면 (14,15,16,17and18의 차이점은 DexPathList#makeDexElements
방법의 서명 변화에 있다. 4에서 13의 변화는 약간 크지만 지금도 14이하의 것을 개발하지 않으면 자세히 보지 않는다)private static void installSecondaryDexes(ClassLoader loader, File dexDir, List files) {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT >= 19) {
V19.install(loader, files, dexDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(loader, files, dexDir);
} else {
V4.install(loader, files);
}
}
}
주로
DexPathList#makeDexElements
의 방법으로dex를 불러오고 되돌아오는 Element
수조로 원래ClassLoader
하의Elements
를 확충한다.private static final class V19 {
private static void install(ClassLoader loader, List additionalClassPathEntries, File optimizedDirectory) {
Field pathListField = findField(loader, "pathList"); //loader#pathList ,DexPathList
Object dexPathList = pathListField.get(loader);
ArrayList suppressedExceptions = new ArrayList();
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makeDexElement", e);
}
//.....
}
}
/**
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
* DexPathList#makeDexElements dex , `Element`
*/
private static Object[] makeDexElements(
Object dexPathList, ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) {
Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,suppressedExceptions);
}
}
Dex 읽기
Dex의 읽기는
MultiDexExtractor#load
방법으로 수행됩니다.MultiDexExtractor.java
static List load(Context context, ApplicationInfo applicationInfo, File dexDir, boolean forceReload) throws IOException {
final File sourceApk = new File(applicationInfo.sourceDir); //data/app/packageName/base.apk
long currentCrc = getZipCrc(sourceApk); // crc32 , MD5?
List files;
// ,
if (!forceReload && !isModified(context, sourceApk, currentCrc)) {
//...
files = loadExistingExtractions(context, sourceApk, dexDir);
//...
} else {
files = performExtractions(sourceApk, dexDir);
putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); //dex sp,
}
return files;
}
private static boolean isModified(Context context, File archive, long currentCrc) {
SharedPreferences prefs = getMultiDexPreferences(context);//multidex.version
return (prefs.getLong(KEY_TIME_STAMP, NO_VALUE) != getTimeStamp(archive)) || (prefs.getLong(KEY_CRC, NO_VALUE) != currentCrc);
}
주로 DEX를 어떻게 읽고 apk 파일의 이름
classesNdex
을 얻는지ZipEntry
, 파일에 쓰기data/data/packageName/code_cache/secondary-dexes/base.apk.classesN.zip
, N은dex의 수량, 2로 시작합니다.안드로이드 시스템은 앱을 시작할 때 첫 번째Classes.dex
만 불러오기 때문에 다른 DEX는 저희가 인공적으로 설치해야 합니다.private static List performExtractions(File sourceApk, File dexDir) throws IOException {
final String extractedFilePrefix = sourceApk.getName() + "classes"; //base.apk.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); // base.apk.classes
List files = new ArrayList();
final ZipFile apk = new ZipFile(sourceApk); //data/app/packageName/base.apk
try {
int secondaryNumber = 2;
ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + "dex"); // ZipEntry
while (dexFile != null) {
String fileName = extractedFilePrefix + secondaryNumber + "zip"; //base.classes2.zip, base.classes3.dex、base.classes4.dex、base.classesN.dex
File extractedFile = new File(dexDir, fileName); //data/data/packageName/code_cache/secondary-dexes/base.classes2.zip
files.add(extractedFile);
int numAttempts = 0;
boolean isExtractionSuccessful = false;
while (numAttempts < 3 && !isExtractionSuccessful) { // 3
numAttempts++;
// Create a zip file (extractedFile) containing only the secondary dex file (dexFile) from the apk.
extract(apk, dexFile, extractedFile, extractedFilePrefix); //ZipEntry
isExtractionSuccessful = verifyZipFile(extractedFile); // zip
// Log the sha1 of the extracted zip file
if (!isExtractionSuccessful) {
// Delete the extracted file
extractedFile.delete();
//...
}
}
if (!isExtractionSuccessful) {
//...
}
secondaryNumber++;
dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX);
} //end while
} finally {
//..
}
return files;
}
data/data/packageName/code_cache/secondary-dexes/
디렉토리에서 시작되지 않은 모든 파일 삭제 base.apk.classes
/**
* This removes any files that do not have the correct prefix.
*/
private static void prepareDexDir(File dexDir, final String extractedFilePrefix) throws IOException {
/* mkdirs() has some bugs, especially before jb-mr1 and we have only a maximum of one parent
* to create, lets stick to mkdir().
*/
File cache = dexDir.getParentFile();
mkdirChecked(cache); //`data/data/packageName/code_cache/`
mkdirChecked(dexDir); //`data/data/packageName/code_cache/secondary-dexes/`
// Clean possible old files
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return !pathname.getName().startsWith(extractedFilePrefix); // base.apk.classes
}
};
File[] files = dexDir.listFiles(filter);
if (files == null) {
return;
}
for (File oldFile : files) {
if (!oldFile.delete()) {
Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
} else {
Log.i(TAG, "Deleted old file " + oldFile.getPath());
}
}
}
ZipEntry
를 파일, 구체적인 파일data/data/packageName/code_cache/secondary-dexes/base.apk.classesN.zip
에 쓰기/**
* apk : apk
* dexFile : Apk zip dex ,classes2.dex…classesN.dex
* extractTo : data/data/packageName/code_cache/secondary-dexes/base.apk.classesN.zip
* extractedFilePrefix : base.apk.classes
*/
private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) {
InputStream in = apk.getInputStream(dexFile);
ZipOutputStream out = null;
File tmp = File.createTempFile(extractedFilePrefix, "zip", extractTo.getParentFile());
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
try {
ZipEntry classesDex = new ZipEntry("classes.dex");
// keep zip entry time since it is the criteria used by Dalvik
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex);
byte[] buffer = new byte[BUFFER_SIZE];
int length = in.read(buffer);
while (length != -1) {
out.write(buffer, 0, length);
length = in.read(buffer);
}
out.closeEntry();
} finally {
out.close();
}
if (!tmp.renameTo(extractTo)) {
//...
}
} finally {
closeQuietly(in);
tmp.delete(); // return status ignored
}
}
참고 자료
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.