Android系統原理與源碼分析:利用Java反射技術阻止通過按鈕關閉對話方塊

來源:互聯網
上載者:User

聲明:本文轉載自CSDN技術文章:http://blog.csdn.net/nokiaguy/archive/2010/07/27/5770263.aspx 作者:nokiaguy

各位轉載時,請註明原出處。

    眾所周知,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();
        }
    };

從這段代碼中可以猜出來,前幾行代碼用來觸發對話方塊中的三個按鈕( Positive 、 Negative 和 Neutral )的單擊事件,而最後的代碼則用來關閉對話方塊(因為我們發現了 MSG_DISMISS_DIALOG 、猜出來的)。

mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
                    .sendToTarget();

上面的代碼並不是直接來關閉對話方塊的,而是通過一個 Handler 來處理,代碼如下:

     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 變數的值。

從本文可以看出,雖然使用普通方法控制對話方塊的某些功能,但通過反射技術可以很容易地做到看似不可能完成的任務。當然,除了控制對話方塊的關閉功能外,還可以控制對話方塊其他的行為,剩下的就靠讀者自己挖掘了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.