轉自 http://weishu.me/2016/01/28/understand-plugin-framework-proxy-hook/android
使用代理機制進行API Hook進而達到方法加強是框架的經常使用手段,好比J2EE框架Spring經過動態代理優雅地實現了AOP編程,極大地提高了Web開發效率;一樣,插件框架也普遍使用了代理機制來加強系統API從而達到插件化的目的。本文將帶你瞭解基於動態代理的Hook機制。git
閱讀本文以前,能夠先clone一份 understand-plugin-framework,參考此項目的dynamic-proxy-hook
模塊。另外,插件框架原理解析系列文章見索引。github
爲何須要代理呢?其實這個代理與平常生活中的「代理」,「中介」差很少;好比你想海淘買東西,總不可能親自飛到國外去購物吧,這時候咱們使用第三方海淘服務好比惠惠購物助手等;一樣拿購物爲例,有時候第三方購物會有折扣好比當初的米折網,這時候咱們能夠少花點錢;固然有時候這個「代理」比較坑,坑咱們的錢,坑咱們的貨。編程
從這個例子能夠看出來,代理能夠實現方法加強,好比經常使用的日誌,緩存等;也能夠實現方法攔截,經過代理方法修改原方法的參數和返回值,從而實現某種不可告人的目的~接下來咱們用代碼解釋一下。
緩存
靜態代理,是最原始的代理方式;假設咱們有一個購物的接口,以下:app
1 |
public interface Shopping { |
它有一個原始的實現,咱們能夠理解爲親自,直接去商店購物:框架
1 |
public class ShoppingImpl implements Shopping { |
好了,如今咱們本身沒時間可是須要買東西,因而咱們就找了個代理幫咱們買:ide
1 |
public class ProxyShopping implements Shopping { |
很不幸,咱們找的這個代理有點坑,坑了咱們的錢還坑了咱們的貨;先忍忍。測試
傳統的靜態代理模式須要爲每個須要代理的類寫一個代理類,若是須要代理的類有幾百個那不是要累死?爲了更優雅地實現代理模式,JDK提供了動態代理方式,能夠簡單理解爲JVM能夠在運行時幫咱們動態生成一系列的代理類,這樣咱們就不須要手寫每個靜態的代理類了。依然以購物爲例,用動態代理實現以下:ui
1 |
public static void main(String[] args) { |
動態代理主要處理InvocationHandler
和Proxy
類;完整代碼能夠見github
咱們知道代理有比原始對象更強大的能力,好比飛到國外買東西,好比坑錢坑貨;那麼很天然,若是咱們本身建立代理對象,而後把原始對象替換爲咱們的代理對象,那麼就能夠在這個代理對象隨心所欲了;修改參數,替換返回值,咱們稱之爲Hook。
下面咱們Hook掉startActivity
這個方法,使得每次調用這個方法以前輸出一條日誌;(固然,這個輸入日誌有點點弱,只是爲了展現原理;只要你想,你想能夠替換參數,攔截這個startActivity
過程,使得調用它致使啓動某個別的Activity,混淆是非!)
首先咱們得找到被Hook的對象,我稱之爲Hook點;什麼樣的對象比較好Hook呢?天然是容易找到的對象。什麼樣的對象容易找到?靜態變量和單例;在一個進程以內,靜態變量和單例變量是相對不容易發生變化的,所以很是容易定位,而普通的對象則要麼沒法標誌,要麼容易改變。咱們根據這個原則找到所謂的Hook點。
而後咱們分析一下startActivity
的調用鏈,找出合適的Hook點。咱們知道對於Context.startActivity
(Activity.startActivity的調用鏈與之不一樣),因爲Context
的實現其實是ContextImpl
;咱們看ConetxtImpl
類的startActivity
方法:
1 |
@Override |
這裏,實際上使用了ActivityThread
類的mInstrumentation
成員的execStartActivity
方法;注意到,ActivityThread
其實是主線程,而主線程一個進程只有一個,所以這裏是一個良好的Hook點。
接下來就是想要Hook掉咱們的主線程對象,也就是把這個主線程對象裏面的mInstrumentation
給替換成咱們修改過的代理對象;要替換主線程對象裏面的字段,首先咱們得拿到主線程對象的引用,如何獲取呢?ActivityThread
類裏面有一個靜態方法currentActivityThread
能夠幫助咱們拿到這個對象類;可是ActivityThread
是一個隱藏類,咱們須要用反射去獲取,代碼以下:
1 |
// 先獲取到當前的ActivityThread對象 |
拿到這個currentActivityThread
以後,咱們須要修改它的mInstrumentation
這個字段爲咱們的代理對象,咱們先實現這個代理對象,因爲JDK動態代理只支持接口,而這個Instrumentation
是一個類,沒辦法,咱們只有手動寫靜態代理類,覆蓋掉原始的方法便可。(cglib
能夠作到基於類的動態代理,這裏先不介紹)
1 |
public class EvilInstrumentation extends Instrumentation { |
Ok,有了代理對象,咱們要作的就是偷樑換柱!代碼比較簡單,採用反射直接修改:
1 |
public static void attachContext() throws Exception{ |
好了,咱們啓動一個Activity測試一下
可見,Hook確實成功了!這就是使用代理進行Hook的原理——偷樑換柱。整個Hook過程簡要總結以下:
完整代碼參照:understand-plugin-framework;裏面留有一個做業:咱們目前僅Hook了Context
類的startActivity
方法,可是Activity
類卻使用了本身的mInstrumentation
;你能夠嘗試Hook掉Activity類的startActivity
方法。
喜歡就點個贊吧~持續更新,請關注github項目 understand-plugin-framework和個人 博客!