Java的注解、反射等機制的產生,讓動態代理成為可能,一般通過全限定名+類名,找到類,可以invoke它的構造方法以及其他方法,可以獲取它的參數(Field)名稱和值。
注解一般用在代碼的注釋上、代碼審查上(有沒有按標准寫,比如inspect)、代碼注入(hook,asbectj),需要考慮的是,在何時注入(編譯期還運行期)
反射一般用在動態將json和Object互相轉化,執行相關底層代碼,比如設置某個類的Accessible為false,防止別人hook修改
例:阿里的FastJson解析:
例Java的默認注解策略:
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler.
默認,編譯時被拋棄 */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior.
默認被編譯器保解釋,但在運行時拋棄 */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. *
被編譯時解釋,運行時仍保存,可以直接被使用 * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
例Hook:
hook一事看似神秘,其實並不是那么難,希望各位看官看過本文之后能有所收獲。
本次是hook Android的點擊事件,也就是OnClickListener,hook的意義在於你能在調用setOnClickListener后做些其他的事,其他一些你想和所有點擊事件一起處理的事,那么在這里,我就以埋點為例吧。
先來展示下效果:
public void onClick(View view) { Map map = new HashMap(); switch (view.getId()) { case R.id.btn_hook1: map.put("巴", "掌"); map.put("菜", "比"); break; case R.id.btn_hook2: map.put("TF-Boys", "嘿嘿嘿"); map.put("id", "111"); break; } view.setTag(R.id.id_hook, map); }
我在onClick內干了三件事:
1、new HashMap
2、map塞你想埋點的數據
3、把數據傳到對應的view里
然后點擊按鈕會彈出一個Toast,如下圖:
那么有意思的地方來了,我們並沒有在點擊事件里彈Toast,那這個Toast哪來的呢?嘿嘿嘿,當然是hook的啦。
Hook
下面開始hook過程:
整個過程濃縮下來就是四個字--移花接木!
分析源代碼
首先來看看android.view.View中的這塊代碼,mOnClickListener變量靜靜的在這里(這里還有別的事件哦,比如OnLongClickListener等,大家學完之后可以試着hook下別的),我們需要做的就是移花接木,把自己的花替換掉這個木,mOnClickListener是ListenerInfo這個類的成員變量,那繼續看看ListenerInfo在View的哪里被初始化了,因為我們最開始拿到的只有View這一個對象。
沒錯,找到了,getListenerInfo()干了這件事,我們從這個方法入手先把ListenerInfo拿下,然后再移花接木。
技術方案已經有了,那么就開始着手擼碼。
實現
hook的過程就是充分利用java反射機制的過程,幾行代碼搞定,我們來看看:
//先拿下View的Class對象 Class clazzView = Class.forName("android.view.View"); //再把getListenerInfo拿到 Method method = clazzView.getDeclaredMethod("getListenerInfo"); //由於getListenerInfo並不是pulic方法,所以需要修改為可訪問 method.setAccessible(true); //繼續拿下ListenerInfo內部類的Class對象 Class clazzInfo = Class.forName("android.view.View$ListenerInfo"); //拿到主角mOnClickListener成員變量 Field field = clazzInfo.getDeclaredField("mOnClickListener"); //截止到這,我們已經完成了百分之95了,只剩最后一步,那就是把我們的木接進來 //那么這里先暫時停留下,我們把木給創建好。 //挖個坑 --> 待會填
由於移花接木有個本質不能忘,那就是尊重原有類型,因此,我們的木也得實現View.OnClickListener接口:
public static class HookListener implements View.OnClickListener { private View.OnClickListener mOriginalListener; //直接在構造函數中傳進來原來的OnClickListener public HookListener(View.OnClickListener originalListener) { mOriginalListener = originalListener; } @Override public void onClick(View v) { if (mOriginalListener != null) { mOriginalListener.onClick(v); } StringBuilder sb = new StringBuilder(); sb.append("hook succeed.\n"); //拿到之前傳遞的參數 Object obj = v.getTag(R.id.id_hook); //下面的操作可以猥瑣欲為了 if (obj != null && obj instanceof HashMap && !((Map) obj).isEmpty()) { for (Map.Entry<String, String> entry : ((Map<String, String>) obj).entrySet()) { sb.append("key => ") .append(entry.getKey()) .append(" ") .append("value => ") .append(entry.getValue()) .append("\n"); } } else { sb.append("params => null\n"); } Toast.makeText(v.getContext(), sb.toString(), Toast.LENGTH_LONG).show(); } }
以上代碼就是我們的木,為了看起來更簡單,我直接通過構造函數把原來的花(OnClickListener)給傳過來了,然后在新的HookListener的onClick()里把原來的事件繼續完成,並加上自己想猥瑣欲為的一些事情。
那么繼續填上之前埋的坑:
field.set(listenerInfo, new HookListener((View.OnClickListener) field.get(listenerInfo)));
接木的過程干了兩件事,一個是把原有的OnClickListener傳給HookListener,二是把新的HookListener替換進ListenerInfo,perfect。
至此,移花接木就完成了,簡單吧。
合適的調用hook
我們把hook方法都寫好了,最后就是調用你需要hook的View了,在大多數情況下,你可以把hook這件事交給Base去做,遍歷當前rootView所有的View,然后每個都調用hook,本文的重點不是這,我就不贅述了。
小結
本文僅僅以埋點為例,= = 其實我覺得埋點這個栗子並不太好,妹的都傳了這么多參數過來了,還在乎在這里調用一下自己的tracker?不管了,沒有栗子會讓本次hook感覺很無力,希望各位同學看過后能對hook不再懵逼,其實和自定義View一樣簡單的啦。
Sample代碼已同步到github上,有問題可以提issue => https://github.com/JeasonWong/ClickTracker
http://nanning.xjwy.cn/f/bencandy.php?fid=43&id=117777
http://nanning.xjwy.cn/f/bencandy.php?fid=43&id=117890
http://nanning.xjwy.cn/f/bencandy.php?fid=43&id=117994
http://nanning.xjwy.cn/f/bencandy.php?fid=43&id=118376
http://www.woaipu.com/shops/zuzhuan/61406