目錄
5.1確認需要hook操作對象OnClickListener實現的實例
5.2獲取要hook干涉執行邏輯的擁有者(ListenerInfo對象);
5.3創建(hook對象)OnClickListener類實例代理類,在代理類內加入自己代碼;
1.Hook是做什麼的?(解決什麼問題?)
API HOOK百度百科解釋如下:
API HOOK技術是一種用於改變API執行結果的技術,Microsoft 自身也在Windows操作系統裏面使用了這個技術,如Windows兼容模式等。 API HOOK 技術並不是計算機病毒專有技術,但是計算機病毒經常使用這個技術來達到隱藏自己的目的。
Hook可以幫助我們在Android中在SDK源代碼邏輯執行過程中,通過代碼手動攔截執行該邏輯,加入自己的代碼邏輯;
例如常見的一些業務統計功能或者方法執行的性能監測功能我們都可以通過Hook技術進行攔截,加入自己代碼邏輯;
以前都是通過友盟,百度創建ID和描述,需要開發將事件記錄一個一個加入到需要統計的點上,很容易出現漏記的情況,修改需要重新發包;在APP開發過程中通常需要統計用戶點擊了那些按鈕,頁面的打開關閉,頁面停留時長都可以通過Hook技術大大減小開發工作量和出錯率;
2.HOOK技術如何改變API執行結果的?
最初有些人對某些api函數的功能不太滿意,就產生了如何修改這些api,使之更好的服務於程序的想法,這樣api hook就自然而然的出現了。
HOOK技術的難點,並不在於hook技術,而在於對SDK源碼的學習和理解,找到使用HOOK的入口函數,進行攔截然後加入自己的代碼;
3.使用Hook技術需要有實現Java反射和閱讀源碼的能力
3.1Java反射
Java反射常用類Class,Method,Field等;
SDK源碼類有很多用@hide被隱藏的方法,成員,類無法直接訪問,需要通過反射訪問方法,成員,類等;
TextView
/**
* @return the size (in scaled pixels) of thee default text size in this TextView.
* @hide
*/
@ViewDebug.ExportedProperty(category = "text")
public float getScaledTextSize() {
return mTextPaint.getTextSize() / mTextPaint.density;
}
3.2SDK源碼
需要閱讀SDK源碼找到入口函數,這樣才能通過Hook技術進行攔截,添加自己的代碼邏輯;當追蹤源碼時經常看見SDK源碼大面積飄紅;
所以,推薦從安卓官網下載整套源碼,然後使用 SourceInsight
查看源碼。
如果不需要跳來跳去的話,直接用 安卓源碼網站 一步到位
4.Hook實現的通用思路
無論多麼複雜的邏輯,當我們需要干涉執行,最終都是需要將自己的代碼邏輯替換干涉執行的邏輯;
a.確認需要hook操作對象A:a;
b.獲取要hook干涉執行邏輯的擁有者(B對象);
例如定義類A和B,B擁有A實例,假如需要操作A實例,需要先拿到B;
Class A{}
Class B{
A a;
}
c.創建(hook對象)A類實例代理類,在代理類內加入自己代碼;
d.將創建的(hook對象)代理類A對象,替換B類中a對象;
5.Hook攔截點擊事件加入統計點擊次數
5.1確認需要hook操作對象OnClickListener實現的實例
tvone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
a.Hook操作onClickListener實例,需要獲取onClickListener擁有者;
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
onClickListener擁有者是ListenerInfo;
b.onClickListener擁有者是mListenerInfo實例
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
mListenerInfo是一個“僞實例“,我們看下ListenerInfo持有的OnClickListener實例:
static class ListenerInfo {
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
...其他Listener實例,例如長按等;
}
5.2獲取要hook干涉執行邏輯的擁有者(ListenerInfo對象);
5.3創建(hook對象)OnClickListener類實例代理類,在代理類內加入自己代碼;
public class HookListener {
/**
* hook核心代碼
* 這個方法唯一目的:用自己的點擊事件,替換掉View的點擊事件
* @param context
* @param view
*/
public static void registerListener(Context context, View view){
//反射執行View類的getListenerInfo()方法,拿到v的mListenerInfo對象,這個對象就是點擊事件的持有者
try {
Method method = View.class.getDeclaredMethod("getListenerInfo");
//由於getListenerInfo()方法並不是public的,所以要加這個代碼來保證訪問權限
method.setAccessible(true);
//拿到mListenerInfo,也就是點擊事件的持有者
Object listenerInfo = method.invoke(view);
//找到mListenerInfo持有的點擊事件對象
//android.view.View$ListenerInfo內部類的表示方法
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener)field.get(listenerInfo);
//創建自己點擊事件的代理類
//方式1:自己實現代理類
//ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
//方式2:由於View.OnClickListener是一個接口,所以可以直接用動態代理模式
//參數:本地類的加載器,要代理實現的接口(用Class數組表示,支持多接口),代理類的實際邏輯封裝在new出來的InvocationHandler內
final Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(),
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
private int clickCount = 0;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
clickCount++;
Log.d("HookOnClickListener", ""+clickCount);
return method.invoke(onClickListenerInstance, args);
}
});
//替換持有者擁有的點擊事件代理類,
field.set(listenerInfo, proxyOnClickListener);
//完成
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
static class ProxyOnClickListener implements View.OnClickListener{
private View.OnClickListener listener;
private int clickCount = 0;
ProxyOnClickListener(View.OnClickListener listener){
this.listener = listener
}
@Override
public void onClick(View v) {
clickCount++;
Log.d("HookOnClickListener", ""+clickCount);
if(this.listener != null){
this.listener.onClick(v);
}
}
}
}
代碼邏輯:
a.藉助於Method,Field,Class拿到點擊事件的持有者mListenerInfo;
//由於getListenerInfo()方法並不是public的,所以要加這個代碼來保證訪問權限
method.setAccessible(true);
b.用點擊事件的代理類替換持有者mListenerInfo點擊事件
//替換持有者擁有的點擊事件代理類,
field.set(listenerInfo, proxyOnClickListener);
c.用Proxy代理類的創建點擊事件的代理類
包含三個參數:本地類的加載器,要代理實現的接口(用Class數組表示,支持多接口),代理類的實際邏輯封裝在new出來的InvocationHandler內
測試代碼:
tvone.setOnClickListener(this);
HookListener.registerListener(this, tvone);
攔擊統計點擊次數輸出日誌:
06-03 04:19:00.563 5603-5603/fan.fragmentdemo D/HookOnClickListener: 3
06-03 04:19:01.023 5603-5603/fan.fragmentdemo D/HookOnClickListener: 4
06-03 04:19:01.543 5603-5603/fan.fragmentdemo D/HookOnClickListener: 5
06-03 04:19:01.733 5603-5603/fan.fragmentdemo D/HookOnClickListener: 6
06-03 04:19:02.013 5603-5603/fan.fragmentdemo D/HookOnClickListener: 7
06-03 04:19:02.243 5603-5603/fan.fragmentdemo D/HookOnClickListener: 8
以上是Hook技術實現整理;
6.其他
實現Hook技術,第一需要先了解Hook實現流程,第二要熟悉SDK源碼,才能幫助我們快速定位需要修改的業務代碼;
參考:
https://baike.baidu.com/item/API%20HOOK?fr=aladdin
https://www.jianshu.com/p/74c12164ffca?tdsourcetag=s_pcqq_aiomsg