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

Android Studio 2.0開始支持 Instant Run 特性锋拖, 使得在開發(fā)過程中能快速將代碼變化更新到設備上韩肝。之前触菜,更新代碼之后需要先編譯一個完整的新Apk,卸載設備上已安裝的這個 Apk (若有)哀峻,再 push 到設備安裝玫氢,再啟動。有了 Instant Run 特性之后谜诫,只需要 push 一些增量到設備上漾峡,直接執(zhí)行,可以為開發(fā)人員節(jié)省大量時間喻旷。當然 Instant Run 特征只在 debug 時有效生逸,對發(fā)布 release 版沒有任何影響。

對于InstantRun不了解的同學可以去查看官方文檔

Instant Run 通過 hot swap且预, warm swap槽袄, code swap 三種 swap 來實現(xiàn)。Android Studio 會根據(jù)代碼的改變自動決定 push 哪種 swap 到設備上锋谐,并根據(jù)不同的 swap 執(zhí)行不同的行為遍尺。

代碼改變內(nèi)容 Instant Run 行為
修改一個實例方法或者一個靜態(tài)方法的實現(xiàn) hot swap: 這是最快的情況,下次調(diào)用該方法時直接使用更新后的方法
修改或者刪除一個資源 warm swap: App 保護運行狀態(tài)涮拗,但是會自動重啟 activity乾戏, 所以屏幕會閃一下
增加迂苛、刪除或修改((1)注解 (2)成員變量/靜態(tài)變量/方法簽名)修改類的繼承關系、實現(xiàn)的接口修改類的靜態(tài)代碼塊利用動態(tài)資源ID改變資源順序 cold swap(Api level >= 21): 需要重啟App若Api level < 21鼓择, 則需要重新編譯整個app
修改 AndroidManifest.xml修改被 AndroidManifest.xml 引用的資源修改 widget UI 需要重新編譯整個App

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

1 運行demo

首先我們來創(chuàng)建一個簡單的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打包的時候?qū)ntant-run.jar包打入到了apk中骑丸。但問題是,我們的代碼(也就是上文中MainActivity)去哪兒了妒貌?

其實用戶代碼都被寫入到apk文件中的instant-run.zip中去了通危,將instant-run.zip解壓后可以看到:

這里寫圖片描述

可以看到在這個路徑下還有很多dex文件,而我們的代碼就被放在slice_9-classes.dex中灌曙,至于instant-run.zip中的打包/分包邏輯菊碟,為啥用戶代碼會被打入到 slice_9-classes.dex 中我還不是太清楚,知道的同學可以給我留言:

這里寫圖片描述

可以看到在刺,在用戶代碼的每一個函數(shù)中都被插入了這樣一段代碼:

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

上述代碼通過判斷$change變量來執(zhí)行不同的邏輯逆害。這也是InstantRunhot swap的實現(xiàn)原理,通過插樁的方式在每一個函數(shù)中植入$change變量及其相關邏輯蚣驼,當相關代碼被修改時魄幕,利用反射的方式將$change重置,從而執(zhí)行修改后的邏輯已達到熱修復的目的颖杏。

另外我們再來看下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在編譯代碼時會通過插樁的方式給每一個函數(shù)植入一段代碼,從而在需要時hook

    ?

2 工程架構

2.1 編譯

通過第一節(jié)我們知道丐膝,InstantRun會在每個函數(shù)中植入一段代碼量愧,達到插樁的效果钾菊。InstantRun通過Gradle Transform API,在代碼完成之后,被轉(zhuǎn)換成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 → 調(diào)用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設置為系統(tǒng)ClassLoader的父loader
  • 將用戶代碼dex文件路徑設置為該classloader加載路徑

由于ClassLoader的加載采用雙親委托模式胧洒,所以當需要加載用戶代碼時肌厨,系統(tǒng)classloader會首先找到BootstrapApplication.class.getClassLoader(),而BootstrapApplication.class.getClassLoader()

又會委托其父loader也即我們創(chuàng)建的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的目的是創(chuàng)建真實的Application實例。真實的Application保存在AppInfo中沛慢,如果用戶自定義了Application赡若,則直接創(chuàng)建該Application實例;否則則創(chuàng)建系統(tǒng)默認的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啟動 → 調(diào)用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成員變量匹厘。
  • 如果當前的已經(jīng)有Activity啟動了嘀趟,還需要替換所有Activity中mAssets成員變量

判斷Server是否已經(jīng)啟動,如果沒有啟動愈诚,則啟動Server去件。然后調(diào)用realApplication的onCreate方法代理realApplication的生命周期。

至此InstantRun的初始化工作就算完成了扰路,接下來就是在監(jiān)聽到代碼變化后熱更新了尤溜。總結一下汗唱,InstantRun在初始化階段主要做了以下幾部分工作:

  • 代碼編譯階段對每一個用戶代碼中的方法進行插樁宫莱,這是hot swap的基礎
  • 創(chuàng)建宿主apk,用戶代碼全部寫到instant-run.zip中
  • 創(chuàng)建宿主Application(BootstrapApplication)哩罪,并在宿主Application初始化時:
    • 通過注入ClassLoader的方式授霸,加載位于instant-run.zip中的用戶代碼
    • 利用反射的方式注入真正的Application

當server啟動后巡验,會持續(xù)監(jiān)聽是否有代碼更新,如果有便加載到本地后進行熱更新碘耳。具體的更新邏輯显设,請看下一篇博客。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辛辨,一起剝皮案震驚了整個濱河市捕捂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斗搞,老刑警劉巖指攒,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異僻焚,居然都是意外死亡允悦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門虑啤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隙弛,“玉大人,你說我怎么就攤上這事狞山∪疲” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵铣墨,是天一觀的道長室埋。 經(jīng)常有香客問我办绝,道長伊约,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任孕蝉,我火速辦了婚禮屡律,結果婚禮上,老公的妹妹穿的比我還像新娘降淮。我一直安慰自己超埋,他們只是感情好,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布佳鳖。 她就那樣靜靜地躺著霍殴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪系吩。 梳的紋絲不亂的頭發(fā)上来庭,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音穿挨,去河邊找鬼月弛。 笑死肴盏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的帽衙。 我是一名探鬼主播菜皂,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼厉萝!你這毒婦竟也來了恍飘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤冀泻,失蹤者是張志新(化名)和其女友劉穎常侣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弹渔,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡胳施,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肢专。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舞肆。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖博杖,靈堂內(nèi)的尸體忽然破棺而出椿胯,到底是詐尸還是另有隱情,我是刑警寧澤剃根,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布哩盲,位于F島的核電站,受9級特大地震影響狈醉,放射性物質(zhì)發(fā)生泄漏廉油。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一苗傅、第九天 我趴在偏房一處隱蔽的房頂上張望抒线。 院中可真熱鬧,春花似錦渣慕、人聲如沸嘶炭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眨猎。三九已至,卻和暖如春强经,著一層夾襖步出監(jiān)牢的瞬間睡陪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工夕凝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宝穗,地道東北人户秤。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像逮矛,于是被迫代替她去往敵國和親鸡号。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容