从一个Dialog的创建看Android的窗口机制(上篇)

本篇从Dialog的创建来看一看Android的窗口是如何添加的,
写个Demo,简单的例子创建一个Dialog,点击button,Dialog显示出来

public class MainActivity extends Activity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("Test")
       .setMessage("hello world")
       .setIcon(R.drawable.ic_launcher_background)
       .setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        })
        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                builder.show();
            }
        });

    }
}

显示出来就是这样的:
在这里插入图片描述

AlertDialog

AlertDialog通过内部的builder设置Dialog的各种属性,如title,message,点击事件等,全部存入AlertController.AlertParams对象,AlertParams是一个类似LayoutParams的类,存储这窗口的各种属性

public class AlertDialog extends Dialog implements DialogInterface {
		......
		public static class Builder {
        @UnsupportedAppUsage
        private final AlertController.AlertParams P;

        public Builder(Context context) {
            this(context, resolveDialogTheme(context, Resources.ID_NULL));
        }

        public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
        }

        public Context getContext() {
            return P.mContext;
        }

        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }


        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }
        public Builder setCustomTitle(View customTitleView) {
            P.mCustomTitleView = customTitleView;
            return this;
        }

        public Builder setMessage(@StringRes int messageId) {
            P.mMessage = P.mContext.getText(messageId);
            return this;
        }
        public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this;
        }

        public Builder setIcon(@DrawableRes int iconId) {
            P.mIconId = iconId;
            return this;
        }


        public Builder setIcon(Drawable icon) {
            P.mIcon = icon;
            return this;
        }

        public Builder setIconAttribute(@AttrRes int attrId) {
            TypedValue out = new TypedValue();
            P.mContext.getTheme().resolveAttribute(attrId, out, true);
            P.mIconId = out.resourceId;
            return this;
        }

        public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
            P.mPositiveButtonText = P.mContext.getText(textId);
            P.mPositiveButtonListener = listener;
            return this;
        }

        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
            P.mPositiveButtonText = text;
            P.mPositiveButtonListener = listener;
            return this;
        }

        public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
            P.mNegativeButtonText = P.mContext.getText(textId);
            P.mNegativeButtonListener = listener;
            return this;
        }


        public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
            P.mNegativeButtonText = text;
            P.mNegativeButtonListener = listener;
            return this;
        }
        public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) {
            P.mNeutralButtonText = P.mContext.getText(textId);
            P.mNeutralButtonListener = listener;
            return this;
        }
        public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
            P.mNeutralButtonText = text;
            P.mNeutralButtonListener = listener;
            return this;
        }
        public Builder setCancelable(boolean cancelable) {
            P.mCancelable = cancelable;
            return this;
        }
        public Builder setOnCancelListener(OnCancelListener onCancelListener) {
            P.mOnCancelListener = onCancelListener;
            return this;
        }

        public Builder setOnDismissListener(OnDismissListener onDismissListener) {
            P.mOnDismissListener = onDismissListener;
            return this;
        }
        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
            P.mOnKeyListener = onKeyListener;
            return this;
        }

        public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
            P.mItems = P.mContext.getResources().getTextArray(itemsId);
            P.mOnClickListener = listener;
            return this;
        }
		......
}

show()

从show方法开始

  public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }

create

AlertDialog的创建

	public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

看下AlertDialog构造方法

  AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
       //调用父类Dialog的构造方法
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);
        mWindow.alwaysReadCloseOnTouchAttr();
        //创建AlertController
        mAlert = AlertController.create(getContext(), this, getWindow());
    }

看下AlertController的构造方法
这里面就是加载Dialog的布局,样式等,我们创建的最简单的Dialog,全部采用默认值

protected AlertController(Context context, DialogInterface di, Window window) {
        ......
        final TypedArray a = context.obtainStyledAttributes(null,
                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
        //默认的Dialog的布局文件为R.layout.alert_dialog
        mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
                //默认为0
        mButtonPanelSideLayout = a.getResourceId(
                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
        ......
                //showTitle为true
        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);

        a.recycle();
        //Feature为FEATURE_NO_TITLE
        window.requestFeature(Window.FEATURE_NO_TITLE);
    }

看下Dialog的构造方法,大概就是创建window对象,设置回调可以接受window的事件进行处理

  Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
  		//这里传进来的是false
        if (createContextThemeWrapper) {
            if (themeResId == Resources.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            //注意,这里的context传的是Activity
            mContext = context;
        }
		//从Activity里获取WindowManagr,获取到的是和Activity同一个WindowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		//创建PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        //给窗口设置回调,Dialog会实现Window中的回调方法,
        //window接受到事件之后就可以传递给Dialog处理
        //这点和Activity添加到Window的流程一样
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        //注意下这个方法,设置WindowManager,后面两个参数为空
        //token为空
        w.setWindowManager(mWindowManager, null, null);
        //调整位置,处于中间
        w.setGravity(Gravity.CENTER);
        //Handler类,处理事件的
        mListenersHandler = new ListenersHandler(this);
    }

以上需要重点注意的是Dialog的WindowMnaager和Activity共用同一个,虽然Dialog自身也通过setWindowManager方法创建了WindowManager,但是并没有将赋值给自身成员变量mWindowManager而是用的Activity的WindowManager

Activity.getSystemService

@Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

Window.setWindowManager

注意Dialog的token为空,Android创建任何窗口都必须要有token,关于窗口和token的更介绍可以看下Android窗口与Token对应策略

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //通过Activity的WindowManager创建了WindowManagerImpl
        //mWindowManager是Dialog的WindowManagerImpl和Activity的
        //WindowManagerImpl不是同一个
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

createLocalWindowManager

  public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

最后主要是创建了WindowManagerImpl类,这里创建的是Dialog中的WindowManagerImpl,传入的是Dialog的PhoneWindow

好了接着回到AlertDialog的create方法,看下P.apply方法

	public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            //设置Dialog数据
            P.apply(dialog.mAlert);
            //设置dialog是否可以取消
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            //设置点击事件
            dialog.setOnCancelListener(P.mOnCancelListener);
            //设置dismiss事件
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

P.apply

很明显就是给Dailog设置具体数据,有setTitle,setIcon,setButton等

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

到这里Dialog已经创建完毕,接着再回到Dialog的show方法

  public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }

dialog.show()

调用父类Dialog的show方法。

   public void show() {
   		//mShowing默认值为false
        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;
        //mCreated默认值为false,保证onCreate只调用一次
        if (!mCreated) {
            //这里面就是调用Dialog的onCreate方法
            dispatchOnCreate(null);
        } else {
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }
        //调用onStart
        onStart();
        //获取DecorView,DecorView就是窗口的顶层布局
        mDecor = mWindow.getDecorView();
       //有ActionBar的情况
        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();
			......
        mWindowManager.addView(mDecor, l);
      		......
        mShowing = true;

        sendShowMessage();
    }

从上面流程看和Activity有点类似,也有onCreate,onStart生命周期,我们看一下AlertDialog的onCreate

onCreate

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }

AlertController.installContent

    public void installContent(AlertParams params) {
    	//给Dailog的布局文件设置数据
        params.apply(this);
        installContent();
    }
    public void installContent() {
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
    }

selectContentView

返回默认布局mAlertDialogLayout,mAlertDialogLayout再AlertController构造方法中赋值为默认值R.layout.alert_dialog,这就是默认Dialog的布局文件,后面会调用setContentView添加到DecorView中

private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        // TODO: use layout hint side for long messages/lists
        return mAlertDialogLayout;
    }

现在只是获取了Dialog默认的布局文件,还没有填充数据,如title,message,icon等,setupView这个方法会获取布局中的控件并将数据设置进去

setupView

一系列的findViewId获取了布局文件的控件之后调用setXXX设置数据

private void setupView() {
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);

        // Install custom content before setting up the title or buttons so
        // that we can handle panel overrides.
        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
        setupCustomContent(customPanel);

        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);

        // Resolve the correct panels and remove the defaults, if needed.
        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
        ......
     .....
  }

mWindow.setContentView()

这完全和Activity的setContentView流程一样

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
        	//创建DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ......
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ......
        } else {
        	//加载Dialog的默认布局到mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
        	//Dialog中的onContentChanged实现为空
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

installDecor

主要看这里面两个核心方法,创建DecorView和创建contentView

private void installDecor() {
	......
  if (mDecor == null) {
            mDecor = generateDecor(-1);
			.....
        }
         if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            		......
            }

}

generateDecor

new了一个DecorView

protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

generateLayout

这个方法代码比较多,主要就是根据主题theme设置对应的xml布局文件以及Feature到DecorView中,并将mContentParent和id为ID_ANDROID_CONTENT(com.android.internal.R.id.content)的ViewGroup
绑定,我们Dialog没有设置特别的主题

protected ViewGroup generateLayout(DecorView decor) {
		......
			layoutResource = R.layout.screen_simple;
			//这个方法会通过mLayoutInflater解析layoutResource,
			//并添加到DecorView中
			mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
			ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
		......
		return contentParent;
}

可以看到DecorView添加的布局文件,就是一个垂直LinearinLayout,包含一个ActionBar和id为content的FrameLayout,Activity的setContentView就是将我们自己创建的布局设置到content中

screen_simple.xml

<LinearinLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

总结下getDecorView:
1.创建DecorView,并根据指定的Theme加载对应布局到DecorView中,默认的布局就是就是一个垂直LinearinLayout,包含一个ActionBar和content
2.获取DecorView加载的布局中id为R.id.content的FrameLayout赋值给contentParent

再回到setContentView中,DecorView和contentParent创建完之后将Dialog的默认布局加载到contentParent中,现在的DecorView已经拥有了可以显示的布局了

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
        	//创建DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ......
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ......
        } else {
        	//加载Dialog的默认布局到mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
        	//Dialog中的onContentChanged实现为空
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

接着再回到dialog.show(),onCreate方法调用之后Dialog的布局已经加载到了DecorView中

   public void show() {
   		//mShowing默认值为false
        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;
        //mCreated默认值为false,保证onCreate只调用一次
        if (!mCreated) {
            //这里面就是调用Dialog的onCreate方法
            dispatchOnCreate(null);
        } else {
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }
        //调用onStart,AlertDialog没有实现
        onStart();
        //获取DecorView,DecorView就是窗口的顶层布局
        mDecor = mWindow.getDecorView();
       //有ActionBar的情况
        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);
        }
		//获取窗口的LayoutParams
        WindowManager.LayoutParams l = mWindow.getAttributes();
        	......
        mWindowManager.addView(mDecor, l);
       		......
        mShowing = true;
        sendShowMessage();
    }

getAttributes

 private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();
    public final WindowManager.LayoutParams getAttributes() {
        return mWindowAttributes;
    }

注意,在创建Dialog的时候是new的一个WindowManager.LayoutParams,我们看下WindowManager.LayoutParams的构造方法,窗口的type是TYPE_APPLICATION,并不是子窗口

 public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
        }

mWindowManager.addView

接着调用addView将Dialog窗口显示出来,前面说了Dialog用的是Activity的WindowManager,所以这里调用的是Activity的WindowManager的addView方法

 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

限于篇幅本篇就讲到这里,本篇主要介绍了Dialog如何一步一步加载到DecorView中的,其实Dialog布局文件的加载和Activity的布局加载几乎一样的,都是onCreate中加载布局文件,之后的流程都一样,到PhoneWindow中创建DecorView并将具体布局文件设置进DecorView的id为content的FrameLayout中
需要特别注意的是Dialog和Activity共用同一个WindowManager,此WindowManager在Activity的attach方法中创建

具体Window如何添加即显示的,下一篇再继续从addView开始分析Window如何一步一步添加的

发布了28 篇原创文章 · 获赞 40 · 访问量 4803

猜你喜欢

转载自blog.csdn.net/qq_34211365/article/details/103833153