1. 程式人生 > >InstantRun框架原始碼分析之二

InstantRun框架原始碼分析之二

4, onCreate

BootstrapApplication的onCreate方法如下,

public void onCreate() {
		if (!AppInfo.usingApkSplits) {
			MonkeyPatcher.monkeyPatchApplication(this, this,
					this.realApplication, this.externalResourcePath);

			MonkeyPatcher.monkeyPatchExistingResources(this,
					this.externalResourcePath, null);
		} else {
			MonkeyPatcher.monkeyPatchApplication(this, this,
					this.realApplication, null);
		}
		super.onCreate();
		if (AppInfo.applicationId != null) {
			try {
				boolean foundPackage = false;
				int pid = Process.myPid();
				ActivityManager manager = (ActivityManager) getSystemService("activity");

				List<ActivityManager.RunningAppProcessInfo> processes = manager
						.getRunningAppProcesses();
				boolean startServer = false;
				if ((processes != null) && (processes.size() > 1)) {
					for (ActivityManager.RunningAppProcessInfo processInfo : processes) {
						if (AppInfo.applicationId
								.equals(processInfo.processName)) {
							foundPackage = true;
							if (processInfo.pid == pid) {
								startServer = true;
								break;
							}
						}
					}
					if ((!startServer) && (!foundPackage)) {
						startServer = true;
						if (Log.isLoggable("InstantRun", 2)) {
							Log.v("InstantRun",
									"Multiprocess but didn't find process with package: starting server anyway");
						}
					}
				} else {
					startServer = true;
				}
				if (startServer) {
					Server.create(AppInfo.applicationId, this);
				}
			} catch (Throwable t) {
				if (Log.isLoggable("InstantRun", 2)) {
					Log.v("InstantRun", "Failed during multi process check", t);
				}
				Server.create(AppInfo.applicationId, this);
			}
		}
		if (this.realApplication != null) {
			this.realApplication.onCreate();
		}
	}

依次呼叫MonkeyPatcher的monkeyPatchApplication/ monkeyPatchExistingResources和Server的create方法,

然後利用反射呼叫realApplication也就是業務程式碼Application類的onCreate方法。

monkeyPatchApplication方法主要邏輯如下,

1.替換ActivityThread的變數mInitialApplication為realApplication

2.替換ActivityThread的變數mAllApplications 中所有的Application為realApplication。

3.替換ActivityThread的變數mPackages,mResourcePackages中的mLoaderApk中的application為realApplication。

反正一句話,替換ActivityThread中和Application有關的所有變數,並且將對應的資原始檔resource.ap_也替換。

monkeyPatchExistingResources方法邏輯如下,

1.如果resource.ap_檔案有改變,那麼新建一個AssetManager物件newAssetManager,

然後用newAssetManager物件替換所有當前Resource、Resource.Theme的mAssets成員變數。

2.如果當前的已經有Activity啟動了,還需要替換所有Activity中mAssets成員變數。

5, Server

Server主要負責熱部署、溫部署和冷部署。呼叫的流程圖如下,


Server的create方法如下,

public static void create(String packageName, Application application) {
		new Server(packageName, application);
	}

直接呼叫Server的構造方法。

內部類SocketServerReplyThread的handle方法從執行緒中讀取資料之後,進行簡單的校驗。

1,如果讀到7,則表示已經讀到檔案的末尾,退出讀取操作

2,如果讀到2,則表示獲取當前Activity活躍狀態,並且進行記錄

3,如果讀到3,讀取UTF-8字串路徑,讀取該路徑下檔案長度,並且進行記錄

4,如果讀到4,讀取UTF-8字串路徑,獲取該路徑下檔案MD5值,如果沒有,則記錄0,否則記錄MD5值和長度。

5,如果讀到5,先校驗輸入的值是否正確(根據token來判斷),如果正確,則在UI執行緒重啟Activity

6,如果讀到1,先校驗輸入的值是否正確(根據token來判斷),如果正確,獲取程式碼變化的ApplicationPatch列表,

首先呼叫Server的handlePatches方法進行處理,然後呼叫Server的restart方法進行重啟

7,如果讀到6,讀取UTF-8字串,showToast

InstantRun內部使用了Socket來進行通訊。也就是說當我們修改完程式點選run之後,

AndroidStudio會通過socket將資料傳遞給我們,最終呼叫的是handlePatches方法。

Server的handlePatches方法如下,

private int handlePatches(List<ApplicationPatch> changes,
			boolean hasResources, int updateMode) {
		if (hasResources) {
			FileManager.startUpdate();
		}
		for (ApplicationPatch change : changes) {
			String path = change.getPath();
			if (path.endsWith(".dex")) {
				handleColdSwapPatch(change);

				boolean canHotSwap = false;
				for (ApplicationPatch c : changes) {
					if (c.getPath().equals("classes.dex.3")) {
						canHotSwap = true;
						break;
					}
				}
				if (!canHotSwap) {
					updateMode = 3;
				}
			} else if (path.equals("classes.dex.3")) {
				updateMode = handleHotSwapPatch(updateMode, change);
			} else if (isResourcePath(path)) {
				updateMode = handleResourcePatch(updateMode, change, path);
			}
		}
		if (hasResources) {
			FileManager.finishUpdate(true);
		}
		return updateMode;
	}

根據ApplicationPatch列表中逐個取出改變的ApplicationPatch檔案,

1.如果字尾為“.dex”,呼叫 handleColdSwapPatch方法進行冷部署處理

2.如果字尾為“classes.dex.3”,呼叫 handleHotSwapPatch方法進行熱部署處理

3.其他情況,溫部署,呼叫 handleResourcePatch方法處理資源

5.1 Cold Swap

Server 的handleColdSwapPatch方法如下,

private static void handleColdSwapPatch(ApplicationPatch patch) {
		if (patch.path.startsWith("slice-")) {
			File file = FileManager.writeDexShard(patch.getBytes(), patch.path);
			if (Log.isLoggable("InstantRun", 2)) {
				Log.v("InstantRun", "Received dex shard " + file);
			}
		}
	}

把dex檔案寫到私有目錄,等待整個app重啟,重啟之後,使用IncrementalClassLoader載入dex。

5.2 Hop Swap

在Server 的handleHotSwapPatch方法中,

首先將patch的dex檔案寫入到臨時目錄,然後使用DexClassLoader去載入dex。AppPatchesLoaderImpl是編譯生成的,在這裡可以看到修改的類等資訊。

然後利用反射呼叫AppPatchesLoaderImpl類的load方法,實際上是呼叫父類AbstractPatchesLoaderImpl的load方法。load方法如下,

public boolean load() {
		try {
			for (String className : getPatchedClasses()) {
				ClassLoader cl = getClass().getClassLoader();
				Class<?> aClass = cl.loadClass(className + "$override");
				Object o = aClass.newInstance();
				Class<?> originalClass = cl.loadClass(className);
				Field changeField = originalClass.getDeclaredField("$change");

				changeField.setAccessible(true);

				Object previous = changeField.get(null);
				if (previous != null) {
					Field isObsolete = previous.getClass().getDeclaredField(
							"$obsolete");
					if (isObsolete != null) {
						isObsolete.set(null, Boolean.valueOf(true));
					}
				}
				changeField.set(null, o);
				if ((Log.logging != null)
						&& (Log.logging.isLoggable(Level.FINE))) {
					Log.logging.log(Level.FINE, String.format("patched %s",
							new Object[] { className }));
				}
			}
		} catch (Exception e) {
			if (Log.logging != null) {
				Log.logging.log(Level.SEVERE, String.format(
						"Exception while patching %s",
						new Object[] { "foo.bar" }), e);
			}
			return false;
		}
		return true;
	}

載入class名稱+override類,給$change賦值,這就是Instance Run的關鍵, $change又是什麼意思呢?

在執行程式的時候,就可以根據該變數,執行被替換的函式。

5.3 Warm Swap

Server 的handleResourcePatch方法如下,

private static int handleResourcePatch(int updateMode,
			ApplicationPatch patch, String path) {
		if (Log.isLoggable("InstantRun", 2)) {
			Log.v("InstantRun", "Received resource changes (" + path + ")");
		}
		FileManager.writeAaptResources(path, patch.getBytes());

		updateMode = Math.max(updateMode, 2);
		return updateMode;
	}

將資源的patch寫入到私有目錄,等到restart之後生效. 可以看到獲取了對應的資原始檔,

就是/data/data/[applicationId]/files/instant-run/resources.ap_,InstantRun直接對它進行了位元組碼操作,

把通過Socket傳過來的修改過的資源傳遞了進去。

最後,Server的restart方法根據不同的InstantRun的updateMode模式,進行重啟,使上述的3中部署模式生效。

6,總結

第一次編譯apk:

1.把Instant-Run.jar和instant-Run-bootstrap.jar打包到主dex中

2.替換AndroidManifest.xml中的application配置

3.使用asm工具,在每個類中新增$change,在每個方法前加邏輯

4.把原始碼編譯成dex,然後存放到壓縮包instant-run.zip中

app執行期:

1.獲取更改後資源resource.ap_的路徑

2.設定ClassLoader。setupClassLoader:

使用IncrementalClassLoader載入apk的程式碼,將原有的BootClassLoader → PathClassLoader改為BootClassLoader

       → IncrementalClassLoader → PathClassLoader繼承關係。

3.createRealApplication:

建立apk真實的application

4.monkeyPatchApplication

反射替換ActivityThread中的各種Application成員變數

5.monkeyPatchExistingResource

反射替換所有存在的AssetManager物件

6.呼叫realApplication的onCreate方法

7.啟動Server,Socket接收patch列表

有程式碼修改時

1.生成對應的$override類

2.生成AppPatchesLoaderImpl類,記錄修改的類列表

3.打包成patch,通過socket傳遞給app

4.app的server接收到patch之後,分別按照handleColdSwapPatch、handleHotSwapPatch、handleResourcePatch等待對patch進行處理

5.restart使patch生效

InstantRun利用了transform api去生成位元組碼,這樣的方式不靈活,因為所有的transform操作是由TransformManager管理的,

也就是說它執行的時機是固定的,如果涉及到混淆,dex等操作,這些task的順序都是不可變的,這樣的就會出錯。

相關推薦

InstantRun框架原始碼分析

4, onCreate BootstrapApplication的onCreate方法如下, public void onCreate() { if (!AppInfo.usingApkSplits) { MonkeyPatcher.monkeyPatchApp

Hadoop原始碼分析(RPC機制Call處理)

下面介紹在整個處理機制中怎麼把具體的Request Call轉換並呼叫到整體的實現邏輯。 主要以NameNode Client PRC Server作為例子來說明,整個轉換通過Google Protocol Buffer RPC來實現。           fina

spark mllib原始碼分析分類邏輯迴歸evaluation

在邏輯迴歸分類中,我們評價分類器好壞的主要指標有精準率(precision),召回率(recall),F-measure,AUC等,其中最常用的是AUC,它可以綜合評價分類器效能,其他的指標主要偏重一些方面。我們介紹下spark中實現的這些評價指標,便於使用sp

zookeeper原始碼分析客戶端啟動

ZooKeeper Client Library提供了豐富直觀的API供使用者程式使用,下面是一些常用的API: create(path, data, flags): 建立一個ZNode, path是其路徑,data是要儲存在該ZNode上的資料,flags常用的有: PERSISTEN, PERSIS

Spark RPC 框架原始碼分析)執行時序

前情提要: Spark RPC 框架原始碼分析(一)簡述 一. Spark RPC 概述概述 上一篇我們已經說明了 Spark RPC 框架的一個簡單例子,以及一些基本概念的說明。這一篇我們主要講述其執行時序,從而揭露 Spark RPC 框架的執行原理。我們將分為兩部分,分別從服務端和客戶端

DDPush開源推送框架原始碼分析Client到DDPush(UDP模式)

在前一篇文章中我們主要分析了AppServer是如何連線到DDPush,並向DDPush推送訊息,還沒有看過的朋友請移步DDPush開源推送框架原始碼分析之APPServer到DDPush。 本篇文章主要講解Client(客戶端)如何連線到DDPush,並向DDPush傳送

spark 原始碼分析十一 -- Task的執行流程

引言 在上兩篇文章 spark 原始碼分析之十九 -- DAG的生成和Stage的劃分 和 spark 原始碼分析之二十 -- Stage的提交 中剖析了Spark的DAG的生成,Stage的劃分以及Stage轉換為TaskSet後的提交。 如下圖,我們在前兩篇文章中剖析了D

spark 原始碼分析-- Task的記憶體管理

問題的提出 本篇文章將回答如下問題: 1.  spark任務在執行的時候,其記憶體是如何管理的? 2. 堆內記憶體的定址是如何設計的?是如何避免由於JVM的GC的存在引起的記憶體地址變化的?其內部的記憶體快取池回收機制是如何設計的? 3. 堆外和堆內記憶體分別是通過什麼來分配的?其資料的偏移

Android框架原始碼解析)OKhttp

原始碼在:https://github.com/square/okhttp 包實在是太多了,OKhttp核心在這塊https://github.com/square/okhttp/tree/master/okhttp 直接匯入Android Studio中即可。 基本使用:

Glide原始碼分析)——從用法來看load&into方法

上一篇,我們分析了with方法,文章連結: https://blog.csdn.net/qq_36391075/article/details/82833260 在with方法中,進行了Glide的初始化,建立了RequesManger,並且綁定了生命週期,最終返回了一個Reques

zigbee ZStack-2.5.1a原始碼分析) 無線接收控制LED

本文描述ZStack-2.5.1a 模板及無線接收移植相關內容。 main HAL_BOARD_INIT // HAL_TURN_OFF_LED1 InitBoard HalDriverInit HalAdcInit

【NLP】【】jieba原始碼分析分詞

【一】詞典載入 利用jieba進行分詞時,jieba會自動載入詞典,這裡jieba使用python中的字典資料結構進行字典資料的儲存,其中key為word,value為frequency即詞頻。 1. jieba中的詞典如下: jieba/dict.txt X光 3 n X光線 3

tornado原始碼分析iostream

在事件驅動模型中,所有任務都是以某個事件的回撥函式的方式新增至事件迴圈中的,如:HTTPServer要從socket中讀取客戶端傳送的request訊息,就必須將該socket新增至ioloop中,並設定回掉函式,在回掉函式中從socket中讀取資料,並且檢查request訊息是否全部接收到了,如果

原始碼分析基於LinkedList手寫HahMap()

package com.mayikt.extLinkedListHashMap; import java.util.LinkedList; import java.util.concurrent.ConcurrentHashMap; /** * 基於linkedList實現hashMap *

Spark core原始碼分析spark叢集的啟動(

2.2 Worker的啟動 org.apache.spark.deploy.worker 1 從Worker的伴生物件的main方法進入 在main方法中首先是得到一個SparkConf例項conf,然後將conf和啟動Worker傳入的引數封裝得到Wor

HBase原始碼分析HRegion上compact流程分析

  2016年03月03日 21:38:04 辰辰爸的技術部落格 閱讀數:2767 版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/lipeng_bigdata/article/details/50791205

JDK1.8集合框架原始碼分析-------------LinkedList

0.LinkedList特點    0.1) 刪除或新增效率高:不會移動資料元素,只會維護內部節點的關係    0.2) 查詢慢: 在其內部沒有下標,也即沒有索引,需要使用for迴圈遍歷,LInkedList為了提高效率,

ArrayList集合(JDK1.8) 【集合框架】JDK1.8原始碼分析ArrayList(六)

簡述   List是繼承於Collection介面,除了Collection通用的方法以外,擴充套件了部分只屬於List的方法。   常用子類  ?ArrayList介紹 1.資料結構   其底層的資料結構是陣列,陣列元素型別為Object型別,即可以存放所

[jjzhu學java]JDK集合框架原始碼分析

Java Collection 圖中實線邊框表示的是實現類(ArrayList, Hashtable等),虛線邊框的是抽象類(AbstractCollection,AbstractSequentialListd等),而點線邊框的是介面(Collect

以太坊原始碼分析 P2P網路(、節點發現流程)

區塊鏈特輯 :https://blog.csdn.net/fusan2004/article/details/80879343,歡迎查閱,原創作品,轉載請標明!上一篇文章簡單介紹了下一些基礎的型別定義,從這一篇開始我們將描述p2p網路的更多細節。從關於節點的定義來看,其實不同