做者:週週java
轉載請聲明出處!android
Hook技術在android開發領域算是一項黑科技,那麼一個新的概念進入視線,咱們最關心的3個問題就是,它是什麼,有什麼用,怎麼用git
本系列將由淺入深 手把手講解這三大問題github
本文是第一篇,
入門篇
編程
一. hook的定義
二. 實用價值
三. 前置技能
四. Hook通用思路
五. 案例實戰
六. 效果展現
hook,鉤子。勾住系統的程序邏輯。在某段
SDK源碼邏輯
執行的過程當中,經過代碼手段攔截執行該邏輯,加入本身的代碼邏輯。數組
hook是中級開發通往高級開發的必經之路。若是把谷歌比喻成 安卓的造物主,那麼安卓SDK源碼裏面就包含了萬事萬物的本源。中級開發者,只在利用萬事萬物,浮於表層,而高級開發者能從本源上去改變萬事萬物,深刻核心。bash
最有用的實用價值:hook是安卓面向切面(AOP)編程的基礎,可讓咱們在
不變動原有業務的前提
下,插入額外的邏輯
. 這樣,既保護了原有業務的完整性,又能讓額外的代碼邏輯不與原有業務產生耦合. (想象一下,讓你在一個成熟的app上面給每個
按鈕添加埋點接口,不說一萬個,就說成百上千個
控件讓你埋點,讓你寫一千次
埋點調用,你是否是要崩潰,hook
能夠輕鬆實現)app學好了hook,就有但願成爲高級工程師, 完成初中級沒法完成的開發任務, 升職,加薪,出任CEO,迎娶白富美,走上人生巔峯,夠不夠實用?ide
java反射 熟練掌握類
Class,方法Method,成員Field
的使用方法源碼內部,不少類和方法都是@hide
的,外部直接沒法訪問,因此只能經過反射,去建立源碼中的類,方法,或者成員.閱讀安卓源碼的能力
hook
的切入點都在源碼內部,不能閱讀源碼,不能理清源碼邏輯,則不用談hook
. 其實使用androidStudio
來閱讀源碼有個坑,,有時候會看到源碼裏面"一片飄紅"
,看似是有什麼東西沒有引用進來,實際上是由於有部分源碼沒有對開發者開放,解決起來很麻煩, 因此,推薦從安卓官網下載整套源碼,而後使用SourceInsight
查看源碼。若是不須要跳來跳去的話,直接用 安卓源碼網站 一步到位
不管多麼複雜的源碼,咱們想要干涉其中的一些執行流程,最終的 殺招
只有一個: 「偷樑換柱」
. 而 「偷樑換柱」
的思路,一般都是一個套路:
1. 根據需求肯定 要hook的對象
2. 尋找要hook的對象的持有者,拿到要hook的對象 (持有:B類 的成員變量裏有 一個是A的對象,那麼B就是A的持有者,以下
class B{
A a;
}
class A{}
複製代碼
3. 定義「要hook的對象」的代理類,而且建立該類的對象
4. 使用上一步建立出來的對象,替換掉要hook的對象
上面的4個步驟可能仍是有點抽象,那麼,下面用一個案例,詳細說明每個步驟.
這是一個最簡單的案例:咱們本身的代碼裏面,給一個view設置了點擊事件,如今要求在不改動這個點擊事件的狀況下,添加額外的點擊事件邏輯
View v = findViewById(R.id.tv);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "別點啦,再點我咬你了...", Toast.LENGTH_SHORT).show();
}
});
複製代碼
這是 view
的點擊事件, toast
了一段話,如今要求,不容許改動這個 OnClickListener
,要在 toast
以前添加日誌打印 Log.d(...)
.
乍一看,無從下手.看 hook
如何解決.
按照上面的思路來:
第一步:根據需求肯定 要hook的對象;咱們的目的是在
OnClickListener
中,插入本身的邏輯.因此,肯定要hook
的,是v.setOnClickListener()
方法的實參。第二步:尋找要hook的對象的持有者,拿到要hook的對象進入
v.setOnClickListener
源碼:發現咱們建立的OnClickListener
對象被賦值給了getListenerInfo().mOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
複製代碼
繼續索引:
getListenerInfo()
是個什麼玩意?繼續追查:
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
複製代碼
結果發現這個實際上是一個
僞單例
,一個View對象中只存在一個ListenerInfo
對象. 進入ListenerInfo內部:發現OnClickListener
對象 被ListenerInfo所持有.
static class ListenerInfo {
...
public OnClickListener mOnClickListener;
...
}
複製代碼
到這裏爲止,完成第二步,找到了點擊事件的實際持有者:
ListenerInfo
.第三步:定義「要
hook
的對象」的代理類,而且建立該類的對象咱們要hook
的是View.OnClickListener
對象,因此,建立一個類 實現View.OnClickListener
接口.
static class ProxyOnClickListener implements View.OnClickListener {
View.OnClickListener oriLis;
public ProxyOnClickListener(View.OnClickListener oriLis) {
this.oriLis = oriLis;
}
@Override
public void onClick(View v) {
Log.d("HookSetOnClickListener", "點擊事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
複製代碼
而後,
new
出它的對象待用。
ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
能夠看到,這裏傳入了一個
View.OnClickListener
對象,它存在的目的,是讓咱們能夠有選擇地使用到原先的點擊事件邏輯。通常hook
,都會保留原有的源碼邏輯. 另外提一句:當咱們要建立的代理類,是被接口所約束的時候,好比如今,咱們建立的ProxyOnClickListenerimplementsView.OnClickListener
,只實現了一個接口,則可使用JDK提供的Proxy類來建立代理對象
Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(),
new Class[] >> {View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d("HookSetOnClickListener", "點擊事件被hook到了");//加入本身的邏輯
return method.invoke(onClickListenerInstance, args);//執行被代理的對象的邏輯
}
});
複製代碼
這個
代理類
並非這次的重點,因此一筆帶過. 到這裏爲止,第三步:定義「要hook的對象」的代理類,而且建立該類的對象
完成。第四步:使用上一步建立出來的對象,替換掉要hook的對象,達成
偷樑換柱
的最終目的. 利用反射,將咱們建立的代理點擊事件對象,傳給這個viewfield.set(mListenerInfo,proxyOnClickListener);
這裏,貼出最終代碼:
/**
* hook的輔助類
* hook的動做放在這裏
*/
public class HookSetOnClickListenerHelper {
/**
* hook的核心代碼
* 這個方法的惟一目的:用本身的點擊事件,替換掉 View原來的點擊事件
*
* @param v hook的範圍僅限於這個view
*/
public static void hook(Context context, final View v) {//
try {
// 反射執行View類的getListenerInfo()方法,拿到v的mListenerInfo對象,這個對象就是點擊事件的持有者
Method method = View.class.getDeclaredMethod("getListenerInfo");
method.setAccessible(true);//因爲getListenerInfo()方法並非public的,因此要加這個代碼來保證訪問權限
Object mListenerInfo = method.invoke(v);//這裏拿到的就是mListenerInfo對象,也就是點擊事件的持有者
//要從這裏面拿到當前的點擊事件對象
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 這是內部類的表示方法
Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真實的mOnClickListener對象
//2\. 建立咱們本身的點擊事件代理類
// 方式1:本身建立代理類
// ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
// 方式2:因爲View.OnClickListener是一個接口,因此能夠直接用動態代理模式
// Proxy.newProxyInstance的3個參數依次分別是:
// 本地的類加載器;
// 代理類的對象所繼承的接口(用Class數組表示,支持多個接口)
// 代理類的實際邏輯,封裝在new出來的InvocationHandler內
Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d("HookSetOnClickListener", "點擊事件被hook到了");//加入本身的邏輯
return method.invoke(onClickListenerInstance, args);//執行被代理的對象的邏輯
}
});
//3\. 用咱們本身的點擊事件代理類,設置到"持有者"中
field.set(mListenerInfo, proxyOnClickListener);
//完成
} catch (Exception e) {
e.printStackTrace();
}
}
// 還真是這樣,自定義代理類
static class ProxyOnClickListener implements View.OnClickListener {
View.OnClickListener oriLis;
public ProxyOnClickListener(View.OnClickListener oriLis) {
this.oriLis = oriLis;
}
@Override
public void onClick(View v) {
Log.d("HookSetOnClickListener", "點擊事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
}
複製代碼
這段代碼閱讀起來的可能難點:
Method,Class,Field
的使用 >method.setAccessible(true);
//因爲getListenerInfo()
方法並非public
的,因此要加這個代碼來保證訪問權限field.set(mListenerInfo,proxyOnClickListener);
//把一個proxyOnClickListener
對象,設置給mListenerInfo
對象的field
屬性.
Proxy.newProxyInstance
的使用Proxy.newProxyInstance
的3個參數依次分別是:本地的類加載器; 代理類的對象所繼承的接口(用Class數組表示,支持多個接口) 代理類的實際邏輯,封裝在new出來的InvocationHandler
內 到這裏,最後一步,也完成了.
先給出Demo:GithubDemo當我點擊這個 hello World:
彈出一個
Toast
,而且:在日誌中能夠看到同時我並無改動
setOnClickListener
的代碼,我只是在它的後面,加了一行HookSetOnClickListenerHelper.hook(this,v);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "別點啦,再點我咬你了...", Toast.LENGTH_SHORT).show();
}
});
HookSetOnClickListenerHelper.hook(this, v);//這個hook的做用,是 用咱們本身建立的點擊事件代理對象,替換掉以前的點擊事件。
ok,目的達成 v.setOnClickListener
已經被 hook
.
前方有坑,高能提示:我曾經嘗試,是否是能夠將上面兩段代碼換個順序.結果證實,換了以後,hook就無論用了,緣由是,hook方法的做用,是將v已有的點擊事件,替換成咱們代理的點擊事件。因此,在v尚未點擊事件的時候進行hook,是沒用的
Hook的水很深,這個只是一個入門級的案例,我寫這個,目的是說明hook技術的套路,無論咱們要hook源碼的哪一段邏輯,都逃不過 hook通用思路 這「三板斧」,套路掌握了,就有能力學習更難的Hook技術.
Hook的學習,須要咱們大量地閱讀源碼,要對SDK有較爲深刻的瞭解,不再是浮於表面,只會對SDK的api進行調用,而是真正地干涉「造物主谷歌」的既定規則. 學習難度很大,可是收益也不小,高級開發和初中級開發的薪資差距巨大,職場競爭力也不可同日而語.
你的贊和關注是我繼續創做的動力~