Java 反射理解以及Android實戰

學會使用 Java 的反射機制,能夠讓你在實際工作中,如虎添翼。

一 什麼是反射

反射指支持程序在運行狀態時,都能夠獲取該類的內部信息,包裹其中的方法,變量等信息,並可於運行時改變方法或者其內部變量。

簡單來說,如果某個系統源碼中某個類,比如 Recyclerview 的 mFirst 變量,我想動態改變這個值,就可以使用 反射獲取到這個值,並改變它。

java 反射的幾個主要的類如下:

類名 用途
Class類 編譯後的Class對象
Constructor類 類的構造方法
Field類 類的成員變量
Method 類的方法成員
Annotaion 類的註解

在上面的幾個類中,比如 Field 類,都有兩種重用格式:

  • getField : 表示獲取某個共有對象
  • getDeclaredField:表示獲取所有對象,包括 private 方法

對其他類的也使用。帶有Declared修飾的方法可以反射到私有的方法,沒有Declared修飾的只能用來反射公有的方法。

二、實例

接着,咱們測試一個簡單例子,再講一些 Android 常常用的幾個方法。下面一個簡單類,要求動態改變數值。
首先寫一個簡單的 Bean:

public class PersonBean {
    private int age;

    public PersonBean(int age) {
        this.age = age;
    }

    public PersonBean() {
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

那麼,如何拿到該類呢?

我們知道,Class 類是程序運行時的入口,即拿到 Class 的實例之後,咱們就可以操作。

2.1、Class 類常用方法

這裏介紹一些常用的。

方法 應用
forName() 根據類名返回類的對象
newInstance() 獲取類的實例
getSuperClass() 獲取類繼承的父類名稱

2.2、Constructor 方法

上面的 newInstance() 是無參數的構造方法,但如果構造方法中有參數呢。可以Constructor 的 aClass.getDeclaredConstructor(Class…).newInstance(Object…)。
比如對上面的PersonBean 參數一個數值:

Class<?> aClass = Class.forName("com.zhengsr.androiddemo.bean.PersonBean");
Object instance = aClass.getDeclaredConstructor(int.class).newInstance(23);

因爲 PersonBean 是我們已知道,所以用Class.forName。當然你直接用 new PersonBean(23)也可以。

2.3、Method 和 Field 類方法

上面已經拿到 PersonBean 的實例,那麼接着怎麼拿到 age 這個變量的值,護着拿到 getAge() 的方法呢?

Field

方法 用途
getField(String name) 獲得某個公有的屬性對象
getFields() 獲得所有公有的屬性對象
getDeclaredField(String name) 獲得某個屬性對象
getDeclaredFields() 獲得所有屬性對象
get(Object obj) 那個變量的值
set(Object obj, Object value) 通過set設值

這裏,我們的代碼可以修改爲:

 try {
     Class<?> aClass = Class.forName("com.zhengsr.androiddemo.bean.PersonBean");
     Object instance = aClass.getDeclaredConstructor(int.class).newInstance(23);

     Field field = aClass.getDeclaredField("age");

     field.setAccessible(true);
     //通過 get 拿到數值
     int age = (int) field.get(instance);
     //通過 set 設置值
     Log.d(TAG, "zsr 拿到私有變量 age: "+age);

     field.set(instance,25);

     int age2 = (int) field.get(instance);
     Log.d(TAG, "zsr 拿到被改變的私有變量 age: "+age2);
     
 } catch (Exception e) {
     e.printStackTrace();
     Log.d(TAG, "zsr error: "+e.getMessage());
 }

在這裏插入圖片描述
可以看到,數值已經被改變了。

Method

方法 用途
getMethod(String name, Class…<?> parameterTypes) 獲得該類某個公有的方法
getMethods() 獲得該類所有公有的方法
getDeclaredMethod(String name, Class…<?> parameterTypes) 獲得該類某個方法
getDeclaredMethods() 獲得該類所有方法
nvoke(Object obj, Object… args) 執行對象的目標方法

這裏,setAge 和 getAge 都是 public 方法,那麼可以使用 getMethod 直接拿到。所以代碼如下:

        try {
            Class<?> aClass = Class.forName("com.zhengsr.androiddemo.bean.PersonBean");
            Object instance = aClass.getDeclaredConstructor(int.class).newInstance(23);


            Method setAge = aClass.getMethod("setAge", int.class);
            setAge.setAccessible(true);
            setAge.invoke(instance,30);

            Method getAge = aClass.getMethod("getAge");
            getAge.setAccessible(true);
            int age = (int) getAge.invoke(instance);
            Log.d(TAG, "zsr 拿到被改變的數值: "+age);


        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "zsr error: "+e.getMessage());
        }

r

這樣,我們就講解完了, 反射的一些基本應用。

三、Android 實戰

學習到上面的反射知識之後,一些 Android 的問題就可以自己修改了。

3.1 修改縮放值

如果你搞過縮放的 ScaleGestureDetector ,就知道,在比較大的屏幕,當縮小時,縮小到一定比例,就不能再縮放了,那是因爲

mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);

這個 限制了大小,而它又是 private 的值,這是如果我們用反射,就可以輕鬆修改了。

 //設置 mMinSpan ,防止不能縮小
   try {
       Field field = mScaleGesture.getClass().getDeclaredField("mMinSpan");
       field.setAccessible(true);
       field.set(mScaleGesture,1);
   } catch (Exception e) {
       e.printStackTrace();
   }

3.2 Banner 與 Recyclerview 結合不滾動的問題

在自己封裝過 Banner 的同學應該知道,當用 ViewPager 做 Banner,與 Recyclerview 配合時,當 Recyclerview 往上劃,下一次回來的時候,Banner 會直接跳到下一頁,而不是有滾動的。
原因就在於 mFirstLayout 這個,因爲 Recyclerview 會讓 ViewPager 調用 onAttachedToWindow() 方法,所以mFirstLayout 又會被設置爲 true,所以就會出現不直接跳到下一頁的問題。

這裏解決也簡單,直接改變mFirstLayout 的值即可。

private boolean firstLayout = true;
 @Override
 protected void onAttachedToWindow() {
     super.onAttachedToWindow();
     //處理因爲recyclerview的回收機制,導致輪播圖不起作用的問題
     if (getAdapter() != null) {
         try {
             Field mFirstLayout = ViewPager.class.getDeclaredField("mFirstLayout");
             mFirstLayout.setAccessible(true);
             mFirstLayout.set(this, firstLayout);
             setCurrentItem(getCurrentItem());
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

如果你有系統權限,但是需要調用一些隱藏的 API 方法。這個時候也可以使用反射。

3.3 截屏

截屏在系統應用比較常見,但是它的類是隱藏的,方法是public 的靜態方法:
在這裏插入圖片描述
在這裏插入圖片描述
其實用反射方法還是挺方便的:

public static Bitmap screenshot(int widht, int height){
        Bitmap bitmap = null;
        try {
            Class<?> sClass = Class.forName("android.view.SurfaceControl");
            Method method = sClass.getMethod("screenshot",int.class,int.class);
            method.setAccessible(true);
            bitmap = (Bitmap) method.invoke(sClass,widht,height);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "zsr --> screenshot: "+e.toString());
        }
        return bitmap;
    }

3.4 刪除任務

當你做系統管家或者其他一些管理類app,需要對後臺任務刪除,這個時候,可以使用 ActivityManager 中的 remvoeTask 方法,但它的方法是 @hide 的。
在這裏插入圖片描述
這個時候 也可以使用反射:

    /**
     * 刪除任務列表
     * @param taskId
     * @return
     * @throws SecurityException
     */
    public static boolean removeTask(Context context,int taskId) throws SecurityException {
        try {

            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            Method method = activityManager.getClass().getDeclaredMethod("removeTask",int.class);
            method.setAccessible(true);
            boolean invoke = (boolean) method.invoke(activityManager, taskId);
            return invoke;
        } catch (Exception e) {
            return false;
        }
    }

最後,學習後反射的基本知識之後,相信你已經不再那麼困惑了。

參考:
Java高級特性——反射

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