WindowManagerService第三讲之Window的创建

在第一讲中我们知道,系统按照Window的type分为:应用窗口、子窗口、系统窗口。我们分别以其中的情况来

1.Activity的Window创建

在WMS系列第一讲中介绍PhoneWindow的时候,我们有介绍PhoneWindow的创建时机是在ActivityThread中调用attach()方法初始化Activity。

这个时候Activity中就创建了Window对象。接下来就需要在窗口中添加真正的显示元素View或者ViewGroup。熟悉Activity启动流程的话,在调用核心方法performLaunchActivity之后,最终调起Activity的onCreat()方法。

这个方法中我们会最最常用的就是调用setContentView()方法,并传入layoutid。(当然传参并不仅仅有这一种)

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow()方法返回的是Window对象。Window类的唯一实现类是PhoneWindow。

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            // 1
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 2
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            // 3
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

注释1:如果mContentParent为空(mContentParent:放置窗口内容的视图)。会调用installDecor()方法。这个方法为Winodw类安装一个窗口装饰。该方法源码就不附上了,他主要是做几件事情:

A.如果mDecor为空,则调用generateDecor()方法创建一个DecorView对象,并将它赋值给mDecor;

B.在generateLayout()方法中根据用户指定的参数选择不同的窗口装饰,然后通过调用findViewById(ID_ANDROID_CONTENT)获得contentParent对象,并将这个值赋值给mContentParent

注释2:当安装完窗口装饰mDecor后,就可以把用户界面layout.xml文件添加到窗口装饰中。就会调用LayoutInflater的inflate()方法来实现。(可以看到mContentParent对象是作为方法参数传入的)。

注释3:最后调用onContentChanged()回调方法,通知应用窗口内容发送了改变。

当Activity准备好了之后就会去通知AMS,AMS中的流程这里不展开了,经过层层调用最终到Activity的makeVisible()方法。

    void makeVisible() {
        if (!mWindowAdded) {
            // 获得ViewManager对象
            ViewManager wm = getWindowManager();
            // 调用addView()方法
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

这里关注下addView()方法种的参数:

第一个参数mDecor是一个DecorView对象,也就是一个用户能看见的Activity全部界面内容;

第二个参数getAttributes()方法返回时WindowManager.LayoutParams类型的mWindowAttributes对象,这个对象在Window类初始化代码中赋值的,赋值方法又调用到WindowManager的LayoutParams()构造函数:

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

我们看到默认LayoutParams是MATCH_PARENT,type类型是TYPE_APPLICATION。

而ViewManager的addView()方法的具体实现直接参见WMS第二讲的内容即可。

2.Toast的Window创建

Toast的创建,一般都是调用一句Toast.makeText().show()。

我们先来看makeText()方法的实现:

    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);

        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

先创建一个Toast对象result,初始化一个LayoutInflater对象,并调用inflate()方法赋值给一个View对象,将传入的参数text附在TextView上。然后将这个view赋值给Toast.mNextView变量,将传参duration赋值给Toast.mDuration变量。

这样一个Toast对象就创建成功,然后继续看show()方法:

    public void show() {
        // 判空操作
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        final int displayId = mContext.getDisplayId();

        try {
            service.enqueueToast(pkg, tn, mDuration, displayId);
        } catch (RemoteException e) {
            // Empty
        }
    }

这里是会调用到INotificationManager的enqueueToast()方法,这是一个IPC的方法。实现是在远端的NotificationManagerService中:

    @VisibleForTesting
    final IBinder mService = new INotificationManager.Stub() {
        // Toasts
        // ============================================================================

        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration,
                int displayId)
        {
            ......

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, callback);
                    // If it's already in the queue, we update it in place, we don't
                    // move it to the end of the queue.
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        // Limit the number of toasts that any given package except the android
                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
                        if (!isSystemToast) {
                            int count = 0;
                            final int N = mToastQueue.size();
                            for (int i=0; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         Slog.e(TAG, "Package has already posted " + count
                                                + " toasts. Not showing more. Package=" + pkg);
                                         return;
                                     }
                                 }
                            }
                        }

                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, displayId);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token,
                                displayId);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveIfNeededLocked(callingPid);
                    }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated.  Call back and tell it to show itself.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

​ 接下来就会执行到showNextToastLocked()方法:

    @GuardedBy("mToastQueue")
    void showNextToastLocked() {
        // 取出mToastQueue中的第一个ToastRecord对象
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                // 1
                record.callback.show(record.token);
                // 2
                scheduleDurationReachedLocked(record);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Object died trying to show notification " + record.callback
                        + " in package " + record.pkg);
                // remove it from the list and let the process die
                int index = mToastQueue.indexOf(record);
                if (index >= 0) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveIfNeededLocked(record.pid);
                if (mToastQueue.size() > 0) {
                    record = mToastQueue.get(0);
                } else {
                    record = null;
                }
            }
        }
    }

注释1:调用其内部的TN对象的show()方法

注释2:发送一个异步延迟消息,使得Toast显示一定时间后清除该Toast窗口,清除动作是在cancelToastLocked()中实现;

我们再来看Toast的子类TN中的show()方法:

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }

发送SHOW消息,最终调用其内部的handleShow()方法:

        public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            // If a cancel/hide is pending - no need to show - at this point
            // the window token is already invalid and no need to do any work.
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                mParams.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                // Since the notification manager service cancels the token right
                // after it notifies us to cancel the toast there is an inherent
                // race and we may attempt to add a window after the token has been
                // invalidated. Let us hedge against that.
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }

先创建一个WindowManager对象,然后对WindowManager.LayoutParams中的常用变量进行赋值更新。最终调用WM的addView()方法,完成窗口的添加。

3.Dialog的Window创建

1.Dialog构造函数

Dialog对应的窗口类型的:TYPE_SYSTEM_DIALOG。属于系统窗口。我们先来看Dialog的构造函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5OZHtq14-1583226714351)(H:\突进面试\QQ截图20200302162338.png)]

最终调用到最后一个构造函数上去:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == Resources.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            // 1
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        // 获取WindowManager对象
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        // 2
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        // 3
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        // 4
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        // 5
        mListenersHandler = new ListenersHandler(this);
    }

注释一:创建Dialog内部的Context对象,并将他赋值给mContext;

注释二:创建一个Window对象;

注释三:调用setCallback()方法,指定该Window的消息回调接口就是这个Dialog对象;

注释四:设置Window对象的内部WindowManager对象;

注释五:创建一个回调句柄mListenersHandler,用于在内部发送异步消息

2.Dialog#show

Dialog的构造函数中创建了内部的window对象之后。如果要将Dialog显示出来就要调用其show()方法。来看源码中的实现:

    public void show() {
        // 1
        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;

        // 2
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
            // Fill the DecorView in on any configuration changes that
            // may have occured while it was removed from the WindowManager.
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        onStart();
        // 3
        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);
        }

        // 4
        WindowManager.LayoutParams l = mWindow.getAttributes();
        boolean restoreSoftInputMode = false;
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            l.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            restoreSoftInputMode = true;
        }

        // 5
        mWindowManager.addView(mDecor, l);
        if (restoreSoftInputMode) {
            l.softInputMode &=
                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        }

        mShowing = true;

        // 6
        sendShowMessage();
    }

注释1:如果窗口已经存在,则直接调用setVisibility()方法显示;

注释2:如果窗口不存在,则调用dispatchOnCreate()方法,回调Dialog的onCreate()方法

注释3:赋值mDecor;

注释4:设置添加Dialog窗口所使用的LayoutParams参数;

注释5:调用WindowManager的addView()方法添加窗口;

注释6:可以调用setOnShowListener()方法为Dialog添加一个回调接口;

你可能感兴趣的:(Android,Frameworks)