深度理解Android InstantRun原理(一)
簡單介紹一下Instant Run,它是Android Studio2.0以后新增的一個運行機制,能夠顯著減少你第二次及以后的構建和部署時間。簡單通俗的解釋就是,當你在Android Studio中改了你的代碼,Instant Run可以很快的讓你看到你修改的效果。而在沒有Instant Run之前,你的一個小小的修改,都肯能需要幾十秒甚至更長的等待才能看到修改后的效果。
傳統的代碼修改及編譯部署流程
構建整個apk → 部署app → app重啟 → 重啟Activity
而Instant Run則需要更少的時間。
Instant Run編譯和部署流程
只構建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署,冷部署
熱部署
Incremental code changes are applied and reflected in the app without needing to relaunch the app or even restart the current Activity. Can be used for most simple changes within method implementations.
方法內的簡單修改,無需重啟app和Activity
溫部署
The Activity needs to be restarted before changes can be seen and used. Typically required for changes to resources.
app無需重啟,但是activity需要重啟,比如資源的修改。
冷部署
The app is restarted (but still not reinstalled). Required for any structural changes such as to inheritance or method signatures.
app需要重啟,比如繼承關系的改變或方法的簽名變化等。
上述說這么多概念,估計大家對Instant Run應該有了大體的認知了。那么它的實現原理是什么呢?其實,在沒有看案例之前,我基本上可以猜測到Instant Run的思路,基于目前比較火的插件化框架,是比較容易理解Instant Run的。但Instant Run畢竟是Google官方的工具,具有很好的借鑒意義。
Demo案例
新建一個簡單的android studio項目,新建自己的MyApplication,在AndroidManifest文件中設置:
首先,我們先反編譯一下APK的構成:
使用的工具:d2j-dex2jar 和jd-gui
里面有2個dex文件和一個instant-run.zip文件。首先分別看一下兩個dex文件的源碼:
classes.dex的反編譯之后的源碼:
里面只有一個AppInfo,保存了app的基本信息,主要包含了包名和applicationClass。
classes2.dex反編譯之后的源碼:
我們赫然發現,兩個dex中竟然沒有一句我們自己寫的代碼??那么代碼在哪里呢?你可能猜到,app真正的業務dex在instant-run.zip中。解壓instant-run.zip之后,如下圖所示:
反編譯之后,我們會發現,我們真正的業務代碼都在這里。
另外,我們再decode看一下AndroidManifest文件
//TODO
我們發現,我們的application也被替換了,替換成了com.android.tools.fd.runtime.BootstrapApplication
看到這里,那么大體的思路,可以猜到:
1.Instant-Run代碼作為一個宿主程序,將app作為資源dex加載起來,和插件化一個思路
2.那么InstantRun是怎么把業務代碼運行起來的呢?
InstantRun啟動app
首先BootstrapApplication分析,按照執行順序,依次分析attachBaseContext和onCreate方法。
1.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方法
1.1.createResources
首先看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中
1.2.setupClassLoaders
private static void setupClassLoaders(Context context, String codeCacheDir,
long apkModified) {
List<String> dexList = FileManager.getDexList(context, apkModified); Class<Server> server = Server.class; Class<MonkeyPatcher> 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);
}
}
繼續看IncrementalClassLoader.inject方法:
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<String> 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<String> dexes,
ClassLoader original) { String pathBuilder = createDexPath(dexes); return new DelegateClassLoader(pathBuilder, new File(codeCacheDir),
nativeLibraryPath, original);
} private static String createDexPath(List<String> 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<String> dexes) {
IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader(
classLoader, nativeLibraryPath, codeCacheDir, dexes);
setParent(classLoader, incrementalClassLoader); return incrementalClassLoader;
}
}
inject方法是用來設置classloader的父子順序的,使用IncrementalClassLoader來加載dex。由于ClassLoader的雙親委托模式,也就是委托父類加載類,父類中找不到再在本ClassLoader中查找。
調用之后的效果如下圖所示:
我們可以在MyApplication中,用代碼驗證一下
@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();
}
}
運行結果:
...06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader
由此,我們已經知道了,當前PathClassLoader委托IncrementalClassLoader加載dex。
來自:http://mp.weixin.qq.com/s?__biz=MzA4MjA0MTc4NQ==&mid=2651574039&idx=1&sn=23e944c522ca55169279b8317c36752a&scene=1&srcid=0827t6aBnCuSjJCZlasyAsdD#wechat_redirect