深入理解 Android_Instant_Run 运行机制

308232176 7年前
   <h2>Instant Run</h2>    <p>Instant Run,是android studio2.0新增的一个运行机制,在你编码开发、测试或debug的时候,它都能显著减少你对当前应用的构建和部署的时间。通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果。而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果。</p>    <h2>传统的代码修改及编译部署流程</h2>    <p>传统的代码修改及编译流程如下:构建整个apk → 部署app → app重启 → 重启Activity</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/465b2823af0c452146b23dde9d424ae8.png"></p>    <h2>Instant Run编译和部署流程</h2>    <p>Instant Run构建项目的流程:构建修改的部分 → 部署修改的dex或资源 → 热部署,温部署,冷部署</p>    <h3>热拔插,温拔插,冷拔插</h3>    <p>热拔插:代码改变被应用、投射到APP上,不需要重启应用,不需要重建当前activity。</p>    <p>场景:适用于多数的简单改变(包括一些方法实现的修改,或者变量值修改)</p>    <p>温拔插:activity需要被重启才能看到所需更改。</p>    <p>场景:典型的情况是代码修改涉及到了资源文件,即resources。</p>    <p>冷拔插:app需要被重启(但是仍然不需要重新安装)</p>    <p>场景:任何涉及结构性变化的,比如:修改了继承规则、修改了方法签名等。</p>    <h2>首次运行Instant Run,Gradle执行过程</h2>    <p>一个新的App Server类会被注入到App中,与Bytecode instrumentation协同监控代码的变化。</p>    <p>同时会有一个新的Application类,它注入了一个自定义类加载器(Class Loader),同时该Application类会启动我们所需的新注入的App Server。于是,Manifest会被修改来确保我们的应用能使用这个新的Application类。(这里不必担心自己继承定义了Application类,Instant Run添加的这个新Application类会代理我们自定义的Application类)</p>    <p>至此,Instant Run已经可以跑起来了,在我们使用的时候,它会通过决策,合理运用冷温热拔插来协助我们大量地缩短构建程序的时间。</p>    <p>在Instant Run运行之前,Android Studio会检查是否能连接到App Server中。并且确保这个App Server是Android Studio所需要的。这同样能确保该应用正处在前台。</p>    <h3>热拔插</h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/070d99404c34976fe060668b9bf13b91.png"></p>    <p>Android Studio monitors: 运行着Gradle任务来生成增量.dex文件(这个dex文件是对应着开发中的修改类) Android Studio会提取这些.dex文件发送到App Server,然后部署到App。</p>    <p>App Server会不断监听是否需要重写类文件,如果需要,任务会被立马执行。新的更改便能立即被响应。我们可以通过打断点的方式来查看。</p>    <h3>温拔插</h3>    <p>温拔插需要重启Activity,因为资源文件是在Activity创建时加载,所以必须重启Activity来重载资源文件。</p>    <p>目前来说,任何资源文件的修改都会导致重新打包再发送到APP。但是,google的开发团队正在致力于开发一个增量包,这个增量包只会包装修改过的资源文件并能部署到当前APP上。</p>    <p>所以温拔插实际上只能应对少数的情况,它并不能应付应用在架构、结构上的变化。</p>    <p>注:温拔插涉及到的资源文件修改,在manifest上是无效的(这里的无效是指不会启动Instant Run),因为,manifest的值是在APK安装的时候被读取,所以想要manifest下资源的修改生效,还需要触发一个完整的应用构建和部署。</p>    <h3>冷拔插</h3>    <p>应用部署的时候,会把工程拆分成十个部分,每部分都拥有自己的.dex文件,然后所有的类会根据包名被分配给相应的.dex文件。当冷拔插开启时,修改过的类所对应的.dex文件,会重组生成新的.dex文件,然后再部署到设备上。</p>    <p>之所以能这么做,是依赖于Android的ART模式,它能允许加载多个.dex文件。ART模式在android4.4(API-19)中加入,但是Dalvik依然是首选,到了android5.0(API-21),ART模式才成为系统默认首选,所以Instant Run只能运行在API-21及其以上版本。</p>    <h2>使用Instant Run一些注意点</h2>    <p>Instant Run是被Android Studio控制的。所以我们只能通过IDE来启动它,如果通过设备来启动应用,Instant Run会出现异常情况。在使用Instant Run来启动Android app的时候,应注意以下几点:</p>    <ol>     <li>如果应用的minSdkVersion小于21,可能多数的Instant Run功能会挂掉,这里提供一个解决方法,通过product flavor建立一个minSdkVersion大于21的新分支,用来debug。</li>     <li>Instant Run目前只能在主进程里运行,如果应用是多进程的,类似微信,把webView抽出来单独一个进程,那热、温拔插会被降级为冷拔插。</li>     <li>在Windows下,Windows Defender Real-Time Protection可能会导致Instant Run挂掉,可用通过添加白名单列表解决。</li>     <li>暂时不支持Jack compiler,Instrumentation Tests,或者同时部署到多台设备。</li>    </ol>    <h2>结合Demo深度理解</h2>    <p>为了方便大家的理解,我们新建一个项目,里面不写任何的逻辑功能,只对application做一个修改:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0fbe65e5f28c6d5f4ca57a7728a0dd15.png"></p>    <p>首先,我们先反编译一下APK的构成,使用的工具:d2j-dex2jar 和jd-gui。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f7acbae33c434bbbc679ef099ebbb74f.png"></p>    <p>我们要看的启动的信息就在这个instant-run.zip文件里面,解压instant-run.zip,我们会发现,我们真正的业务代码都在这里。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ee0d81204192952a728e35ce7af64b6a.png"></p>    <p>从instant-run文件中我们猜想是BootstrapApplication替换了我们的application,Instant-Run代码作为一个宿主程序,将app作为资源dex加载起来。</p>    <p>那么InstantRun是怎么把业务代码运行起来的呢?</p>    <h2>Instant Run如何启动app</h2>    <p>按照我们上面对instant-run运行机制的猜想,我们首先看一下appliaction的分析attachBaseContext和onCreate方法。</p>    <h3>attachBaseContext()</h3>    <pre>  <code class="language-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);  }  }  }</code></pre>    <p>我们依次需要关注的方法有:</p>    <p>createResources → setupClassLoaders → createRealApplication → 调用realApplication的attachBaseContext方法</p>    <p>createResources()</p>    <pre>  <code class="language-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);  }  }  }</code></pre>    <p>说明:该方法主要是判断资源resource.ap_是否改变,然后保存resource.ap_的路径到externalResourcePath中。</p>    <p>setupClassLoaders()</p>    <pre>  <code class="language-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);  }  }</code></pre>    <p>说明,该方法是初始化一个ClassLoaders并调用IncrementalClassLoader。</p>    <p>IncrementalClassLoader的源码如下:</p>    <pre>  <code class="language-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;  }  }</code></pre>    <p>inject方法是用来设置classloader的父子顺序的,使用IncrementalClassLoader来加载dex。由于ClassLoader的双亲委托模式,也就是委托父类加载类,父类中找不到再在本ClassLoader中查找。</p>    <p>调用的效果图如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0b8b0e9263ebd358fe98547428c712e9.png"></p>    <p>为了方便我们对委托父类加载机制的理解,我们可以做一个实验,在我们的application做一些Log。</p>    <pre>  <code class="language-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();  }  }</code></pre>    <p>输出结果:</p>    <pre>  <code class="language-java">03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader  03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader  03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader</code></pre>    <p>由此,我们知道,当前PathClassLoader委托IncrementalClassLoader加载dex。</p>    <p>我们继续对attachBaseContext()继续分析:</p>    <pre>  <code class="language-java">attachBaseContext.invoke(this.realApplication,  new Object[] { context });</code></pre>    <p>createRealApplication</p>    <pre>  <code class="language-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();  }  }</code></pre>    <p>该方法就是用classes.dex中的AppInfo类的applicationClass常量中保存的app真实的application。由例子的分析我们可以知道applicationClass就是com.xzh.demo.MyApplication。通过反射的方式,创建真是的realApplication。</p>    <p>看完attachBaseContext我们继续看BootstrapApplication();</p>    <h3>BootstrapApplication()</h3>    <p>我们首先看一下onCreate方法:</p>    <p>onCreate()</p>    <pre>  <code class="language-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();  }  }</code></pre>    <p>在onCreate()中我们需要注意以下方法:</p>    <p>monkeyPatchApplication → monkeyPatchExistingResources → Server启动 → 调用realApplication的onCreate方法</p>    <p>monkeyPatchApplication</p>    <pre>  <code class="language-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);  }  }</code></pre>    <p>说明:该方法的作用是替换所有当前app的application为realApplication。</p>    <p>替换的过程如下:</p>    <p>1.替换ActivityThread的mInitialApplication为realApplication</p>    <p>2.替换mAllApplications 中所有的Application为realApplication</p>    <p>3.替换ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application为realApplication。</p>    <p>monkeyPatchExistingResources</p>    <pre>  <code class="language-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);  }  }</code></pre>    <p>说明:该方法的作用是替换所有当前app的mAssets为newAssetManager。</p>    <p>monkeyPatchExistingResources的流程如下:</p>    <p>1.如果resource.ap_文件有改变,那么新建一个AssetManager对象newAssetManager,然后用newAssetManager对象替换所有当前Resource、Resource.Theme的mAssets成员变量。</p>    <p>2.如果当前的已经有Activity启动了,还需要替换所有Activity中mAssets成员变量</p>    <p>判断Server是否已经启动,如果没有启动,则启动Server。然后调用realApplication的onCreate方法代理realApplication的生命周期。</p>    <p>接下来我们分析下Server负责的**热部署**、**温部署**和**冷部署**等问题。</p>    <h2>Server热部署、温部署和冷部署</h2>    <p>首先重点关注一下Server的内部类SocketServerReplyThread。</p>    <p>SocketServerReplyThread</p>    <pre>  <code class="language-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;  }  }  }  }  }</code></pre>    <p>说明:socket开启后,开始读取数据,当读到1时,获取代码变化的ApplicationPatch列表,然后调用handlePatches来处理代码的变化。</p>    <p>handlePatches</p>    <pre>  <code class="language-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;  }</code></pre>    <p>说明:本方法主要通过判断Change的内容,来判断采用什么模式(热部署、温部署或冷部署)</p>    <ul>     <li>如果后缀为“.dex”,冷部署处理handleColdSwapPatch</li>     <li>如果后缀为“classes.dex.3”,热部署处理handleHotSwapPatch</li>     <li>其他情况,温部署,处理资源handleResourcePatch</li>    </ul>    <p>handleColdSwapPatch冷部署</p>    <pre>  <code class="language-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);  }  }  }</code></pre>    <p>说明:该方法把dex文件写到私有目录,等待整个app重启,重启之后,使用前面提到的IncrementalClassLoader加载dex即可。</p>    <p>handleHotSwapPatch热部署</p>    <pre>  <code class="language-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;  }</code></pre>    <p>说明:该方法将patch的dex文件写入到临时目录,然后使用DexClassLoader去加载dex。然后反射调用AppPatchesLoaderImpl类的load方法。</p>    <p>需要强调的是:AppPatchesLoaderImpl继承自抽象类AbstractPatchesLoaderImpl,并实现了抽象方法:getPatchedClasses。而AbstractPatchesLoaderImpl抽象类代码如下:</p>    <pre>  <code class="language-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;  }  }</code></pre>    <h3>Instant Run热部署原理</h3>    <p>由上面的代码分析,我们对Instant Run的流程可以分析如下:</p>    <p>1,在第一次构建apk时,在每一个类中注入了一个$change的成员变量,它实现了IncrementalChange接口,并在每一个方法中,插入了一段类似的逻辑。</p>    <pre>  <code class="language-java">IncrementalChange localIncrementalChange = $change;  if (localIncrementalChange != null) {  localIncrementalChange.access$dispatch(  "onCreate.(Landroid/os/Bundle;)V", new Object[] { this,  ... });  return;  }</code></pre>    <p>当$change不为空的时候,执行IncrementalChange方法。</p>    <p>2,当我们修改代码中方法的实现之后,点击InstantRun,它会生成对应的patch文件来记录你修改的内容。patch文件中的替换类是在所修改类名的后面追加$override,并实现IncrementalChange接口。</p>    <p>3,生成AppPatchesLoaderImpl类,继承自AbstractPatchesLoaderImpl,并实现getPatchedClasses方法,来记录哪些类被修改了。</p>    <p>4,调用load方法之后,根据getPatchedClasses返回的修改过的类的列表,去加载对应的$override类,然后把原有类的$change设置为对应的实现了IncrementalChange接口的$override类。</p>    <h2>Instant Run运行机制总结</h2>    <p>Instant Run运行机制主要涉及到热部署、温部署和冷部署,主要是在第一次运行,app运行时期,有代码修改时。</p>    <h3>第一次编译</h3>    <p>1.把Instant-Run.jar和instant-Run-bootstrap.jar打包到主dex中</p>    <p>2.替换AndroidManifest.xml中的application配置</p>    <p>3.使用asm工具,在每个类中添加$change,在每个方法前加逻辑</p>    <p>4.把源代码编译成dex,然后存放到压缩包instant-run.zip中</p>    <h3>app运行时</h3>    <p>1.获取更改后资源resource.ap_的路径</p>    <p>2.设置ClassLoader。setupClassLoader:</p>    <p>使用IncrementalClassLoader加载apk的代码,将原有的BootClassLoader → PathClassLoader改为BootClassLoader → IncrementalClassLoader → PathClassLoader继承关系。</p>    <p>3.createRealApplication:</p>    <p>创建apk真实的application</p>    <p>4.monkeyPatchApplication</p>    <p>反射替换ActivityThread中的各种Application成员变量</p>    <p>5.monkeyPatchExistingResource</p>    <p>反射替换所有存在的AssetManager对象</p>    <p>6.调用realApplication的onCreate方法</p>    <p>7.启动Server,Socket接收patch列表</p>    <h3>有代码修改时</h3>    <p>1.生成对应的$override类</p>    <p>2.生成AppPatchesLoaderImpl类,记录修改的类列表</p>    <p>3.打包成patch,通过socket传递给app</p>    <p>4.app的server接收到patch之后,分别按照handleColdSwapPatch、handleHotSwapPatch、handleResourcePatch等待对patch进行处理</p>    <p>5.restart使patch生效</p>    <p>在Android插件化、Android热修复、apk加壳/脱壳中借鉴了Instant Run运行机制,所以理解Instant Run运行机制对于向更深层次的研究是很有帮助的,对于我们自己书写框架也是有借鉴意义的。</p>    <p> </p>    <p>来自:https://yq.aliyun.com/articles/72566?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&2017323&utm_content=m_14685</p>    <p> </p>