日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

作者:享學課堂終身VIP周周

轉載請聲明出處!

引子

Hook技術在Android開發領域算是一項黑科技,那么一個新的概念進入視線,我們最關心的3個問題就是,它是什么,有什么用,怎么用本系列將由淺入深 手把手講解這三大問題本文是第一篇, 入門篇

正文大綱

一. hook的定義二. 實用價值三. 前置技能四. Hook通用思路五. 案例實戰六. 效果展示

Demo地址

https://github.com/18598925736/OnClickListenerHookDemo

正文

一. hook的定義

hook,鉤子。勾住系統的程序邏輯。在某段 SDK源碼邏輯執行的過程中,通過代碼手段攔截執行該邏輯,加入自己的代碼邏輯。

二. 實用價值

hook是中級開發通往高級開發的必經之路。如果把谷歌比喻成 安卓的造物主,那么安卓SDK源碼里面就包含了萬事萬物的本源。中級開發者,只在利用萬事萬物,浮于表層,而高級開發者能從本源上去改變萬事萬物,深入核心。最有用的實用價值:hook是安卓面向切面(AOP)編程的基礎,可以讓我們在 不變更原有業務的前提下,插入 額外的邏輯. 這樣,既保護了原有業務的完整性,又能讓額外的代碼邏輯不與原有業務產生耦合. (想象一下,讓你在一個成熟的App上面給 每一個按鈕添加埋點接口,不說一萬個,就說 成百上千個控件讓你埋點,讓你寫 一千次埋點調用,你是不是要崩潰, hook可以輕松實現)學好了hook,就有希望成為高級工程師, 完成初中級無法完成的開發任務, 升職,加薪,出任CEO,迎娶白富美,走上人生巔峰,夠不夠實用?

三. 前置技能

  • JAVA反射 熟練掌握類 Class,方法Method,成員Field的使用方法源碼內部,很多類和方法都是 @hide的,外部直接無法訪問,所以只能通過反射,去創建源碼中的類,方法,或者成員.
  • 閱讀安卓源碼的能力 hook的切入點都在源碼內部,不能閱讀源碼,不能理清源碼邏輯,則不用談 hook. 其實使用 androidStudio來閱讀源碼有個坑,,有時候會看到源碼里面 "一片飄紅",看似是有什么東西沒有引用進來,其實是因為有部分源碼沒有對開發者開放,解決起來很麻煩, 所以,推薦從安卓官網下載整套源碼,然后使用 SourceInsight 查看源碼。如果不需要跳來跳去的話,直接用 安卓源碼網站 一步到位

四. Hook通用思路

無論多么復雜的源碼,我們想要干涉其中的一些執行流程,最終的 殺招只有一個: “偷梁換柱”. 而 “偷梁換柱”的思路,通常都是一個套路:

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的對象,達成 偷梁換柱的最終目的. 利用反射,將我們創建的代理點擊事件對象,傳給這個view field.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
android程序員hook技術之入門篇

 


彈出一個 Toast,并且:在日志中可以看到
android程序員hook技術之入門篇

 

 

同時我并沒有改動 setOnClickListener的代碼,我只是在它的后面,加了一行 HookSetOnClickListenerHelper.hook(this,v);

  1. v.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. Toast.makeText(MainActivity.this, "別點啦,再點我咬你了...", Toast.LENGTH_SHORT).show();
  5. }
  6. });
  7. HookSetOnClickListenerHelper.hook(this, v);//這個hook的作用,是 用我們自己創建的點擊事件代理對象,替換掉之前的點擊事件。

</pre>

ok,目的達成 v.setOnClickListener已經被 hook.

前方有坑,高能提示:我曾經嘗試,是不是可以將上面兩段代碼換個順序.結果證明,換了之后,hook就不管用了,原因是,hook方法的作用,是將v已有的點擊事件,替換成我們代理的點擊事件。所以,在v還沒有點擊事件的時候進行hook,是沒用的

結語

Hook的水很深,這個只是一個入門級的案例,我寫這個,目的是說明hook技術的套路,不管我們要hook源碼的哪一段邏輯,都逃不過 hook通用思路 這“三板斧”,套路掌握了,就有能力學習更難的Hook技術.Hook的學習,需要我們大量地閱讀源碼,要對SDK有較為深入的了解,再也不是浮于表面,只會對SDK的api進行調用,而是真正地干涉“造物主谷歌”的既定規則. 學習難度很大,但是收益也不小,高級開發和初中級開發的薪資差距巨大,職場競爭力也不可同日而語.

你的贊和關注是我繼續創作的動力~

分享到:
標簽:android
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定