Java 反射理解以及Android實戰

夏至的稻穗發表於2020-04-06

學會使用 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高階特性——反射

相關文章