1. 程式人生 > >InstantRun原理(1)——初始化邏輯

InstantRun原理(1)——初始化邏輯

Android Studio 2.0開始支援 Instant Run 特性, 使得在開發過程中能快速將程式碼變化更新到裝置上。之前,更新程式碼之後需要先編譯一個完整的新Apk,解除安裝裝置上已安裝的這個 Apk (若有),再 push 到裝置安裝,再啟動。有了 Instant Run 特性之後,只需要 push 一些增量到裝置上,直接執行,可以為開發人員節省大量時間。當然 Instant Run 特徵只在 debug 時有效,對釋出 release 版沒有任何影響。

對於InstantRun不瞭解的同學可以去檢視官方文件

Instant Run 通過 hot swap, warm swap, code swap 三種 swap 來實現。Android Studio 會根據程式碼的改變自動決定 push 哪種 swap 到裝置上,並根據不同的 swap 執行不同的行為。

程式碼改變內容 Instant Run 行為
修改一個例項方法或者一個靜態方法的實現 hot swap: 這是最快的情況,下次呼叫該方法時直接使用更新後的方法
修改或者刪除一個資源 warm swap: App 保護執行狀態,但是會自動重啟 activity, 所以螢幕會閃一下
增加、刪除或修改((1)註解 (2)成員變數/靜態變數/方法簽名)修改類的繼承關係、實現的介面修改類的靜態程式碼塊利用動態資源ID改變資源順序 cold swap(Api level >= 21): 需要重啟App若Api level < 21, 則需要重新編譯整個app
修改 AndroidManifest.xml修改被 AndroidManifest.xml 引用的資源修改 widget UI 需要重新編譯整個App

接下來我們就以一個簡單的例子來介紹InstantRun的原理。

1 執行demo

首先我們來建立一個簡單的demo,demo很簡單,只有一個activity,activity中有一個button:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button btn;

    @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.btn = (Button) findViewById(R.id.btn); this.btn.setOnClickListener(this); } @Override public void onClick(View view) { Toast.makeText(this, "click", Toast.LENGTH_SHORT).show(); } }

執行該demo,效果很簡單就不截圖了。重點來看下其apk檔案:將打包出來的apk解壓後結構如下:

這裡寫圖片描述

首先我們把classes.dex和classes2.dex反編譯出來看看(反編譯軟體推薦使用dex2jar,相關使用方式可以參考:user guide):

  • classes.dex:

    這裡寫圖片描述

  • classes2.dex

    這裡寫圖片描述

可以看到,兩個dex檔案完全沒有包含任何工程程式碼,看上去全部都是無關程式碼。其實這些程式碼都是instant-run.jar中的程式碼,也就是說InstantRun工程在進行apk打包的時候將intant-run.jar包打入到了apk中。但問題是,我們的程式碼(也就是上文中MainActivity)去哪兒了?

其實使用者程式碼都被寫入到apk檔案中的instant-run.zip中去了,將instant-run.zip解壓後可以看到:

這裡寫圖片描述

可以看到在這個路徑下還有很多dex檔案,而我們的程式碼就被放在slice_9-classes.dex中,至於instant-run.zip中的打包/分包邏輯,為啥使用者程式碼會被打入到 slice_9-classes.dex 中我還不是太清楚,知道的同學可以給我留言:

這裡寫圖片描述

可以看到,在使用者程式碼的每一個函式中都被插入了這樣一段程式碼:

    IncrementalChange localIncrementalChange = $change;
    if (localIncrementalChange != null)
    {
      localIncrementalChange.access$dispatch("onClick.(Landroid/view/View;)V", new Object[] { this, paramView });
      return;
    }

上述程式碼通過判斷$change變數來執行不同的邏輯。這也是InstantRunhot swap的實現原理,通過插樁的方式在每一個函式中植入$change變數及其相關邏輯,當相關程式碼被修改時,利用反射的方式將$change重置,從而執行修改後的邏輯已達到熱修復的目的。

另外我們再來看下AndroidManifest.xml檔案:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.alibaba.sdk.instandemo" platformBuildVersionCode="25" platformBuildVersionName="7.1.1">
    <meta-data android:name="android.support.VERSION" android:value="25.3.1"/>
    <application android:allowBackup="true" android:debuggable="true"
                 android:icon="@mipmap/ic_launcher" 
                 android:label="@string/app_name"             android:name="com.android.tools.fd.runtime.BootstrapApplication" android:supportsRtl="true" android:theme="@style/AppTheme">
        <activity android:name="com.alibaba.sdk.instandemo.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

可以看到,工程中的Application被篡改成了com.android.tools.fd.runtime.BootstrapApplication,這個類輸入intents-run.jar包,不難猜測,Application的初始化過程也被instant-run所代理了。

看到這裡,一個instant-run功能的大致結構基本就清晰了:

  • InstantRun工程實際上是一個宿主工程,使用者程式碼以資源的形式放入到apk中

  • InstantRun工程通過com.android.tools.fd.runtime.BootstrapApplication代理app的初始化過程,猜測在初始化的過程中,com.android.tools.fd.runtime.BootstrapApplication會去載入使用者程式碼

  • InstantRun在編譯程式碼時會通過插樁的方式給每一個函式植入一段程式碼,從而在需要時hook

2 工程架構

2.1 編譯

通過第一節我們知道,InstantRun會在每個函式中植入一段程式碼,達到插樁的效果。InstantRun通過Gradle Transform API,在程式碼完成之後,被轉換成dex之前將相應邏輯插入。InstantRun使用ASM完成插樁。

2.2 執行時

看完了編譯階段,接下來看下執行時階段相關原理。由於com.android.tools.fd.runtime.BootstrapApplication代理了整個應用的初始化工作,從而成為了整個應用的入口。我們就從com.android.tools.fd.runtime.BootstrapApplication開始入手。

2.2.1attachBaseContext

首先來看下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

2.2.1.1 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中。

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

該方法將使用者程式碼dexList注入到一個自定義ClassLoader例項中,並將該classloader設定為預設class loader:BootstrapApplication.class.getClassLoader()的父loader。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; 
     } 
}

上述程式碼總過做了兩件事:

  • 將一個DelegateClassLoader設定為系統ClassLoader的父loader
  • 將使用者程式碼dex檔案路徑設定為該classloader載入路徑

由於ClassLoader的載入採用雙親委託模式,所以當需要載入使用者程式碼時,系統classloader會首先找到BootstrapApplication.class.getClassLoader(),而BootstrapApplication.class.getClassLoader()

又會委託其父loader也即我們建立的DelegateClassLoader例項,該例項會負責完成使用者程式碼的載入。

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

createRealApplication的目的是建立真實的Application例項。真實的Application儲存在AppInfo中,如果使用者自定義了Application,則直接建立該Application例項;否則則建立系統預設的Application例項。

2.2.2 onCreate

接下來我們再來看下com.android.tools.fd.runtime.BootstrapApplicationonCreate方法:

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

2.2.2.1 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:

  • 替換ActivityThread的mInitialApplication為realApplication
  • 替換mAllApplications 中所有的Application為realApplication
  • 替換ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application為realApplication
2.2.2.2 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的流程如下:

  • 如果resource.ap_檔案有改變,那麼新建一個AssetManager物件newAssetManager,然後用newAssetManager物件替換所有當前Resource、Resource.Theme的mAssets成員變數。
  • 如果當前的已經有Activity啟動了,還需要替換所有Activity中mAssets成員變數

判斷Server是否已經啟動,如果沒有啟動,則啟動Server。然後呼叫realApplication的onCreate方法代理realApplication的生命週期。

至此InstantRun的初始化工作就算完成了,接下來就是在監聽到程式碼變化後熱更新了。總結一下,InstantRun在初始化階段主要做了以下幾部分工作:

  • 程式碼編譯階段對每一個使用者程式碼中的方法進行插樁,這是hot swap的基礎
  • 建立宿主apk,使用者程式碼全部寫到instant-run.zip中
  • 建立宿主Application(BootstrapApplication),並在宿主Application初始化時:
    • 通過注入ClassLoader的方式,載入位於instant-run.zip中的使用者程式碼
    • 利用反射的方式注入真正的Application

當server啟動後,會持續監聽是否有程式碼更新,如果有便載入到本地後進行熱更新。具體的更新邏輯,請看下一篇部落格。