前言
本篇文章比较长,涉及到Launcher启动APP
、ClassLoader工作过程
以及Multidex解决方案
,可以静下心来看,整个看完一套流程很多东西自然就相通了~~
Launcher
首先 Launcher
这个东西我们不用把他想的太复杂,可以简单认为它就是一个桌面应用,也就是说它也是个Activity,拥有Activity同样的生命周期,系统启动的时候启动了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 mOriginalValues;
protected final IconResizer mIconResizer;
protected final LayoutInflater mInflater;
protected List 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 makeListItems() {
// Load all matching activities and sort correctly
List list = onQueryPackageManager(mIntent);
onSortResultList(list);
ArrayList result = new ArrayList(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 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
?,举个例子,我们在调用系统分享的时候经常会弹窗分享到哪里,因为是通过隐式启动调用的,就可能会存在多个符合规则的分享页面供用户选择
以上关于
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 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”字符串,反射出该对象并执行ActivityThread的main方法对其进行初始化.
Launcher的分析到此先告一段落,如若想了解详细的Activity启动流程,点击传送门->Activity启动流程
ClassLoader
JVM类加载机制
根(Bootstrap)类加载器
:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。
扩展(Extension)类加载器
:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
系统(System)类加载器
:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
ClassLoader的双亲委托模式
:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ; 中间级:extension classLoader (扩展类加载器) 最低级 app classLoader(应用类加载器)
双亲委托的工作过程
:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载,这样可以避免重复以及无效加载
DVM类加载机制
主要是由BaseDexClassLoader的两个子类 PathClassLoader、DexClassLoader 来完成。继承自Java的ClassLoader。
PathClassLoader
:用来加载系统类和应用类。只能加载已安装的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 suppressedExceptions = new ArrayList();
//从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的构造方法,只是用这些参数,创建了一个DexPathList的实例,DexPathList 使用 Element 数组存储了所有的 dex 信息,在 dex 被存到 Element 数组后,所有的类都会在 Element 数组中寻找,不会再次从文件中加载,此处可作用于热修复
以及下文介绍的Multidex
合并
DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
// 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
从上面看来只有一个参数的区分,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(reinterpret_cast(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 是否为空,分别执行不同的方法
1.当 outputName 不为空时【DexClassLoader】
执行FindOrCreateOatFileForDexLocation函数,通过 outputName拿到 oat_location ,然后尝试调用 FindDexFileInOatLocation 从 oat_location 中寻找到 dex ,这就是我们经常用到到热修复的原理了,通过在sd卡中存放新的补丁dex/jar/apk替代旧的,来实现更新。
2.当 outputName 为空时【PathClassLoader】
执行 FindDexFileInOatFileFromDexLocation 函数,从 dex_location 中拿到 dex 文件,这个 dex_location 也就是 BaseDexClassLoader 的 dexPath 参数中分割出来的某个存放文件的路径。在 Android 中,系统使用 PathClassLoader 来加载apk中的dex存放到Element数组中,因此apk中的classes.dex都是通过它来加载的。
到这里我们对经常接触的PathClassLoader
、DexClassLoader
、BaseDexClassLoader
、都有了一定程度的了解,现在思考一个问题我们在上文通过Launcher
启动一个APP
,那肯定是通过PathClassLoader
来加载所有的Dex,我有点好奇这个PathClassLoader
是什么时候被加载的呢?我们继续跟踪源码,我们现在ClassLoader
的构造函数中打个断点,然后Debug APP
看一下调用链
这个结果一幕了然,接着上文说到
Zygote
fork出我们的进程之后,走到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
,这里看到一些眼熟的东西,创建主线程的Looper
,通过H
绑定Application,创建ContextImpl
,创建ApplicationLoaders
,这里面会创建parentCloassLoader
,也就是BootClassLoader
将parentCloassLoader传递给ClassLoaderFactory
, 查看ClassLoaderFactory.createClassLoader
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName,
List 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系统在安装应用时会将dex文件转换成odex
,odex相当于从安装包中将dex解压出来同时在文件格式上有一定优化执行效率会比dex高,这个过程称之为DexOpt
。在早期的Android系统中,DexOpt有一个问题,DexOpt会把dex中所有类的方法id检索起来,存在一个链表结构里面,但是这个链表的长度是用一个short类型
来保存的,short为两个byte也就是16位,2的16次方即65536,导致了方法id的数目不能够超过65536个。随着Android的迅速发展一个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
及之前的低版本设备才存在这个问题,随着后续高版本的覆盖这类机型会越来越少,所以这里不做Multidex源码分析,有兴趣的话可以参考Multidex源码分析,我们之前讲到BaseDexClassLoader
中会将dex 文件解析出来放在pathList
中,Multidex.install
的主要步骤:
1.反射获取ClassLoader中的pathList字段
2.反射调用DexPathList对象中的makeDexElements方法,将刚刚提取出来的zip文件包装成Element对象
3.将包装成的Element对象扩展到DexPathList中的dexElements数组字段里
4.makeDexElements中有dexopt的操作,是一个耗时的过程,产物是一个优化过的odex文件
google方案碰到的问题
黑屏、假死(ANR)
:随着APP越来越大,最终分包会得到很多dex,而且odex是一个非常耗时的操作,所以假死是经常碰到的问题
LinearAlloc限制
:dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃,由于此类机型占比非常低这个问题也可以认为它不是个问题
google方案优化之一:异步进程加载Multidex
另起一个多进程的Activity来加载Multidex,同时让主进程处于休眠状态,加载进程加载完毕之后自行关闭,主进程在进行后续操作,直接撸代码吧
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.创建临时文件充当Multidex.install加载完成的flag,当加载进程加载完毕时会将此文件删除
4.启动LoadMultidexActivity
5.阻塞Application,直到文件flag被删除
6.重新调用Multidex.install,多进程ClassLoader不同,此步骤只是为了替换ClassLoader,由于dexopt等耗时操作都在加载进程中完成了,所以这个步骤会非常快
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转发到ActivityThread中,由于此时主线程处于休眠状态时被阻塞的,广播在阻塞时是进不来的。
google方案优化之二:脚本自定义分包,自定义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
https://www.jianshu.com/p/a4e68316d8eb
http://gityuan.com/2017/03/19/android-classloader/
https://blog.csdn.net/u012267215/article/details/91406211
https://www.jianshu.com/p/e164ee033928
https://tech.meituan.com/2015/06/15/mt-android-auto-split-dex.html