从零开始分析 InstantRun 源码,kotlin 实现接口
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
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_。
评论