Android系統原理與源碼分析(1):利用Java反射技術阻止經過按鈕關閉對話框

衆所周知,AlertDialog類用於顯示對話框。關於AlertDialog的基本用法在這裏就不詳細介紹了,網上有不少,讀者能夠本身搜索。那麼本文要介紹的是如何爲所欲爲地控制AlertDialog。
    如今咱們來看看第一個需求:若是某個應用須要彈出一個對話框。當單擊「肯定「按鈕時完成某些工做,若是這些工做失敗,對話框不能關閉。而當成功完成工做後,則關閉對話框。固然,不管何程度狀況,單擊「取消」按鈕都會關閉對話框。
    這個需求並不複雜,也並不過度(雖然咱們能夠本身弄個Activity來完成這個工做,也可在View上本身放按鈕,但這顯示有些大炮打蚊子了,若是對話 框上只有一行文本,費這麼多勁太不值了)。但使用過AlertDialog的讀者都知道,不管單擊的哪一個按鈕,不管按鈕單擊事件的執行狀況如何,對話框是 確定要關閉的。也就是說,用戶沒法控制對話框的關閉動做。實際上,關閉對話框的動做已經在Android SDK寫死了,而且未給使用者留有任何接口。但個人座右銘是「宇宙中沒有什麼是不能控制的」。
    既然要控制對放框的關閉行爲,首先就得分析是哪些類、哪些代碼使這個對話框關閉的。進入AlertDialog類的源代碼。在AlertDialog中只 定義了一個變量:mAlert。這個變量是AlertController類型。AlertController類是Android的內部類,在 com.android.internal.app包中,沒法經過普通的方式訪問。也沒法在Eclipse中經過按Ctrl鍵跟蹤進源代碼。但能夠直接在 Android源代碼中找到AlertController.java。咱們再回到AlertDialog類中。AlertDialog類實際上只是一個 架子。象設置按鈕、設置標題等工做都是由AlertController類完成的。所以,AlertController類纔是關鍵。
    找到AlertController.java文件。打開後不要感到頭暈哦,這個文件中的代碼是不少地。不過這麼多代碼對本文的主題也沒什麼用處。下面就找一下控制按鈕的代碼。
    在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來處理,代碼以下:
java

 

複製代碼
代碼
     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類(也可叫其 他的名)。
android

複製代碼
代碼
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,下面咱們就來見證奇蹟的時刻。
app

複製代碼
代碼
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)
{
}
  顯示對話框
ertDialog.show();
複製代碼

      咱們發現,若是加上try   catch語句,單擊對話框中的肯定按鈕不會關閉對話框(除非在代碼中調用dismiss方法),單擊取消按鈕則會關閉對話框(由於調用了dismiss方法)。若是去了try…catch代碼段,對話框又會恢復正常了。
     雖然上面的代碼已經解決了問題,但須要編寫的代碼仍然比較多,爲此,咱們也可採用另一種方法來阻止關閉對話框。這種方法不須要定義任何的類。
     這種方法須要用點技巧。因爲系統經過調用dismiss來關閉對話框,那麼咱們能夠在dismiss方法上作點文章。在系統調用dismiss方法時會首 先判斷對話框是否已經關閉,若是對話框已經關閉了,就會退出dismiss方法而再也不繼續關閉對話框了。所以,咱們能夠欺騙一下系統,當調用 dismiss方法時咱們可讓系統覺得對話框已經關閉(雖然對話框尚未關閉),這樣dismiss方法就失效了,這樣即便系統調用了dismiss方 法也沒法關閉對話框了。
     下面讓咱們回到AlertDialog的源代碼中,再繼續跟蹤到AlertDialog的父類Dialog的源代碼中。找到dismissDialog方 法。實際上,dismiss方法是經過dismissDialog方法來關閉對話框的,dismissDialog方法的代碼以下: ide

複製代碼
代碼
  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,代碼以下: ui

複製代碼
代碼
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變量的值。 this

     從本文能夠看出,雖然使用普通方法控制對話框的某些功能,但經過反射技術能夠很容易地作到看似不可能完成的任務。固然,除了控制對話框的關閉功能外,還能夠控制對話框其餘的行爲,剩下的就靠讀者本身挖掘了。
相關文章
相關標籤/搜索