一、什麼是反射機制
在程序運行期間,java系統始終爲所有的對象維護一個運行時的類型標識,這個信息保存着每個對象所屬的類的足跡,虛擬機利用運行時信息選擇相應的方法執行。
然而,可以通過專門的java類訪問這些信息,保存這些信息的類被稱爲Class(這個Class不是類名關鍵字的Class),這個名字很容易讓人混淆。Object類中的getClass()方法將返回一個Class類型的實例!
例如,最常用的Class的方法是getName(),這個方法將返回類的名字。
- Date date = new Date();
- Class cl = date.getClass();
- String name = cl.getName();
再比如,
getFields()將返回一個包含Field對象的數組,這些對象記錄了這個類或其超類的公有域。
getDeclaredField()也將返回一個包含Field對象的數組,這些對象記錄了這個類的全部域。
(如果類中沒有域,或者Class對象描述的是基本類型或數組類型,這些方法將返回一個長度爲0的數組)
getMethods()將返回包含Method對象的數組,這些對象包含所有的公有方法,包括從超類繼承來的公有方法。
getDeclaredMethods()也將返回包含Method對象的數組,包含這個類或者接口的全部方法,但不包括由超類繼承了的方法。
實際上,java安全機制只允許查看任意對象有哪些域,而不允許讀取他們的值。
反射機制的默認行爲受限於java的訪問控制,然而,可以用setAccessible(true)來獲得讀取權限。
二、代碼舉例
下面轉載一個例子加深理解(http://blog.csdn.net/nokiaguy/article/details/5770263):
現在我們來看看第一個需求:如果某個應用需要彈出一個對話框。當單擊“確定“按鈕時完成某些工作,如果這些工作失敗,對話框不能關閉。而當成功完成工作後,則關閉對話框。當然,無論何程度情況,單擊“取消”按鈕都會關閉對話框。這個需求並不複雜,也並不過分(雖然我們可以自己弄個Activity來完成這個工作,也可在View上自己放按鈕,但這顯示有些大炮打蚊子了,如果對話框上只有一行文本,費這麼多勁太不值了)。
既然要控制對放框的關閉行爲,首先就得分析是哪些類、哪些代碼使這個對話框關閉的。進入AlertDialog類的源代碼。在AlertDialog中只定義了一個變量:mAlert。這個變量是AlertController類型。AlertController類是Android的內部類,在com.android.internal.app包中,無法通過普通的方式訪問。也無法在Eclipse中通過按Ctrl鍵跟蹤進源代碼。但可以直接在Android源代碼中找到AlertController.java。我們再回到AlertDialog類中。AlertDialog類實際上只是一個架子。象設置按鈕、設置標題等工作都是由AlertController類完成的。因此,AlertController類纔是關鍵。
- View.OnClickListener mButtonHandler = new View.OnClickListener() {
- public void onClick(View v) {
- Message m = null;
- if (v == mButtonPositive && mButtonPositiveMessage != null) {
- m = Message.obtain(mButtonPositiveMessage);
- } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
- m = Message.obtain(mButtonNegativeMessage);
- } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
- m = Message.obtain(mButtonNeutralMessage);
- }
- if (m != null) {
- m.sendToTarget();
- }
- // Post a message so we dismiss after the above handlers are executed
- mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
- .sendToTarget();
- }
- };
上面代碼通過Handler發送MSG_DISMISS_DIALOG消息來關閉對話框,下面看下Handle的處理:
- private static final class ButtonHandler extends Handler {
- // Button clicks have Message.what as the BUTTON{1,2,3} constant
- private static final int MSG_DISMISS_DIALOG = 1;
- private WeakReference<DialogInterface> mDialog;
- public ButtonHandler(DialogInterface dialog) {
- mDialog = new WeakReference<DialogInterface>(dialog);
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case DialogInterface.BUTTON_POSITIVE:
- case DialogInterface.BUTTON_NEGATIVE:
- case DialogInterface.BUTTON_NEUTRAL:
- ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
- break;
- case MSG_DISMISS_DIALOG:
- ((DialogInterface) msg.obj).dismiss();
- }
- }
- }
從上面代碼的最後可以找到 ((DialogInterface) msg.obj).dismiss();。現在看了這麼多源代碼,我們來總結一下對話框按鈕單擊事件的處理過程。在AlertController處理對話框按鈕時會爲每一個按鈕添加一個onclick事件。而這個事件類的對象實例就是上面的mButtonHandler。在這個單擊事件中首先會通過發送消息的方式調用爲按鈕設置的單擊事件(也就是通過setPositiveButton等方法的第二個參數設置的單擊事件),在觸發完按鈕的單擊事件後,會通過發送消息的方式調用dismiss方法來關閉對話框。而在AlertController類中定義了一個全局的mHandler變量。在AlertController類中通過ButtonHandler類來對象來爲mHandler賦值。因此,我們只要使用我們自己Handler對象替換ButtonHandler就可以阻止調用dismiss方法來關閉對話框。下面先在自己的程序中建立一個新的ButtonHandler類(也可叫其他的名)。
- class ButtonHandler extends Handler
- {
- private WeakReference<DialogInterface> mDialog;
- public ButtonHandler(DialogInterface dialog)
- {
- mDialog = new WeakReference<DialogInterface>(dialog);
- }
- @Override
- public void handleMessage(Message msg)
- {
- switch (msg.what)
- {
- case DialogInterface.BUTTON_POSITIVE:
- case DialogInterface.BUTTON_NEGATIVE:
- case DialogInterface.BUTTON_NEUTRAL:
- ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog
- .get(), msg.what);
- break;
- }
- }
- }
我們可以看到,上面的類和AlertController中的ButtonHandler類很像,只是支掉了switch語句的最後一個case子句(用於調用dismiss方法)和相關的代碼。
下面我們就要爲AlertController中的mHandler重新賦值。由於mHandler是private變量,因此,在這裏需要使用Java的反射技術來爲mHandler賦值。由於在AlertDialog類中的mAlert變量同樣也是private,因此,也需要使用同樣的反射技術來獲得mAlert變量。代碼如下:
先建立一個AlertDialog對象:
- AlertDialog alertDialog = new AlertDialog.Builder(this)
- .setTitle("abc")
- .setMessage("content")
- .setIcon(R.drawable.icon)
- .setPositiveButton( “確定”,
- new OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog,
- int which)
- {
- }
- }).setNegativeButton("取消", new OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- dialog.dismiss();
- }
- }).create()
上面的對話框很普通,單擊哪個按鈕都會關閉對話框。下面在調用show方法之前來修改一個mHandler變量的值,OK,下面我們就來見證奇蹟的時刻:
- try
- {
- Field field = alertDialog1.getClass().getDeclaredField("mAlert");
- field.setAccessible(true);
- // 獲得mAlert變量的值
- Object obj = field.get(alertDialog1);
- field = obj.getClass().getDeclaredField("mHandler");
- field.setAccessible(true);
- // 修改mHandler變量的值,使用新的ButtonHandler類
- field.set(obj, new ButtonHandler(alertDialog1));
- }
- catch (Exception e)
- {
- }
- // 顯示對話框
- alertDialog.show();
我們發現,如果加上try catch語句,單擊對話框中的確定按鈕不會關閉對話框(除非在代碼中調用dismiss方法),單擊取消按鈕則會關閉對話框(因爲調用了dismiss方法)。如果去了try…catch代碼段,對話框又會恢復正常了。
雖然上面的代碼已經解決了問題,但需要編寫的代碼仍然比較多,爲此,我們也可採用另外一種方法來阻止關閉對話框。這種方法不需要定義任何的類。
這種方法需要用點技巧。由於系統通過調用dismiss來關閉對話框,那麼我們可以在dismiss方法上做點文章。在系統調用dismiss方法時會首先判斷對話框是否已經關閉,如果對話框已經關閉了,就會退出dismiss方法而不再繼續關閉對話框了。因此,我們可以欺騙一下系統,當調用dismiss方法時我們可以讓系統以爲對話框已經關閉(雖然對話框還沒有關閉),這樣dismiss方法就失效了,這樣即使系統調用了dismiss方法也無法關閉對話框了。
下面讓我們回到AlertDialog的源代碼中,再繼續跟蹤到AlertDialog的父類Dialog的源代碼中。找到dismissDialog方法。實際上,dismiss方法是通過dismissDialog方法來關閉對話框的,dismissDialog方法的代碼如下:
- private void dismissDialog() {
- if (mDecor == null) {
- if (Config.LOGV) Log.v(LOG_TAG,
- "[Dialog] dismiss: already dismissed, ignore");
- return;
- }
- if (!mShowing) {
- if (Config.LOGV) Log.v(LOG_TAG,
- "[Dialog] dismiss: not showing, ignore");
- return;
- }
- mWindowManager.removeView(mDecor);
- mDecor = null;
- mWindow.closeAllPanels();
- onStop();
- mShowing = false;
- sendDismissMessage();
- }
該方法後面的代碼不用管它,先看if(!mShowing){…}這段代碼。這個mShowing變量就是判斷對話框是否已關閉的。因此,我們在代碼中通過設置這個變量就可以使系統認爲對話框已經關閉,就不再繼續關閉對話框了。由於mShowing也是private變量,因此,也需要反射技術來設置這個變量。我們可以在對話框按鈕的單擊事件中設置mShowing,代碼如下:
- try
- {
- Field field = dialog.getClass()
- .getSuperclass().getDeclaredField(
- "mShowing");
- field.setAccessible(true);
- // 將mShowing變量設爲false,表示對話框已關閉
- field.set(dialog, false);
- dialog.dismiss();
- }
- catch (Exception e)
- {
- }
將上面的代碼加到哪個按鈕的單擊事件代碼中,哪個按鈕就再也無法關閉對話框了。如果要關閉對話框,只需再將mShowing設爲true即可。要注意的是,在一個按鈕裏設置了mShowing變量,也會影響另一個按鈕的關閉對話框功能,因此,需要在每一個按鈕的單擊事件裏都設置mShowing變量的值。
從本文可以看出,雖然使用普通方法控制對話框的某些功能,但通過反射技術可以很容易地做到看似不可能完成的任務。當然,除了控制對話框的關閉功能外,還可以控制對話框其他的行爲,剩下的就靠讀者自己挖掘了。
原文鏈接:http://blog.csdn.net/boyupeng/article/details/46975073