写点什么

从零开始分析 InstantRun 源码,kotlin 实现接口

用户头像
Android架构
关注
发布于: 刚刚

git clone https://android.googlesource.com/platform/tools/adt/idea


我们可以在 build.gradle 中添加一行代码,查看启动 gradle 的命令和全部参数


println getGradle().getStartParameter()


我在 3.4.2 的 Android Studio 中看到 projectProperties={android.optional.compilation=INSTANT_DEV,这里就表示开启 instant-run 支持了


StartParameter{taskRequests=[DefaultTaskExecutionRequest{args=[:app:assembleDebug],projectPath='null'}], excludedTaskNames=[], currentDir=F:\GitAndroid\RxDemo, searchUpwards=true, projectProperties={android.optional.compilation=INSTANT_DEV, android.injected.build.density=xxhdpi, android.injected.coldswap.mode=MULTIAPK, android.injected.build.api=28, android.injected.invoked.from.ide=true, android.injected.build.abi=arm64-v8a,armeabi-v7a,armeabi, android.injected.restrict.variant.name=debug, android.injected.restrict.variant.project=:app}, systemPropertiesArgs={}, gradleUserHomeDir=C:\Users\Jackie.gradle, gradleHome=C:\Users\Jackie.gradle\wrapper\dists\gradle-4.4-all\9br9xq1tocpiv8o6njlyu5op1\gradle-4.4, logLevel=LIFECYCLE, showStacktrace=INTERNAL_EXCEPTIONS, buildFile=null, initScripts=[], dryRun=false, rerunTasks=false, recompileScripts=false, offline=false, refreshDependencies=false, parallelProjectExecution=false, configureOnDemand=false, maxWorkerCount=8, buildCacheEnabled=false, interactive=false}:app:buildInfoDebugLoader


在 3.6.0 的 Android Studio 中就看不到了


StartParameter{taskRequests=[DefaultTaskExecutionRequest{args=[:app:assembleDebug],projectPath='null'}], excludedTaskNames=[], currentDir=/Users/jackie/Desktop/WorkPlace/AndroidWorkPlace/MyApplication2, searchUpwards=true, projectProperties={android.injected.build.density=xhdpi, android.injected.build.api=29, android.injected.invoked.from.ide=true, android.injected.build.abi=x86}, systemPropertiesArgs={idea.active=true, idea.version=3.6}, gradleUserHomeDir=/Users/jackie/.gradle, gradleHome=/Users/jackie/.gradle/wrapper/dists/gradle-5.6.4-all/ankdp27end7byghfw1q2sw75f/gradle-5.6.4, logLevel=LIFECYCLE, showStacktrace=INTERNAL_EXCEPTIONS, buildFile=null, initScripts=[], dryRun=false, rerunTasks=false, recompileScripts=false, offline=false, refreshDependencies=false, parallelProjectExecution=false, configureOnDemand=false, maxWorkerCount=12, buildCacheEnabled=false, interactive=false, writeDependencyLocks=false}app: 'annotationProcessor' dependencies won't be recognized as kapt annotation processors. Please change the configuration name to 'kapt' for these artifacts: 'com.alibaba:arouter-compiler:1.2.2'.


到了 android.gradle 插件的执行逻辑里,会被转成如下枚举定义,分别表示不同的编译类型:


//源码路径 gradle3.0.0 版本//Users/jackie/Desktop/WorkPlace/InstantRun/base/build-system/builder-、model/src/main/java/com/android/builder/model/OptionalCompilationStep.java


/**


  • enum describing possible optional compilation steps. This can be used to turn on java byte code

  • manipulation in order to support instant reloading, or profiling, or anything related to

  • transforming java compiler .class files before they are processed into .dex files.*/public enum OptionalCompilationStep {


/**


  • presence will turn on the InstantRun feature./INSTANT_DEV,/*

  • Force rebuild of cold swap artifacts.

  • <p>Dex files and/or resources.ap_ for ColdswapMode.MULTIDEX and some split APKs for

  • ColdswapMode.MULTIAPK./RESTART_ONLY,/*

  • Force rebuild of fresh install artifacts.

  • <p>A full apk for ColdswapMode.MULTIDEX and all the split apks for ColdswapMode.MULTIAPK.*/FULL_APK,}


####Gradle4.1 的 Instant Run


源码分析是基于 Gradle4.1 版本研究的 Instant Run,但是这个版本的 Instant Run 功能已经削减很多了,下面还会介绍其他版本的 Gradle


运行后反编译 app-debug.apk 会找到多个 dex(一般是两个),一开始是通过 dex2jar-2.0 和 jd-gui,但是有时候有些方法无法进行反编译而是依旧显示初始的字节码,很不方便阅读,后来使用了 jadx-gui 进行直接反编译 apk,使用很方便,但依旧还是会有些方法还是显示字节码,所以我是两者交叉着看,但是有时候甚至两者都是字节码,只能上网上直接找别人的博客代码了。


因为我研究的版本是基于 Gradle4.1 的,仅仅剩下可怜的热插拔和处理资源补丁,而且我还找不到 intant-run.zip 了,所以我找不到项目中的代码了,不在 dex 文件中,原本这玩意解压 apk 之后就有了,所以暂时只能在 build 下的目录里面寻找了,后面再看看这些文件时如何弄到 apk 当中。


InstantRunContentProvider 的 onCreate 方法中初始化 Socket


public boolean onCreate() {if (isMainProcess()) { //只支持主进程 Log.i("InstantRun", "starting instant run server: is main process");Server.create(getContext());return true;}Log.i("InstantRun", "not starting instant run server: not main process");return true;}


然后启动一个 socket 监听 Android Studio 推送的消息


private class SocketServerThread extends Thread {private SocketServerThread() {}


public void run() {while (true) {try {LocalServerSocket localServerSocket = Server.this.serverSocket;if (localServerSocket == null)return;LocalSocket localSocket = localServerSocket.accept();if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Received connection from IDE: spawning connection thread");(new Server.SocketServerReplyThread(localSocket)).run();if (wrongTokenCount > 50) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Stopping server: too many wrong token connections");Server.this.serverSocket.close();return;}} catch (Throwable throwable) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Fatal error accepting connection on local socket", throwable);}}}}


然后在 SocketServerReplyThread 的 run 方法值接受数据并处理


//处理补丁 private int handlePatches(List<ApplicationPatch> paramList, boolean paramBoolean, int paramInt) {if (paramBoolean)FileManager.startUpdate();for (ApplicationPatch applicationPatch : paramList) {String str = applicationPatch.getPath();if (str.equals("classes.dex.3")) { //如果有 classes.dex.3 处理热插拔 paramInt = handleHotSwapPatch(paramInt, applicationPatch);continue;}if (isResourcePath(str))//处理资源补丁 paramInt = handleResourcePatch(paramInt, applicationPatch, str);}if (paramBoolean)FileManager.finishUpdate(true);return paramInt;}


这里先来看看 ApplicationPatch 是什么


public static List<ApplicationPatch> read(DataInputStream paramDataInputStream) throws IOException {int j = paramDataInputStream.readInt();if (Log.logging != null && Log.logging.isLoggable(Level.FINE))Log.logging.log(Level.FINE, "Receiving " + j + " changes");ArrayList<ApplicationPatch> arrayList = new ArrayList(j);for (int i = 0; i < j; i++) {String str = paramDataInputStream.readUTF();byte[] arrayOfByte = new byte[paramDataInputStream.readInt()];paramDataInputStream.readFully(arrayOfByte);arrayList.add(new ApplicationPatch(str, arrayOfByte));}return arrayList;}


可以看到 ApplicationPatch 是从 Socket 接收到的数据输入流中调用 readFully 来读取的,关于 readFully 的使用 while 循环判断 byte 数组是否已经读满所有数据,如果没有读满则继续读取补充直到读满为止,从而改善输入流出现空档,造成 read 方法直接跳出的问题。即通过缓冲来保证数量的完整,也算是常用的一种方法。所以以后若要读取特定长度的数据,使用 readFully 读取更加安全。


#####1.处理热插拔


下面来看看是如何处理热插拔的


//处理热插拔 private int handleHotSwapPatch(int paramInt, ApplicationPatch paramApplicationPatch) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Received incremental code patch");try {//创建或获取“data/data/applicationid/files/instant-run/dex”文件路径 String str1 = FileManager.writeTempDexFile(paramApplicationPatch.getBytes());if (str1 == null) {Log.e("InstantRun", "No file to write the code to");return paramInt;}if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Reading live code from " + str1);


String str2 = FileManager.getNativeLibraryFolder().getPath();//反射构造 AppPatchesLoaderImpl 实例 Class<?> clazz = Class.forName("com.android.tools.fd.runtime.AppPatchesLoaderImpl", true, (ClassLoader)new DexClassLoader(str1, this.context.getCacheDir().getPath(), str2, getClass().getClassLoader()));try {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Got the patcher class " + clazz);PatchesLoader patchesLoader = (PatchesLoader)clazz.newInstance();if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Got the patcher instance " + patchesLoader);//获取热修复所要替换的类的 classnameString[] arrayOfString = (String[])clazz.getDeclaredMethod("getPatchedClasses", new Class[0]).invoke(patchesLoader, new Object[0]);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Got the list of classes ");int j = arrayOfString.le


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ngth;for (int i = 0; i < j; i++) {String str = arrayOfString[i];Log.v("InstantRun", "class " + str);}}//执行 AppPatchesLoaderImpl 的 load 方法进行类修复 boolean bool = patchesLoader.load();if (!bool)paramInt = 3;} catch (Exception exception) {}} catch (Throwable throwable) {Log.e("InstantRun", "Couldn't apply code changes", throwable);paramInt = 3;}return paramInt;}//AppPatchesLoaderImpl 实现抽象类 AbstractPatchesLoaderImpl,重写 getPatchedClasses 方法,重写方法中有提供需要热更新的类 public abstract class AbstractPatchesLoaderImpl implements PatchesLoader {public abstract String[] getPatchedClasses();


public boolean load() {try {//《《《《《《最关键的方法,一个个替换类中要替换的方法》》》》》》for (String str : getPatchedClasses()) {ClassLoader classLoader = getClass().getClassLoader();Object object1 = classLoader.loadClass(str + "change");field.setAccessible(true);Object object2 = field.get(null);if (object2 != null) {object2 = object2.getClass().getDeclaredField("$obsolete");if (object2 != null)object2.set(null, Boolean.valueOf(true));}field.set(null, object1);if (Log.logging != null && Log.logging.isLoggable(Level.FINE))Log.logging.log(Level.FINE, String.format("patched %s", new Object[] { str }));}} catch (Exception exception) {if (Log.logging != null)Log.logging.log(Level.SEVERE, String.format("Exception while patching %s", new Object[] { "foo.bar" }), exception);return false;}return true;}}//AppPatchesLoaderImpl 继承 AbstractPatchesLoaderImpl,真正的实现类,这里我们只要求 change 的 MainActivity 这个类里面的方法 public class AppPatchesLoaderImpl extends AbstractPatchesLoaderImpl {public static final long BUILD_ID = 1597285889481L;


public AppPatchesLoaderImpl() {}


public String[] getPatchedClasses() {return new String[]{"com.example.jackie.instantrundemo.MainActivity"};}}


其中 DexClassLoader 的构造方法定义


//dexPath:被解压的 apk 路径,不能为空。//optimizedDirectory:解压后的.dex 文件的存储路径,不能为空。这个路径强烈建议使用应用程序的私有路径,不要放到 sdcard 上,否则代码容易被注入攻击。//libraryPath:os 库的存放路径,可以为空,若有 os 库,必须填写。//parent:父类加载器,一般为 context.getClassLoader(),使用当前上下文的类加载器。DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)


我们的 MainActivity 会被修改成这样


public class MainActivity extends AppCompatActivity {public static final long serialVersionUID = 2158910920756968252L;


//重写空构造方法,方便于替换该方法的实现 public MainActivity() {IncrementalChange var1 = change;if(var1 != null) {Object[] var10001 = (Object[])var1.accessdispatch("initdispatch("init$body.(Lcom/example/jackie/instantrundemo/MainActivity;[Ljava/lang/Object;)V", var2);} else {super();}}


public void onCreate(Bundle savedInstanceState) {IncrementalChange var2 = change;if(var2 != null) {var2.accessdispatch("onCreate.(Landroid/os/Bundle;)V", new Object[]{this, savedInstanceState});} else {super.onCreate(savedInstanceState);this.setContentView(2130968603);if(this.test(30) > 20333005) {Log.d("jackie", "==4444099994==sf=dd=ddecf==999=abc==");} else {Log.d("jackie", "==999999999999=");}


byte b = 0;Toast.makeText(this, "hellodd4fdddd", 1).show();Log.d("jackie", "===d=666==dd=dddd==abc==" + b);}}


public int test(int a) {IncrementalChange var2 = change;if(var2 != null) {return ((Number)var2.accessdispatch("test.(I)I", new Object[]{this, new Integer(a)})).intValue();} else {byte age = 100;int b = 300189 + age;


for(int i = 0; i < b + 9; ++i) {a += b;}


return 20 + a;}}//增加类的构造方法,方便与修改类的任何构造方法 MainActivity(Object[] var1, InstantReloadException var2) {String var3 = (String)var1[1];switch(var3.hashCode()) {case -2089128195:super();return;case 173992496:this();return;default:throw new InstantReloadException(String.format("String switch could not find '%s' with hashcode %s in %s", new Object[]{var3, Integer.valueOf(var3.hashCode()), "com/example/jackie/instantrundemo/MainActivity"}));}}}


在 MainActivity 中做个小修改,点击小闪电执行 Instant Run,可以看到 build 下面文件夹 4000 中会找一个 MainActivity$override.class 和 AppPatchesLoaderImpl.class


要替换的 MainActivity$override 实现了 IncrementalChange,从这里面进行方法的替换,所有的方法都会被替换,因为change值不为空


public class MainActivityoverride implements IncrementalChange {public MainActivityoverride() {}


public static Object init$args(MainActivity[] var0, Object[] var1) {Object[] var2 = new Object[]{new Object[]{var0, new Object[0]}, "android/support/v7/app/AppCompatActivity.()V"};return var2;}


public static void initthis, Object[] var1) {AndroidInstantRuntime.setPrivateField($this, new Integer(100), MainActivity.class, "cmd");}


public static void onCreate(MainActivity this, Bundle savedInstanceState) {Object[] var2 = new Object[]{savedInstanceState};MainActivity.accesssuper(this.setContentView(2130968603);if($this.test(30) > 20333005) {Log.d("jackie", "==44440999940==sf=dd=ddecf==999=abc==");} else {Log.d("jackie", "==999999999999=");}


byte b = 0;//因为修改了全局变量的值,所以进行处理 AndroidInstantRuntime.setPrivateField(this, MainActivity.class, "cmd")).intValue() + 100), MainActivity.class, "cmd");Toast.makeText($this, "hellodd4fdddd", 1).show();Log.d("jackie", "===d=666==dd=dddd==abc==" + b);}


public static int test(MainActivity this, int a) {int ageabc = 100 + ((Number)AndroidInstantRuntime.getPrivateField(this, MainActivity.class, "cmd")).intValue();int b = 300189 + ageabc;


for(int i = 0; i < b + 9; ++i) {a += b;}


return 20 + a;}


//替换相对应的方法,最后两个方法我估计是前面 MainActivity 类里面的 copypublic Object accessdispatch(String var1, Object... var2) {switch(var1.hashCode()) {case -1227667971:return new Integer(test((MainActivity)var2[0], ((Number)var2[1]).intValue()));case -641568046:onCreate((MainActivity)var2[0], (Bundle)var2[1]);return null;case 435530788:return initargs((MainActivity[])var2[0], (Object[])var2[1]);case 1043612718:init$body((MainActivity)var2[0], (Object[])var2[1]);return null;default:throw new InstantReloadException(String.format("String switch could not find '%s' with hashcode %s in %s", new Object[]{var1, Integer.valueOf(var1.hashCode()), "com/example/jackie/instantrundemo/MainActivity"}));}}}


#####2.处理资源补丁(温插拔)


下面来看看是如何处理资源补丁的,但是设计到资源的处理需要重启当前界面,我们先来看看重启 App 这种状况下的逻辑


//Server 类的 handle()case 5:if (authenticate(param1DataInputStream)) {Activity activity1 = Restarter.getForegroundActivity(Server.this.context);if (activity1 != null) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Restarting activity per user request");Restarter.restartActivityOnUiThread(activity1);}continue;}return;case 1:if (authenticate(param1DataInputStream)) {List<ApplicationPatch> list = ApplicationPatch.read(param1DataInputStream);if (list != null) {bool = Server.hasResources(list);i = param1DataInputStream.readInt();//处理 acitivity 或者把资源写到文件中 i = Server.this.handlePatches(list, bool, i);boolean bool1 = param1DataInputStream.readBoolean();param1DataOutputStream.writeBoolean(true);//重启 ActivityServer.this.restart(i, bool, bool1);}continue;}


我们先来看看 handlePatches 里面的 handleResourcePatch 是如何处理资源的


//Server.class//处理资源补丁 private static int handleResourcePatch(int paramInt, ApplicationPatch paramApplicationPatch, String paramString) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Received resource changes (" + paramString + ")");FileManager.writeAaptResources(paramString, paramApplicationPatch.getBytes());return Math.max(paramInt, 2);}//FileManager.class//writeAaptResourcespublic static void writeAaptResources(String paramString, byte[] paramArrayOfbyte) {File file1 = getResourceFile(getWriteFolder(false)); //获取资源文件 File file2 = file1.getParentFile();if (!file2.isDirectory() && !file2.mkdirs()) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Cannot create local resource file directory " + file2);return;}//把内容写到 resources.ap_资源中 if (paramString.equals("resources.ap_")) {writeRawBytes(file1, paramArrayOfbyte);return;}//把 raw 资源写入 writeRawBytes(file1, paramArrayOfbyte);}//getWriteFolderpublic static File getWriteFolder(boolean paramBoolean) {String str;if (leftIsActive()) {str = "right";} else {str = "left";}File file = new File(getDataFolder(), str);if (paramBoolean && file.exists()) {delete(file);if (!file.mkdirs())Log.e("InstantRun", "Failed to create folder " + file);}return file;}//getResourceFileprivate static File getResourceFile(File paramFile) { return new File(paramFile, "resources.ap_"); }


//writeRawBytespublic static boolean writeRawBytes(File paramFile, byte[] paramArrayOfbyte) {try {BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(paramFile));try {bufferedOutputStream.write(paramArrayOfbyte);bufferedOutputStream.flush();return true;} finally {bufferedOutputStream.close();}} catch (IOException iOException) {Log.wtf("InstantRun", "Failed to write file, clean project and rebuild " + paramFile, iOException);throw new RuntimeException(String.format("InstantRun could not write file %1$s, clean project and rebuild ", new Object[] { paramFile }));}}public static String writeTempDexFile(byte[] paramArrayOfbyte) {File file = getTempDexFile();if (file != null) {writeRawBytes(file, paramArrayOfbyte);return file.getPath();}Log.e("InstantRun", "No file to write temp dex content to");return null;}


下面来看看Server.this.restart(i, bool, bool1)是如何处理的,不必拘泥于细节是如何启动的,在里面找到一行关键代码


MonkeyPatcher.monkeyPatchExistingResources(this.context, str, list);


具体实现如下


public static void monkeyPatchExistingResources(Context context, String externalResourceFile, Collection<Activity> activities) {Collection<WeakReference<Resources>> references;if (externalResourceFile != null) {//通过反射获取 AssetManagerAssetManager newAssetManager = AssetManager.class.getConstructor(new Class[0]).newInstance(new Object[0]);Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", new Class[]{String.class});mAddAssetPath.setAccessible(true);//将当前的资源文件路径添加到 AssetManager 中 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);//进行资源初始化 StringBlock 对象 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 e) {throw new IllegalStateException(e);}Resources.Theme theme = activity.getTheme();try {Field ma = Resources.Theme.class.getDeclaredField("mAssets");ma.setAccessible(true);ma.set(theme, newAssetManager);} catch (NoSuchFieldException e2) {Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");themeField.setAccessible(true);Object impl = themeField.get(theme);Field ma2 = impl.getClass().getDeclaredField("mAssets");ma2.setAccessible(true);ma2.set(impl, newAssetManager);} catch (Throwable e3) {Log.e(Logging.LOG_TAG, "Failed to update existing theme for activity " + activity, e3);}Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");mt.setAccessible(true);mt.set(activity, (Object) null);Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme", new Class[0]);mtm.setAccessible(true);mtm.invoke(activity, new Object[0]);if (Build.VERSION.SDK_INT < 24) {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);}pruneResourceCaches(resources);}}//获取当前 JVM 中的 ResourcesManager 的 final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResourcesif (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((Object) null, new Object[0]);try {Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");fMActiveResources.setAccessible(true);references = ((ArrayMap) fMActiveResources.get(resourcesManager)).values();} catch (NoSuchFieldException e4) {Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");mResourceReferences.setAccessible(true);references = (Collection) mResourceReferences.get(resourcesManager);}} else {Class<?> activityThread = Class.forName("android.app.ActivityThread");Field fMActiveResources2 = activityThread.getDeclaredField("mActiveResources");fMActiveResources2.setAccessible(true);references = ((HashMap) fMActiveResources2.get(getActivityThread(context, activityThread))).values();}//循环便利当前 Resources,将其成员变量 mAssets 指向自定义的 newAssetManagerfor (WeakReference<Resources> wr : references) {Resources resources2 = (Resources) wr.get();if (resources2 != null) {try {Field mAssets2 = Resources.class.getDeclaredField("mAssets");mAssets2.setAccessible(true);mAssets2.set(resources2, newAssetManager);} catch (Throwable th) {Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");mResourcesImpl.setAccessible(true);Object resourceImpl = mResourcesImpl.get(resources2);Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");implAssets.setAccessible(true);implAssets.set(resourceImpl, newAssetManager);}//更新资源 resources2.updateConfiguration(resources2.getConfiguration(), resources2.getDisplayMetrics());}}}}


在研究过程中,本来想基于退出 gradle4.1 然后在 gradle2.2.3 重新再搞一下,后面想想不把 4.1 研究透再去搞 2.2.3 总是心有不甘,网上也基本找不到 gradle 4.1 的研究的,其实在 2.3.0 之后就没有 instant-run.zip 包了,但是好像所有人都没有提到这点,难道他们都是基于 2.2.3 或者更早的?


####找不到的项目代码去哪里了


下面来看看我们的 MainActivity,MyApplication 等文件在去哪里了,找到之前的 Server(SocketServerThread),明白我们是从 AS 端接受这些文件的,接受这些 dex 文件后,存储在 app 的 cache 文件中(getWriteFolder),并进行处理,


private class SocketServerThread extends Thread {private SocketServerThread() {}


public void run() {while (true) {try {LocalServerSocket localServerSocket = Server.this.serverSocket;if (localServerSocket == null)return;//接受 AS 发过来的文件 LocalSocket localSocket = localServerSocket.accept();if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Received connection from IDE: spawning connection thread");(new Server.SocketServerReplyThread(localSocket)).run();if (wrongTokenCount > 50) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Stopping server: too many wrong token connections");Server.this.serverSocket.close();return;}} catch (Throwable throwable) {if (Log.isLoggable("InstantRun", 2))Log.v("InstantRun", "Fatal error accepting connection on local socket", throwable);}}}}


下面可以使用我们一开始下载的源码,在 instant-run 下面的 intant-run-client,InstantClient 类中,将文件发送到设备中


private void transferBuildIdToDevice(@NonNull IDevice device, @NonNull String buildId) {try {String remoteIdFile = getDeviceIdFolder(mPackageName);//noinspection SSBasedInspection This should workFile local = File.createTempFile("build-id", "txt");local.deleteOnExit();Files.write(buildId, local, Charsets.UTF_8);device.pushFile(local.getPath(), remoteIdFile);} catch (IOException ioe) {mLogger.warning("Couldn't write build id file: %s", ioe);} catch (AdbCommandRejectedException | TimeoutException | SyncException e) {mLogger.warning("%s", Throwables.getStackTraceAsString(e));}}


我们回看之前的 handleHotSwapPatch 方法中读取文件的方式,可以看到在 cache 文件中


private int handleHotSwapPatch(int updateMode, @NonNull ApplicationPatch patch) {···try {String dexFile = FileManager.writeTempDexFile(patch.getBytes());···String nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();DexClassLoader dexClassLoader = new DexClassLoader(dexFile,context.getCacheDir().getPath(), nativeLibraryPath,getClass().getClassLoader());


####使用 InstantRun 更新的文件去哪里了


下面来看看我们的MainActivity$override,MyApplication$override,AppPatchesLoaderImpl 等文件在去哪里了,我们进入应用内部的/data/data/com.example.jackie.instantrundemo/files/instant-run/dex-temp中会发现一个 reload0x0000.dex 文件,里面就有提供更新的内容,instant-run 里面中的 right 是用于存储 resource.ap_。

其他一些 Gradle 版本

Gradle2.2.3
用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
从零开始分析InstantRun源码,kotlin实现接口