從Instant run談Android替換Application和動態載入機制
轉自http://www.tuicool.com/articles/ZFbaaub
Android studio 2.0 Stable版本中集成了Install
run
即時編譯技術,官方描述可以大幅加速編譯速度,我們團隊在第一時間更新並使用,總體用下來感覺,恩…也就那樣吧,還不如不用的快。所以就去看了下Install
run
的實現方式,其中有一個整體框架的基礎,也就是今天的文章的主題,Android替換Application和動態載入機制。
Instant run
Instant run
的大概實現原理可以看下這篇Instant
Run 淺析,我們需要知道Instant
run
使用的gradle
plugin2.0.0
Instant
run
的實現原理,但是並沒有深入細節,特別是替換Application和動態載入機制。
關於動態載入,實際上Instant run
提供了兩種動態載入的機制:
1.修改java程式碼需要重啟應用載入補丁dex,而在Application初始化時替換了Application,新建了一個自定義的ClassLoader去載入所有的dex檔案。我們稱為重啟更新機制
2.修改程式碼不需要重啟,新建一個ClassLoader
去載入修改部分。我們稱為熱更新機制
Application入口
在編譯時Instant run
用到了Transform
API
AndroidManifest.xml
檔案也被修改,如下:
/app/build/intermediates/bundles/production/instant-run/AndroidManifest.xml
,其中的Application
標籤
<application
name="com.aa.bb.MyApplication"
android:name="com.android.tools.fd.runtime.BootstrapApplication"
... />
多了一個com.android.tools.fd.runtime.BootstrapApplication
gradle
plugin
中的instant-run-server
目錄下找到該檔案。
實際上BootstrapApplication
是我們app的實際入口,我們自己的Application
即MyApplication
採用反射機制呼叫。
我們知道Application
是ContextWrapper
的子類
// android.app.Application
public class Application extends ContextWrapper {
// ...
public application() {
super(null);
}
// ...
}
// android.content.ContextWrapper
public class ContextWrapper extends Context {
Context mBase;
// ...
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
// ...
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources()
{
return mBase.getResources();
}
// ...
}
ContextWrapper一方面繼承了Context,一方面又包含(composite)了一個Context物件(稱為mBase),對Context的實現為轉發給mBase物件處理。上面的程式碼表示,在attachBaseContext
方式呼叫之前Application是沒有用的,因為mBase是空的。所以我們看下BootstrapApplication
的attachBaseContext
方法
protected void attachBaseContext(Context context) {
if (!AppInfo.usingApkSplits) {
createResources(apkModified);
//新建一個ClassLoader並設定為原ClassLoader的parent
setupClassLoaders(context, context.getCacheDir().getPath(), apkModified);
}
//通過Manifest中我們的實際Application即MyApplication名反射生成物件
createRealApplication();
//呼叫attachBaseContext完成初始化
super.attachBaseContext(context);
if (realApplication != null) {
//反射呼叫實際Application的attachBaseContext方法
try {
Method attachBaseContext =
ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
attachBaseContext.setAccessible(true);
attachBaseContext.invoke(realApplication, context);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
初始化ClassLoader
//BootstrapApplication.setupClassLoaders
private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) {
// /data/data/package_name/files/instant-run/dex/目錄下的dex列表
List<String> dexList = FileManager.getDexList(context, apkModified);
ClassLoader classLoader = BootstrapApplication.class.getClassLoader();
String nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath")
.invoke(classLoader);
IncrementalClassLoader.inject(
classLoader,
nativeLibraryPath,
codeCacheDir,
dexList);
}
}
//IncrementalClassLoader.inject
public static ClassLoader inject(
ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir,
List<String> dexes) {
//新建一個自定義ClassLoader,dexPath為引數中的dexList
IncrementalClassLoader incrementalClassLoader =
new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes);
//設定為原ClassLoader的parent
setParent(classLoader, incrementalClassLoader);
return incrementalClassLoader;
}
動態載入
新建一個自定義的ClassLoader
名為IncrementalClassLoader,該ClassLoader
很簡單,就是BaseDexClassLoader
的一個子類,並且將IncrementalClassLoader
設定為原ClassLoader的parent,熟悉JVM載入機制的同學應該都知道,由於ClassLoader採用雙親委託模式,即委託父類載入類,父類找不到再自己去找。這樣IncrementalClassLoader
就變成了整個App的所有類的載入的ClassLoader,並且dexPath是/data/data/package_name/files/instant-run/dex
目錄下的dex列表,這意味著什麼呢?
//``BaseDexClassLoader``的``findClass``
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
可以看到,查詢Class的任務通過pathList完成;這個pathList是一個DexPathList類的物件,它的findClass方法如下:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
這個DexPathList內部有一個叫做dexElements的陣列,然後findClass的時候會遍歷這個陣列來查詢Class。看到了嗎,這個dexElements就是從dexPath來的,也就說是IncrementalClassLoader
用來載入dexPath(/data/data/package_name/files/instant-run/dex/)下面的dex檔案。感興趣的同學可以看下,我們app中的所有第三方庫和自己專案中的程式碼,都被打包成若干個slice
dex分片,該目錄下有幾十個dex檔案。每當修改程式碼用Instant
run
完成編譯,該目錄下的dex檔案就會有一個或者幾個的更新時間發生改變。
正常情況下,apk被安裝之後,APK檔案的程式碼以及資源會被系統存放在固定的目錄(比如/data/app/package_name/base-1.apk )系統在進行類載入的時候,會自動去這一個或者幾個特定的路徑來尋找這個類。而使用Install
run
則完全不管之前的載入路徑,所有的分片dex檔案和資源都在dexPath下,用IncrementalClassLoader
去載入。也就是載入不存在APK固定路徑之外的類,即動態載入。
但是僅僅有ClassLoader是不夠的。因為每個被修改的類都被改了名字,類名在原名後面新增$override
,目錄在app/build/intermediates/transforms/instantRun/debug/folders/4000
。AndroidManifest中並沒有註冊這些被改了名字的Activity。>
因此正常情況下系統無法載入我們外掛中的類;因此也沒有辦法建立Activity的物件。
解決這個問題有兩個思路,要麼全盤接管這個類載入的過程;要麼告知系統我們使用的外掛存在於哪裡,讓系統幫忙載入;這兩種方式或多或少都需要干預這個類載入的過程。
動態載入的兩種方案
先來看下系統如何完成類的載入過程。
Activity
的建立過程
java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl);
通過ClassLoader
和類名載入,反射呼叫生成Activity
物件,其中的ClassLoader
從LoadedApk
的一個物件r.packageInfo
中獲得的。LoadedApk
物件是APK檔案在記憶體中的表示。
Apk檔案的相關資訊,諸如Apk檔案的程式碼和資源,甚至程式碼裡面的Activity
,Service
等元件的資訊我們都可以通過此物件獲取。
r.packageInfo的來源:
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// 獲取userid資訊
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
// 嘗試獲取快取資訊
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 快取沒有命中,直接new
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
// 省略。。更新快取
return packageInfo;
}
}
重要的是這個快取mPackage
,LoadedApk
物件packageInfo
就是從這個快取中取的,所以我們只要在mPackage
修改裡面的ClassLoader
控制類的載入就能完成動態載入。
『激進方案』中我們自定義了外掛的ClassLoader,並且繞開了Framework的檢測;利用ActivityThread對於LoadedApk的快取機制,我們把攜帶這個自定義的ClassLoader的外掛資訊新增進mPackages中,進而完成了類的載入過程。
『保守方案』中我們深入探究了系統使用ClassLoader findClass的過程,發現應用程式使用的非系統類都是通過同一個PathClassLoader載入的;而這個類的最終父類BaseDexClassLoader通過DexPathList完成類的查詢過程;我們hack了這個查詢過程,從而完成了外掛類的載入。
激進方案由於是一個外掛一個Classloader
也叫多ClassLoader
方案,代表作DroidPlugin;保守方案也叫做單ClassLoader
方案,代表作,Small、眾多熱更新框架如nuwa等。
Instant run的重啟更新機制
繞了一大圈,終於能接著往下看了。接上面,我們繼續看BootstrapApplication
的onCreate
方法
public void onCreate() {
MonkeyPatcher.monkeyPatchApplication(
BootstrapApplication.this, BootstrapApplication.this,
realApplication, externalResourcePath);
MonkeyPatcher.monkeyPatchExistingResources(BootstrapApplication.this,
externalResourcePath, null);
super.onCreate();
...
//手機客戶端app和Android Studio建立Socket通訊,AS是客戶端發訊息,app //是服務端接收訊息作出相應操作。Instant run的通訊方式。不在本文範圍內
Server.create(AppInfo.applicationId, BootstrapApplication.this);
if (realApplication != null) {
//還記得這個realApplication嗎,我們app中實際的Application
realApplication.onCreate();
}
}
上面程式碼,手機客戶端app和Android Studio建立Socket通訊,AS是客戶端發訊息,app是服務端接收訊息作出相應操作,這是Instant run的通訊方式,不在本文範圍內。然後反射呼叫實際Application
的onCreate
方法。
那麼前面的兩個MonkeyPatcher
的方法是幹嘛的呢
先看MonkeyPatcher.monkeyPatchApplication
public static void monkeyPatchApplication(@Nullable Context context,
@Nullable Application bootstrap,
@Nullable Application realApplication,
@Nullable String externalResourceFile) {
try {
// Find the ActivityThread instance for the current thread
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object currentActivityThread = getActivityThread(context, activityThread);
// Find the mInitialApplication field of the ActivityThread to the real application
Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");
mInitialApplication.setAccessible(true);
Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);
if (realApplication != null && initialApplication == bootstrap) {
//**2.替換掉ActivityThread.mInitialApplication**
mInitialApplication.set(currentActivityThread, realApplication);
}
// Replace all instance of the stub application in ActivityThread#mAllApplications with the
// real one
if (realApplication != null) {
Field mAllApplications = activityThread.getDeclaredField("mAllApplications");
mAllApplications.setAccessible(true);
List<Application> allApplications = (List<Application>) mAllApplications
.get(currentActivityThread);
for (int i = 0; i < allApplications.size(); i++) {
if (allApplications.get(i) == bootstrap) {
//**1.替換掉ActivityThread.mAllApplications**
allApplications.set(i, realApplication);
}
}
}
// Figure out how loaded APKs are stored.
// API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know.
Class<?> loadedApkClass;
try {
loadedApkClass = Class.forName("android.app.LoadedApk");
} catch (ClassNotFoundException e) {
loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
}
Field mApplication = loadedApkClass.getDeclaredField("mApplication");
mApplication.setAccessible(true);
Field mResDir = loadedApkClass.getDeclaredField("mResDir");
mResDir.setAccessible(true);
// 10 doesn't have this field, 14 does. Fortunately, there are not many Honeycomb devices
// floating around.
Field mLoadedApk = null;
try {
mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
} catch (NoSuchFieldException e) {
// According to testing, it's okay to ignore this.
}
// Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and
// ActivityThread#mResourcePackages and do two things:
// - Replace the Application instance in its mApplication field with the real one
// - Replace mResDir to point to the external resource file instead of the .apk. This is
// used as the asset path for new Resources objects.
// - Set Application#mLoadedApk to the found LoadedApk instance
for (String fieldName : new String[]{"mPackages", "mResourcePackages"}) {
Field field = activityThread.getDeclaredField(fieldName);
field.setAccessible(true);
Object value = field.get(currentActivityThread);
for (Map.Entry<String, WeakReference<?>> entry :
((Map<String, WeakReference<?>>) value).entrySet()) {
Object loadedApk = entry.getValue().get();
if (loadedApk == null) {
continue;
}
if (mApplication.get(loadedApk) == bootstrap) {
if (realApplication != null) {
//**3.替換掉mApplication**
mApplication.set(loadedApk, realApplication);
}
if (externalResourceFile != null) {
//替換掉資源目錄
mResDir.set(loadedApk, externalResourceFile);
}
if (realApplication != null && mLoadedApk != null) {
//**4.替換掉mLoadedApk**
mLoadedApk.set(realApplication, loadedApk);
}
}
}
}
} catch (Throwable e) {
thrownew IllegalStateException(e);
}
}
這裡做了三件事情:
1.替換Application物件
BootstrapApplication
的作用就是載入realApplication
也就是MyApplication
,所以我們就要把所有Framework層的BootstrapApplication
物件替換為MyApplication
物件。包括:
baseContext.mPackageInfo.mApplication 程式碼3處
baseContext.mPackageInfo.mActivityThread.mInitialApplication 程式碼2處
baseContext.mPackageInfo.mActivityThread.mAllApplications 程式碼1處
2.替換資源相關物件mResDir,前面我們已經說過,正常情況下尋找資源都是在/data/app/package_name/base-1.apk
目錄下,而Instant
run
將資源也抽出來放在/data/data/package_name/files/instant-run/
,載入目錄也更改為後者
3.替換mLoadedApk
物件
還記得前面的講的LoadedApk
嗎,這裡面有載入類的ClassLoader
,由於BootstrapApplication
在attachBaseContext
方法中就將其已經替換為了IncrementalClassLoader
,所以程式碼4處反射將BootstrapApplication
的mLoadedApk
賦值給了MyApplication
,那麼接下來MyApplication的所有類的載入都將由IncrementalClassLoader
來負責。
MonkeyPatcher.monkeyPatchExistingResources
更新資源補丁,不在本文範圍內就不講了。
這些工作做完之後呼叫
轉自http://www.tuicool.com/articles/ZFbaaub
Android studio 2.0 Stable版本中集成了Install
run即時編譯技術,官方描述可以大幅加速編譯速度,我們團隊在第一時間更新並使用,總體用下來感覺,恩…也就那樣吧 Android中序列化有兩種方式:Serializable以及Parcelable。其中Serializable是Java自帶的,而Parcelable是安卓專有的。
一、Serializable序列化
serializable使用比較簡單,只需要對某個類實現Serializable 介面即可。
Ser
首先看log日誌:has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{1f60f27e V.E..... R.....ID 0,0-388,240} that was orig
這裡寫一些關於Android Framework比較重要的知識點,這些東西對於之後理解和研究Android Framework有很
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
  程序優先順序低到高:
空程序 後臺進程序 服務程序 可見程序 前臺程序
垃圾回收機制:
引用計數法(未採用),無法處理迴圈引用問題.egA引用B,B引用C,C引用A。
標註並清理回收法(mark and sweep GC)
程式不停的建立新的物件,直到記憶體耗盡。再建立新的物件時,系統暫停其他元件執
前言
WebView是Android裡的元件,下面將全面介紹WebView的常用用法。
1.簡介
WebView是一個基於webkit引擎、展現web頁面的控制元件。Android的Webview
android 庫的編譯依賴於nkd-build,使用之前請先安裝NDK。
編譯依賴 Android.mk 和 Application.mk 兩個檔案。
Android.mk(同時編譯靜態庫和動態庫):
LOCAL_PATH := $(call my-di
8 Activity觸控事件傳輸機制介紹
當我們觸控式螢幕幕的時候,程式會收到對應的觸控事件,這個事件是在app端去讀取的嗎?肯定不是,如果app能讀取,那會亂套的,所以app不會有這個許可權,系統按鍵的讀取以及分發都是通過WindowManagerService來完成
最近專案中有用到Picasso和Glide來載入本地圖片,發現有些區別
圖片路徑:
String framePicPath="/storage/sdcard1/Android/data/com.example.lshapp.shortvideodemo
/c
一、什麼是廣播
BroadcastReceiver是android 系統的四大元件之一,本質上就是一個全域性的監聽器,用於監聽系統全域性的廣播訊息,可以方便的實現系統中不同元件之間的通訊。
程式可以通過呼叫context的sendBroadcast()方法來啟動指定的Br
1)靜態註冊:在AndroidManifest.xml註冊,android不能自動銷燬廣播接收器,也就是說當應用程式關閉後,還是會接收廣播。 2)動態註冊:在程式碼中通過registerReceive
一.動態編譯
在某些情況下,我們需要動態生成java程式碼,通過動態編譯,然後執行程式碼。JAVA API提供了相應的工具(JavaCompiler)來實現動態編譯。
//獲取JavaCompiler JavaCompiler compiler = ToolProvider.getSyste
靜態連結庫(LIB)和動態連結庫(DLL),DLL的靜態載入和動態載入,兩種LIB檔案。 一、 靜態連結庫(LIB,也簡稱“靜態庫”)與動態連結庫(DLL,也簡稱“動態庫”)的區別 靜態連結庫與動態連結庫都是共享程式碼的方式,如果採用靜態連結庫,則無論你願不願意,lib 中的指令都全部被直接包含在最
一、概述
代理是一種常用的設計模式,其目的就是為其他物件提供一個代理以控制對某個物件的訪問。代理類負責為委託類預處理訊息,過濾訊息並轉發訊息,以及進行訊息被委託類執行後的後續處理。根據代理類的生成時間不同可以將代理分為靜態代理和動態代理兩
Android中的動態載入機制
http:
目錄
編譯時
載入
連線
初始化
類載入器
類的載入
參考:
什麼是Java虛擬機器
從Java虛擬機器所做的事情上去理解,可以分為兩個階段,編譯時和執行時。編譯時主要是一個由編譯器將原始碼譯為虛擬機器指令集的一個過程;而執行 在面向物件程式設計實踐中,我們通過眾多的類來組織一個複雜的系統,這些類之間相互關聯、呼叫使他們的關係形成了一個複雜緊密的網路。當系統啟動時,出於效能、資源利用多方面的考慮,我們不可能要求 JVM 一次性將全部的類都載入完成,而是隻載入能夠支援系統順利啟動和執行的類和資源即可。那麼在系統執行過程中如果需要使用未
一、首先是說java的靜態載入:
1.建立了幾個類,“老師”、“學生”、“職員”,每個人群有個屬性方法,程式碼如下:
public class Student {
public void belongNature() {
Syst
我們都知道JAVA初始化一個類的時候可以用new 操作符來初始化,也可通過Class.forName的方式來得到一個Class型別的例項,然後通過這個Class型別的例項的newInstance來初始化.我們把前者叫做JAVA的靜態載入,把後者叫做動態載入.後者在很多框架中 MyApplication
的
相關推薦
從Instant run談Android替換Application和動態載入機制
淺談Android中Serializable和Parcelable使用區別
從窗體洩漏談android:configChanges屬性
淺談Android FrameWork框架和它在android的四層架構起到的作用
Android apk動態載入機制的研究(二) 資源載入和activity生命週期管理
Android程序優先順序和垃圾回收機制
Android WebView用法和WebView載入提升網頁速度
android 靜態庫和動態庫編譯
淺談Android之Activity觸控事件傳輸機制介紹
android的Picasso和Glide載入本地圖片的區別
android 靜態廣播和動態廣播的區別和用法
Android靜態註冊和動態註冊廣播的區別
Java動態編譯和動態載入詳解
靜態連結庫(LIB)和動態連結庫(DLL),DLL的靜態載入和動態載入,兩種LIB檔案。
Java代理和動態代理機制分析和應用
Android中的動態載入機制 Android中的動態載入機制
Java虛擬機器(一):Java編譯器和類載入機制
探祕類載入器和類載入機制
Java的靜態載入和動態載入區別
JAVA類的靜態載入和動態載入以及NoClassDefFoundError和ClassNotFoundException