从Instant run谈Android替换Application和动态加载机制
来源:互联网 发布:php 数组去除空值 编辑:程序博客网 时间:2024/06/15 17:34
背景
Instant run
Application入口
1
2
3
4
<application
name="com.aa.bb.MyApplication"
android:name="com.android.tools.fd.runtime.BootstrapApplication"
... />
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
// android.app.Application
public class Application extends ContextWrapper {
// ...
public application() {
super(null);
}
// ...
}
// android.content.ContextWrapper
public class ContextWrapper extends Context {
Context mBase;
// ...
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
// ...
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources()
{
return mBase.getResources();
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void attachBaseContext(Context context) {
if (!AppInfo.usingApkSplits) {
createResources(apkModified);
//新建一个ClassLoader并设置为原ClassLoader的parent
setupClassLoaders(context, context.getCacheDir().getPath(), apkModified);
}
//通过Manifest中我们的实际Application即MyApplication名反射生成对象
createRealApplication();
//调用attachBaseContext完成初始化
super.attachBaseContext(context);
if (realApplication != null) {
//反射调用实际Application的attachBaseContext方法
try {
Method attachBaseContext =
ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
attachBaseContext.setAccessible(true);
attachBaseContext.invoke(realApplication, context);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
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
//BootstrapApplication.setupClassLoaders
private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) {
// /data/data/package_name/files/instant-run/dex/目录下的dex列表
List<String> dexList = FileManager.getDexList(context, apkModified);
ClassLoader classLoader = BootstrapApplication.class.getClassLoader();
String nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath")
.invoke(classLoader);
IncrementalClassLoader.inject(
classLoader,
nativeLibraryPath,
codeCacheDir,
dexList);
}
}
//IncrementalClassLoader.inject
public static ClassLoader inject(
ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir,
List<String> dexes) {
//新建一个自定义ClassLoader,dexPath为参数中的dexList
IncrementalClassLoader incrementalClassLoader =
new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes);
//设置为原ClassLoader的parent
setParent(classLoader, incrementalClassLoader);
return incrementalClassLoader;
}
动态加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//``BaseDexClassLoader``的``findClass``
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
动态加载的两种方案
1
2
3
4
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
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
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// 获取userid信息
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
// 尝试获取缓存信息
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);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 缓存没有命中,直接new
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 省略。。更新缓存
return packageInfo;
}
}
Instant run的重启更新机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void onCreate() {
MonkeyPatcher.monkeyPatchApplication(
BootstrapApplication.this, BootstrapApplication.this,
realApplication, externalResourcePath);
MonkeyPatcher.monkeyPatchExistingResources(BootstrapApplication.this,
externalResourcePath, null);
super.onCreate();
...
//手机客户端app和Android Studio建立Socket通信,AS是客户端发消息,app//是服务端接收消息作出相应操作。Instant run的通信方式。不在本文范围内
Server.create(AppInfo.applicationId, BootstrapApplication.this);
if (realApplication != null) {
//还记得这个realApplication吗,我们app中实际的Application
realApplication.onCreate();
}
}
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public static void monkeyPatchApplication(@Nullable Context context,
@Nullable Application bootstrap,
@Nullable Application realApplication,
@Nullable String externalResourceFile) {
try {
// Find the ActivityThread instance for the current thread
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object currentActivityThread = getActivityThread(context, activityThread);
// Find the mInitialApplication field of the ActivityThread to the real application
Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");
mInitialApplication.setAccessible(true);
Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);
if (realApplication != null && initialApplication == bootstrap) {
//**2.替换掉ActivityThread.mInitialApplication**
mInitialApplication.set(currentActivityThread, realApplication);
}
// Replace all instance of the stub application in ActivityThread#mAllApplications with the
// real one
if (realApplication != null) {
Field mAllApplications = activityThread.getDeclaredField("mAllApplications");
mAllApplications.setAccessible(true);
List<Application> allApplications = (List<Application>) mAllApplications
.get(currentActivityThread);
for (int i = 0; i < allApplications.size(); i++) {
if (allApplications.get(i) == bootstrap) {
//**1.替换掉ActivityThread.mAllApplications**
allApplications.set(i, realApplication);
}
}
}
// Figure out how loaded APKs are stored.
// API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know.
Class<?> loadedApkClass;
try {
loadedApkClass = Class.forName("android.app.LoadedApk");
} catch (ClassNotFoundException e) {
loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
}
Field mApplication = loadedApkClass.getDeclaredField("mApplication");
mApplication.setAccessible(true);
Field mResDir = loadedApkClass.getDeclaredField("mResDir");
mResDir.setAccessible(true);
// 10 doesn't have this field, 14 does. Fortunately, there are not many Honeycomb devices
// floating around.
Field mLoadedApk = null;
try {
mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
} catch (NoSuchFieldException e) {
// According to testing, it's okay to ignore this.
}
// Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
// ActivityThread#mResourcePackages and do two things:
// - Replace the Application instance in its mApplication field with the real one
// - Replace mResDir to point to the external resource file instead of the .apk. This is
// used as the asset path for new Resources objects.
// - Set Application#mLoadedApk to the found LoadedApk instance
for (String fieldName : new String[]{"mPackages", "mResourcePackages"}) {
Field field = activityThread.getDeclaredField(fieldName);
field.setAccessible(true);
Object value = field.get(currentActivityThread);
for (Map.Entry<String, WeakReference<?>> entry :
((Map<String, WeakReference<?>>) value).entrySet()) {
Object loadedApk = entry.getValue().get();
if (loadedApk == null) {
continue;
}
if (mApplication.get(loadedApk) == bootstrap) {
if (realApplication != null) {
//**3.替换掉mApplication**
mApplication.set(loadedApk, realApplication);
}
if (externalResourceFile != null) {
//替换掉资源目录
mResDir.set(loadedApk, externalResourceFile);
}
if (realApplication != null && mLoadedApk != null) {
//**4.替换掉mLoadedApk**
mLoadedApk.set(realApplication, loadedApk);
}
}
}
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
1
2
3
baseContext.mPackageInfo.mApplication 代码3处
baseContext.mPackageInfo.mActivityThread.mInitialApplication 代码2处
baseContext.mPackageInfo.mActivityThread.mAllApplications 代码1处
Instant run 热更新机制
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
private int handleHotSwapPatch(int updateMode, @NonNull ApplicationPatch patch) {
try {
String dexFile = FileManager.writeTempDexFile(patch.getBytes());
String nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();
//新建一个ClassLoader,dexFile是刚更新的插件
DexClassLoader dexClassLoader = new DexClassLoader(dexFile,
mApplication.getCacheDir().getPath(), nativeLibraryPath,
getClass().getClassLoader());
// we should transform this process with an interface/impl
Class<?> aClass = Class.forName(
"com.android.tools.fd.runtime.AppPatchesLoaderImpl", true, dexClassLoader);
try {
PatchesLoader loader = (PatchesLoader) aClass.newInstance();
String[] getPatchedClasses = (String[]) aClass
.getDeclaredMethod("getPatchedClasses").invoke(loader);
//loader是PatchesLoader的一个实例,调用load方法加载插件
if (!loader.load()) {
updateMode = UPDATE_MODE_COLD_SWAP;
}
} catch (Exception e) {
updateMode = UPDATE_MODE_COLD_SWAP;
}
} catch (Throwable e) {
updateMode = UPDATE_MODE_COLD_SWAP;
}
return updateMode;
}
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
@Override
public boolean load() {
try {
//遍历已记录的所有修改的类
for (String className : getPatchedClasses()) {
ClassLoader cl = getClass().getClassLoader();
//我们刚才说的修改的类名后面都有$override
Class<?> aClass = cl.loadClass(className + "$override");
Object o = aClass.newInstance();
//1.**反射修改原类中的$change字段为修改后的值**
Class<?> originalClass = cl.loadClass(className);
Field changeField = originalClass.getDeclaredField("$change");
// force the field accessibility as the class might not be "visible"
// from this package.
changeField.setAccessible(true);
// If there was a previous change set, mark it as obsolete:
Object previous = changeField.get(null);
if (previous != null) {
Field isObsolete = previous.getClass().getDeclaredField("$obsolete");
if (isObsolete != null) {
isObsolete.set(null, true);
}
}
changeField.set(null, o);
}
} catch (Exception e) {
return false;
}
return true;
}
总结
0 0
- 从Instant run谈Android替换Application和动态加载机制
- 从Instant run谈Android替换Application和动态加载机制
- Instant-Run与Tinker中Application替换
- Android Instant Run
- Android Instant Run
- 从Instant-Run出发,谈谈Android上的热修复
- 从Instant-Run出发,谈谈Android上的热修复
- Android Studio Instant Run注意事项
- Android Studio之Instant Run
- Android Instant Run 热更新
- Android Studio中的Instant Run
- android studio Instant Run 问题
- Android Studio中Instant Run
- Android动态加载机制
- 关于热更新动态加载替换application相关1
- Instant Run
- Instant Run
- Android Studio 2.0 Instant Run问题
- 台大机器学习——可行性证明1
- jQuery中标签的事件
- 如何高效的构建nodejs项目
- 米斯特白帽培训讲义 漏洞篇 代码执行
- Java设计模式一——策略模式
- 从Instant run谈Android替换Application和动态加载机制
- 设计模式——单例模式
- Java8新特性
- 关于全局变量的初始化问题与野指针
- VLC SDK在VS2010中的配置及简单使用举例
- codeforces 749C Voting
- Git Tag 标签
- hackinglab.cn脚本关之十
- 如何让区块链连接外面的世界