android程序員hook技術之入門篇

做者:週週java

轉載請聲明出處!android

引子

Hook技術在android開發領域算是一項黑科技,那麼一個新的概念進入視線,咱們最關心的3個問題就是,它是什麼,有什麼用,怎麼用git

本系列將由淺入深 手把手講解這三大問題github

本文是第一篇, 入門篇編程

正文大綱

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

Demo地址

github.com/18598925736…api

正文

一. hook的定義

hook,鉤子。勾住系統的程序邏輯。在某段 SDK源碼邏輯執行的過程當中,經過代碼手段攔截執行該邏輯,加入本身的代碼邏輯。數組

二. 實用價值

hook是中級開發通往高級開發的必經之路。若是把谷歌比喻成 安卓的造物主,那麼安卓SDK源碼裏面就包含了萬事萬物的本源。中級開發者,只在利用萬事萬物,浮於表層,而高級開發者能從本源上去改變萬事萬物,深刻核心。bash

最有用的實用價值:hook是安卓面向切面(AOP)編程的基礎,可讓咱們在 不變動原有業務的前提下,插入 額外的邏輯. 這樣,既保護了原有業務的完整性,又能讓額外的代碼邏輯不與原有業務產生耦合. (想象一下,讓你在一個成熟的app上面給 每個按鈕添加埋點接口,不說一萬個,就說 成百上千個控件讓你埋點,讓你寫 一千次埋點調用,你是否是要崩潰, hook能夠輕鬆實現)app

學好了hook,就有但願成爲高級工程師, 完成初中級沒法完成的開發任務, 升職,加薪,出任CEO,迎娶白富美,走上人生巔峯,夠不夠實用?ide

三. 前置技能

  • 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

彈出一個 Toast,而且:在日誌中能夠看到

同時我並無改動 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的做用,是 用咱們本身建立的點擊事件代理對象,替換掉以前的點擊事件。

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

前方有坑,高能提示:我曾經嘗試,是否是能夠將上面兩段代碼換個順序.結果證實,換了以後,hook就無論用了,緣由是,hook方法的做用,是將v已有的點擊事件,替換成咱們代理的點擊事件。因此,在v尚未點擊事件的時候進行hook,是沒用的

結語

Hook的水很深,這個只是一個入門級的案例,我寫這個,目的是說明hook技術的套路,無論咱們要hook源碼的哪一段邏輯,都逃不過 hook通用思路 這「三板斧」,套路掌握了,就有能力學習更難的Hook技術.

Hook的學習,須要咱們大量地閱讀源碼,要對SDK有較爲深刻的瞭解,不再是浮於表面,只會對SDK的api進行調用,而是真正地干涉「造物主谷歌」的既定規則. 學習難度很大,可是收益也不小,高級開發和初中級開發的薪資差距巨大,職場競爭力也不可同日而語.

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

相關文章
相關標籤/搜索