zoukankan      html  css  js  c++  java
  • Dialog源码分析

    目录介绍

    • 1.简单用法
    • 2.AlertDialog源码分析
      • 2.1 AlertDialog.Builder的构造方法
      • 2.2 通过AlertDialog.Builder对象设置属性
      • 2.3 builder.create方法
      • 2.4 看看create方法中的P.apply(dialog.mAlert)源码
      • 2.5 看看AlertDialog的show方法
    • 3.Dialog源码分析
      • 3.1 Dialog的构造方法
      • 3.2 Dialog生命周期
      • 3.3 Dialog中show方法展示弹窗
      • 3.4 Dialog的dismiss销毁弹窗
    • 4.Dialog弹窗问题分析
    • 5.Dialog弹窗总结

    好消息

    • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
    • 链接地址:https://github.com/yangchong211/YCBlogs
    • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
    • DialogFragment封装库项目地址:https://github.com/yangchong211/YCDialog
    • 02.Toast源码深度分析
      • 最简单的创建,简单改造避免重复创建,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为何Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
    • 03.DialogFragment源码分析
      • 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展示和销毁源码,使用中show()方法遇到的IllegalStateException分析
    • 05.PopupWindow源码分析
      • 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为何弹窗点击一下就dismiss呢?
    • 06.Snackbar源码分析
      • 最简单的创建,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为什么Snackbar总是显示在最下面
    • 07.弹窗常见问题
      • DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程导致崩溃如何解决?
    • 08.Builder模式
      • Builder模式使用场景,简单案例,Builder模式实际案例Demo展示,看看AlertDialog.Builder源代码如何实现,为什么AlertDialog要使用builder模式呢?builder模式优缺点分析。关于builder模式经典的案例可以参考我的弹窗封装库: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的构造方法

    • 先来看一下AlertDialog.Builder的构造方法,这里的Builder是AlertDialog的内部类,用于封装AlertDialog的构造过程,看一下Builder的构造方法:
      public Builder(Context context) {
          this(context, resolveDialogTheme(context, 0));
      }
      
      • 然后调用的是Builder的重载构造方法:
      public Builder(Context context, int themeResId) {
          P = new AlertController.AlertParams(new ContextThemeWrapper(
                  context, resolveDialogTheme(context, themeResId)));
      }
      
    • 接着这里的P是AlertDialog.Builder中的一个AlertController.AlertParams类型的成员变量,可见在这里执行了P的初始化操作。
      public AlertParams(Context context) {
          mContext = context;
          mCancelable = true;
          mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      }
      
    • 可以看到这里主要执行了AlertController.AlertParams的初始化操作,初始化了一些成员变量。这样执行了一系列操作之后我们的代码:
      AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
      

    2.2 通过AlertDialog.Builder对象设置属性

    • 调用了builder.setIcon方法,这里看一下setIcon方法的具体实现:
      • 可以看到AlertDialog的Builder的setIcon方法,这里执行的就是给类型为AlertController.AlertParams的P的mIconId赋值为传递的iconId,并且这个方法返回的类型就是Builder。
      public Builder setIcon(@DrawableRes int iconId) {
          P.mIconId = iconId;
          return this;
      }
      
    • 调用了builder.setMessage方法,可以看一下builder.setMessage方法的具体实现:
      • 跟setIcon方法的实现逻辑类似,都是给成员变量的mMessage赋值为我们传递的Message值,且和setIcon方法类似的,这个方法返回值也是Builder。
      public Builder setMessage(CharSequence message) {
          P.mMessage = message;
          return this;
      }
      
    • 然后看一下builder.setTitle方法:
      • 发现builder的setIcon、setMessage、setTitle等方法都是给Builder的成员变量P的icon,message,title赋值。
      public Builder setTitle(CharSequence title) {
          P.mTitle = title;
          return this;
      }
      
    • 接着看一下builder.setView方法:
      • 发现这里的setView和setIcon,setMessage,setTitle等方法都是类似的,都是将我们传递的数据值赋值给Builder的成员变量P。
      public Builder setView(int layoutResId) {
          P.mView = null;
          P.mViewLayoutResId = layoutResId;
          P.mViewSpacingSpecified = false;
          return this;
      }
      

    2.3 builder.create方法

    • 然后调用了builder.create方法,并且这个方法返回了AlertDialog。
      • image
    • 可以看到这里首先构造了一个AlertDialog,我们可以看一下这个构造方法的具体实现:
      • 可以看到这里首先调用了super的构造方法,而我们的AlertDialog继承于Dialog,所以这里执行的就是Dialog的构造方法【备注:高版本是继承AppCompatDialog,然后AppCompatDialog再继承Dialog】
      • image
    • 回到AlertDialog的构造方法中,在构造方法中,除了调用Dialog的构造方法之外还执行了
      • 相当于初始化了AlertDiaog的成员变量mAlert。
      mAlert = new AlertController(getContext(), this, getWindow());
      

    2.4 看看create方法中的P.apply(dialog.mAlert)源码

    • 再AlertDialog.Builder.create方法,在创建了一个AlertDialog之后,又执行了P.apply(dialog.mAlert);这里的P是一个AlertController.AlertParams的变量,而dialog.mAlert是刚刚创建的AlertDialog中的一个AlertController类型的变量,来看一下apply方法的具体实现:
      • 在初始化AlertDialog.Builder的时候设置的icon、title、message赋值给了AlertController.AlertParams,这里就是将初始化时候设置的属性值赋值给我们创建的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方法

    • 看看如下所示,可以发现直接调用了dialog中的show方法。下面接着分析
      • image

    3.Dialog源码分析

    3.1 Dialog的构造方法

    • 如下所示
      • 看源码可知在Dialog的构造方法中直接直接构造了一个PhoneWindow,并赋值给Dialog的成员变量mWindow,从这里可以看出其实Dialog和Activity的显示逻辑都是类似的,都是通过对应的Window变量来实现窗口的加载与显示的。然后我们执行了一些Window对象的初始化操作,比如设置回调函数为本身,然后调用Window类的setWindowManager方法,并传入了WindowManager。然后创建一个对话框监听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;
              }
              //创建一个Context
              mContext = new ContextThemeWrapper(context, themeResId);
          } else {
              mContext = context;
          }
      
          //获取一个WindowManager对象
          mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
          //创建一个Window对象
          final Window w = new PhoneWindow(mContext);
          //将Window对象w赋值给mWindow
          mWindow = w;
          //为Windowd对象设置回调,并且它本身实现了这些回调函数
          w.setCallback(this);
          w.setOnWindowDismissedCallback(this);
          //为Window对象设置WindowManager对象
          w.setWindowManager(mWindowManager, null, null);
          w.setGravity(Gravity.CENTER);
          //创建一个对话框监听Handler
          mListenersHandler = new ListenersHandler(this);
      }
      
    • 接着看看w.setWindowManager(mWindowManager, null, null)里面的源代码
      • 可以看到跟Activity的Window对象的windowManager的获取方式是相同的,都是通过new的方式创建一个新的WindowManagerImpl对象。
      • image

    3.2 Dialog生命周期

    • dialog的生命周期如下所示
      /**
       * 类似于Activity的onCreate函数,可以在这个方法中进行Dialog的一些初始化操作
       * 包括调用setContentView方法
       */ 
      protected void onCreate(Bundle savedInstanceState) { } 
      /**
       * 当对话框启动的时候被调用.
       */ 
      protected void onStart() { } 
      /**
       * 当对话框停止的时候被调用.
       */ 
      protected void onStop() { }
      

    3.3 Dialog中show方法展示弹窗

    • 源码如下所示,关于重点的逻辑,我在这里只是简单的注释了一下。
      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;
          /* 判断对话框是否创建过,如果没有创建过
           * 在dispatchOnCreate里面就会回调onCreate函数
           */
          if (!mCreated) {
              dispatchOnCreate(null);
          }
          //回调onStart函数
          onStart();
          //获取Window对象总的DecorView,如果调用了setContentView就会创建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 {
          }
      }
      
    • 方法体的内容比较多,由于一开始mShowing变量用于表示当前dialog是否正在显示,由于我们刚刚开始调用执行show方法,所以这里的mShowing变量的值为false,所以if分支的内容不会被执行,继续往下看:
      if (!mCreated) {
          dispatchOnCreate(null);
      }
      
    • mCreated这个控制变量控制dispatchOnCreate方法只被执行一次,由于是第一次执行,所以这里会执行dispatchOnCreate方法,好吧,看一下dispatchOnCreate方法的执行逻辑:
      • 可以看到代码的执行逻辑很简单就是回调了Dialog的onCreate方法
      void dispatchOnCreate(Bundle savedInstanceState) {
          if (!mCreated) {
              onCreate(savedInstanceState);
              mCreated = true;
          }
      }
      
    • 调用了onStart方法,这个方法主要用于设置ActionBar,这里不做过多的说明,然后初始化WindowManager.LayoutParams对象,并最终调用我们的mWindowManager.addView()方法。
      protected void onStart() {
          if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
      }
      
    • 最后调用了sendShowMessage方法,可以看一下这个方法的实现:
      • 那么发送这个消息主要是什么作用呢,逗比们,接着往下看看。
      private void sendShowMessage() {
          if (mShowMessage != null) {
              // Obtain a new message so this dialog can be re-used
              Message.obtain(mShowMessage).sendToTarget();
          }
      }
      
    • 这里会发送一个Dialog已经显示的异步消息,该消息最终会在ListenersHandler中的handleMessage方法中被执行:
      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,所以会执行OnShowListener.onShow方法,那么这个OnShowListener是何时赋值的呢?还记得我们构造AlertDialog.Builder么?
      alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
          @Override
          public void onShow(DialogInterface dialog) {
      
          }
      });
      
    • 这样就为我们的AlertDialog.Builder设置了OnShowListener,可以看一下setOnShowListener方法的具体实现:
      • 这样就为我们的Dialog中的mListenersHandler构造了Message对象,并且在Dialog中发送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()方法
    • 调用alertDialog.cancel()或者alertDialog.dismiss()都可以达到销毁弹窗的效果。
      • 首先看一下Dialog的cannel方法的具体实现:
      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没有取消,并且设置了取消message,则调用Message.obtain(mCancel).sendToTarget(),前面已经分析过这里的sendToTarget方法会回调注册的异步消息处理逻辑:
      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时,设置了setOnCancelListener,那么就会执行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;
              }
          }
      }
      
    • 调用的是设置的OnCancelListener的onCancel方法,也就是说调用dialog.cancel方法时首先会判断dialog是否调用了setOnCancelListener若设置了,则先调用OnCancelListener的onCancel方法,然后再次执行dismiss方法,若我们没有为Dialog.Builder设置OnCancelListener那么cancel方法和dismiss方法是等效的。
    3.4.2 看看dismiss方法
    • 如下所示
      • 可以看到,这里首先判断当前线程的Looper是否是主线程的Looper(由于mHandler是在主线程中创建的,所以mHandler.getLooper返回的是主线程中创建的Looper对象),若是的话,则直接执行dismissDialog()方法,否则的话,通过mHandler发送异步消息至主线程中,简单来说就是判断当前线程是否是主线程,若是主线程则执行dismissDialog方法否则发送异步消息
      public void dismiss() {
          if (Looper.myLooper() == mHandler.getLooper()) {
              dismissDialog();
          } else {
              mHandler.post(mDismissAction);
          }
      }
      
    • 然后看一下mHandler对异步消息的处理机制,由于这里的mDismissAction是一个Runnable对象,所以这里直接看一下mDismissAction的定义:
      • 这里的异步消息最终也是调用的dismissDialog方法
      private final Runnable mDismissAction = new Runnable() {
          public void run() {
              dismissDialog();
          }
      };
      
    3.4.3 cancel和dismiss方法都调用dismissDialog方法
    • 所以无论执行的cancel方法还是dismiss方法,无论方法是在主线程执行还是子线程中执行,最终调用的都是dismissDialog方法,那么就看一下dismissDialog是怎么个执行逻辑。
      • 首先判断当前的mDector是否为空,或者当前Dialog是否在显示,若为空或者没有在显示,则直接return掉,也就是说当前我们的dialog已经不再显示了,则不需要往下在执行。然后调用了mWindow.isDestroyed()方法,判断Window对象是否已经被销毁,若已经被销毁,则直接return,并打印错误日志。
      • 然后再调用了mWindowManager.removeViewImmediate(mDector),这里的mDector是Dialog窗口的根布局,看这个方法的名字应该就是Dialog去除根布局的操作了,可以看一下这个方法的具体实现。
      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方法应该是WindowManagerImpl中的方法,看一下它的具体实现:
      @Override
      public void removeViewImmediate(View view) {
          mGlobal.removeView(view, true);
      }
      
    • 可以发现,这里它调用了mGlobal.removeView方法,而这里的mGlobal是WindowManagerGlobal的实例,所以再看一下WIndowManagerGlobal中removeView的实现逻辑:
      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);
          }
      }
      
    • 可以发现,这里在获取了保存的mDector组件之后,又调用了removeViewLocked方法,在看一下这个方法的具体实现逻辑:
      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,然后调用了其的die方法,通过这个方法实现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!
      " +
                      "  window=" + this + ", title=" + mWindowAttributes.getTitle());
          }
          mHandler.sendEmptyMessage(MSG_DIE);
          return true;
      }
      
    • 可以看到在方法体中有调用了doDie方法,看名字应该就是真正执行window销毁工作的方法了,我们在看一下doDie方法的具体实现:
      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);
      }
      
    • 可以看到方法体中,首先调用了checkThread方法,判断当前执行代码的线程,若不是主线程,则抛出异常:
      void checkThread() {
          if (mThread != Thread.currentThread()) {
              throw new CalledFromWrongThreadException(
                      "Only the original thread that created a view hierarchy can touch its views.");
          }
      }
      
    • 顺着doDie的方法往下看,又调用了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();
      }
      
    • 可以看到在方法中调用了mView.dispatchDetachedFromWindow方法,这个方法的作用就是将mView从Window中detach出来,可以看一下这个方法的具体实现:
      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方法是一个空的回调方法,这里重点看一下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方法的方法体也不是特别长,都是一些调用函数,这里看一下destropDrawingCache方法,这个方法主要是销毁View的缓存Drawing,我们来看一下具体实现:
      • 这里的mDrawingCache其实就是一个Bitmap类型的成员变量,而这里调用的recycler和置空操作其实就是把View中执行draw方法之后缓存的bitmap清空。
      • 这里需要说明的是,我们View组件的最终显示落实是通过draw方法实现绘制的,而我们的draw方法的参数是一个Canvas,这是一个画布的对象,通过draw方法就是操作这个对象并显示出来,而Canvas对象之所以能够实现显示的效果是因为其内部保存着一个Bitmap对象,通过操作Canvas对象实质上是操作Canvas对象内部的Bitmap对象,而View组件的显示也就是通过这里的Bitmap来实现的。
      • 而我们上文中置空了bitmap对象就相当于把View组件的显示效果置空了,就是相当于我们取消了View的draw方法的执行效果,继续回到我们的dispatchDetachedFromWindow方法,在执行了mView.dispatchDetachedFromWindow()方法之后,又调用了mView = null;方法,这里设置mView为空,这样我们有取消了View的meature和layouot的执行效果。
      public void destroyDrawingCache() {
          if (mDrawingCache != null) {
              mDrawingCache.recycle();
              mDrawingCache = null;
          }
          if (mUnscaledDrawingCache != null) {
              mUnscaledDrawingCache.recycle();
              mUnscaledDrawingCache = null;
          }
      }
      

    5.Dialog弹窗总结

    • Dialog中的Window对象与Activity中的Window对象是相似的,都对应着一个WindowManager对象;Dialog和Activity的显示逻辑是相似的都是内部管理这一个Window对象,用WIndow对象实现界面的加载与显示逻辑。
    • Dialog相关的几个类:Dialog,AlertDialog,AlertDialog.Builder,AlertController,AlertController.AlertParams,其中Dialog是窗口的父类,主要实现Window对象的初始化和一些共有逻辑,而AlertDialog是具体的Dialog的操作实现类,AlertDialog.Builder类是AlertDialog的内部类,主要用于构造AlertDialog,AlertController是AlertDialog的控制类,AlertController.AlertParams类是控制参数类;
    • 构造AlertDialog用到了很经典的buidler构造者模式。关于buidler模式,可以参考Builder模式。构造显示Dialog的一般流程,构造AlertDialog.Builder,然后设置各种属性,最后调用AlertDialog.Builder.create方法获取AlertDialog对象,并且create方法中会执行,构造AlertDialog,设置dialog各种属性的操作。最后我们调用Dialog.show方法展示窗口,初始化Dialog的布局文件,Window对象等,然后执行mWindowManager.addView方法,开始执行绘制View的操作,并最终将Dialog显示出来。
    • 窗口的取消绘制流程是相似的,包括Activity和Dialog等;通过调用WindowManager.removeViewImmediate方法,开始执行Window窗口的取消绘制流程;Window窗口的取消绘制流程,通过清空bitma撤销draw的执行效果,通过置空View撤销meature和layout的执行效果。

    关于其他内容介绍

    01.关于博客汇总链接

    02.关于我的博客

  • 相关阅读:
    PAT (Advanced Level) 1080. Graduate Admission (30)
    PAT (Advanced Level) 1079. Total Sales of Supply Chain (25)
    PAT (Advanced Level) 1078. Hashing (25)
    PAT (Advanced Level) 1077. Kuchiguse (20)
    PAT (Advanced Level) 1076. Forwards on Weibo (30)
    PAT (Advanced Level) 1075. PAT Judge (25)
    PAT (Advanced Level) 1074. Reversing Linked List (25)
    PAT (Advanced Level) 1073. Scientific Notation (20)
    PAT (Advanced Level) 1072. Gas Station (30)
    PAT (Advanced Level) 1071. Speech Patterns (25)
  • 原文地址:https://www.cnblogs.com/yc211/p/9812816.html
Copyright © 2011-2022 走看看