阿里云ApsaraMobile(移动云) 关注
手机版
  1. 云栖社区>
  2. 阿里云ApsaraMobile(移动云)>
  3. 博客>
  4. 正文

InstantRun原理(1)——初始化逻辑

李牙刷儿 2017-09-11 17:33:30 浏览2523 评论0 发表于: 阿里云ApsaraMobile(移动云)

android LOG 阿里技术协会 string class activity

摘要: Android Studio 2.0开始支持 Instant Run 特性, 使得在开发过程中能快速将代码变化更新到设备上。之前,更新代码之后需要先编译一个完整的新Apk,卸载设备上已安装的这个 Apk (若有),再 push 到设备安装,再启动。

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启动后,会持续监听是否有代码更新,如果有便加载到本地后进行热更新。具体的更新逻辑,请看下一篇博客。

本文为云栖社区原创内容,未经允许不得转载,如需转载请发送邮件至yqeditor@list.alibaba-inc.com;如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

用云栖社区APP,舒服~

【云栖快讯】浅析混合云和跨地域网络构建实践,分享高性能负载均衡设计,9月21日阿里云专家和你说说网络那些事儿,足不出户看直播,赶紧预约吧!  详情请点击

网友评论

一站式提供企业即时通讯、销售管理、协同办公。

基于深度学习技术及阿里巴巴多年的海量数据支撑, 提供多样化的内容识别服务,能有效帮助用户降低违规风险。其产品包括...

阿里云移动APP解决方案,助力开发者轻松应对移动app中随时可能出现的用户数量的爆发式增长、复杂的移动安全挑战等...

为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...