隨筆 - 312, 文章 - 14, 評(píng)論 - 1393, 引用 - 0
          數(shù)據(jù)加載中……

          Android系統(tǒng)原理與源碼分析(1):利用Java反射技術(shù)阻止通過(guò)按鈕關(guān)閉對(duì)話框

          本文為原創(chuàng),如需轉(zhuǎn)載,請(qǐng)注明作者和出處,謝謝!

              眾所周知,AlertDialog類(lèi)用于顯示對(duì)話框。關(guān)于AlertDialog的基本用法在這里就不詳細(xì)介紹了,網(wǎng)上有很多,讀者可以自己搜索。那么本文要介紹的是如何隨心所欲地控制AlertDialog。
              現(xiàn)在我們來(lái)看看第一個(gè)需求:如果某個(gè)應(yīng)用需要彈出一個(gè)對(duì)話框。當(dāng)單擊“確定“按鈕時(shí)完成某些工作,如果這些工作失敗,對(duì)話框不能關(guān)閉。而當(dāng)成功完成工作后,則關(guān)閉對(duì)話框。當(dāng)然,無(wú)論何程度情況,單擊“取消”按鈕都會(huì)關(guān)閉對(duì)話框。
              這個(gè)需求并不復(fù)雜,也并不過(guò)分(雖然我們可以自己弄個(gè)Activity來(lái)完成這個(gè)工作,也可在View上自己放按鈕,但這顯示有些大炮打蚊子了,如果對(duì)話框上只有一行文本,費(fèi)這么多勁太不值了)。但使用過(guò)AlertDialog的讀者都知道,無(wú)論單擊的哪個(gè)按鈕,無(wú)論按鈕單擊事件的執(zhí)行情況如何,對(duì)話框是肯定要關(guān)閉的。也就是說(shuō),用戶(hù)無(wú)法控制對(duì)話框的關(guān)閉動(dòng)作。實(shí)際上,關(guān)閉對(duì)話框的動(dòng)作已經(jīng)在Android SDK寫(xiě)死了,并且未給使用者留有任何接口。但我的座右銘是“宇宙中沒(méi)有什么是不能控制的”。
              既然要控制對(duì)放框的關(guān)閉行為,首先就得分析是哪些類(lèi)、哪些代碼使這個(gè)對(duì)話框關(guān)閉的。進(jìn)入AlertDialog類(lèi)的源代碼。在AlertDialog中只定義了一個(gè)變量:mAlert。這個(gè)變量是AlertController類(lèi)型。AlertController類(lèi)是Android的內(nèi)部類(lèi),在com.android.internal.app包中,無(wú)法通過(guò)普通的方式訪問(wèn)。也無(wú)法在Eclipse中通過(guò)按Ctrl鍵跟蹤進(jìn)源代碼。但可以直接在Android源代碼中找到AlertController.java。我們?cè)倩氐紸lertDialog類(lèi)中。AlertDialog類(lèi)實(shí)際上只是一個(gè)架子。象設(shè)置按鈕、設(shè)置標(biāo)題等工作都是由AlertController類(lèi)完成的。因此,AlertController類(lèi)才是關(guān)鍵。
              找到AlertController.java文件。打開(kāi)后不要感到頭暈哦,這個(gè)文件中的代碼是很多地。不過(guò)這么多代碼對(duì)本文的主題也沒(méi)什么用處。下面就找一下控制按鈕的代碼。
              在AlertController類(lèi)的開(kāi)頭就會(huì)看到如下的代碼:
             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();
                  }
              };

          從這段代碼中可以猜出來(lái),前幾行代碼用來(lái)觸發(fā)對(duì)話框中的三個(gè)按鈕(PositiveNegativeNeutral)的單擊事件,而最后的代碼則用來(lái)關(guān)閉對(duì)話框(因?yàn)槲覀儼l(fā)現(xiàn)了MSG_DISMISS_DIALOG、猜出來(lái)的)。

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

          上面的代碼并不是直接來(lái)關(guān)閉對(duì)話框的,而是通過(guò)一個(gè)Handler來(lái)處理,代碼如下:

              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();。現(xiàn)在看了這么多源代碼,我們來(lái)總結(jié)一下對(duì)話框按鈕單擊事件的處理過(guò)程。在AlertController處理對(duì)話框按鈕時(shí)會(huì)為每一個(gè)按鈕添加一個(gè)onclick事件。而這個(gè)事件類(lèi)的對(duì)象實(shí)例就是上面的mButtonHandler。在這個(gè)單擊事件中首先會(huì)通過(guò)發(fā)送消息的方式調(diào)用為按鈕設(shè)置的單擊事件(也就是通過(guò)setPositiveButton等方法的第二個(gè)參數(shù)設(shè)置的單擊事件),在觸發(fā)完按鈕的單擊事件后,會(huì)通過(guò)發(fā)送消息的方式調(diào)用dismiss方法來(lái)關(guān)閉對(duì)話框。而在AlertController類(lèi)中定義了一個(gè)全局的mHandler變量。在AlertController類(lèi)中通過(guò)ButtonHandler類(lèi)來(lái)對(duì)象來(lái)為mHandler賦值。因此,我們只要使用我們自己Handler對(duì)象替換ButtonHandler就可以阻止調(diào)用dismiss方法來(lái)關(guān)閉對(duì)話框。下面先在自己的程序中建立一個(gè)新的ButtonHandler類(lèi)(也可叫其他的名)。

          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;
                  }
              }
          }

           我們可以看到,上面的類(lèi)和AlertController中的ButtonHandler類(lèi)很像,只是支掉了switch語(yǔ)句的最后一個(gè)case子句(用于調(diào)用dismiss方法)和相關(guān)的代碼。
              下面我們就要為AlertController中的mHandler重新賦值。由于mHandler是private變量,因此,在這里需要使用Java的反射技術(shù)來(lái)為mHandler賦值。由于在AlertDialog類(lèi)中的mAlert變量同樣也是private,因此,也需要使用同樣的反射技術(shù)來(lái)獲得mAlert變量。代碼如下:

           先建立一個(gè)AlertDialog對(duì)象

          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()

          上面的對(duì)話框很普通,單擊哪個(gè)按鈕都會(huì)關(guān)閉對(duì)話框。下面在調(diào)用show方法之前來(lái)修改一個(gè)mHandler變量的值,OK,下面我們就來(lái)見(jiàn)證奇跡的時(shí)刻。

                  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類(lèi)
                      field.set(obj, new ButtonHandler(alertDialog1));
                  }
                  
          catch (Exception e)
                  {
                  }
                
          //  顯示對(duì)話框
                alertDialog.show();

            我們發(fā)現(xiàn),如果加上try   catch語(yǔ)句,單擊對(duì)話框中的確定按鈕不會(huì)關(guān)閉對(duì)話框(除非在代碼中調(diào)用dismiss方法),單擊取消按鈕則會(huì)關(guān)閉對(duì)話框(因?yàn)檎{(diào)用了dismiss方法)。如果去了try…catch代碼段,對(duì)話框又會(huì)恢復(fù)正常了。
              雖然上面的代碼已經(jīng)解決了問(wèn)題,但需要編寫(xiě)的代碼仍然比較多,為此,我們也可采用另外一種方法來(lái)阻止關(guān)閉對(duì)話框。這種方法不需要定義任何的類(lèi)。
              這種方法需要用點(diǎn)技巧。由于系統(tǒng)通過(guò)調(diào)用dismiss來(lái)關(guān)閉對(duì)話框,那么我們可以在dismiss方法上做點(diǎn)文章。在系統(tǒng)調(diào)用dismiss方法時(shí)會(huì)首先判斷對(duì)話框是否已經(jīng)關(guān)閉,如果對(duì)話框已經(jīng)關(guān)閉了,就會(huì)退出dismiss方法而不再繼續(xù)關(guān)閉對(duì)話框了。因此,我們可以欺騙一下系統(tǒng),當(dāng)調(diào)用dismiss方法時(shí)我們可以讓系統(tǒng)以為對(duì)話框已經(jīng)關(guān)閉(雖然對(duì)話框還沒(méi)有關(guān)閉),這樣dismiss方法就失效了,這樣即使系統(tǒng)調(diào)用了dismiss方法也無(wú)法關(guān)閉對(duì)話框了。
              下面讓我們回到AlertDialog的源代碼中,再繼續(xù)跟蹤到AlertDialog的父類(lèi)Dialog的源代碼中。找到dismissDialog方法。實(shí)際上,dismiss方法是通過(guò)dismissDialog方法來(lái)關(guān)閉對(duì)話框的,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){}這段代碼。這個(gè)mShowing變量就是判斷對(duì)話框是否已關(guān)閉的。因此,我們?cè)诖a中通過(guò)設(shè)置這個(gè)變量就可以使系統(tǒng)認(rèn)為對(duì)話框已經(jīng)關(guān)閉,就不再繼續(xù)關(guān)閉對(duì)話框了。由于mShowing也是private變量,因此,也需要反射技術(shù)來(lái)設(shè)置這個(gè)變量。我們可以在對(duì)話框按鈕的單擊事件中設(shè)置mShowing,代碼如下:

          try
          {
              Field field 
          = dialog.getClass()
                      .getSuperclass().getDeclaredField(
                              
          "mShowing");
              field.setAccessible(
          true);
              
          //  將mShowing變量設(shè)為false,表示對(duì)話框已關(guān)閉
              field.set(dialog, false);
              dialog.dismiss();

          }
          catch (Exception e)
          {

          }

          將上面的代碼加到哪個(gè)按鈕的單擊事件代碼中,哪個(gè)按鈕就再也無(wú)法關(guān)閉對(duì)話框了。如果要關(guān)閉對(duì)話框,只需再將mShowing設(shè)為true即可。要注意的是,在一個(gè)按鈕里設(shè)置了mShowing變量,也會(huì)影響另一個(gè)按鈕的關(guān)閉對(duì)話框功能,因此,需要在每一個(gè)按鈕的單擊事件里都設(shè)置mShowing變量的值。

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





          Android開(kāi)發(fā)完全講義(第2版)(本書(shū)版權(quán)已輸出到臺(tái)灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱(chēng):李寧_Lining

          posted on 2010-07-27 23:05 銀河使者 閱讀(4482) 評(píng)論(2)  編輯  收藏 所屬分類(lèi): java 原創(chuàng)移動(dòng)(mobile)Android/OPhone

          評(píng)論

          # re: Android系統(tǒng)原理與源碼分析(1):利用Java反射技術(shù)阻止通過(guò)按鈕關(guān)閉對(duì)話框  回復(fù)  更多評(píng)論   

          void android.app.Activity.dismissDialog(int id)
          不是可以dismiss一個(gè)dialog么?
          2010-07-28 14:51 | Kurt

          # re: Android系統(tǒng)原理與源碼分析(1):利用Java反射技術(shù)阻止通過(guò)按鈕關(guān)閉對(duì)話框  回復(fù)  更多評(píng)論   

          dismiss內(nèi)部調(diào)用的是dismissDialog方法關(guān)閉對(duì)話框的。
          2010-07-28 18:19 | 銀河使者
          主站蜘蛛池模板: 都安| 牟定县| 修水县| 嘉义市| 新干县| 图木舒克市| 汉川市| 汕头市| 湾仔区| 开封市| 金昌市| 女性| 嘉黎县| 建宁县| 滨州市| 同心县| 龙陵县| 米脂县| 河源市| 垫江县| 天等县| 成都市| 黑河市| 农安县| 民丰县| 双桥区| 云霄县| 东光县| 布拖县| 肥东县| 灵寿县| 中方县| 盐山县| 奉新县| 永仁县| 临城县| 新余市| 安丘市| 苍山县| 卫辉市| 丹棱县|