Java高級之註解、反射

 

 

本文來自http://blog.csdn.net/liuxian13183/ ,引用必須註明出處!

 

Java的註解、反射等機制的產生,讓動態代理成爲可能,一般通過全限定名+類名,找到類,可以invoke它的構造方法以及其他方法,可以獲取它的參數(Field)名稱和值。

註解一般用在代碼的註釋上、代碼審查上(有沒有按標準寫,比如inspect)、代碼注入(hook,asbectj),需要考慮的是,在何時注入(編譯期還運行期)

基礎反射:獲取類的變量名和變量值

    public static Map<String, Object> toMap(Object obj) {
        Map<String, Object> reMap = new HashMap<>();
        Class<?> clz = obj.getClass();//獲取此對象運行期的Class對象
        while (clz != null) {
            Field[] fields = clz.getDeclaredFields();//獲得類裏聲明的變量
            for (Field field : fields) {
                try {
                    String fieldName = field.getName();//獲得變量名稱
                    if (!extraParam(fieldName)) {
                        Field f = clz.getDeclaredField(fieldName);
                        f.setAccessible(true);
                        Object o = f.get(obj);//獲得變量值
                        reMap.put(fieldName, o);
                    }
                } catch (NoSuchFieldException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            clz = clz.getSuperclass();//獲得父類的Class對象,繼續遞歸
        }
        return reMap;
    }

    private static boolean extraParam(String fieldName) {
        //以下三種變量,通常不需要寫入
        return fieldName.equals("serialVersionUID")//用於根據變量產生,在恢復本地文件時,如果id不同,則類不同,就不需要再恢復(說明緩存已經過期)
                || fieldName.equals("shadow$_klass_")//API20以後出現,運行期對象的Class變量
                || fieldName.equals("shadow$_monitor_");//API20以後出現
    }

 

 

反射一般用在動態將json和Object互相轉化,執行相關底層代碼,比如設置某個類的Accessible爲false,防止別人hook修改

例:阿里的FastJson解析:
@Override public <T> T json2Object(String json, Class<T> clazz) {

return JSON.parseObject(json, clazz);

}

@Override public String object2Json(Object instance) {

return JSON.toJSONString(instance);

}

 

 

 

例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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章