深度理解Android InstantRun原理以及源碼分析

#深度理解Android InstantRun原理以及源碼分析

@Author 莫川

##Instant Run官方介紹

簡單介紹一下Instant Run,它是Android Studio2.0以后新增的一個運行機制,能夠顯著減少你第二次及以后的構建和部署時間。簡單通俗的解釋就是,當你在Android Studio中改了你的代碼,Instant Run可以很快的讓你看到你修改的效果。而在沒有Instant Run之前,你的一個小小的修改,都肯能需要幾十秒甚至更長的等待才能看到修改后的效果。

###傳統的代碼修改及編譯部署流程


*構建整個apk → 部署app → app重啟 → 重啟Activity*

而Instant Run則需要更少的時間。

###Instant Run編譯和部署流程


*只構建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署,冷部署*

####熱部署

Incremental code changes are applied and reflected in the app without needing to relaunch the app or even restart the current Activity. Can be used for most simple changes within method implementations.

方法內的簡單修改,無需重啟app和Activity

####溫部署

The Activity needs to be restarted before changes can be seen and used. Typically required for changes to resources.

app無需重啟,但是activity需要重啟,比如資源的修改。

####冷部署

The app is restarted (but still not reinstalled). Required for any structural changes such as to inheritance or method signatures.

app需要重啟,比如繼承關系的改變或方法的簽名變化等。

上述說這么多概念,估計大家對Instant Run應該有了大體的認知了。那么它的實現原理是什么呢?其實,在沒有看案例之前,我基本上可以猜測到Instant Run的思路,基于目前比較火的插件化框架,是比較容易理解Instant Run的。但Instant Run畢竟是Google官方的工具,具有很好的借鑒意義。

##Demo案例

新建一個簡單的android studio項目,新建自己的MyApplication,在AndroidManifest文件中設置:



AndroidManifest

首先,我們先反編譯一下APK的構成:

使用的工具:d2j-dex2jar 和jd-gui


apk直接解壓之后的文件

里面有2個dex文件和一個instant-run.zip文件。首先分別看一下兩個dex文件的源碼:

classes.dex的反編譯之后的源碼:


classes.dex反編譯結果

里面只有一個AppInfo,保存了app的基本信息,主要包含了包名和applicationClass。

classes2.dex反編譯之后的源碼:


classes2.dex反編譯結果

我們赫然發現,兩個dex中竟然沒有一句我們自己寫的代碼??那么代碼在哪里呢?你可能猜到,app真正的業務dex在instant-run.zip中。解壓instant-run.zip之后,如下圖所示:


instant-run.zip解壓之后的文件


instant-run。

反編譯之后,我們會發現,我們真正的業務代碼都在這里。

另外,我們再decode看一下AndroidManifest文件


反編譯之后的AndroidManifest.xml

//TODO

我們發現,我們的application也被替換了,替換成了com.android.tools.fd.runtime.BootstrapApplication

看到這里,那么大體的思路,可以猜到:

1.Instant-Run代碼作為一個宿主程序,將app作為資源dex加載起來,和插件化一個思路

2.那么InstantRun是怎么把業務代碼運行起來的呢?

##InstantRun啟動app

首先BootstrapApplication分析,按照執行順序,依次分析attachBaseContext和onCreate方法。

####1.attachBaseContext方法

```java

...

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方法

#####1.1.createResources

首先看createResources方法:

```java

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中

#####1.2.setupClassLoaders

```java

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

}

}

```

繼續看IncrementalClassLoader.inject方法:

IncrementalClassLoader的源碼如下:

```java

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中查找。

調用之后的效果如下圖所示:


classloader關系圖

我們可以在MyApplication中,用代碼驗證一下

```java

@Override

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

}

}

```

運行結果:

```txt

...

06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader

06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader

06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader

```

由此,我們已經知道了,當前PathClassLoader委托IncrementalClassLoader加載dex。繼續回到BootstrapApplication的attachBaseContext方法繼續分析。

#####1.3.createRealApplication

```java

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。由上面的反編譯截圖可以知道,demo中的applicationClass就是mobctrl.net.testinstantrun.MyApplication。通過反射的方式,創建真是的realApplication。

#####1.4.調用realApplication的attachBaseContext方法

代理realApplication的生命周期,通過反射調用realApplication的attachBaseContext方法,以當前的Context為參數。

attachBaseContext方法執行結束之后,我們繼續往下看,到BootstrapApplication的onCreate方法

###2.onCreate

源碼如下:

```java

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

}

}

```

我們依次需要關注的方法有:

monkeyPatchApplication → monkeyPatchExistingResources → Server啟動 → 調用realApplication的onCreate方法

####2.1 monkeyPatchApplication

該方法的目的可以總結為:替換所有當前app的application為realApplication。

```java

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

}

}

```

具體做的事情可以總結為:

######1.替換ActivityThread的mInitialApplication為realApplication

######2.替換mAllApplications 中所有的Application為realApplication

######3.替換ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application為realApplication。

####2.2 monkeyPatchExistingResources

替換所有當前app的mAssets為newAssetManager。

```java

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

}

}

```

改方法的目的總結為:

1.如果resource.ap_文件有改變,那么新建一個AssetManager對象newAssetManager,然后用newAssetManager對象替換所有當前Resource、Resource.Theme的mAssets成員變量。

2.如果當前的已經有Activity啟動了,還需要替換所有Activity中mAssets成員變量

####2.3 Server啟動

判斷Server是否已經啟動,如果沒有啟動,則啟動Server

####2.4 調用realApplication的onCreate方法

和1.4的目的一樣,代理realApplication的生命周期。

至此,我們的app就啟動起來了。下一步就要分析,Server啟動之后,到底是如何進行熱部署、溫部署和冷部署了。

###3.Server負責的熱部署、溫部署和冷部署

首先重點關注一下Server的內部類SocketServerReplyThread

####3.1 SocketServerReplyThread

```java

...

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來處理代碼的變化。

####3.2 handlePatches

源碼如下:

```java

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;

}

```

1.如果后綴為“.dex”,冷部署處理handleColdSwapPatch

2.如果后綴為“classes.dex.3”,熱部署處理handleHotSwapPatch

3.其他情況,溫部署,處理資源handleResourcePatch

#####handleColdSwapPatch冷部署

```java

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熱部署

```java

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抽象類的源碼,注意看load方法:

```java

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;

}

}

```

由此,我們大概理清楚了InstantRun熱部署的原理:

######1

在第一次構建apk時,在每一個類中注入了一個$change的成員變量,它實現了IncrementalChange接口,并在每一個方法中,插入了一段類似的邏輯。

```java

IncrementalChange localIncrementalChange = $change;

if (localIncrementalChange != null) {

localIncrementalChange.access$dispatch(

"onCreate.(Landroid/os/Bundle;)V", new Object[] { this,

... });

return;

}

```

就是當$change不為空的時候,執行IncrementalChange中的方法。

比如:

demo的MainActivity源代碼

```java

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

setSupportActionBar(toolbar);

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

fab.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)

.setAction("Action", null).show();

}

});

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.menu_main, menu);

return true;

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

int id = item.getItemId();

if (id == R.id.action_settings) {

return true;

}

return super.onOptionsItemSelected(item);

}

}

```

編譯之后的代碼為:(反編譯)

```java

public class MainActivity extends AppCompatActivity {

public MainActivity() {

}

MainActivity(Object[] paramArrayOfObject,

InstantReloadException paramInstantReloadException) {

}

public void onCreate(Bundle paramBundle) {

IncrementalChange localIncrementalChange = $change;

if (localIncrementalChange != null) {

localIncrementalChange.access$dispatch(

"onCreate.(Landroid/os/Bundle;)V", new Object[] { this,

paramBundle });

return;

}

super.onCreate(paramBundle);

setContentView(2130968601);

setSupportActionBar((Toolbar) findViewById(2131492969));

((FloatingActionButton) findViewById(2131492970))

.setOnClickListener(new View.OnClickListener() {

public void onClick(View paramAnonymousView) {

IncrementalChange localIncrementalChange = $change;

if (localIncrementalChange != null) {

localIncrementalChange.access$dispatch(

"onClick.(Landroid/view/View;)V",

new Object[] { this, paramAnonymousView });

return;

}

Snackbar.make(paramAnonymousView,

"Replace with your own action", 0)

.setAction("Action", null).show();

}

});

}

public boolean onCreateOptionsMenu(Menu paramMenu) {

IncrementalChange localIncrementalChange = $change;

if (localIncrementalChange != null) {

return ((Boolean) localIncrementalChange.access$dispatch(

"onCreateOptionsMenu.(Landroid/view/Menu;)Z", new Object[] {

this, paramMenu })).booleanValue();

}

getMenuInflater().inflate(2131558400, paramMenu);

return true;

}

public boolean onOptionsItemSelected(MenuItem paramMenuItem) {

boolean bool = true;

IncrementalChange localIncrementalChange = $change;

if (localIncrementalChange != null) {

bool = ((Boolean) localIncrementalChange.access$dispatch(

"onOptionsItemSelected.(Landroid/view/MenuItem;)Z",

new Object[] { this, paramMenuItem })).booleanValue();

}

while (paramMenuItem.getItemId() == 2131492993) {

return bool;

}

return super.onOptionsItemSelected(paramMenuItem);

}

}

```

可以看到,每個方法前,都注入了這段邏輯。

######2

當我們修改代碼中方法的實現之后,點擊InstantRun,它會生成對應的patch文件來記錄你修改的內容。patch文件中的替換類是在所修改類名的后面追加$override,并實現IncrementalChange接口。

比如,以MainActivity為例

在目錄../build/intermediates/transforms/instantRun/debug/folders/4000/5下查找到.

生成了MainActivity$override類。

```java

public class MainActivity$override implements IncrementalChange {

public MainActivity$override() {

}

public static Object init$args(Object[] var0) {

Object[] var1 = new Object[]{"android/support/v7/app/AppCompatActivity.()V"};

return var1;

}

public static void init$body(MainActivity $this) {

}

public static void onCreate(MainActivity $this, Bundle savedInstanceState) {

Object[] var2 = new Object[]{savedInstanceState};

MainActivity.access$super($this, "onCreate.(Landroid/os/Bundle;)V", var2);

$this.setContentView(2130968601);

Toolbar toolbar = (Toolbar)$this.findViewById(2131492969);

$this.setSupportActionBar(toolbar);

FloatingActionButton fab = (FloatingActionButton)$this.findViewById(2131492970);

Object[] var5 = new Object[]{$this};

Class[] var10002 = new Class[]{MainActivity.class};

String var10003 = "";

fab.setOnClickListener((1)((1)AndroidInstantRuntime.newForClass(var5, var10002, 1.class)));

AndroidInstantRuntime.setPrivateField($this, (TextView)$this.findViewById(2131492971), MainActivity.class, "textView");

((TextView)AndroidInstantRuntime.getPrivateField($this, MainActivity.class, "textView")).setText("myHello");

}

public static boolean onCreateOptionsMenu(MainActivity $this, Menu menu) {

$this.getMenuInflater().inflate(2131558400, menu);

return true;

}

public static boolean onOptionsItemSelected(MainActivity $this, MenuItem item) {

int id = item.getItemId();

if(id == 2131492993) {

return true;

} else {

Object[] var3 = new Object[]{item};

return ((Boolean)MainActivity.access$super($this, "onOptionsItemSelected.(Landroid/view/MenuItem;)Z", var3)).booleanValue();

}

}

public Object access$dispatch(String var1, Object... var2) {

switch(var1.hashCode()) {

case -1635453101:

return new Boolean(onCreateOptionsMenu((MainActivity)var2[0], (Menu)var2[1]));

case -1630101479:

return init$args((Object[])var2[0]);

case -641568046:

onCreate((MainActivity)var2[0], (Bundle)var2[1]);

return null;

case -604658433:

init$body((MainActivity)var2[0]);

return null;

case 1893326613:

return new Boolean(onOptionsItemSelected((MainActivity)var2[0], (MenuItem)var2[1]));

default:

throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var1, Integer.valueOf(var1.hashCode()), "mobctrl/net/testinstantrun/MainActivity"}));

}

}

```

######3

生成AppPatchesLoaderImpl類,繼承自AbstractPatchesLoaderImpl,并實現getPatchedClasses方法,來記錄哪些類被修改了。

比如,仍然在目錄../build/intermediates/transforms/instantRun/debug/folders/4000/5下查找AppPatchesLoaderImpl.class

```java

public class AppPatchesLoaderImpl extends AbstractPatchesLoaderImpl {

public AppPatchesLoaderImpl() {

}

public String[] getPatchedClasses() {

return new String[]{"android.support.design.R$id", "mobctrl.net.testinstantrun.MainActivity$1", "mobctrl.net.testinstantrun.R$id", "mobctrl.net.testinstantrun.MainActivity", "android.support.v7.appcompat.R$id"};

}

}

```

######4

調用load方法之后,根據getPatchedClasses返回的修改過的類的列表,去加載對應的$override類,然后把原有類的$change設置為對應的實現了IncrementalChange接口的$override類。


然后等待restart之后生效

#####handleResourcePatch

```java

private static int handleResourcePatch(int updateMode,

ApplicationPatch patch, String path) {

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

Log.v("InstantRun", "Received resource changes (" + path + ")");

}

FileManager.writeAaptResources(path, patch.getBytes());

updateMode = Math.max(updateMode, 2);

return updateMode;

}

```

將資源的patch寫入到私有目錄,等到restart之后生效.

####restart

根據不同的InstantRun的updateMode模式,進行重啟,使上述的3中部署模式生效!

```java

private void restart(int updateMode, boolean incrementalResources,

boolean toast) {

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

Log.v("InstantRun", "Finished loading changes; update mode ="

+ updateMode);

}

if ((updateMode == 0) || (updateMode == 1)) {

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

Log.v("InstantRun", "Applying incremental code without restart");

}

if (toast) {

Activity foreground = Restarter

.getForegroundActivity(this.mApplication);

if (foreground != null) {

Restarter.showToast(foreground,

"Applied code changes without activity restart");

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

Log.v("InstantRun",

"Couldn't show toast: no activity found");

}

}

return;

}

List activities = Restarter.getActivities(this.mApplication,

false);

if ((incrementalResources) && (updateMode == 2)) {

File file = FileManager.getExternalResourceFile();

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

Log.v("InstantRun", "About to update resource file=" + file

+ ", activities=" + activities);

}

if (file != null) {

String resources = file.getPath();

MonkeyPatcher.monkeyPatchApplication(this.mApplication, null,

null, resources);

MonkeyPatcher.monkeyPatchExistingResources(this.mApplication,

resources, activities);

} else {

Log.e("InstantRun", "No resource file found to apply");

updateMode = 3;

}

}

Activity activity = Restarter.getForegroundActivity(this.mApplication);

if (updateMode == 2) {

if (activity != null) {

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

Log.v("InstantRun", "Restarting activity only!");

}

boolean handledRestart = false;

try {

Method method = activity.getClass().getMethod(

"onHandleCodeChange", new Class[] { Long.TYPE });

Object result = method.invoke(activity,

new Object[] { Long.valueOf(0L) });

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

Log.v("InstantRun", "Activity " + activity

+ " provided manual restart method; return "

+ result);

}

if (Boolean.TRUE.equals(result)) {

handledRestart = true;

if (toast) {

Restarter.showToast(activity, "Applied changes");

}

}

} catch (Throwable ignore) {

}

if (!handledRestart) {

if (toast) {

Restarter.showToast(activity,

"Applied changes, restarted activity");

}

Restarter.restartActivityOnUiThread(activity);

}

return;

}

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

Log.v("InstantRun",

"No activity found, falling through to do a full app restart");

}

updateMode = 3;

}

if (updateMode != 3) {

if (Log.isLoggable("InstantRun", 6)) {

Log.e("InstantRun", "Unexpected update mode: " + updateMode);

}

return;

}

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

Log.v("InstantRun",

"Waiting for app to be killed and restarted by the IDE...");

}

}

```

##總體總結

總結起來,做了一下幾件事:

###第一次編譯apk:

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生效

##Instant Run的借鑒意義

###Android插件化框架改進

###Android熱修復方案

###app加殼

<未完待續>

##InstantRun源碼

我自己通過jd-gui反編譯獲取的,可以參考:
[https://github.com/nuptboyzhb/AndroidInstantRun](https://github.com/nuptboyzhb/AndroidInstantRun)


##參考博文

[1].[Instant Run: How Does it Work?!](https://medium.com/google-developers/instant-run-how-does-it-work-294a1633367f#.oqes7tpmm)

[2].[Instant Run工作原理及用法](http://www.lxweimin.com/p/2e23ba9ff14b)

[3].[Instant Run: An Android Tool Time Deep Dive](https://www.youtube.com/watch?v=StqAZ1OQbqA)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容