學會使用 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());
}
複製程式碼
這樣,我們就講解完了, 反射的一些基本應用。
三、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高階特性——反射