前言
本篇文章比較長,涉及到Launcher啟動APP、ClassLoader工作過程以及Multidex解決方案,可以靜下心來看,整個看完一套流程很多東西自然就相通了~~
Launcher
首先 Launcher這個東西我們不用把他想的太復雜,可以簡單認為它就是一個桌面應用,也就是說它也是個Activity,擁有Activity同樣的生命周期,系統(tǒng)啟動的時候啟動了Launcher這個桌面應用。

我們再看一下
LauncherActivity的源碼,這個Activity是個抽象類,并不是實際我們看到展示在桌面上的Activity,桌面的Activity應該是繼承了LauncherActivity
//抽象類
public abstract class LauncherActivity extends ListActivity
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//拿到PackageManager用于獲取所有安裝包信息
mPackageManager = getPackageManager();
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setProgressBarIndeterminateVisibility(true);
}
onSetContentView();
mIconResizer = new IconResizer();
mIntent = new Intent(getTargetIntent());
mIntent.setComponent(null);
//設置適配器
mAdapter = new ActivityAdapter(mIconResizer);
setListAdapter(mAdapter);
getListView().setTextFilterEnabled(true);
updateAlertTitle();
updateButtonText();
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
setProgressBarIndeterminateVisibility(false);
}
}
//適配器
private class ActivityAdapter extends BaseAdapter implements Filterable {
private final Object lock = new Object();
private ArrayList<ListItem> mOriginalValues;
protected final IconResizer mIconResizer;
protected final LayoutInflater mInflater;
protected List<ListItem> mActivitiesList;
private Filter mFilter;
private final boolean mShowIcons;
public ActivityAdapter(IconResizer resizer) {
mIconResizer = resizer;
mInflater = (LayoutInflater) LauncherActivity.this.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mShowIcons = onEvaluateShowIcons();
mActivitiesList = makeListItems();
}
}
//獲取Items
public List<ListItem> makeListItems() {
// Load all matching activities and sort correctly
List<ResolveInfo> list = onQueryPackageManager(mIntent);
onSortResultList(list);
ArrayList<ListItem> result = new ArrayList<ListItem>(list.size());
int listSize = list.size();
for (int i = 0; i < listSize; i++) {
ResolveInfo resolveInfo = list.get(i);
result.add(new ListItem(mPackageManager, resolveInfo, null));
}
return result;
}
//ListItem的數據結構
public static class ListItem {
public ResolveInfo resolveInfo;
//appname,對應AndroidManifest Application label ?
public CharSequence label;
//圖標
public Drawable icon;
//包名
public String packageName;
public String className;
public Bundle extras;
}
//ResolveInfo數據結構
public ResolveInfo(ResolveInfo orig) {
activityInfo = orig.activityInfo;
serviceInfo = orig.serviceInfo;
providerInfo = orig.providerInfo;
filter = orig.filter;
priority = orig.priority;
preferredOrder = orig.preferredOrder;
match = orig.match;
specificIndex = orig.specificIndex;
labelRes = orig.labelRes;
nonLocalizedLabel = orig.nonLocalizedLabel;
icon = orig.icon;
resolvePackageName = orig.resolvePackageName;
noResourceId = orig.noResourceId;
iconResourceId = orig.iconResourceId;
system = orig.system;
targetUserId = orig.targetUserId;
handleAllWebDataURI = orig.handleAllWebDataURI;
isInstantAppAvailable = orig.isInstantAppAvailable;
instantAppAvailable = isInstantAppAvailable;
}
以上這段代碼已經很清晰了,LauncherActivity中通過PackageManager拿到適配器的所有數據ListItem列表,單個ListItem中有些關鍵數據label、icon、packageName、resolveInfo,從ResolveInfo的數據結構來看,這個是不是就是我們要啟動的那個Activity的所有信息?我查找了一下ResolveInfo的引用,只有在PackageManagerService里面查到相關引用,PackageManagerService是管理所有包的service
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, List<ResolveInfo> query, int userId) {
if (query != null) {
final int N = query.size();
if (N == 1) {
return query.get(0);
} else if (N > 1) {
final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
// If there is more than one activity with the same priority,
// then let the user decide between them.
ResolveInfo r0 = query.get(0);
ResolveInfo r1 = query.get(1);
if (DEBUG_INTENT_MATCHING || debug) {
Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
+ r1.activityInfo.name + "=" + r1.priority);
}
// If the first activity has a higher priority, or a different
// default, then it is always desirable to pick it.
if (r0.priority != r1.priority
|| r0.preferredOrder != r1.preferredOrder
|| r0.isDefault != r1.isDefault) {
return query.get(0);
}
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
flags, query, r0.priority, true, false, debug, userId);
if (ri != null) {
return ri;
}
// If we have an ephemeral app, use it
for (int i = 0; i < N; i++) {
ri = query.get(i);
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final String packageName = ri.activityInfo.packageName;
final PackageSetting ps = mSettings.mPackages.get(packageName);
final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
final int status = (int)(packedStatus >> 32);
if (status != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
return ri;
}
}
}
ri = new ResolveInfo(mResolveInfo);
ri.activityInfo = new ActivityInfo(ri.activityInfo);
ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
// If all of the options come from the same package, show the application's
// label and icon instead of the generic resolver's.
// Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
// and then throw away the ResolveInfo itself, meaning that the caller loses
// the resolvePackageName. Therefore the activityInfo.labelRes above provides
// a fallback for this case; we only set the target package's resources on
// the ResolveInfo, not the ActivityInfo.
final String intentPackage = intent.getPackage();
if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
ri.resolvePackageName = intentPackage;
if (userNeedsBadging(userId)) {
ri.noResourceId = true;
} else {
ri.icon = appi.icon;
}
ri.iconResourceId = appi.icon;
ri.labelRes = appi.labelRes;
}
ri.activityInfo.applicationInfo = new ApplicationInfo(
ri.activityInfo.applicationInfo);
if (userId != 0) {
ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
}
// Make sure that the resolver is displayable in car mode
if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
return ri;
}
}
return null;
}
其中chooseBestActivity為什么會有多個ResolveInfo ?,舉個例子,我們在調用系統(tǒng)分享的時候經常會彈窗分享到哪里,因為是通過隱式啟動調用的,就可能會存在多個符合規(guī)則的分享頁面供用戶選擇

以上關于
ResolveInfo只是個人看法,還存在疑問,如有異議請及時指正,感激不敬~~
Launcher 啟動APP
當用戶點擊圖標的時候,就相當于LauncherActivity去啟動另外一個Activity,關于Activity的啟動這里稍微解釋一下,主要流程如下

在Activity啟動流程中,有個關鍵的點是當需要啟動的Activity的進程還沒有啟動需要通過
Zygote去fork一個新的進程,移步AMS Activity啟動源碼
###ActivityManagerService.java
@Override
public void startProcess(String processName, ApplicationInfo info,
boolean knownToBeDead, String hostingType, ComponentName hostingName) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "startProcess:"
+ processName);
}
synchronized (ActivityManagerService.this) {
startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
new HostingRecord(hostingType, hostingName),
false /* allowWhileBooting */, false /* isolated */,
true /* keepIfLarge */);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
###ProcessList.java
@GuardedBy("mService")
final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
String abiOverride) {
return startProcessLocked(app, hostingRecord,
false /* disableHiddenApiChecks */, false /* mountExtStorageFull */, abiOverride);
}
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
long startTime) {
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
app.processName);
checkSlow(startTime, "startProcess: asking zygote to start proc");
final Process.ProcessStartResult startResult;
if (hostingRecord.usesWebviewZygote()) {
startResult = startWebView(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
} else if (hostingRecord.usesAppZygote()) {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
startResult = appZygote.getProcess().start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*useUsapPool=*/ false,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
return startResult;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
###Process.java
public static ProcessStartResult start(@NonNull final String processClass,
@Nullable final String niceName,
int uid, int gid, @Nullable int[] gids,
int runtimeFlags,
int mountExternal,
int targetSdkVersion,
@Nullable String seInfo,
@NonNull String abi,
@Nullable String instructionSet,
@Nullable String appDataDir,
@Nullable String invokeWith,
@Nullable String packageName,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
/*useUsapPool=*/ true, zygoteArgs);
}
#ZygoteProcess.java
private Process.ProcessStartResult startViaZygote(@NonNull final String processClass,
@Nullable final String niceName,
final int uid, final int gid,
@Nullable final int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
@Nullable String seInfo,
@NonNull String abi,
@Nullable String instructionSet,
@Nullable String appDataDir,
@Nullable String invokeWith,
boolean startChildZygote,
@Nullable String packageName,
boolean useUsapPool,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<>();
// --runtime-args, --setuid=, --setgid=,
// and --setgroups= must go first
argsForZygote.add("--runtime-args");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
argsForZygote.add("--runtime-flags=" + runtimeFlags);
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
argsForZygote.add("--mount-external-default");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
argsForZygote.add("--mount-external-read");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
argsForZygote.add("--mount-external-write");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) {
argsForZygote.add("--mount-external-full");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
argsForZygote.add("--mount-external-installer");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_LEGACY) {
argsForZygote.add("--mount-external-legacy");
}
...
synchronized(mLock) {
// The USAP pool can not be used if the application will not use the systems graphics
// driver. If that driver is requested use the Zygote application start path.
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
useUsapPool,
argsForZygote);
}
}
@GuardedBy("mLock")
private void attemptConnectionToPrimaryZygote() throws IOException {
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
primaryZygoteState =
ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);
maybeSetApiBlacklistExemptions(primaryZygoteState, false);
maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);
}
}
ActivityManagerService.startProcess跳轉到ProcessList.startProcess,再經過Process.start跳轉到ZygoteProcess.startViaZygote的,最終在attemptConnectionToPrimaryZygote通過socket通信的方式讓Zygote進程fork出一個新的進程,并根據傳遞的”android.app.ActivityThread”字符串,反射出該對象并執(zhí)行ActivityThread的main方法對其進行初始化.
Launcher的分析到此先告一段落,如若想了解詳細的Activity啟動流程,點擊傳送門->Activity啟動流程

ClassLoader
JVM類加載機制
根(Bootstrap)類加載器:該加載器沒有父加載器。它負責加載虛擬機的核心類庫,如java.lang.*等。例如java.lang.Object就是由根類加載器加載的。根類加載器從系統(tǒng)屬性sun.boot.class.path所指定的目錄中加載類庫。根類加載器的實現依賴于底層操作系統(tǒng),屬于虛擬機的實現的一部分,它并沒有繼承java.lang.ClassLoader類。
擴展(Extension)類加載器:它的父加載器為根類加載器。它從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫,或者從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫,如果把用戶創(chuàng)建的JAR文件放在這個目錄下,也會自動由擴展類加載器加載。擴展類加載器是純Java類,是java.lang.ClassLoader類的子類。
系統(tǒng)(System)類加載器:也稱為應用類加載器,它的父加載器為擴展類加載器。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中加載類,它是用戶自定義的類加載器的默認父加載器。系統(tǒng)類加載器是純Java類,是java.lang.ClassLoader類的子類。
ClassLoader的雙親委托模式:classloader 按級別分為三個級別:最上級 : bootstrap classLoader(根類加載器) ; 中間級:extension classLoader (擴展類加載器) 最低級 app classLoader(應用類加載器)
雙親委托的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父加載器去完成,每一個層次的加載器都是如此,因此所有的類加載請求都會傳給頂層的啟動類加載器,只有當父加載器反饋自己無法完成該加載請求(該加載器的搜索范圍中沒有找到對應的類)時,子加載器才會嘗試自己去加載,這樣可以避免重復以及無效加載

DVM類加載機制
主要是由BaseDexClassLoader的兩個子類 PathClassLoader、DexClassLoader 來完成。繼承自Java的ClassLoader。
PathClassLoader :用來加載系統(tǒng)類和應用類。只能加載已安裝的apk。
DexClassLoader :用來加載jar、apk、dex文件。從SD卡中加載未安裝的apk,插件化的主要加載實現

ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
//委托雙親加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//從pathList的Element數組中找類,找不到就報ClassNotFoundException
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
BaseDexClassLoader的構造方法,只是用這些參數,創(chuàng)建了一個DexPathList的實例,DexPathList 使用 Element 數組存儲了所有的 dex 信息,在 dex 被存到 Element 數組后,所有的類都會在 Element 數組中尋找,不會再次從文件中加載,此處可作用于熱修復以及下文介紹的Multidex合并
DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions,definingContext);
...
}
DexPathList 通過 makeDexElements 得到了一個 Element[]類型的 dexElements對象數組,里面存放了app的所有dex相關信息
PathClassLoader.java
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
DexClassLoader.java
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
PathClassLoader和DexClassLoader從上面看來只有一個參數的區(qū)分,DexClassLoader多傳了一個optimizedDirectory路徑,這個是用來緩存我們需要加載的dex文件的,其實也能猜想到,PathClassLoader用來加載安裝過的APK,那么這個Dex的緩存路徑其實已經是已知的,但是DexClassLoader加載的是沒有安裝過的APK所以需要指定Dex的緩存路徑,我們再跟蹤一下DexFile的源碼:
DexFile .java
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
DexClassLoader.java
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
...
const DexFile* dex_file;
if (outputName.c_str() == NULL) {// 如果outputName為空,則dex_file由sourceName確定
dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum);
} else {// 如果outputName不為空,則在outputName目錄中去尋找dex_file
std::string oat_location(outputName.c_str());
dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);
}
...
return static_cast<jint>(reinterpret_cast<uintptr_t>(dex_file));
}
const DexFile* ClassLinker::FindOrCreateOatFileForDexLocation(const std::string& dex_location,uint32_t dex_location_checksum,const std::string& oat_location) {
WriterMutexLock mu(Thread::Current(), dex_lock_); // 互鎖
return FindOrCreateOatFileForDexLocationLocked(dex_location, dex_location_checksum, oat_location);
}
const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,uint32_t dex_location_checksum,const std::string& oat_location) {
const DexFile* dex_file = FindDexFileInOatLocation(dex_location,dex_location_checksum,oat_location);
if (dex_file != NULL) {
// 如果順利打開,則返回
return dex_file;
}
const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,!Runtime::Current()->IsCompiler());
if (oat_file == NULL) {
return NULL;
}
const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
if (oat_dex_file == NULL) {
return NULL;
}
const DexFile* result = oat_dex_file->OpenDexFile();
return result;
}
在 DexFile 的構造方法中,調用了 openDexFile 去生成一個 mCookie,可以看到,不管是哪個類型的ClassLoader,最終都會調用 native 方法 openDexFileNative 來實現具體的加載邏輯。判斷傳入的 outputName 是否為空,分別執(zhí)行不同的方法
1.當 outputName 不為空時【DexClassLoader】
執(zhí)行FindOrCreateOatFileForDexLocation函數,通過 outputName拿到 oat_location ,然后嘗試調用 FindDexFileInOatLocation 從 oat_location 中尋找到 dex ,這就是我們經常用到到熱修復的原理了,通過在sd卡中存放新的補丁dex/jar/apk替代舊的,來實現更新。
2.當 outputName 為空時【PathClassLoader】
執(zhí)行 FindDexFileInOatFileFromDexLocation 函數,從 dex_location 中拿到 dex 文件,這個 dex_location 也就是 BaseDexClassLoader 的 dexPath 參數中分割出來的某個存放文件的路徑。在 Android 中,系統(tǒng)使用 PathClassLoader 來加載apk中的dex存放到Element數組中,因此apk中的classes.dex都是通過它來加載的。
到這里我們對經常接觸的PathClassLoader、DexClassLoader、BaseDexClassLoader、都有了一定程度的了解,現在思考一個問題我們在上文通過Launcher啟動一個APP,那肯定是通過PathClassLoader來加載所有的Dex,我有點好奇這個PathClassLoader是什么時候被加載的呢?我們繼續(xù)跟蹤源碼,我們現在ClassLoader的構造函數中打個斷點,然后Debug APP

看一下調用鏈

這個結果一幕了然,接著上文說到
Zygotefork出我們的進程之后,走到ZygoteInit.java中的main方法
@UnsupportedAppUsage
public static void main(String argv[]) {
ZygoteServer zygoteServer = null;
/
Runnable caller;
try {
...
zygoteServer = new ZygoteServer(isPrimaryZygote);
...
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
throw ex;
} finally {
if (zygoteServer != null) {
zygoteServer.closeServerSocket();
}
}
// We're in the child process and have exited the select loop. Proceed to execute the
// command.
if (caller != null) {
caller.run();
}
}
通過zygoteServer.runSelectLoop拿到caller,這是一個MethodAndArgsCaller,這里面有個反射方法
static class MethodAndArgsCaller implements Runnable {
/** method to call */
private final Method mMethod;
/** argument array */
private final String[] mArgs;
public MethodAndArgsCaller(Method method, String[] args) {
mMethod = method;
mArgs = args;
}
public void run() {
try {
mMethod.invoke(null, new Object[] { mArgs });
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(ex);
}
}
}
通過這個方法跳轉到我們熟悉的ActivityThread的main,這里看到一些眼熟的東西,創(chuàng)建主線程的Looper,通過H綁定Application,創(chuàng)建ContextImpl,創(chuàng)建ApplicationLoaders,這里面會創(chuàng)建parentCloassLoader,也就是BootClassLoader

將parentCloassLoader傳遞給ClassLoaderFactory, 查看ClassLoaderFactory.createClassLoader
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName,
List<ClassLoader> sharedLibraries) {
ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
? null
: sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent,
arrayOfSharedLibraries);
}
throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}
嘿嘿,是不是看見我們要找的PathClassLoader了,到這里關于ClassLoader差不多也介紹完了~

Multidex分析
Multidex的由來:Android系統(tǒng)在安裝應用時會將dex文件轉換成odex,odex相當于從安裝包中將dex解壓出來同時在文件格式上有一定優(yōu)化執(zhí)行效率會比dex高,這個過程稱之為DexOpt。在早期的Android系統(tǒng)中,DexOpt有一個問題,DexOpt會把dex中所有類的方法id檢索起來,存在一個鏈表結構里面,但是這個鏈表的長度是用一個short類型來保存的,short為兩個byte也就是16位,2的16次方即65536,導致了方法id的數目不能夠超過65536個。隨著Android的迅速發(fā)展一個APP所有的方法數肯定會超過65536,所以google推出了Multidex方案去解決這個問題。
使用google的方案:
defaultConfig {
//開啟multidex
multiDexEnabled true
}
dependencies {
//multidex庫依賴
implementation 'com.android.support:multidex:1.0.3'
}
public class MineApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//Multidex合并
MultiDex.install(base);
}
}
Multidex只在API 19及之前的低版本設備才存在這個問題,隨著后續(xù)高版本的覆蓋這類機型會越來越少,所以這里不做Multidex源碼分析,有興趣的話可以參考Multidex源碼分析,我們之前講到BaseDexClassLoader中會將dex 文件解析出來放在pathList中,Multidex.install的主要步驟:
1.反射獲取ClassLoader中的pathList字段
2.反射調用DexPathList對象中的makeDexElements方法,將剛剛提取出來的zip文件包裝成Element對象
3.將包裝成的Element對象擴展到DexPathList中的dexElements數組字段里
4.makeDexElements中有dexopt的操作,是一個耗時的過程,產物是一個優(yōu)化過的odex文件
google方案碰到的問題
黑屏、假死(ANR):隨著APP越來越大,最終分包會得到很多dex,而且odex是一個非常耗時的操作,所以假死是經常碰到的問題
LinearAlloc限制:dexopt使用LinearAlloc來存儲應用的方法信息。Android 2.2和2.3的緩沖區(qū)只有5MB,Android 4.x提高到了8MB或16MB。當方法數量過多導致超出緩沖區(qū)大小時,會造成dexopt崩潰,由于此類機型占比非常低這個問題也可以認為它不是個問題
google方案優(yōu)化之一:異步進程加載Multidex
另起一個多進程的Activity來加載Multidex,同時讓主進程處于休眠狀態(tài),加載進程加載完畢之后自行關閉,主進程在進行后續(xù)操作,直接擼代碼吧
MineApplication .java
public class MineApplication extends Application {
private final String TAG = getClass().getSimpleName();
private File multidexFlagFile = null;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//主進程并且vm不支持多dex的情況下才使用 Multidex
if (ProcessUtil.isMainAppProcess(base) && MultidexUtils.supportMultidex()) {
loadMultiDex(base);
}
}
private void loadMultiDex(final Context context) {
createMultidexFlagFile(context);
//啟動多進程去加載MultiDex
Intent intent = new Intent(context, LoadMultidexActivity.class);
intent.putExtra(LoadMultidexActivity.MULTIDEX_FLAG_FILE, multidexFlagFile.getPath());
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
//阻塞當前線程,在Application中是不會出現anr的
while (multidexFlagFile.exists()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long startTime = System.currentTimeMillis();
MultiDex.install(context);
Log.e(TAG, TAG + " const:" + (System.currentTimeMillis() - startTime));
}
private void createMultidexFlagFile(Context context) {
try {
multidexFlagFile = new File(context.getCacheDir().getAbsolutePath(), "multidex_flag.tmp");
if (!multidexFlagFile.exists()) {
Log.d(TAG, "crate multidex flag file success");
multidexFlagFile.createNewFile();
}
} catch (Throwable th) {
th.printStackTrace();
}
}
}
Application主要邏輯
1.判斷時是否主進程,因為開啟了多進程加載而我們只需要在主進程進行加載
2.判斷是否需要進行Multidex,此判斷copy Multidex.install()源碼
3.創(chuàng)建臨時文件充當Multidex.install加載完成的flag,當加載進程加載完畢時會將此文件刪除
4.啟動LoadMultidexActivity
5.阻塞Application,直到文件flag被刪除
6.重新調用Multidex.install,多進程ClassLoader不同,此步驟只是為了替換ClassLoader,由于dexopt等耗時操作都在加載進程中完成了,所以這個步驟會非???/p>
LoadMultidexActivity.class
public class LoadMultidexActivity extends Activity {
public static final String MULTIDEX_FLAG_FILE = "MULTIDEX_FLAG_FILE";
String path = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_load_multidex);
path = getIntent().getStringExtra(MULTIDEX_FLAG_FILE);
new Thread(new Runnable() {
@Override
public void run() {
long time = System.currentTimeMillis();
MultiDex.install(LoadMultidexActivity.this);
try {
//模擬加載耗時
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("MineApplication", "LoadMultidexActivity const:" + (System.currentTimeMillis() - time));
File file = new File(path);
if (file.exists()) {
file.delete();
Log.e("MineApplication", "delete flag file");
}
finish();
}
}).start();
}
@Override
public void onBackPressed() {
//不響應返回鍵
}
}
1.另開一條線程去處理Multidex.install防止ANR
2.不響應返回鍵,防止用戶關閉該Activity
3.處理完刪除flag文件同時關閉Activity
源碼Demo傳送門
此方案碰到的問題:ClassNotFoundException,在Multidex.install合并之前APP會先啟動一個主Dex,假如LoadMultidexActivity不在主Dex,ClassLoader.findClass就會拋出這個問題,可以通過配置multiDexKeepFile或者multiDexKeepProguard解決,也就是指定某些類放入主Dex
defaultConfig {
multiDexEnabled true
multiDexKeepFile file('maindexlist.keep')
}
maindexlist.keep 文件內容
com/code/multidexinstall/LoadMultidexActivity.class
com/code/multidexinstall/LoadMultidexActivity$1.class
對于此方案有些人會懷疑Application被阻塞時如果有廣播進來會不會導致ANR,我也寫了個demo去驗證這個問題,結果表明這個問題時不存在的,廣播確實能收到,但是時在Multidex.install之后才打印了相關日志,斷點一下廣播的接受流程

廣播也是通過Loop然后通過H轉發(fā)到ActivityThread中,由于此時主線程處于休眠狀態(tài)時被阻塞的,廣播在阻塞時是進不來的。
google方案優(yōu)化之二:腳本自定義分包,自定義Multidex加載邏輯
這種方案涉及到的點比較多,要求較高,主要邏輯為
1:編寫腳本干預分包流程將部分一級頁面打入主dex
2:將Multidex.install流程另起service去操作,Application中不進行等待直接跳進主界面,由于主界面的Activity都在maindex中所以不會出現ClassNotFoundException
3.防止在Multidex.install完成之前用戶點擊跳轉不在maindex中的頁面,Hook Instrumentation.execStartActivity(和插件化異曲同工),展示loading等待Multidex.install完成在做跳入
具體參考:https://tech.meituan.com/2015/06/15/mt-android-auto-split-dex.html

本文參考以下內容:https://blog.csdn.net/black_dreamer/article/details/81665728
https://blog.csdn.net/csdn_aiyang/article/details/76199823
http://www.itdecent.cn/p/a4e68316d8eb
http://gityuan.com/2017/03/19/android-classloader/
https://blog.csdn.net/u012267215/article/details/91406211
http://www.itdecent.cn/p/e164ee033928
https://tech.meituan.com/2015/06/15/mt-android-auto-split-dex.html