Android Studio 2.0開始支持 Instant Run 特性, 使得在開發過程當中能快速將代碼變化更新到設備上。以前,更新代碼以後須要先編譯一個完整的新Apk,卸載設備上已安裝的這個 Apk (如有),再 push 到設備安裝,再啓動。有了 Instant Run 特性以後,只須要 push 一些增量到設備上,直接執行,能夠爲開發人員節省大量時間。固然 Instant Run 特徵只在 debug 時有效,對發佈 release 版沒有任何影響。java
Instant Run 經過 hot swap, warm swap, code swap 三種 swap 來實現。Android Studio 會根據代碼的改變自動決定 push 哪一種 swap 到設備上,並根據不一樣的 swap 執行不一樣的行爲。android
代碼改變內容 | Instant Run 行爲 |
---|---|
修改一個實例方法或者一個靜態方法的實現 | hot swap: 這是最快的狀況,下次調用該方法時直接使用更新後的方法 |
修改或者刪除一個資源 | warm swap: App 保護運行狀態,可是會自動重啓 activity, 因此屏幕會閃一下 |
|
|
|
須要從新編譯整個App |
下面分析 Instant Run 的實現原理。Instant Run 特性是經過 gradle plugin (版本大於2.0)與 instant-run.jar 來實現的。gradle plugin 會對dex做一些必要的修改, 而 instant-run.jar 會被編譯進 dex, 在運行的時候執行相應的功能。數組
<!-- 若 Apk 無 Application, 則在 AndroidManifest 中增長:--> <application android:name="com.android.tools.fd.runtime.BootstrapApplication" ...... > <!-- 若 Apk 已經有 Application,則更改成: --> <application android:name="com.android.tools.fd.runtime.BootstrapApplication" name="com.testbugrpt.MyApplication" ...... >
自動增長依賴 Jar 包,instant-run.jar服務器
先介紹幾個類,剩下的後面遇到再介紹:數據結構
Server:主要是創建一個 socket 服務器等待 Android Studio 的鏈接。Anroid Studio 發送相關的命令及數據(hot, warm, cold..., 命令字定義在 ProtocolConstants 中),Server 接收並執行,實現 Instant Run。app
IncrementalChange:是一個接口:socket
public interface IncrementalChange { Object access$dispatch(String arg1, Object[] arg2); }
AppInfo:含有一些基本信息:ide
BootstrapApplication, 因爲被設置成了 Apk 的 Application,因此 App 啓動時最先獲得執行機會:函數
首先會執行 attachBaseContext 方法:gradle
在 setupClassLoaders 方法中,new 一個 IncrementalClassLoader, 用這個 loader 加載補丁 dex, 並將這個loader設置爲當前 classloader 的 parent, 最後啓動 server。
初始化 apk 本來 application, 並執行 attachBaseContext 方法。
再執行onCreate方法:
MonkeyPatcher.mokeyPatchApplication 主要是經過反射調用各類未導出的方法,將 ActivityThread 中的一些關於 application 的變量由 BootstrapApplication 更改成原 apk 的 application, 這一步與加殼中的邏輯是同樣的。
MonkeyPatcher.MonkeyPathExisingResources 處理資源相關內容, 後面再詳細介紹。
寫個簡單的類GenString:
public class GenString { public String genString (int i){ return String.valueOf(i); } public static String genString2(int i){ return String.valueOf(i); } }
反編譯生成的 Debug 版本 Apk, 分析對應的 GenString.smali:
增長靜態變量:
# static fields
.field public static volatile synthetic $change:Lcom/android/tools/fd/runtime/IncrementalChange;
方法 genString ( genString2 相似)被更改成:
public String genString(int i) { Object v0_1; IncrementalChange v0 = GenString.$change; if(v0 != null) { v0_1 = v0.access$dispatch("genString.(I)Ljava/lang/String;", new Object[]{this, new Integer(i)}); } else { String v0_2 = String.valueOf(i); } return ((String)v0_1); }
能夠看到整個函數的流程被類靜態變量 $change 控制,若 $change 爲 null,則執行原始邏輯;若 $change 不爲 null, 則執行 $change.access$dispatch 方法,該方法的第一個參數爲 getString 方法的簽名,第二個參數爲一個數組,用於放置getString 的全部參數。
第一次運行時,$change 會被設置爲 null, 因此就是執行原始邏輯。當有 genString 有更改並啓動 instant run 時,$change 就會被賦值,這樣當執行到 genString 時,會調用 $change.access$dispath 方法。
下面分析這個 $change 什麼時候會被設置,會被設置成什麼, 以及 access$dispatch 的實現。
如今更改 genString2 方法爲:
public static String genString2(int i){ return "helloworld_" + String.valueOf(i); }
instant run以後,發現/data/data/com.testbugrpt/files/instant-run/dex-temp目錄下增長文件:
-rw------- u0_a239 u0_a239 2276 2016-04-19 02:25 reload0x0000.dex
能夠發現這個Dex幾乎只是包含了被修改的類。
AppPatchesLoaderImpl中包含有全部被修改了代碼的類名。
另外在被修改類的類名後面加上 $override 組成一個新 classname, 並實現 IncrementalChange 接口。在這個類中:
如今能夠猜測到:上一節分析到的 $change 會被設置爲 GenString$override, $change.access$dispath 會根據方法的簽名調用對應的修改後的方法。下面確認這一過程。
Server接收到請求後:
請求數據包格式大概像這樣子:
對於 hot swap
動態加載 Android Studio 傳送過來的補丁 Dex, 並生成 com.android.tools.fd.runtime.AppPatchesLoaderImpl 的一個對象。因爲 AppPatchesLoaderImpl 繼承自 AbstractPatchesLoaderImpl, 因此上面的 load 方法實際上是 AbstractPatchesLoaderImpl 的:
將每個須要 patch 的 class 的靜態變量 $change 設置爲補丁 dex 中的 oriClassName$override。
這樣,若是下次調用這些修改後的方法,就會由於 $change 爲非空而調用 $change.access$dispath 方法, 這個方法經過第一個參數(即方法簽名)從而肯定到補丁 Dex 中的相應方法,最終實現 hot swap。
點擊運行後,發現增長了一個文件:
root@g520:/data/data/com.testbugrpt/files/instant-run/right # ll
-rw------- u0_a240 u0_a240 231210 2016-04-20 01:53 resources.ap_
實際上是一個 zip 資源包, 就像系統的 framework-res.apk 這個包裏包含了當前的全部資源,包含了更新後的資源。
先簡單介紹一下 Activity 獲取資源時所涉及的幾個主要數據結構(因系統版本不一樣有差別):
Activity 處理資源是經過 mResource 來進行的,而 Resource 會將這些操做都代理給 mAsset。
/system/framework/framework-res.apk 是系統資源,/data/app/xxx.apk 是 App 自身路徑,表示能夠訪問本身自己的資源。若是把這個路徑指向其它的資源路徑,那就能夠訪問其它的資源了。Instant Run 熱更新資源的思路就是,新建一個 AssetManager 對象,調用 addAssetPath 將 resources.ap_ 加到它的 path 上面。而後遍歷全部的 Activity, 將每個 Activity 的 mResource 中的 mAsset 設置爲新建的 AssetManager 對象。
固然還有許多細節要處理。Activity中關於theme中的AssetManger對象也須要更新,須要清空當前Resource對象的cache等等。
這個實現代碼主要在 MonkeyPatcher.MonkeyPathExisingResources。
Instant Run可能存在的問題:
一、Instant Run 須要爲 class 增長 method 來實現,若原 dex 的方法數量接近64K, 使用 Instant Run 大約將增長(140 + 3 * class個數)method, 致使超過64K上限,引發 build 出錯。
二、若是已經使用 multi-dex, 而主 dex 方法接近65K, 也可能致使 build 出錯。
三、multiprocess 時,可能禁用 instant run。