android之Window学习

Window

  • window,顾名思义,窗口,他是一个抽象的概念,我们可以将Window理解为手机屏幕的区域
  • 他是一个抽象类,Window的具体实现类是PhoneWindow,实现位于WindowManagerService中,如果我们接触过View事件分发的原理,就会知道一个Activity的最底层就是一个PhoneWindow,然后再是DecorWindow等

一.简单使用

  • 先来看一下简单的Window和WindowManager使用方法
Button floatingbt = new Button(this);
floatingbt.setText("按钮");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        0,0,
        PixelFormat.TRANSPARENT
);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingbt,layoutParams);
  • 代码很简单,构建一个View,然后在构建一个LayoutParams参数,然后添加到Activity自身的Window中去
  • 这里面比较重要的是WindowManager.LayoutParams的flags和type参数

flags

  • 表示Window的属性,通过这些属性可以控制Window的显示特性,下面我列举一些常用的属性
属性名 意义
FLAG_LAYOUT_NO_LIMITS 允许窗口延伸到屏幕外部。
FLAG_NOT_FOCUSABLE 此窗口不会获得键输入焦点,因此用户无法向其发送键或其他按钮事件。
FLAG_NOT_TOUCHABLE 此窗口永远不会接收触摸事件。
FLAG_NOT_TOUCH_MODAL 即使此窗口是可聚焦的(其FLAG_NOT_FOCUSABLE未设置),也允许窗口外的任何指针事件发送到它后面的窗口。此标记一般都需要开启,不然其他Window将无法收到单击事件
FLAG_SHOW_WALLPAPER 要求系统壁纸显示在窗口后面。

type

  • 这个参数表示Window的类型,Window有三种类型,应用Window,子Window,系统Window
  • 应用类Window对应一个Activity
  • 子Window依赖于特定的父Window而存在,比如常见的dialog
  • 系统级Window需要申请权限才能创建,比如Toast,系统状态栏这种,都是系统级Window,系统级Window独立存在

Window的分层

  • 一个Window是分层的,它有它自己对应的Z-ordered,层级大的会覆盖层级小的Window上面,在三类分层中,应用Window的层级范围是1~99,子Window的层级范围是1000~1999,系统级Window的层级是2000~2999,这些层级范围分别对应着WindowManager.LayoutParams的type参数
  • 通过查阅官网文档,可以看到在WindowManager.LayoutParams的那些type属性,他的值本身就是在上述范围的一个整数值

二.WindowManager的探索

  • WindowManager是一个接口,继承自ViewManager接口,ViewManager里面只有三个待实现的方法
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
  • 对于WindowManager来说,日常操作Window的就这三种方式
  • 往Window里面添加View,更新Window里面的View,删除Window,对于删除Window来说,我们只需要删除他内部的所有View即可
  • 由此可见,对于Window的操作,其实本质上是去操作WIndow里面的View,Window相当于一个虚拟容器,而View是它的实体表现

Window的内部机制

  • Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来互相联系
  • 这个过程具体由WindowManager的AddView方法来实现,而这个方法的具体的实现逻辑是由接口WindowManager的具体实现类WindowManagerImpl来实现,先来看看WIndowManagerImpl的AddView方法

WIndowManagerImpl的添加View过程

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
  • 第一个方法是对Window属性的自适应好像,看起来没太大关系,我们直接看第二行
  • 第二行可以看到将AddView这件事交给了mGlobal这个私有变量,我们去看看这个变量是怎么做的,代码有点长,一点点来分析
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        //可以猜到,有父Window时,这个属性要对父布局进行自适应,如果没有父布局,则要根据View本身的context的属性适当设置该View的属性
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
        //为新View创建新的ViewRootImpl,并将最终的layoutparams设置给View
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  • 可以看到,到最后我们发现了几个貌似比较重要的集合
//View集合存储所有Window所对应的View
private final ArrayList<View> mViews = new ArrayList<View>();
//mRoots存的是所有Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//mParams存储的是所有View对应的属性
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
  • 并且最终添加时的View状态设置是由ViewRootImpl来做的,看看他的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

        ...


                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
//在这里实现添加
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                ....
    }
  • 这个方法比较长,在他的内部是调用了requestLayout()这个方法来为View设置布局的
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
  • 然后在下面通过一个私有变量mWindowSession来实现添加的过程,而这个变量是通过接口来定义的,他的真正变量来自于IWindowSession的实现类Session,来看看这个类的方法
 public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
  • 可以看到,addWindow的任务原封不动的交给了MService这个私有变量,而这个变量是WindowManagerService实例
  • 可以看到,从最开始的Window的addView,一直交给了WindowManagerService,而这中间是涉及到了Ipc过程的

WIndowManagerImpl的更新View过程

public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
}
  • 继续去mGlobal看
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }
  • 可以看到这个方法在开始的时候先重新设置一下View的layoutParams属性值,来看看这个 view.setLayoutParams(wparams);
public void setLayoutParams(ViewGroup.LayoutParams params) {
        if (params == null) {
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        resolveLayoutParams();
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        requestLayout();
    }
  • 可以看到,在最后调用了requestLayout这个方法
public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
  • 可以看到,这个View调用的请求布局是对View的一些基本参数进行判断,以及对父布局存在的情况下的处理
  • 我们再来看看底下的root.setLayoutParams(wparams, false);,这个也就是View自身派生的属性类
  • 这个方法比较长,这里就不贴了,到最后他调用的是scheduleTraversals();这个方法
  • 这里scheduleTraversals()会对View重新布局(测量,布局,重绘)

WIndowManagerImpl的删除View过程

public void removeView(View view) {
    mGlobal.removeView(view, false);
}
  • 继续
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);
        }
    }
  • 进入这个方法removeViewLocked(index, immediate);
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);
            }
        }
    }
  • 进入boolean deferred = root.die(immediate);
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(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
  • 根据之前传入的false,我们可以看到在最低下是用Handler来处理的,通过追踪,来到Handler的handMesage方法下
  • 这里因为方法实在太长,我就只截图到他接收到MSG_DIE这个消息的部分
    这里写图片描述

  • dodie()方法

void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "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);
    }
  • 顾名思义,我们来看看dispatchDetachedFromWindow()这个方法
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();
    }
  • 通过简单的查询,我们发现,这个方法主要是释放View所占有的资源,并分离与Window建立的链接
  • 最终的结果还是调用的是最后一句方法,进入
void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }
  • 可以看到,所有的东西都已被移除,到这里就完全释放了,也就是删除完毕
  • 我们发现,在die方法里面,除了最后通过Handler发送Message执行dodoe方法之外,当传入的Boolean值为true的时候直接执行dodie方法,这就分出了两条路,异步与同步,Window源码在这里选择的是异步删除

三.window的创建过程

  • 通过如上的分析,我们基本上可以清晰的看出来,View就是一个依托于Window这个抽象概念存在的具象事物,比如说,一些应用级Window(Activity),一些系统级Window(dialog,Toast),一些子Window(PopWindow,菜单,等),他们都是视图,他们都是通过其内部的VIew具象表现,接下来 我们就简单分析分析一些常见Window的创建过程

1.Activity的创建过程

  • Activity的启动过程太过复杂,这里只简单的看一下Window的创建过程
  • Activity的启动最终会由ActivityThread中的PerformLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量
  • performLaunchActivity方法的部分代码
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
.
.
.
if (activity != null) {
                CharSequence title =r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
  • 在activity的attach方法里,系统会创建activity所属的Window对象并为他设置回调接口,Window对象的创建是通过PolicManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法,
  • Activity的attach方法节选
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }
  • 通过这个方法的上半部分我们可以看到,Activity的Window是PhoneWindow类的实例,到这里Window已经创建完成,接下来从Activity的onCreate方法的第二句setContentView(R.layout.activity_main);来看看我们的布局是怎么依赖在Window之上的
  • activity的setcontentView()方法
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
  • 这里的getWindow得到的就是刚才说的PhoneWindow的那个实例,进入他的PhoneWindow的SetContentView方法
public void setContentView(int layoutResID) {

        if (mContentParent == null) {
            installDecor(); //如果还没创建DecorView就创建
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
        //将布局添加到DecorView
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
  • 所以到这里我们的布局ID已经设置给DecorVIew的mContentParent了
  • 因为Activity实现了Window的Callback接口,这里表示Activity的布局文件已经加载到DecorView了,所以要通知Activity的onContentChanged方法。
  • 不过这里的方法只是个空实现
  • 到这里DecorView已经被初始化完毕,我们的Window也算是有东西了
  • 不过在这里要说的是,在attach方法里面,虽然我们的WIndow已经被创建,但是他还没有View被传入,也就是说它还不能提供具体的功能
  • 在ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着会调用Activity的makeVisible(),正是在这个方法中,DecorView才真正的完成了添加和显示这两个过程,Activity的视图才被用户看到

2.Dialog的Window创建过程

(1).创建Window

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

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

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

        mListenersHandler = new ListenersHandler(this);
    }
  • Dialog的Window创建过程和Activity的创建过程其实大同小异,在这里不再说明

(2).初始化DecorView并将怒局文件添加到DecorView中去

public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }
  • 这里同样和Activity一样

(4).将DecorView添加到Window并显示

  • 我们在显示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;

        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();
        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;
        }
    //添加到Window
        mWindowManager.addView(mDecor, l);
        mShowing = true;

        sendShowMessage();
    }
  • 方法的最后面调用WindowManager来添加View

3.Toast的Window创建过程

  • Toast与Dialog不同,他的定时工作稍微复杂,不过Toast也同样是基于Window来实现的,不过Toast具有定时取消这一功能,所以在其内部采用了Handler
  • Toast在内部有两类IPC过程,第一类是Toast访问NotificationManagerService,另一个是NotificationManagerService回调Toast里的TN接口
  • 一般使用Toast都是这样使用
Toast.makeText(this,"123",Toast.LENGTH_SHORT).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;
    }
  • 可见他是先new一个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;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
  • 可见,最后应该是一个IPc过程
  • 再看看他的Cancel方法
public void cancel() {
        mTN.cancel();
    }
  • 这里的mTN是他内部实现ITransientNotification.Stub的一个私有静态类
  • 这个类的cancel方法
public void cancel() {
            if (localLOGV) Log.v(TAG, "CANCEL: " + this);
            mHandler.obtainMessage(CANCEL).sendToTarget();
        }
  • 可见,是通过Handler来cancel,在handler控制的逻辑下,具体的cancel过程是这样
handleHide();                         
mNextView = null;
try {
    getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
  • 可以看到,显示和隐藏Toast都需要通过NotificationManagerService来实现,但是NotificationManagerService运行在系统进程中,所以只能通过远程调用的方式来显示和隐藏Toast
  • 需要注意的是,TN这个类是一个Binder类,在Toast和NotificationManagerService在进行IPC的过程中,当NotificationManagerService在处理 显示或者隐藏的请求的时候,会跨进程回调TN中的方法,这时候由于TN是运行在Binder线程池中,所以需要通过Handler将其切换到当前线程,这里的当前线程指的是发送Toast请求的线程,注意,这里由于使用了Handler,所以Toast是无法在没有LOOPer的线程中弹出的
  • 先看看Show的过程
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;

try {
    service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
    // Empty
}
  • service.enqueueToast(pkg, tn, mDuration);
  • 第一个参数表示包名,第二个表示远程回调,第三个表示时长
  • 这里进入这个方法
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }

            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
                            || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                                ? " due to package suspended by administrator."
                                : " by user request."));
                return;
            }
        //这里前面的不看,往下就是判断是否已经存在,之类的
            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++;
 //这里判断包名,确定同一个应用不能有超过50个Toast,避免DOS(Denial of  Service)
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         Slog.e(TAG, "Package has already posted " + count
                                                + " toasts. Not showing more. Package=" + pkg);
                                         return;
                                     }
                                 }
                            }
                        }
            //当排查完毕,就构建一个新的ToastRecord,然后加入队列
                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        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);
                }
            }
        }
  • 至于Toast的显示,是由ToastRecord的callback来完成,callback实际上就是Toast的TN的远程BInder,通过callback来访问TN中的方法IPC过程,最终被调用的TN中的方法会运行在发起Toast请求的应用的BInder线程池中
  • 来看一下showNextToastLocked();这个方法,根据他的名字,可以猜到,应该就是显示有关
void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show(record.token);
                scheduleTimeoutLocked(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;
                }
            }
        }
    }
  • 可以看到,通过自身的callback调用show方法,并且最后设置一个延迟(scheduleTimeoutLocked(record);)用来停止显示
private void scheduleTimeoutLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        mHandler.sendMessageDelayed(m, delay);
    }
  • 这里通过sendMessage最后调用的是handleTimeout((ToastRecord)msg.obj);
private void handleTimeout(ToastRecord record)
    {
        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.callback);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    }
  • cancelToastLocked(index);
void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to hide notification " + record.callback
                    + " in package " + record.pkg);
            // don't worry about this, we're about to remove it from
            // the list anyway
        }

        ToastRecord lastToast = mToastQueue.remove(index);
        mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);

        keepProcessAliveIfNeededLocked(record.pid);
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
            // after this point.
            showNextToastLocked();
        }
    }
  • 可见,在取消之后又接着去看下一个消息
  • 在这里之后的show和隐藏就是TN的任务了,之前已经贴过

总结

  • 基本到这里Window的基本过程就总结完了,不懂得地方还有很多,不过总算是把整个过程过了一遍,以后在深究吧

猜你喜欢

转载自blog.csdn.net/asffghfgfghfg1556/article/details/81172261