源码角度分析App启动

App启动点

ActivityThread.performLaunchActivity

一个APP是从桌面开始启动。其实也是启动一个新的ACTIVITY的过程。因此主要关注ActivityThread.performLaunchActivity

1
2
3
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
}

能够看出这个时候去创建了一个Application。因此LoadApkapk的启动点

LoadApk.makeApplication

1
2
3
4
5
6
7
if (mApplication != null) {
return mApplication;
}
...
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
  • 如果application已经存在,那么直接返回
  • 否则会创建ContextImpl,然后通过Instrumentation反射创建出Application

LoadApk的由来

ActivityRecordClient.packageInfo

在源码里可以看出LoadApk是在H Handler中创建的

1
2
3
4
5
6
7
8
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
}
  • 获取LoadApk

ActivityThread.getPackageInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}

...
//创建新的LoadApk
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
}
  • 如果app已经启动,那么按照包名从缓存中获取。这里的缓存都是弱引用
  • 如果是一个未启动的app,那么会创建新的LoadApk。然后放入缓存

LoadedApk中,能看到其用了系统classLoader

1
mClassLoader = ClassLoader.getSystemClassLoader();

ClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");

// String[] paths = classPath.split(":");
// URL[] urls = new URL[paths.length];
// for (int i = 0; i < paths.length; i++) {
// try {
// urls[i] = new URL("file://" + paths[i]);
// }
// catch (Exception ex) {
// ex.printStackTrace();
// }
// }
//
// return new java.net.URLClassLoader(urls, null);

// TODO Make this a java.net.URLClassLoader once we have those?
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
  • 最终会根据classPath,libraryPath以及BootClassLoader来创建PathClassLoader。其首个参数代表的就是apk中的classes.dex的路径
1
2
3
4
5
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
}
  • 这里会创建DexPathList
1
2
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);

DexPathList.makeDexElements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();

if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}

if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
  • 遍历所有文件,如果是目录,将目录存到Element数组中
    8 如果是.dex文件,那么尝试加载,然后存在Element数组中。因此最终.dex的信息都会在Element数组中,那么如果替换dex,那么原理上也是可以的。很多热修复框架就是基于这个原理
  • 上述这个是不分包的情况

MultiDexApplication

1
2
3
4
5
6
7
public class MultiDexApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
...
String apkPath = applicationInfo.sourceDir;
if (installedApk.contains(apkPath)) {
return;
}
installedApk.add(apkPath);
...
clearOldDexDir(context);

File dexDir = getDexDir(context, applicationInfo);
List<? extends File> files =
MultiDexExtractor.load(context, applicationInfo, dexDir, false);
installSecondaryDexes(loader, dexDir, files);
  • apk若已经安装过,那么直接返回
  • 清除旧的dex目录
  • 获取Dex目录
  • 加载dex文件
1
2
3
4
5
6
7
8
9
static List<? extends File> load(Context context, ApplicationInfo applicationInfo, File dexDir,
boolean forceReload) throws IOException {
...
//如果没有修改过,那么加载之前存在的dex
files = loadExistin
gExtractions(context, sourceApk, dexDir);
...
//解压新的dex
}
  • 加载旧的
1
2
3
4
5
6
7
private static List<ExtractedDex> loadExistingExtractions(
Context context, File sourceApk, File dexDir)
throws IOException {
...for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
...
}
}
  • 依次加载从2开始的classes

  • 加载新的

1
2
3
4
5
private static List<ExtractedDex> performExtractions(File sourceApk, File dexDir)
throws IOException {
...
prepareDexDir(dexDir, extractedFilePrefix);
}
  • 首先移除老的dex
  • 依次从classes2.dex加载文件

MultiDex.installSecondaryDexes

1
2
3
4
5
6
7
8
9
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);
}
}

按系统版本区分加载

v19以上

1
2
3
4
5
6
7
8
9
10
private static void install(ClassLoader loader,
List<? extends File> additionalClassPathEntries,
File optimizedDirectory)
...
Field pathListField = findField(loader, "pathList");
...
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
}
  • 通过反射找到pathList
  • 将加载出来的dex数组合并到原有的dexElements后面

v14 跟 v19一样,只是少了很多异常处理

v4 合并path, files, zips, dex。之后的版本有Element来存储这些信息

热修复可以基于这种dex的原理,来加载自己的dex文件。