提綱
java反射基礎
反射在Android中的應用
Java動態代理
動態代理在Android的應用
java反射基礎
相關定義和簡單調用
Java允許程序在運行時透過Reflection APIs加載一個運行時才得知名稱的class,獲得其完整結構,包括其modifiers(諸如public, static 等)、superclass(例如Object)、實現之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于運行時改變fields內容或喚起methods。
在程序運行時,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性。這種?動態的獲取信息?以及?動態調用對象的方法?的功能稱為?java 的反射機制
獲取類,類的對象,類具有的成員變量和方法,調用方法,調用構造器創建新類對象,xxx替換為以下方式,可以獲得相應目標構造方法,屬性
Constructor:代表類的單個構造方法,通過Constructor我們可執行一個類的某個構造方法(有參或者無參)來創建對象時。?????????
Method:代表類中的單個方法,可以用于執行類的某個普通方法,有參或無參,并可以接收返回值。????????
Field:代表類中的單個屬性,用于set或get屬性??
Java通過反射訪問私有屬性
在java中,類的構造方法,方法,屬性,都繼承于AccessibleObject,AccessibleObject提供了構造方法,普通方法,和屬性的訪問控制的能力
setAccessible(booleanflag)方法,將此對象的accessible 標志設置為指示的布爾值。值為true 則指示反射的對象在使用時應該取消Java 語言訪問檢查。值為false 則指示反射的對象應該實施Java 語言訪問檢查。
setAccessible是啟用和禁用訪問安全檢查的開關,并不是為true就能訪問為false就不能訪問。通過設置setAccessible(true),java可以調用類的私有屬性和方法
Java通過反射修改final修飾的常量值
常量是指使用?final?修飾符修飾的成員屬性,與變量的區別就在于有無?final?關鍵字修飾。
而java中會有個Modifier接口對訪問修飾符進行解碼
Java 虛擬機(JVM)在編譯?.java?文件得到?.class?文件時,會優化我們的代碼以提升效率。其中一個優化就是:JVM 在編譯階段會把引用常量的代碼替換成具體的常量值
對于 int 、long 、boolean 以及 String 這些基本類型
JVM 會優化,而對于Integer 、Long 、Boolean 這種包裝類型,或者其他諸如Date 、Object 類型則不會被優化。
總結來說:對于基本類型的靜態常量,JVM 在編譯階段會把引用此常量的代碼替換成具體的常量值。
我們在程序運行時刻依然可以使用反射修改常量的值(后面會代碼驗證),但是JVM 在編譯階段得到的.class 文件已經將常量優化為具體的值,在運行階段就直接使用具體的值了,所以即使修改了常量的值也已經毫無意義了。
避免在編譯時刻被優化,這樣我們通過反射修改常量之后才有意義,指向一個可變對象,或經過計算(如三目運算符)賦值常量(這些都是運行時進行賦值的,所以有效)
Java反射效率相比直接調用低
java反射是要動態的獲取實例調用方法,將內存中的對象進行解析,消耗資源去查找和計算,涉及到與底層c語言的交互,反射的過程中有遞歸和遍歷的操作,這些都需要時間
反射在Android中的應用
反射在Android源碼中的應用
反射在安卓用的使用相當廣泛,源碼中也有通過反射創建實例的過程,如Activity的啟動過程中Activity的對象的創建
用于調用SDK中@hide標注的隱藏方法
Hook源碼,添加自己的處理
通過替換源碼中的具體實現類為代理類,添加相關操作,達成部分特殊功能
Example:監聽activity跳轉信息,跳轉至未在AndroidManifest注冊的activity
反射在部分安卓框架中的應用
如EventBus 事件總線
?Register:通過反射的方式獲取訂閱類中有Subscribe注解的方法,將方法信息,參數類,訂閱類及實例通過map保存起來
?Post:根據參數類,獲取map保存的訂閱實例和方法,一一發到分配器
?Dispatch:根據注解Subscribe中的信息,分配至不同的處理器poster。分配至不同線程
?Run:指定線程,調用invokeSubscriber的方法,調用實例的訂閱方法,就是反射調用方法invoke。
也由于EventBus對反射的調用過多,容易導致性能下降問題,EventBus也對此作出了優化
編譯之后,索引文件在build/generated/source/apt/debug/com/XXX/packge/EventBusIndex.java。
如Butterknife (反射+annotationProcessor)
基于反射和注解解析的IOC框架
原理:通過annotationProcessor處理注解,反射得到各個需要綁定的成員和類型,生成Target_ViewBinding類,再通過ButterKnife.bind方法綁定實例。
當然其中還涉及到一個更有趣的技術,編譯時注解,后續總結下再分享
大概原理就是聲明的注解的生命周期為CLASS,然后繼承AbstractProcessor類。繼承這個類后,在編譯的時候,編譯器會掃描所有帶有你要處理的注解的類,然后再調用AbstractProcessor的process方法,對注解進行處理,那么我們就可以在處理的時候,動態生成綁定事件或者控件的java代碼,然后在運行的時候,直接調用方法完成綁定
參考
ButterKnife解讀 http://www.lxweimin.com/p/2967ff971177
ButterKnife源碼解析https://blog.csdn.net/silenceoo/article/details/77897718
JDK動態代理
定義
?靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
?動態代理:在程序運行時,運用反射機制動態創建而成。
動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需手工編寫源代碼。
相關的類或接口
?java.lang.reflect.Proxy:這是 Java 動態代理機制的主類,它提供了一組靜態方法來為一組接口動態地生成代理類及其對象
https://docs.oracle.com/javase/9/docs/api/java/lang/reflect/Proxy.html
?java.lang.reflect.InvocationHandler:這是調用處理器接口,它自定義了一個invoke方法,用于幾種處理在動態代理類對象上的方法調用。通常在該方法中實現對委托類的代理訪問。
?java.lang.ClassLoader:Proxy靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是由JVM 在運行時動態生成的而非預存在于任何一個.class 文件中。
三種動態代理的簡單demo
動態代理的四個步驟
?通過實現 InvocationHandler 接口創建自己的調用處理器;
?通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類
?通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型
?通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數被傳入
動態生成的代理類本身的一些特點
1.包:如果所代理的接口都是public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非public 的接口(因為接口不能被定義為protect或private,所以除 public之外就是默認的package訪問級別,那么它將被定義在該接口所在包,這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義并訪問;
2.類修飾符:該代理類具有final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;
3.類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表Proxy 類第 N 次生成的動態代理類,值得注意的一點是,并不是每次調用Proxy 的靜態方法創建動態代理類都會使得N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象,而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。
4. 類繼承關系:Proxy 類是它的父類,這個規則適用于所有由
Proxy 創建的動態代理類。而且該類還實現了其所代理的一組接口;
但Proxy只能對interface進行代理,無法實現對class的動態代理。
JDK動態代理的應用
hook源碼,動態代理實現 ServiceHook
在Android中,Binder是 ServiceManager 連接各種Manager (ActivityManager,WindowManager,等等)和相應 ManagerService 的橋梁;從Android 應用層來說,Binder 是客戶端和服務端進行通信的媒介,當
bindService 的時候,服務端會返回一個包含了服務端業務調用的
IBinder 對象
這四者的關系類似于網絡訪問
?Binder
Client 只需要知道自己要使用的 Binder 的名字及其在ServiceManager 中的引用即可獲取該Binder 的引用,得到引用后就可以像普通方法調用一樣調用Binder 實體的方法;
?Binder
Server 在生成一個 IBinder 實體時會為其綁定一個名稱并傳遞給Binder Driver,Binder Driver 會在內核空間中創建相應的Binder 實體節點和節點引用,并將引用傳遞給ServiceManager。
?ServiceManager 會將該 Binder 的名字和引用插入一張數據表中,這樣Binder Client 就能夠獲取該Binder 實體的引用,并調用上面的方法;ServiceManager 相當于 DNS服務器,負責映射Binder 名稱及其引用,其本質同樣是一個標準的Binder Server
?Binder Driver 則相當于一個路由器。
先通過 ServiceManager.getService 方法獲取一個IBinder 對象,但是這個IBinder 對象不能直接調用,必須要通過asInterface 方法轉成對應的IInterface 對象才可以使用,如果在同一個進程中(當然,這是跨進程通信,這種情況很少),就會直接返回通過attachInterface 方法設置的IInterface 對象(上面代碼所述),但是如果不是在同一個進程中,就會先通過IBinder 對象創建一個Proxy 對象,然后在Proxy 對象中通過調用IBinder 對象的 transact 方法調用到主進程的Service 中,實現了跨進程的通信。
常見的,我們可以通過動態代理替換的方式,對android的復制操作的文本,先理解ClipBoardManager的邏輯如上圖。
應用在使用剪切板服務,通過getSyetemService方法獲取到的對象就是ClipboardManager,但是內容是先通過ServiceManager獲取到ClipBoardManager的遠程Ibinder對象, 然后通過Iclipboard$Stub方法將binder轉化為本地對象,就是iClipboard$proxy對象
接下來開始我們的替換操作,具體流程如下
由于我們動態代理的是本地進程ServiceManager緩存中的Ibinder對象,應用進程的 ServiceManager 其實也就相當于一個 Binder Client,sCache 里面獲取不到對應的 Service,它就會自動通過 Binder Driver 和 Binder Server (主進程的 ServiceManager)通信去獲取對應的 Service,所以修改 Binder Client,其實是不會影響 Binder Server的操作,所以不會影響別的應用app。
JDK動態代理在框架中的應用
完成插件化邏輯(360Android 插件項目 DroidPlugin)
https://github.com/Qihoo360/DroidPlugin
360完成的框架DroidPlugin就是基于動態代理hook android系統service的原理
進行了深度的hook,ams,pms,service,brocastReceiver等
例如 Hook掉ActivityManagerNative對于startActivity方法的調用,將真的intent藏起來
Hook掉ActivityThread的handler,修改handleLaunchActivity
DroidPlugin開源插件研究資料整理 https://blog.csdn.net/hmmhhmmhmhhm/article/details/74352139
Retrofit網絡加載框架? ? Retrofit流程如圖
? Retrofit的實現實際就是動態代理的方式
Retrofit create(finalClass<T> service) 動態代理
Retrofit loadServiceMethod 加載方法統一處理,將retrofit上的注解修改為request的內容
ServiceMethod toCall 完成從ServiceMethod到call的轉化轉化
createCallAdapter 根據返回類型和注解返回對應adapter
簡單看看源碼,可以很清晰的看到,這是動態代理的實現,當然Retrofit確實是很優秀的框架,不只是動態代理的使用,其中涉及很多良好的設計模式。
參考鏈接
反射詳解?https://juejin.im/post/598ea9116fb9a03c335a99a4
EventBus 3.0 最佳實踐和原理淺析?https://xiazdong.github.io/2017/08/02/eventbus3/
ProxyGenerator 真正生成字節碼的實現類 http://www.docjar.com/html/api/sun/misc/ProxyGenerator.java.html
方法說明及解析 https://www.cnblogs.com/liuyun1995/p/8144706.html