新聞中心
Instant Run

創(chuàng)新互聯(lián)專(zhuān)注于企業(yè)營(yíng)銷(xiāo)型網(wǎng)站建設(shè)、網(wǎng)站重做改版、瓊結(jié)網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5頁(yè)面制作、購(gòu)物商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為瓊結(jié)等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。
Instant Run,是android studio2.0新增的一個(gè)運(yùn)行機(jī)制,在你編碼開(kāi)發(fā)、測(cè)試或debug的時(shí)候,它都能顯著減少你對(duì)當(dāng)前應(yīng)用的構(gòu)建和部署的時(shí)間。通俗的解釋就是,當(dāng)你在Android Studio中改了你的代碼,Instant Run可以很快的讓你看到你修改的效果。而在沒(méi)有Instant Run之前,你的一個(gè)小小的修改,都肯能需要幾十秒甚至更長(zhǎng)的等待才能看到修改后的效果。
傳統(tǒng)的代碼修改及編譯部署流程
傳統(tǒng)的代碼修改及編譯流程如下:構(gòu)建整個(gè)apk → 部署app → app重啟 → 重啟Activity
Instant Run編譯和部署流程
Instant Run構(gòu)建項(xiàng)目的流程:構(gòu)建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署,冷部署
熱拔插,溫拔插,冷拔插
熱拔插:代碼改變被應(yīng)用、投射到APP上,不需要重啟應(yīng)用,不需要重建當(dāng)前activity。
場(chǎng)景:適用于多數(shù)的簡(jiǎn)單改變(包括一些方法實(shí)現(xiàn)的修改,或者變量值修改)
溫拔插:activity需要被重啟才能看到所需更改。
場(chǎng)景:典型的情況是代碼修改涉及到了資源文件,即resources。
冷拔插:app需要被重啟(但是仍然不需要重新安裝)
場(chǎng)景:任何涉及結(jié)構(gòu)性變化的,比如:修改了繼承規(guī)則、修改了方法簽名等。
首次運(yùn)行Instant Run,Gradle執(zhí)行過(guò)程
一個(gè)新的App Server類(lèi)會(huì)被注入到App中,與Bytecode instrumentation協(xié)同監(jiān)控代碼的變化。
同時(shí)會(huì)有一個(gè)新的Application類(lèi),它注入了一個(gè)自定義類(lèi)加載器(Class Loader),同時(shí)該Application類(lèi)會(huì)啟動(dòng)我們所需的新注入的App Server。于是,Manifest會(huì)被修改來(lái)確保我們的應(yīng)用能使用這個(gè)新的Application類(lèi)。(這里不必?fù)?dān)心自己繼承定義了Application類(lèi),Instant Run添加的這個(gè)新Application類(lèi)會(huì)代理我們自定義的Application類(lèi))
至此,Instant Run已經(jīng)可以跑起來(lái)了,在我們使用的時(shí)候,它會(huì)通過(guò)決策,合理運(yùn)用冷溫?zé)岚尾鍋?lái)協(xié)助我們大量地縮短構(gòu)建程序的時(shí)間。
在Instant Run運(yùn)行之前,Android Studio會(huì)檢查是否能連接到App Server中。并且確保這個(gè)App Server是Android Studio所需要的。這同樣能確保該應(yīng)用正處在前臺(tái)。
熱拔插
Android Studio monitors: 運(yùn)行著Gradle任務(wù)來(lái)生成增量.dex文件(這個(gè)dex文件是對(duì)應(yīng)著開(kāi)發(fā)中的修改類(lèi)) Android Studio會(huì)提取這些.dex文件發(fā)送到App Server,然后部署到App(Gradle修改class的原理,請(qǐng)戳鏈接)。
App Server會(huì)不斷監(jiān)聽(tīng)是否需要重寫(xiě)類(lèi)文件,如果需要,任務(wù)會(huì)被立馬執(zhí)行。新的更改便能立即被響應(yīng)。我們可以通過(guò)打斷點(diǎn)的方式來(lái)查看。
溫拔插
溫拔插需要重啟Activity,因?yàn)橘Y源文件是在Activity創(chuàng)建時(shí)加載,所以必須重啟Activity來(lái)重載資源文件。
目前來(lái)說(shuō),任何資源文件的修改都會(huì)導(dǎo)致重新打包再發(fā)送到APP。但是,google的開(kāi)發(fā)團(tuán)隊(duì)正在致力于開(kāi)發(fā)一個(gè)增量包,這個(gè)增量包只會(huì)包裝修改過(guò)的資源文件并能部署到當(dāng)前APP上。
所以溫拔插實(shí)際上只能應(yīng)對(duì)少數(shù)的情況,它并不能應(yīng)付應(yīng)用在架構(gòu)、結(jié)構(gòu)上的變化。
注:溫拔插涉及到的資源文件修改,在manifest上是無(wú)效的(這里的無(wú)效是指不會(huì)啟動(dòng)Instant Run),因?yàn)椋琺anifest的值是在APK安裝的時(shí)候被讀取,所以想要manifest下資源的修改生效,還需要觸發(fā)一個(gè)完整的應(yīng)用構(gòu)建和部署。
冷拔插
應(yīng)用部署的時(shí)候,會(huì)把工程拆分成十個(gè)部分,每部分都擁有自己的.dex文件,然后所有的類(lèi)會(huì)根據(jù)包名被分配給相應(yīng)的.dex文件。當(dāng)冷拔插開(kāi)啟時(shí),修改過(guò)的類(lèi)所對(duì)應(yīng)的.dex文件,會(huì)重組生成新的.dex文件,然后再部署到設(shè)備上。
之所以能這么做,是依賴于Android的ART模式,它能允許加載多個(gè).dex文件。ART模式在android4.4(API-19)中加入,但是Dalvik依然是選擇,到了android5.0(API-21),ART模式才成為系統(tǒng)默認(rèn)選擇,所以Instant Run只能運(yùn)行在API-21及其以上版本。
使用Instant Run一些注意點(diǎn)
Instant Run是被Android Studio控制的。所以我們只能通過(guò)IDE來(lái)啟動(dòng)它,如果通過(guò)設(shè)備來(lái)啟動(dòng)應(yīng)用,Instant Run會(huì)出現(xiàn)異常情況。在使用Instant Run來(lái)啟動(dòng)Android app的時(shí)候,應(yīng)注意以下幾點(diǎn):
如果應(yīng)用的minSdkVersion小于21,可能多數(shù)的Instant Run功能會(huì)掛掉,這里提供一個(gè)解決方法,通過(guò)product flavor建立一個(gè)minSdkVersion大于21的新分支,用來(lái)debug。
Instant Run目前只能在主進(jìn)程里運(yùn)行,如果應(yīng)用是多進(jìn)程的,類(lèi)似微信,把webView抽出來(lái)單獨(dú)一個(gè)進(jìn)程,那熱、溫拔插會(huì)被降級(jí)為冷拔插。
在Windows下,Windows Defender Real-Time Protection可能會(huì)導(dǎo)致Instant Run掛掉,可用通過(guò)添加白名單列表解決。
暫時(shí)不支持Jack compiler,Instrumentation Tests,或者同時(shí)部署到多臺(tái)設(shè)備。
結(jié)合Demo深度理解
為了方便大家的理解,我們新建一個(gè)項(xiàng)目,里面不寫(xiě)任何的邏輯功能,只對(duì)application做一個(gè)修改:
首先,我們先反編譯一下APK的構(gòu)成,使用的工具:d2j-dex2jar 和jd-gui。
我們要看的啟動(dòng)的信息就在這個(gè)instant-run.zip文件里面,解壓instant-run.zip,我們會(huì)發(fā)現(xiàn),我們真正的業(yè)務(wù)代碼都在這里。
從instant-run文件中我們猜想是BootstrapApplication替換了我們的application,Instant-Run代碼作為一個(gè)宿主程序,將app作為資源dex加載起來(lái)。
那么InstantRun是怎么把業(yè)務(wù)代碼運(yùn)行起來(lái)的呢?
Instant Run如何啟動(dòng)app
按照我們上面對(duì)instant-run運(yùn)行機(jī)制的猜想,我們首先看一下appliaction的分析attachBaseContext和onCreate方法。
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);
- }
- }
- }
我們依次需要關(guān)注的方法有:
createResources → setupClassLoaders → createRealApplication → 調(diào)用realApplication的attachBaseContext方法
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);
- }
- }
- }
說(shuō)明:該方法主要是判斷資源resource.ap_是否改變,然后保存resource.ap_的路徑到externalResourcePath中。
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);
- }
- }
說(shuō)明,該方法是初始化一個(gè)ClassLoaders并調(diào)用IncrementalClassLoader。
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 %
網(wǎng)頁(yè)名稱:深入理解AndroidInstantRun運(yùn)行機(jī)制
路徑分享:http://m.fisionsoft.com.cn/article/dhhscog.html


咨詢
建站咨詢
