Java進階知識-反射
獲取Class物件
有三種方式獲取Class物件:
- 根據類的完整包名獲取Class
Class clazz = Class.forName("com.example.xjp.demo.reflect.PersonInfo");
- 根據類名直接獲取Class
Class clazz = PersonInfo.class;
- 根據例項類的物件獲取Class
PersonInfo personInfo = new PersonInfo();
Class clazz = personInfo.getClass();
建立例項
通過反射來生成物件主要有兩種方式
1. 使用Class物件的newInstance()方法來建立Class物件對應類的例項
Class clazz = PersonInfo.class;
PersonInfo personInfo = (PersonInfo) clazz.newInstance();
2.使用Class物件的構造器來建立例項
Constructor constructor = clazz.getConstructor(PersonInfo.class);
//有構造器來建立例項,可以傳引數給newInstance(Object ... initargs)
PersonInfo personInfo = (PersonInfo) constructor.newInstance();
獲取方法
通過反射,可以獲取某個類中的所有方法,包括private,public,protect型別的方法
1. 獲取類的所有申明的方法,包括public,private,protect型別的方法
Class clazz = PersonInfo.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
2.獲取類中所有public方法
Class clazz = PersonInfo.class;
Method[] methods = clazz.getMethods();
3.獲取類中指定的public方法
Class clazz = PersonInfo.class;
PersonInfo personInfo = (PersonInfo) clazz.newInstance();
//第一個引數是方法名,第二個引數是該方法引數的型別
Method method = clazz.getMethod("getName", String.class);
//呼叫 PersonInfo 類中的 getName()方法
String name = (String) method.invoke(personInfo , "是最帥的");
4.獲取指定的private方法
Class clazz = PersonInfo.class;
PersonInfo personInfo = (PersonInfo) clazz.newInstance();
//第一個引數是方法名,第二個引數是該方法引數的型別
Method method = clazz.getDeclaredMethod("getAge", Integer.class);
//由於該方法是private的,所以需要設定訪問許可權
method.setAccessible(true);
//呼叫PersonInfo 類中的 getAge()方法
int age = (int) method.invoke(personInfo, 18);
獲取類的成員變數
- 獲取類中所有成員變數,包括public,private,protect型別
Field[] declaredFields = clazz.getDeclaredFields();
2.獲取類中所有public型別的成員變數
Field[] fields = clazz.getFields();
3.獲取指定的成員變數,public型別
Field nameField = clazz.getField("mName");
//修改成員變數mName的值為Tom
nameField.set(personInfo, "Tom");
//得到成員變數nName的值
String name = nameField.get(personInfo);
4.獲取指定的成員變數,private型別
//得到私有的成員變數 mAge
Field ageField = clazz.getDeclaredField("mAge");
//設定其訪問許可權
ageField.setAccessible(true);
//修改成員變數 mAge 的值
ageField.set(test, 23);
//獲取該成員變數的值
int age = (int) ageField.get(test);
public class PersonInfo {
private int mAge = 18;
public String mName = "xjp";
private int getAge(int age) {
return mAge;
}
public String getName(String msg) {
return mName + ":" + msg;
}
}
反射的應用場景
我們來做一個有意思的功能,在你的應用所有啟動Activity之前的地方加一個列印,打印出一些相關資訊。你可能會想到如下策略:
應用中所有的Activity都繼承自一個BaseActivity基類,基類中實現一個startActivity方法,在該方法之前加上一句列印,那麼所有startActivity的地方都呼叫基類中的方法。
但是有這麼一種情況,專案已經進行了80%,大部分Activity已經寫好了,好多Activity都不是繼承自同一個BaseActivity基類,如果需要改的話,改動地方太多,改動大,不划算。那麼有沒有一種更好的辦法,修改少,又能實現該功能的方法呢?
答案就是利用反射,在系統呼叫startActivity的地方換成我們自己的startActivity方法,這一招叫做偷樑換柱。那麼怎麼實現呢?
我們先找到Android系統的startActivity方法是怎麼實現的,該方法是Context類的方法,而Context只是一個藉口,其實現類是ContextImpl類,程式碼如下:
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in.
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
最終啟動activity是有mMainThread物件的getInstrumentation()方法獲取Instrumentation物件,然後由該物件呼叫execStartActivity()方法來啟動activity。而mMainThread物件是ActivityThread型別,該類是我們的主執行緒類,裡面有有一個mInstrumentation成員變數,該成員變數屬於Instrumentation型別。
我們的思路是替換ActivityThread類總的mInstrumentation物件,使用我們自己的 Instrumentation物件。實現如下:
public class ProxyInstrumentation extends Instrumentation {
private static final String TAG = "ProxyInstrumentation";
// ActivityThread中原始的物件, 儲存起來
Instrumentation mBase;
public ProxyInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// Hook之前, XXX到此一遊!
Log.d(TAG, "\n執行了startActivity, 引數如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
// 開始呼叫原始的方法, 調不呼叫隨你,但是不呼叫的話, 所有的startActivity都失效了.
// 由於這個方法是隱藏的,因此需要使用反射呼叫;首先找到這個方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (Instrumentation.ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
// 某該死的rom修改了 需要手動適配
throw new RuntimeException("do not support!!! pls adapt it");
}
}
然後通過反射拿到ActivityThread類中的mInstrumentation,程式碼如下:
public static void attachContext() throws Exception{
// 先獲取到當前的ActivityThread物件
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到原始的 mInstrumentation欄位
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 建立代理物件
Instrumentation evilInstrumentation = new ProxyInstrumentation(mInstrumentation);
// 偷樑換柱
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
然後在你應用的Application類中呼叫如上方法即可:
public class DemoApplication extends Application {
private static final String TAG = "DemoApplication";
private static Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = DemoApplication.this.getApplicationContext();
String processName = mContext.getApplicationInfo().processName;
Log.i(TAG, "onCreate: the processName=" + processName);
}
public static Context getContext(){
return mContext;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
attachContext();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void attachContext() throws Exception{
// 先獲取到當前的ActivityThread物件
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到原始的 mInstrumentation欄位
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 建立代理物件
Instrumentation evilInstrumentation = new ProxyInstrumentation(mInstrumentation);
// 偷樑換柱
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
}
列印如下: