Android应用开发:深入理解Android Instant Run运行机制
安安 2017-11-14 来源 :网络 阅读 2381 评论 0

摘要:本篇Android应用开发教程将为大家讲解Android编程的知识点,看完这篇文章会让你对Android编程的知识点有更加清晰的理解和运用。

本篇Android应用开发教程将为大家讲解Android编程的知识点,看完这篇文章会让你对Android编程的知识点有更加清晰的理解和运用。

 

Instant Run

Instant Run,是Android studio2.0新增的一个运行机制,在你编码开发、测试或debug的时候,它都能显著减少你对当前应用的构建和部署的时间。通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果。而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果。

传统的代码修改及编译部署流程

传统的代码修改及编译流程如下:构建整个apk → 部署app → app重启 → 重启Activity

 Android应用开发:深入理解Android Instant Run运行机制

Instant Run编译和部署流程

Instant Run构建项目的流程:构建修改的部分 → 部署修改的dex或资源 → 热部署,温部署,冷部署

热拔插,温拔插,冷拔插

热拔插:代码改变被应用、投射到APP上,不需要重启应用,不需要重建当前activity。
场景:适用于多数的简单改变(包括一些方法实现的修改,或者变量值修改)
**温拔插:**activity需要被重启才能看到所需更改。
场景:典型的情况是代码修改涉及到了资源文件,即resources。
**冷拔插:**app需要被重启(但是仍然不需要重新安装)
场景:任何涉及结构性变化的,比如:修改了继承规则、修改了方法签名等。

首次运行Instant Run,Gradle执行过程

一个新的App Server类会被注入到App中,与Bytecode instrumentation协同监控代码的变化。

同时会有一个新的Application类,它注入了一个自定义类加载器(Class Loader),同时该Application类会启动我们所需的新注入的App Server。于是,Manifest会被修改来确保我们的应用能使用这个新的Application类。(这里不必担心自己继承定义了Application类,Instant Run添加的这个新Application类会代理我们自定义的Application类)

至此,Instant Run已经可以跑起来了,在我们使用的时候,它会通过决策,合理运用冷温热拔插来协助我们大量地缩短构建程序的时间。

在Instant Run运行之前,Android Studio会检查是否能连接到App Server中。并且确保这个App Server是Android Studio所需要的。这同样能确保该应用正处在前台。

热拔插

 Android应用开发:深入理解Android Instant Run运行机制

Android Studio monitors: 运行着Gradle任务来生成增量.dex文件(这个dex文件是对应着开发中的修改类) Android Studio会提取这些.dex文件发送到App Server,然后部署到App(Gradle修改class的原理,请戳链接)。

App Server会不断监听是否需要重写类文件,如果需要,任务会被立马执行。新的更改便能立即被响应。我们可以通过打断点的方式来查看。

温拔插

温拔插需要重启Activity,因为资源文件是在Activity创建时加载,所以必须重启Activity来重载资源文件。

目前来说,任何资源文件的修改都会导致重新打包再发送到APP。但是,google的开发团队正在致力于开发一个增量包,这个增量包只会包装修改过的资源文件并能部署到当前APP上。

所以温拔插实际上只能应对少数的情况,它并不能应付应用在架构、结构上的变化。

注:温拔插涉及到的资源文件修改,在manifest上是无效的(这里的无效是指不会启动Instant Run),因为,manifest的值是在APK安装的时候被读取,所以想要manifest下资源的修改生效,还需要触发一个完整的应用构建和部署。

冷拔插

应用部署的时候,会把工程拆分成十个部分,每部分都拥有自己的.dex文件,然后所有的类会根据包名被分配给相应的.dex文件。当冷拔插开启时,修改过的类所对应的.dex文件,会重组生成新的.dex文件,然后再部署到设备上。

之所以能这么做,是依赖于Android的ART模式,它能允许加载多个.dex文件。ART模式在android4.4(API-19)中加入,但是Dalvik依然是首选,到了android5.0(API-21),ART模式才成为系统默认首选,所以Instant Run只能运行在API-21及其以上版本。

使用Instant Run一些注意点

Instant Run是被Android Studio控制的。所以我们只能通过IDE来启动它,如果通过设备来启动应用,Instant Run会出现异常情况。在使用Instant Run来启动Android app的时候,应注意以下几点:

1. 如果应用的minSdkVersion小于21,可能多数的Instant Run功能会挂掉,这里提供一个解决方法,通过product flavor建立一个minSdkVersion大于21的新分支,用来debug。

2. Instant Run目前只能在主进程里运行,如果应用是多进程的,类似微信,把webView抽出来单独一个进程,那热、温拔插会被降级为冷拔插。

3. 在Windows下,Windows Defender Real-Time Protection可能会导致Instant Run挂掉,可用通过添加白名单列表解决。

4. 暂时不支持Jack compiler,Instrumentation Tests,或者同时部署到多台设备。

结合Demo深度理解

为了方便大家的理解,我们新建一个项目,里面不写任何的逻辑功能,只对application做一个修改:

 Android应用开发:深入理解Android Instant Run运行机制

首先,我们先反编译一下APK的构成,使用的工具:d2j-dex2jar 和jd-gui。

 Android应用开发:深入理解Android Instant Run运行机制

我们要看的启动的信息就在这个instant-run.zip文件里面,解压instant-run.zip,我们会发现,我们真正的业务代码都在这里。

Android应用开发:深入理解Android Instant Run运行机制 

从instant-run文件中我们猜想是BootstrapApplication替换了我们的application,Instant-Run代码作为一个宿主程序,将app作为资源dex加载起来。

那么InstantRun是怎么把业务代码运行起来的呢?

Instant Run如何启动app

按照我们上面对instant-run运行机制的猜想,我们首先看一下appliaction的分析attachBaseContext和onCreate方法。

attachBaseContext()

protected void attachBaseContext(Context context) {if (!AppInfo.usingApkSplits) {

String apkFile = context.getApplicationInfo().sourceDir;long apkModified = apkFile != null ? new File(apkFile)

.lastModified() : 0L;

createResources(apkModified);

setupClassLoaders(context, context.getCacheDir().getPath(),

apkModified);

}

createRealApplication();super.attachBaseContext(context);if (this.realApplication != null) {try {

Method attachBaseContext = ContextWrapper.class

.getDeclaredMethod("attachBaseContext",new Class[] { Context.class });

attachBaseContext.setAccessible(true);

attachBaseContext.invoke(this.realApplication,new Object[] { context });

} catch (Exception e) {throw new IllegalStateException(e);

}

}

}

我们依次需要关注的方法有:

createResources → setupClassLoaders → createRealApplication → 调用realApplication的attachBaseContext方法

createResources()

private void createResources(long apkModified) {

FileManager.checkInbox();

File file = FileManager.getExternalResourceFile();this.externalResourcePath = (file != null ? file.getPath() : null);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Resource override is "

+ this.externalResourcePath);

}if (file != null) {try {long resourceModified = file.lastModified();if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Resource patch last modified: "

+ resourceModified);

Log.v("InstantRun", "APK last modified: " + apkModified

+ " "

+ (apkModified > resourceModified ? ">" : "<")

+ " resource patch");

}if ((apkModified == 0L) || (resourceModified <= apkModified)) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Ignoring resource file, older than APK");

}this.externalResourcePath = null;

}

} catch (Throwable t) {

Log.e("InstantRun", "Failed to check patch timestamps", t);

}

}

}

说明:该方法主要是判断资源resource.ap_是否改变,然后保存resource.ap_的路径到externalResourcePath中。

setupClassLoaders()

private static void setupClassLoaders(Context context, String codeCacheDir,long apkModified) {

List dexList = FileManager.getDexList(context, apkModified);

Class server = Server.class;

Class patcher = MonkeyPatcher.class;if (!dexList.isEmpty()) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Bootstrapping class loader with dex list "

+ join('\n', dexList));

}

ClassLoader classLoader = BootstrapApplication.class

.getClassLoader();

String nativeLibraryPath;try {

nativeLibraryPath = (String) classLoader.getClass()

.getMethod("getLdLibraryPath", new Class[0])

.invoke(classLoader, new Object[0]);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Native library path: "

+ nativeLibraryPath);

}

} catch (Throwable t) {

Log.e("InstantRun", "Failed to determine native library path "

+ t.getMessage());

nativeLibraryPath = FileManager.getNativeLibraryFolder()

.getPath();

}

IncrementalClassLoader.inject(classLoader, nativeLibraryPath,

codeCacheDir, dexList);

}

}

说明,该方法是初始化一个ClassLoaders并调用IncrementalClassLoader。

IncrementalClassLoader的源码如下:

public class IncrementalClassLoader extends ClassLoader {public static final boolean DEBUG_CLASS_LOADING = false;private final DelegateClassLoader delegateClassLoader;public IncrementalClassLoader(ClassLoader original,

String nativeLibraryPath, String codeCacheDir, List dexes) {super(original.getParent());this.delegateClassLoader = createDelegateClassLoader(nativeLibraryPath,

codeCacheDir, dexes, original);

}public Class findClass(String className) throws ClassNotFoundException {try {return this.delegateClassLoader.findClass(className);

} catch (ClassNotFoundException e) {throw e;

}

}private static class DelegateClassLoader extends BaseDexClassLoader {private DelegateClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {super(dexPath, optimizedDirectory, libraryPath, parent);

}public Class findClass(String name) throws ClassNotFoundException {try {return super.findClass(name);

} catch (ClassNotFoundException e) {throw e;

}

}

}private static DelegateClassLoader createDelegateClassLoader(

String nativeLibraryPath, String codeCacheDir, List dexes,

ClassLoader original) {

String pathBuilder = createDexPath(dexes);return new DelegateClassLoader(pathBuilder, new File(codeCacheDir),

nativeLibraryPath, original);

}private static String createDexPath(List dexes) {

StringBuilder pathBuilder = new StringBuilder();boolean first = true;for (String dex : dexes) {if (first) {

first = false;

} else {

pathBuilder.append(File.pathSeparator);

}

pathBuilder.append(dex);

}if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Incremental dex path is "

+ BootstrapApplication.join('\n', dexes));

}return pathBuilder.toString();

}private static void setParent(ClassLoader classLoader, ClassLoader newParent) {try {

Field parent = ClassLoader.class.getDeclaredField("parent");

parent.setAccessible(true);

parent.set(classLoader, newParent);

} catch (IllegalArgumentException e) {throw new RuntimeException(e);

} catch (IllegalAccessException e) {throw new RuntimeException(e);

} catch (NoSuchFieldException e) {throw new RuntimeException(e);

}

}public static ClassLoader inject(ClassLoader classLoader,

String nativeLibraryPath, String codeCacheDir, List dexes) {

IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader(

classLoader, nativeLibraryPath, codeCacheDir, dexes);

setParent(classLoader, incrementalClassLoader);return incrementalClassLoader;

}

}

inject方法是用来设置classloader的父子顺序的,使用IncrementalClassLoader来加载dex。由于ClassLoader的双亲委托模式,也就是委托父类加载类,父类中找不到再在本ClassLoader中查找。

调用的效果图如下:

 Android应用开发:深入理解Android Instant Run运行机制

为了方便我们对委托父类加载机制的理解,我们可以做一个实验,在我们的application做一些Log。

@Overridepublic void onCreate() {super.onCreate();try{

Log.d(TAG,"###onCreate in myApplication");

String classLoaderName = getClassLoader().getClass().getName();

Log.d(TAG,"###onCreate in myApplication classLoaderName = "+classLoaderName);

String parentClassLoaderName = getClassLoader().getParent().getClass().getName();

Log.d(TAG,"###onCreate in myApplication parentClassLoaderName = "+parentClassLoaderName);

String pParentClassLoaderName = getClassLoader().getParent().getParent().getClass().getName();

Log.d(TAG,"###onCreate in myApplication pParentClassLoaderName = "+pParentClassLoaderName);

}catch (Exception e){

e.printStackTrace();

}

}

输出结果:

03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader

由此,我们知道,当前PathClassLoader委托IncrementalClassLoader加载dex。

我们继续对attachBaseContext()继续分析:

attachBaseContext.invoke(this.realApplication,new Object[] { context });

createRealApplication

private void createRealApplication() {if (AppInfo.applicationClass != null) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","About to create real application of class name = "

+ AppInfo.applicationClass);

}try {

Class realClass = (Class) Class

.forName(AppInfo.applicationClass);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Created delegate app class successfully : "

+ realClass + " with class loader "

+ realClass.getClassLoader());

}

Constructor constructor = realClass

.getConstructor(new Class[0]);this.realApplication = ((Application) constructor

.newInstance(new Object[0]));if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Created real app instance successfully :"

+ this.realApplication);

}

} catch (Exception e) {throw new IllegalStateException(e);

}

} else {this.realApplication = new Application();

}

}

该方法就是用classes.dex中的AppInfo类的applicationClass常量中保存的app真实的application。由例子的分析我们可以知道applicationClass就是com.xzh.demo.MyApplication。通过反射的方式,创建真是的realApplication。

看完attachBaseContext我们继续看BootstrapApplication();

BootstrapApplication()

我们首先看一下onCreate方法:

onCreate()

public void onCreate() {if (!AppInfo.usingApkSplits) {

MonkeyPatcher.monkeyPatchApplication(this, this,this.realApplication, this.externalResourcePath);

MonkeyPatcher.monkeyPatchExistingResources(this,this.externalResourcePath, null);

} else {

MonkeyPatcher.monkeyPatchApplication(this, this,this.realApplication, null);

}super.onCreate();if (AppInfo.applicationId != null) {try {boolean foundPackage = false;int pid = Process.myPid();

ActivityManager manager = (ActivityManager) getSystemService("activity");

List processes = manager

.getRunningAppProcesses();boolean startServer = false;if ((processes != null) && (processes.size() > 1)) {for (ActivityManager.RunningAppProcessInfo processInfo : processes) {if (AppInfo.applicationId

.equals(processInfo.processName)) {

foundPackage = true;if (processInfo.pid == pid) {

startServer = true;break;

}

}

}if ((!startServer) && (!foundPackage)) {

startServer = true;if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Multiprocess but didn't find process with package: starting server anyway");

}

}

} else {

startServer = true;

}if (startServer) {

Server.create(AppInfo.applicationId, this);

}

} catch (Throwable t) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Failed during multi process check", t);

}

Server.create(AppInfo.applicationId, this);

}

}if (this.realApplication != null) {this.realApplication.onCreate();

}

}

在onCreate()中我们需要注意以下方法:

monkeyPatchApplication → monkeyPatchExistingResources → Server启动 → 调用realApplication的onCreate方法

monkeyPatchApplication

public static void monkeyPatchApplication(Context context,

Application bootstrap, Application realApplication,

String externalResourceFile) {try {

Class activityThread = Class

.forName("android.app.ActivityThread");

Object currentActivityThread = getActivityThread(context,

activityThread);

Field mInitialApplication = activityThread

.getDeclaredField("mInitialApplication");

mInitialApplication.setAccessible(true);

Application initialApplication = (Application) mInitialApplication

.get(currentActivityThread);if ((realApplication != null) && (initialApplication == bootstrap)) {

mInitialApplication.set(currentActivityThread, realApplication);

}if (realApplication != null) {

Field mAllApplications = activityThread

.getDeclaredField("mAllApplications");

mAllApplications.setAccessible(true);

List allApplications = (List) mAllApplications

.get(currentActivityThread);for (int i = 0; i < allApplications.size(); i++) {if (allApplications.get(i) == bootstrap) {

allApplications.set(i, realApplication);

}

}

}

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);

Field mLoadedApk = null;try {

mLoadedApk = Application.class.getDeclaredField("mLoadedApk");

} catch (NoSuchFieldException e) {

}for (String fieldName : new String[] { "mPackages","mResourcePackages" }) {

Field field = activityThread.getDeclaredField(fieldName);

field.setAccessible(true);

Object value = field.get(currentActivityThread);for (Map.Entry> entry : ((Map>) value)

.entrySet()) {

Object loadedApk = ((WeakReference) entry.getValue()).get();if (loadedApk != null) {if (mApplication.get(loadedApk) == bootstrap) {if (realApplication != null) {

mApplication.set(loadedApk, realApplication);

}if (externalResourceFile != null) {

mResDir.set(loadedApk, externalResourceFile);

}if ((realApplication != null)

&& (mLoadedApk != null)) {

mLoadedApk.set(realApplication, loadedApk);

}

}

}

}

}

} catch (Throwable e) {throw new IllegalStateException(e);

}

}

说明:该方法的作用是替换所有当前app的application为realApplication。
替换的过程如下:

1.替换ActivityThread的mInitialApplication为realApplication
2.替换mAllApplications 中所有的Application为realApplication
3.替换ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application为realApplication。

monkeyPatchExistingResources

public static void monkeyPatchExistingResources(Context context,

String externalResourceFile, Collection activities) {if (externalResourceFile == null) {return;

}try {

AssetManager newAssetManager = (AssetManager) AssetManager.class

.getConstructor(new Class[0]).newInstance(new Object[0]);

Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", new Class[] { String.class });

mAddAssetPath.setAccessible(true);if (((Integer) mAddAssetPath.invoke(newAssetManager,new Object[] { externalResourceFile })).intValue() == 0) {throw new IllegalStateException("Could not create new AssetManager");

}

Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]);

mEnsureStringBlocks.setAccessible(true);

mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);if (activities != null) {for (Activity activity : activities) {

Resources resources = activity.getResources();try {

Field mAssets = Resources.class

.getDeclaredField("mAssets");

mAssets.setAccessible(true);

mAssets.set(resources, newAssetManager);

} catch (Throwable ignore) {

Field mResourcesImpl = Resources.class

.getDeclaredField("mResourcesImpl");

mResourcesImpl.setAccessible(true);

Object resourceImpl = mResourcesImpl.get(resources);

Field implAssets = resourceImpl.getClass()

.getDeclaredField("mAssets");

implAssets.setAccessible(true);

implAssets.set(resourceImpl, newAssetManager);

}

Resources.Theme theme = activity.getTheme();try {try {

Field ma = Resources.Theme.class

.getDeclaredField("mAssets");

ma.setAccessible(true);

ma.set(theme, newAssetManager);

} catch (NoSuchFieldException ignore) {

Field themeField = Resources.Theme.class

.getDeclaredField("mThemeImpl");

themeField.setAccessible(true);

Object impl = themeField.get(theme);

Field ma = impl.getClass().getDeclaredField("mAssets");

ma.setAccessible(true);

ma.set(impl, newAssetManager);

}

Field mt = ContextThemeWrapper.class

.getDeclaredField("mTheme");

mt.setAccessible(true);

mt.set(activity, null);

Method mtm = ContextThemeWrapper.class

.getDeclaredMethod("initializeTheme",new Class[0]);

mtm.setAccessible(true);

mtm.invoke(activity, new Object[0]);

Method mCreateTheme = AssetManager.class

.getDeclaredMethod("createTheme", new Class[0]);

mCreateTheme.setAccessible(true);

Object internalTheme = mCreateTheme.invoke(

newAssetManager, new Object[0]);

Field mTheme = Resources.Theme.class

.getDeclaredField("mTheme");

mTheme.setAccessible(true);

mTheme.set(theme, internalTheme);

} catch (Throwable e) {

Log.e("InstantRun","Failed to update existing theme for activity "

+ activity, e);

}

pruneResourceCaches(resources);

}

}

Collection> references;if (Build.VERSION.SDK_INT >= 19) {

Class resourcesManagerClass = Class

.forName("android.app.ResourcesManager");

Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance", new Class[0]);

mGetInstance.setAccessible(true);

Object resourcesManager = mGetInstance.invoke(null,new Object[0]);try {

Field fMActiveResources = resourcesManagerClass

.getDeclaredField("mActiveResources");

fMActiveResources.setAccessible(true);

ArrayMap> arrayMap = (ArrayMap) fMActiveResources

.get(resourcesManager);

references = arrayMap.values();

} catch (NoSuchFieldException ignore) {

Field mResourceReferences = resourcesManagerClass

.getDeclaredField("mResourceReferences");

mResourceReferences.setAccessible(true);

references = (Collection) mResourceReferences

.get(resourcesManager);

}

} else {

Class activityThread = Class

.forName("android.app.ActivityThread");

Field fMActiveResources = activityThread

.getDeclaredField("mActiveResources");

fMActiveResources.setAccessible(true);

Object thread = getActivityThread(context, activityThread);

HashMap> map = (HashMap) fMActiveResources

.get(thread);

references = map.values();

}for (WeakReference wr : references) {

Resources resources = (Resources) wr.get();if (resources != null) {try {

Field mAssets = Resources.class

.getDeclaredField("mAssets");

mAssets.setAccessible(true);

mAssets.set(resources, newAssetManager);

} catch (Throwable ignore) {

Field mResourcesImpl = Resources.class

.getDeclaredField("mResourcesImpl");

mResourcesImpl.setAccessible(true);

Object resourceImpl = mResourcesImpl.get(resources);

Field implAssets = resourceImpl.getClass()

.getDeclaredField("mAssets");

implAssets.setAccessible(true);

implAssets.set(resourceImpl, newAssetManager);

}

resources.updateConfiguration(resources.getConfiguration(),

resources.getDisplayMetrics());

}

}

} catch (Throwable e) {throw new IllegalStateException(e);

}

}

说明:该方法的作用是替换所有当前app的mAssets为newAssetManager。

monkeyPatchExistingResources的流程如下:

1.如果resource.ap_文件有改变,那么新建一个AssetManager对象newAssetManager,然后用newAssetManager对象替换所有当前Resource、Resource.Theme的mAssets成员变量。
2.如果当前的已经有Activity启动了,还需要替换所有Activity中mAssets成员变量

判断Server是否已经启动,如果没有启动,则启动Server。然后调用realApplication的onCreate方法代理realApplication的生命周期。

接下来我们分析下Server负责的热部署、温部署和冷部署等问题。

Server热部署、温部署和冷部署

首先重点关注一下Server的内部类SocketServerReplyThread。

SocketServerReplyThread

private class SocketServerReplyThread extends Thread {private final LocalSocket mSocket;

SocketServerReplyThread(LocalSocket socket) {this.mSocket = socket;

}public void run() {try {

DataInputStream input = new DataInputStream(this.mSocket.getInputStream());

DataOutputStream output = new DataOutputStream(this.mSocket.getOutputStream());try {

handle(input, output);

} finally {try {

input.close();

} catch (IOException ignore) {

}try {

output.close();

} catch (IOException ignore) {

}

}return;

} catch (IOException e) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Fatal error receiving messages", e);

}

}

}private void handle(DataInputStream input, DataOutputStream output)throws IOException {long magic = input.readLong();if (magic != 890269988L) {

Log.w("InstantRun","Unrecognized header format " + Long.toHexString(magic));return;

}int version = input.readInt();

output.writeInt(4);if (version != 4) {

Log.w("InstantRun","Mismatched protocol versions; app is using version 4 and tool is using version "

+ version);

} else {int message;for (;;) {

message = input.readInt();switch (message) {case 7:if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Received EOF from the IDE");

}return;case 2:boolean active = Restarter

.getForegroundActivity(Server.this.mApplication) != null;

output.writeBoolean(active);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Received Ping message from the IDE; returned active = "

+ active);

}break;case 3:

String path = input.readUTF();long size = FileManager.getFileSize(path);

output.writeLong(size);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Received path-exists(" + path

+ ") from the " + "IDE; returned size="

+ size);

}break;case 4:long begin = System.currentTimeMillis();

path = input.readUTF();byte[] checksum = FileManager.getCheckSum(path);if (checksum != null) {

output.writeInt(checksum.length);

output.write(checksum);if (Log.isLoggable("InstantRun", 2)) {long end = System.currentTimeMillis();

String hash = new BigInteger(1, checksum)

.toString(16);

Log.v("InstantRun", "Received checksum(" + path

+ ") from the " + "IDE: took "

+ (end - begin) + "ms to compute "

+ hash);

}

} else {

output.writeInt(0);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Received checksum(" + path

+ ") from the "

+ "IDE: returning ");

}

}break;case 5:if (!authenticate(input)) {return;

}

Activity activity = Restarter

.getForegroundActivity(Server.this.mApplication);if (activity != null) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Restarting activity per user request");

}

Restarter.restartActivityOnUiThread(activity);

}break;case 1:if (!authenticate(input)) {return;

}

List changes = ApplicationPatch

.read(input);if (changes != null) {boolean hasResources = Server.hasResources(changes);int updateMode = input.readInt();

updateMode = Server.this.handlePatches(changes,

hasResources, updateMode);boolean showToast = input.readBoolean();

output.writeBoolean(true);

Server.this.restart(updateMode, hasResources,

showToast);

}break;case 6:

String text = input.readUTF();

Activity foreground = Restarter

.getForegroundActivity(Server.this.mApplication);if (foreground != null) {

Restarter.showToast(foreground, text);

} else if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun","Couldn't show toast (no activity) : "

+ text);

}break;

}

}

}

}

}

说明:socket开启后,开始读取数据,当读到1时,获取代码变化的ApplicationPatch列表,然后调用handlePatches来处理代码的变化。

handlePatches

private int handlePatches(List changes,boolean hasResources, int updateMode) {if (hasResources) {

FileManager.startUpdate();

}for (ApplicationPatch change : changes) {

String path = change.getPath();if (path.endsWith(".dex")) {

handleColdSwapPatch(change);boolean canHotSwap = false;for (ApplicationPatch c : changes) {if (c.getPath().equals("classes.dex.3")) {

canHotSwap = true;break;

}

}if (!canHotSwap) {

updateMode = 3;

}

} else if (path.equals("classes.dex.3")) {

updateMode = handleHotSwapPatch(updateMode, change);

} else if (isResourcePath(path)) {

updateMode = handleResourcePatch(updateMode, change, path);

}

}if (hasResources) {

FileManager.finishUpdate(true);

}return updateMode;

}

说明:本方法主要通过判断Change的内容,来判断采用什么模式(热部署、温部署或冷部署)

· 如果后缀为“.dex”,冷部署处理handleColdSwapPatch

· 如果后缀为“classes.dex.3”,热部署处理handleHotSwapPatch

· 其他情况,温部署,处理资源handleResourcePatch

handleColdSwapPatch冷部署

private static void handleColdSwapPatch(ApplicationPatch patch) {if (patch.path.startsWith("slice-")) {

File file = FileManager.writeDexShard(patch.getBytes(), patch.path);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Received dex shard " + file);

}

}

}

说明:该方法把dex文件写到私有目录,等待整个app重启,重启之后,使用前面提到的IncrementalClassLoader加载dex即可。

handleHotSwapPatch热部署

private int handleHotSwapPatch(int updateMode, ApplicationPatch patch) {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Received incremental code patch");

}try {String dexFile = FileManager.writeTempDexFile(patch.getBytes());if (dexFile == null) {

Log.e("InstantRun", "No file to write the code to");return updateMode;

}if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Reading live code from " + dexFile);

}String nativeLibraryPath = FileManager.getNativeLibraryFolder()

.getPath();

DexClassLoader dexClassLoader = new DexClassLoader(dexFile,this.mApplication.getCacheDir().getPath(),

nativeLibraryPath, getClass().getClassLoader());

Class aClass = Class.forName("com.android.tools.fd.runtime.AppPatchesLoaderImpl", true,

dexClassLoader);try {if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Got the patcher class " + aClass);

}

PatchesLoader loader = (PatchesLoader) aClass.newInstance();if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Got the patcher instance " + loader);

}String[] getPatchedClasses = (String[]) aClass

.getDeclaredMethod("getPatchedClasses", new Class[0])

.invoke(loader, new Object[0]);if (Log.isLoggable("InstantRun", 2)) {

Log.v("InstantRun", "Got the list of classes ");for (String getPatchedClass : getPatchedClasses) {

Log.v("InstantRun", "class " + getPatchedClass);

}

}if (!loader.load()) {

updateMode = 3;

}

} catch (Exception e) {

Log.e("InstantRun", "Couldn't apply code changes", e);

e.printStackTrace();

updateMode = 3;

}

} catch (Throwable e) {

Log.e("InstantRun", "Couldn't apply code changes", e);

updateMode = 3;

}return updateMode;

}

说明:该方法将patch的dex文件写入到临时目录,然后使用DexClassLoader去加载dex。然后反射调用AppPatchesLoaderImpl类的load方法。

需要强调的是:AppPatchesLoaderImpl继承自抽象类AbstractPatchesLoaderImpl,并实现了抽象方法:getPatchedClasses。而AbstractPatchesLoaderImpl抽象类代码如下:

public abstract class AbstractPatchesLoaderImpl implements PatchesLoader {public abstract String[] getPatchedClasses();public boolean load() {try {for (String className : getPatchedClasses()) {

ClassLoader cl = getClass().getClassLoader();

Class aClass = cl.loadClass(className + "$override");

Object o = aClass.newInstance();

Class originalClass = cl.loadClass(className);

Field changeField = originalClass.getDeclaredField("$change");

changeField.setAccessible(true);

Object previous = changeField.get(null);if (previous != null) {

Field isObsolete = previous.getClass().getDeclaredField("$obsolete");if (isObsolete != null) {

isObsolete.set(null, Boolean.valueOf(true));

}

}

changeField.set(null, o);if ((Log.logging != null)

&& (Log.logging.isLoggable(Level.FINE))) {

Log.logging.log(Level.FINE, String.format("patched %s",new Object[] { className }));

}

}

} catch (Exception e) {if (Log.logging != null) {

Log.logging.log(Level.SEVERE, String.format("Exception while patching %s",new Object[] { "foo.bar" }), e);

}return false;

}return true;

}

}

Instant Run热部署原理

由上面的代码分析,我们对Instant Run的流程可以分析如下:

1,在第一次构建apk时,在每一个类中注入了一个$change的成员变量,它实现了IncrementalChange接口,并在每一个方法中,插入了一段类似的逻辑。

IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {localIncrementalChange.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[] { this,

... });return;

}

当$change不为空的时候,执行IncrementalChange方法。

2,当我们修改代码中方法的实现之后,点击InstantRun,它会生成对应的patch文件来记录你修改的内容。patch文件中的替换类是在所修改类名的后面追加$override,并实现IncrementalChange接口。

3,生成AppPatchesLoaderImpl类,继承自AbstractPatchesLoaderImpl,并实现getPatchedClasses方法,来记录哪些类被修改了。

4,调用load方法之后,根据getPatchedClasses返回的修改过的类的列表,去加载对应的override类,然后把原有类的change设置为对应的实现了IncrementalChange接口的$override类。

Instant Run运行机制总结

Instant Run运行机制主要涉及到热部署、温部署和冷部署,主要是在第一次运行,app运行时期,有代码修改时。

第一次编译

· 1.把Instant-Run.jar和instant-Run-bootstrap.jar打包到主dex中

· 2.替换AndroidManifest.xml中的application配置

· 3.使用asm工具,在每个类中添加$change,在每个方法前加逻辑

· 4.把源代码编译成dex,然后存放到压缩包instant-run.zip中

app运行时

· 1.获取更改后资源resource.ap_的路径

· 2.设置ClassLoader。setupClassLoader:

· 使用IncrementalClassLoader加载apk的代码,将原有的BootClassLoader → PathClassLoader改为BootClassLoader → IncrementalClassLoader → PathClassLoader继承关系。

· 3.createRealApplication:

· 创建apk真实的application

· 4.monkeyPatchApplication

· 反射替换ActivityThread中的各种Application成员变量

· 5.monkeyPatchExistingResource

· 反射替换所有存在的AssetManager对象

· 6.调用realApplication的onCreate方法

· 7.启动Server,Socket接收patch列表

有代码修改时

· 1.生成对应的$override类

· 2.生成AppPatchesLoaderImpl类,记录修改的类列表

· 3.打包成patch,通过socket传递给app

· 4.app的server接收到patch之后,分别按照handleColdSwapPatch、handleHotSwapPatch、handleResourcePatch等待对patch进行处理

· 5.restart使patch生效

在Android插件化、Android热修复、apk加壳/脱壳中借鉴了Instant Run运行机制,所以理解Instant Run运行机制对于向更深层次的研究是很有帮助的,对于我们自己书写框架也是有借鉴意义的。


希望这篇文章可以帮助到你。总之,同学们,你想要的职坐标Android频道都能找到!

本文由 @安安 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved