溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Dialog源碼分析

發(fā)布時間:2020-05-31 19:10:40 來源:網(wǎng)絡(luò) 閱讀:217 作者:楊充 欄目:移動開發(fā)
目錄介紹
  • 1.簡單用法
  • 2.AlertDialog源碼分析
    • 2.1 AlertDialog.Builder的構(gòu)造方法
    • 2.2 通過AlertDialog.Builder對象設(shè)置屬性
    • 2.3 builder.create方法
    • 2.4 看看create方法中的P.apply(dialog.mAlert)源碼
    • 2.5 看看AlertDialog的show方法
  • 3.Dialog源碼分析
    • 3.1 Dialog的構(gòu)造方法
    • 3.2 Dialog生命周期
    • 3.3 Dialog中show方法展示彈窗
    • 3.4 Dialog的dismiss銷毀彈窗
  • 4.Dialog彈窗問題分析
  • 5.Dialog彈窗總結(jié)

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客,Python學習筆記等等,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
  • DialogFragment封裝庫項目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源碼深度分析
    • 最簡單的創(chuàng)建,簡單改造避免重復創(chuàng)建,show()方法源碼分析,scheduleTimeoutLocked吐司如何自動銷毀的,TN類中的消息機制是如何執(zhí)行的,普通應(yīng)用的Toast顯示數(shù)量是有限制的,用代碼解釋為何Activity銷毀后Toast仍會顯示,Toast偶爾報錯Unable to add window是如何產(chǎn)生的,Toast運行在子線程問題,Toast如何添加系統(tǒng)窗口的權(quán)限等等
  • 03.DialogFragment源碼分析
    • 最簡單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析,重點分析彈窗展示和銷毀源碼,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源碼分析
    • 顯示PopupWindow,注意問題寬和高屬性,showAsDropDown()源碼,dismiss()源碼分析,PopupWindow和Dialog有什么區(qū)別?為何彈窗點擊一下就dismiss呢?
  • 06.Snackbar源碼分析
    • 最簡單的創(chuàng)建,Snackbar的make方法源碼分析,Snackbar的show顯示與點擊消失源碼分析,顯示和隱藏中動畫源碼分析,Snackbar的設(shè)計思路,為什么Snackbar總是顯示在最下面
  • 07.彈窗常見問題
    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常見產(chǎn)生的?Toast偶爾報錯Unable to add window,Toast運行在子線程導致崩潰如何解決?
  • 08.Builder模式
    • Builder模式使用場景,簡單案例,Builder模式實際案例Demo展示,看看AlertDialog.Builder源代碼如何實現(xiàn),為什么AlertDialog要使用builder模式呢?builder模式優(yōu)缺點分析。關(guān)于builder模式經(jīng)典的案例可以參考我的彈窗封裝庫:https://github.com/yangchong211/YCDialog

1.簡單用法

  • 一般都是在使用AlertDialog,但AlertDialog主要也是繼承自Dialog。下面我們來分析分析Dialog源碼。
  • 最簡單用法如下所示
    private AlertDialog alertDialog=null;
    public void showDialog(){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setIcon(R.mipmap.ic_launcher);
        builder.setMessage("瀟湘劍雨");
        builder.setTitle("這個是標題");
        builder.setView(R.layout.activity_main);
        builder.setPositiveButton("確定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                alertDialog.dismiss();
            }
        });
        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                alertDialog.dismiss();
            }
        });
        alertDialog = builder.create();
        alertDialog.show();
    }

2.AlertDialog源碼分析

2.1 AlertDialog.Builder的構(gòu)造方法
  • 先來看一下AlertDialog.Builder的構(gòu)造方法,這里的Builder是AlertDialog的內(nèi)部類,用于封裝AlertDialog的構(gòu)造過程,看一下Builder的構(gòu)造方法:
    public Builder(Context context) {
        this(context, resolveDialogTheme(context, 0));
    }
    • 然后調(diào)用的是Builder的重載構(gòu)造方法:
      public Builder(Context context, int themeResId) {
      P = new AlertController.AlertParams(new ContextThemeWrapper(
              context, resolveDialogTheme(context, themeResId)));
      }
  • 接著這里的P是AlertDialog.Builder中的一個AlertController.AlertParams類型的成員變量,可見在這里執(zhí)行了P的初始化操作。
    public AlertParams(Context context) {
        mContext = context;
        mCancelable = true;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
  • 可以看到這里主要執(zhí)行了AlertController.AlertParams的初始化操作,初始化了一些成員變量。這樣執(zhí)行了一系列操作之后我們的代碼:
    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
2.2 通過AlertDialog.Builder對象設(shè)置屬性
  • 調(diào)用了builder.setIcon方法,這里看一下setIcon方法的具體實現(xiàn):
    • 可以看到AlertDialog的Builder的setIcon方法,這里執(zhí)行的就是給類型為AlertController.AlertParams的P的mIconId賦值為傳遞的iconId,并且這個方法返回的類型就是Builder。
      public Builder setIcon(@DrawableRes int iconId) {
      P.mIconId = iconId;
      return this;
      }
  • 調(diào)用了builder.setMessage方法,可以看一下builder.setMessage方法的具體實現(xiàn):
    • 跟setIcon方法的實現(xiàn)邏輯類似,都是給成員變量的mMessage賦值為我們傳遞的Message值,且和setIcon方法類似的,這個方法返回值也是Builder。
      public Builder setMessage(CharSequence message) {
      P.mMessage = message;
      return this;
      }
  • 然后看一下builder.setTitle方法:
    • 發(fā)現(xiàn)builder的setIcon、setMessage、setTitle等方法都是給Builder的成員變量P的icon,message,title賦值。
      public Builder setTitle(CharSequence title) {
      P.mTitle = title;
      return this;
      }
  • 接著看一下builder.setView方法:
    • 發(fā)現(xiàn)這里的setView和setIcon,setMessage,setTitle等方法都是類似的,都是將我們傳遞的數(shù)據(jù)值賦值給Builder的成員變量P。
      public Builder setView(int layoutResId) {
      P.mView = null;
      P.mViewLayoutResId = layoutResId;
      P.mViewSpacingSpecified = false;
      return this;
      }
2.3 builder.create方法
  • 然后調(diào)用了builder.create方法,并且這個方法返回了AlertDialog。
    • Dialog源碼分析
  • 可以看到這里首先構(gòu)造了一個AlertDialog,我們可以看一下這個構(gòu)造方法的具體實現(xiàn):
    • 可以看到這里首先調(diào)用了super的構(gòu)造方法,而我們的AlertDialog繼承于Dialog,所以這里執(zhí)行的就是Dialog的構(gòu)造方法【備注:高版本是繼承AppCompatDialog,然后AppCompatDialog再繼承Dialog】
    • Dialog源碼分析
  • 回到AlertDialog的構(gòu)造方法中,在構(gòu)造方法中,除了調(diào)用Dialog的構(gòu)造方法之外還執(zhí)行了
    • 相當于初始化了AlertDiaog的成員變量mAlert。
      mAlert = new AlertController(getContext(), this, getWindow());
2.4 看看create方法中的P.apply(dialog.mAlert)源碼
  • 再AlertDialog.Builder.create方法,在創(chuàng)建了一個AlertDialog之后,又執(zhí)行了P.apply(dialog.mAlert);這里的P是一個AlertController.AlertParams的變量,而dialog.mAlert是剛剛創(chuàng)建的AlertDialog中的一個AlertController類型的變量,來看一下apply方法的具體實現(xiàn):
    • 在初始化AlertDialog.Builder的時候設(shè)置的icon、title、message賦值給了AlertController.AlertParams,這里就是將初始化時候設(shè)置的屬性值賦值給我們創(chuàng)建的Dialog對象的mAlert成員變量
      ublic void apply(AlertController dialog) {
      if (mCustomTitleView != null) {
          dialog.setCustomTitle(mCustomTitleView);
      } else {
          if (mTitle != null) {
              dialog.setTitle(mTitle);
          }
          if (mIcon != null) {
              dialog.setIcon(mIcon);
          }
          if (mIconId != 0) {
              dialog.setIcon(mIconId);
          }
          if (mIconAttrId != 0) {
              dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
          }
      }
      if (mMessage != null) {
          dialog.setMessage(mMessage);
      }
      if (mPositiveButtonText != null) {
          dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                  mPositiveButtonListener, null);
      }
      if (mNegativeButtonText != null) {
          dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                  mNegativeButtonListener, null);
      }
      if (mNeutralButtonText != null) {
          dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                  mNeutralButtonListener, null);
      }
      if (mForceInverseBackground) {
          dialog.setInverseBackgroundForced(true);
      }
      // For a list, the client can either supply an array of items or an
      // adapter or a cursor
      if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
          createListView(dialog);
      }
      if (mView != null) {
          if (mViewSpacingSpecified) {
              dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                      mViewSpacingBottom);
          } else {
              dialog.setView(mView);
          }
      } else if (mViewLayoutResId != 0) {
          dialog.setView(mViewLayoutResId);
      }
      }
2.5 看看AlertDialog的show方法
  • 看看如下所示,可以發(fā)現(xiàn)直接調(diào)用了dialog中的show方法。下面接著分析
    • Dialog源碼分析

3.Dialog源碼分析

3.1 Dialog的構(gòu)造方法
  • 如下所示

    • 看源碼可知在Dialog的構(gòu)造方法中直接直接構(gòu)造了一個PhoneWindow,并賦值給Dialog的成員變量mWindow,從這里可以看出其實Dialog和Activity的顯示邏輯都是類似的,都是通過對應(yīng)的Window變量來實現(xiàn)窗口的加載與顯示的。然后我們執(zhí)行了一些Window對象的初始化操作,比如設(shè)置回調(diào)函數(shù)為本身,然后調(diào)用Window類的setWindowManager方法,并傳入了WindowManager。然后創(chuàng)建一個對話框監(jiān)聽handler對象。

      Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
      if (createContextThemeWrapper) {
          if (themeResId == 0) {
              final TypedValue outValue = new TypedValue();
              context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
              themeResId = outValue.resourceId;
          }
          //創(chuàng)建一個Context
          mContext = new ContextThemeWrapper(context, themeResId);
      } else {
          mContext = context;
      }
      
      //獲取一個WindowManager對象
      mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
      //創(chuàng)建一個Window對象
      final Window w = new PhoneWindow(mContext);
      //將Window對象w賦值給mWindow
      mWindow = w;
      //為Windowd對象設(shè)置回調(diào),并且它本身實現(xiàn)了這些回調(diào)函數(shù)
      w.setCallback(this);
      w.setOnWindowDismissedCallback(this);
      //為Window對象設(shè)置WindowManager對象
      w.setWindowManager(mWindowManager, null, null);
      w.setGravity(Gravity.CENTER);
      //創(chuàng)建一個對話框監(jiān)聽Handler
      mListenersHandler = new ListenersHandler(this);
      }
  • 接著看看w.setWindowManager(mWindowManager, null, null)里面的源代碼
    • 可以看到跟Activity的Window對象的windowManager的獲取方式是相同的,都是通過new的方式創(chuàng)建一個新的WindowManagerImpl對象。
    • Dialog源碼分析
3.2 Dialog生命周期
  • dialog的生命周期如下所示
    /**
     * 類似于Activity的onCreate函數(shù),可以在這個方法中進行Dialog的一些初始化操作
     * 包括調(diào)用setContentView方法
     */ 
    protected void onCreate(Bundle savedInstanceState) { } 
    /**
     * 當對話框啟動的時候被調(diào)用.
     */ 
    protected void onStart() { } 
    /**
     * 當對話框停止的時候被調(diào)用.
     */ 
    protected void onStop() { }
3.3 Dialog中show方法展示彈窗
  • 源碼如下所示,關(guān)于重點的邏輯,我在這里只是簡單的注釋了一下。

    public void show() {
        //首先判斷對話框是否顯示
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }
    
        mCanceled = false;
        /* 判斷對話框是否創(chuàng)建過,如果沒有創(chuàng)建過
         * 在dispatchOnCreate里面就會回調(diào)onCreate函數(shù)
         */
        if (!mCreated) {
            dispatchOnCreate(null);
        }
        //回調(diào)onStart函數(shù)
        onStart();
        //獲取Window對象總的DecorView,如果調(diào)用了setContentView就會創(chuàng)建DecorView
        mDecor = mWindow.getDecorView();
        //下面就會獲取布局的一些屬性
        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new WindowDecorActionBar(this);
        }
    
        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }
    
        try {
            //將DecorView添加到WindowManager中,這些就會顯示了
            mWindowManager.addView(mDecor, l);
            //將mShowing置為true
            mShowing = true;
            sendShowMessage();
        } finally {
        }
    }
  • 方法體的內(nèi)容比較多,由于一開始mShowing變量用于表示當前dialog是否正在顯示,由于我們剛剛開始調(diào)用執(zhí)行show方法,所以這里的mShowing變量的值為false,所以if分支的內(nèi)容不會被執(zhí)行,繼續(xù)往下看:
    if (!mCreated) {
        dispatchOnCreate(null);
    }
  • mCreated這個控制變量控制dispatchOnCreate方法只被執(zhí)行一次,由于是第一次執(zhí)行,所以這里會執(zhí)行dispatchOnCreate方法,好吧,看一下dispatchOnCreate方法的執(zhí)行邏輯:
    • 可以看到代碼的執(zhí)行邏輯很簡單就是回調(diào)了Dialog的onCreate方法
      void dispatchOnCreate(Bundle savedInstanceState) {
      if (!mCreated) {
          onCreate(savedInstanceState);
          mCreated = true;
      }
      }
  • 調(diào)用了onStart方法,這個方法主要用于設(shè)置ActionBar,這里不做過多的說明,然后初始化WindowManager.LayoutParams對象,并最終調(diào)用我們的mWindowManager.addView()方法。
    protected void onStart() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
    }
  • 最后調(diào)用了sendShowMessage方法,可以看一下這個方法的實現(xiàn):
    • 那么發(fā)送這個消息主要是什么作用呢,逗比們,接著往下看看。
      private void sendShowMessage() {
      if (mShowMessage != null) {
          // Obtain a new message so this dialog can be re-used
          Message.obtain(mShowMessage).sendToTarget();
      }
      }
  • 這里會發(fā)送一個Dialog已經(jīng)顯示的異步消息,該消息最終會在ListenersHandler中的handleMessage方法中被執(zhí)行:
    private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;
        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(dialog);
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }
  • 由于我們的msg.what = SHOW,所以會執(zhí)行OnShowListener.onShow方法,那么這個OnShowListener是何時賦值的呢?還記得我們構(gòu)造AlertDialog.Builder么?

    alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
    
        }
    });
  • 這樣就為我們的AlertDialog.Builder設(shè)置了OnShowListener,可以看一下setOnShowListener方法的具體實現(xiàn):
    • 這樣就為我們的Dialog中的mListenersHandler構(gòu)造了Message對象,并且在Dialog中發(fā)送showMessage的時候被mListenersHandler所接收。
      public void setOnShowListener(OnShowListener listener) {
      if (listener != null) {
          mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
      } else {
          mShowMessage = null;
      }
      }
3.4 Dialog的dismiss銷毀彈窗
3.4.1 看看cancel()方法
  • 調(diào)用alertDialog.cancel()或者alertDialog.dismiss()都可以達到銷毀彈窗的效果。
    • 首先看一下Dialog的cannel方法的具體實現(xiàn):
      public void cancel() {
      if (!mCanceled && mCancelMessage != null) {
          mCanceled = true;
          // Obtain a new message so this dialog can be re-used
          Message.obtain(mCancelMessage).sendToTarget();
      }
      dismiss();
      }
  • 可以看到方法體中,若當前Dialog沒有取消,并且設(shè)置了取消message,則調(diào)用Message.obtain(mCancel).sendToTarget(),前面已經(jīng)分析過這里的sendToTarget方法會回調(diào)注冊的異步消息處理邏輯:
    public void setOnCancelListener(final OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }
  • 可以看到如果在初始化AlertDialog.Builder時,設(shè)置了setOnCancelListener,那么就會執(zhí)行mListenersHandler的異步消息處理,好吧,這里看一下mListenersHandler的定義:

    private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;
    
        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(dialog);
        }
    
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }
  • 調(diào)用的是設(shè)置的OnCancelListener的onCancel方法,也就是說調(diào)用dialog.cancel方法時首先會判斷dialog是否調(diào)用了setOnCancelListener若設(shè)置了,則先調(diào)用OnCancelListener的onCancel方法,然后再次執(zhí)行dismiss方法,若我們沒有為Dialog.Builder設(shè)置OnCancelListener那么cancel方法和dismiss方法是等效的。
3.4.2 看看dismiss方法
  • 如下所示
    • 可以看到,這里首先判斷當前線程的Looper是否是主線程的Looper(由于mHandler是在主線程中創(chuàng)建的,所以mHandler.getLooper返回的是主線程中創(chuàng)建的Looper對象),若是的話,則直接執(zhí)行dismissDialog()方法,否則的話,通過mHandler發(fā)送異步消息至主線程中,簡單來說就是判斷當前線程是否是主線程,若是主線程則執(zhí)行dismissDialog方法否則發(fā)送異步消息
      public void dismiss() {
      if (Looper.myLooper() == mHandler.getLooper()) {
          dismissDialog();
      } else {
          mHandler.post(mDismissAction);
      }
      }
  • 然后看一下mHandler對異步消息的處理機制,由于這里的mDismissAction是一個Runnable對象,所以這里直接看一下mDismissAction的定義:
    • 這里的異步消息最終也是調(diào)用的dismissDialog方法
      private final Runnable mDismissAction = new Runnable() {
      public void run() {
          dismissDialog();
      }
      };
3.4.3 cancel和dismiss方法都調(diào)用dismissDialog方法
  • 所以無論執(zhí)行的cancel方法還是dismiss方法,無論方法是在主線程執(zhí)行還是子線程中執(zhí)行,最終調(diào)用的都是dismissDialog方法,那么就看一下dismissDialog是怎么個執(zhí)行邏輯。

    • 首先判斷當前的mDector是否為空,或者當前Dialog是否在顯示,若為空或者沒有在顯示,則直接return掉,也就是說當前我們的dialog已經(jīng)不再顯示了,則不需要往下在執(zhí)行。然后調(diào)用了mWindow.isDestroyed()方法,判斷Window對象是否已經(jīng)被銷毀,若已經(jīng)被銷毀,則直接return,并打印錯誤日志。
    • 然后再調(diào)用了mWindowManager.removeViewImmediate(mDector),這里的mDector是Dialog窗口的根布局,看這個方法的名字應(yīng)該就是Dialog去除根布局的操作了,可以看一下這個方法的具體實現(xiàn)。

      void dismissDialog() {
      if (mDecor == null || !mShowing) {
          return;
      }
      
      if (mWindow.isDestroyed()) {
          Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
          return;
      }
      
      try {
          mWindowManager.removeViewImmediate(mDecor);
      } finally {
          if (mActionMode != null) {
              mActionMode.finish();
          }
          mDecor = null;
          mWindow.closeAllPanels();
          onStop();
          mShowing = false;
      
          sendDismissMessage();
      }
      }
  • mWindowManager其實是WindowManagerImpl的實例,所以這里的removeViewImmediate方法應(yīng)該是WindowManagerImpl中的方法,看一下它的具體實現(xiàn):
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
  • 可以發(fā)現(xiàn),這里它調(diào)用了mGlobal.removeView方法,而這里的mGlobal是WindowManagerGlobal的實例,所以再看一下WIndowManagerGlobal中removeView的實現(xiàn)邏輯:

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
    
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
    
            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
  • 可以發(fā)現(xiàn),這里在獲取了保存的mDector組件之后,又調(diào)用了removeViewLocked方法,在看一下這個方法的具體實現(xiàn)邏輯:

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();
    
        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
  • 看到了么,我們獲取了mDector組件的ViewRootImpl,然后調(diào)用了其的die方法,通過這個方法實現(xiàn)Window組件的銷毀流程。

    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
    
        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
  • 可以看到在方法體中有調(diào)用了doDie方法,看名字應(yīng)該就是真正執(zhí)行window銷毀工作的方法了,我們在看一下doDie方法的具體實現(xiàn):

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
    
            if (mAdded && !mFirst) {
                destroyHardwareRenderer();
    
                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }
    
                    mSurface.release();
                }
            }
    
            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
  • 可以看到方法體中,首先調(diào)用了checkThread方法,判斷當前執(zhí)行代碼的線程,若不是主線程,則拋出異常:
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
  • 順著doDie的方法往下看,又調(diào)用了dispatchDetachedFromWindow()方法,這個方法主要是銷毀Window中的各中成員變量,臨時變量等

    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }
    
        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();
    
        destroyHardwareRenderer();
    
        setAccessibilityFocus(null, null);
    
        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;
    
        mSurface.release();
    
        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
    
        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
    
     mDisplayManager.unregisterDisplayListener(mDisplayListener);
    
        unscheduleTraversals();
    }
  • 可以看到在方法中調(diào)用了mView.dispatchDetachedFromWindow方法,這個方法的作用就是將mView從Window中detach出來,可以看一下這個方法的具體實現(xiàn):

    void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
            }
        }
    
        onDetachedFromWindow();
        onDetachedFromWindowInternal();
    
        InputMethodManager imm = InputMethodManager.peekInstance();
        if (imm != null) {
            imm.onViewDetachedFromWindow(this);
        }
    
        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }
    
        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
        }
    
        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }
    }
  • 其中onDetachedFromWindow方法是一個空的回調(diào)方法,這里重點看一下onDetachedFromWindowInternal方法:

    protected void onDetachedFromWindowInternal() {
        mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
    
        removeUnsetPressCallback();
        removeLongPressCallback();
        removePerformClickCallback();
        removeSendViewScrolledAccessibilityEventCallback();
        stopNestedScroll();
    
        // Anything that started animating right before detach should already
        // be in its final state when re-attached.
        jumpDrawablesToCurrentState();
    
        destroyDrawingCache();
    
        cleanupDraw();
        mCurrentAnimation = null;
    }
  • onDetachedFromWindowInternal方法的方法體也不是特別長,都是一些調(diào)用函數(shù),這里看一下destropDrawingCache方法,這個方法主要是銷毀View的緩存Drawing,我們來看一下具體實現(xiàn):
    • 這里的mDrawingCache其實就是一個Bitmap類型的成員變量,而這里調(diào)用的recycler和置空操作其實就是把View中執(zhí)行draw方法之后緩存的bitmap清空。
    • 這里需要說明的是,我們View組件的最終顯示落實是通過draw方法實現(xiàn)繪制的,而我們的draw方法的參數(shù)是一個Canvas,這是一個畫布的對象,通過draw方法就是操作這個對象并顯示出來,而Canvas對象之所以能夠?qū)崿F(xiàn)顯示的效果是因為其內(nèi)部保存著一個Bitmap對象,通過操作Canvas對象實質(zhì)上是操作Canvas對象內(nèi)部的Bitmap對象,而View組件的顯示也就是通過這里的Bitmap來實現(xiàn)的。
    • 而我們上文中置空了bitmap對象就相當于把View組件的顯示效果置空了,就是相當于我們?nèi)∠薞iew的draw方法的執(zhí)行效果,繼續(xù)回到我們的dispatchDetachedFromWindow方法,在執(zhí)行了mView.dispatchDetachedFromWindow()方法之后,又調(diào)用了mView = null;方法,這里設(shè)置mView為空,這樣我們有取消了View的meature和layouot的執(zhí)行效果。
      public void destroyDrawingCache() {
      if (mDrawingCache != null) {
          mDrawingCache.recycle();
          mDrawingCache = null;
      }
      if (mUnscaledDrawingCache != null) {
          mUnscaledDrawingCache.recycle();
          mUnscaledDrawingCache = null;
      }
      }

5.Dialog彈窗總結(jié)

  • Dialog中的Window對象與Activity中的Window對象是相似的,都對應(yīng)著一個WindowManager對象;Dialog和Activity的顯示邏輯是相似的都是內(nèi)部管理這一個Window對象,用WIndow對象實現(xiàn)界面的加載與顯示邏輯。
  • Dialog相關(guān)的幾個類:Dialog,AlertDialog,AlertDialog.Builder,AlertController,AlertController.AlertParams,其中Dialog是窗口的父類,主要實現(xiàn)Window對象的初始化和一些共有邏輯,而AlertDialog是具體的Dialog的操作實現(xiàn)類,AlertDialog.Builder類是AlertDialog的內(nèi)部類,主要用于構(gòu)造AlertDialog,AlertController是AlertDialog的控制類,AlertController.AlertParams類是控制參數(shù)類;
  • 構(gòu)造AlertDialog用到了很經(jīng)典的buidler構(gòu)造者模式。關(guān)于buidler模式,可以參考Builder模式。構(gòu)造顯示Dialog的一般流程,構(gòu)造AlertDialog.Builder,然后設(shè)置各種屬性,最后調(diào)用AlertDialog.Builder.create方法獲取AlertDialog對象,并且create方法中會執(zhí)行,構(gòu)造AlertDialog,設(shè)置dialog各種屬性的操作。最后我們調(diào)用Dialog.show方法展示窗口,初始化Dialog的布局文件,Window對象等,然后執(zhí)行mWindowManager.addView方法,開始執(zhí)行繪制View的操作,并最終將Dialog顯示出來。
  • 窗口的取消繪制流程是相似的,包括Activity和Dialog等;通過調(diào)用WindowManager.removeViewImmediate方法,開始執(zhí)行Window窗口的取消繪制流程;Window窗口的取消繪制流程,通過清空bitma撤銷draw的執(zhí)行效果,通過置空View撤銷meature和layout的執(zhí)行效果。

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接
  • 1.技術(shù)博客匯總
  • 2.開源項目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總
02.關(guān)于我的博客
  • 我的個人站點:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網(wǎng)上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI