美文网首页
理解Window和WindowManager(三)

理解Window和WindowManager(三)

作者: 阿泽Leo | 来源:发表于2018-09-16 21:35 被阅读0次

    Window的创建过程

    有View的地方必须要有Window才能显示,比如Activity、Dialog、Toast。


    Activity的Window创建过程

    Activity启动过程是通过ActivityThread中的performLaunchActivity()完成,内部会通过类加载器创建Activity实例对象,并调用
    attch()。

    • 通过查看attch()源码发现,api level为23以下的是通过PolicyManager的makeNewWindow方法创建:
    mWindow = PolicyManager.makeNewWindow(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    

    而PolicyManager实现的几个工厂方法都在IPolicy中,PolicyManager的真正实现类是Policy。

    public interface IPolicy {
        public Window makeNewWindow(Context context);
        public LayoutInflater makeNewLayoutInflater(Context context);
        public WindowManagerPolicy makeNewWindowManager();
        public FallbackEventHandler makeNewFallbackEventHandler(Context context);
    }
    
    public class Policy implements IPolicy {
        public Window makeNewWindow(Context context) {
            return new PhoneWindow(context);
        }
    
        public LayoutInflater makeNewLayoutInflater(Context context) {
            return new PhoneLayoutInflater(context);
        }
    
        public WindowManagerPolicy makeNewWindowManager() {
            return new PhoneWindowManager();
        }
    
        public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
            return new PhoneFallbackEventHandler(context);
        }
    }
    
    • 通过查看attch()源码发现,api level为23以上的是直接创建PhoneWindow对象:
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    

    通过观察发现在api level为23以上,IPolicy接口已经不存在。但可以说明一点的是Window的具体实现就是PhoneWindow。

    • 这个时候Window已经创建出来了,接下来看一下Activity的视图是怎么附属在Window上。我们看Activity的setContentView方法。
    public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
    }
    

    可以看到实际上是交给了PhoneWindow处理。在PhoneWindow中,setContentView()大致做了如下步骤:

    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) {
            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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);//第二步
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();//第三步
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
    
    1. 如果没有DecorView,就创建它。
      installDecor()内部调用:
      generateDecor()创建DecorView。
      generateLayout()加载布局。
    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
    protected ViewGroup generateLayout(DecorView decor) {
        ...
    }
    
    1. 将View添加到DecorView的mContentParent中
      创建完DecorView后直接将Activity的视图添加到mContentParent中即可。
    mLayoutInflater.inflate(layoutResID, mContentParent);
    
    1. 回调Activity的onContentChanged方法通知Activity视图已经发生改变
      由于Activity实现了Callback接口,那么回调onContentChanged即可。在Activity中这个方法是空实现,我们可以自己处理这个回调。
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    
    • 完成以上三个步骤,DecorView已经创建并将Activity的布局加到DecorView的mContentParent中了,但是这个时候还没被WindowManager正式添加到Window中,还不能显示出来。
      最后要通过ActivityThread的handleResumeActivity方法中在调用onResume方法后的makeVisible方法,才会真正添加和显示。
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
    

    Dialog的Window创建过程

    1. 创建Window
      这一步与Activity类似,api23为分水岭,一个使用PolicyManager,一个直接new。
    2. 初始化DecorView并将Dialog的视图添加到DecorView中
      这一步也与Activity类似,都是通过Window的setContentView方法添加指定的布局文件。
    public void setContentView(int layoutResID) {
        mWindow.setContentView(layoutResID);
    }
    
    1. 将DecorView添加到Window中并显示
      在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中。
    public void show() {
        ...
        onStart();
        mDecor = mWindow.getDecorView();
        ...
        try {
            mWindowManager.addView(mDecor, l);
        ...
        } finally {
        }
    }
    

    当Dialog关闭时,则会通过WindowManager来移除DecorView。

    @Override
    public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            mHandler.post(mDismissAction);
        }
    }
    
    void dismissDialog() {
        ...
        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            ...
        }
    }
    

    要注意的是,应用级视图的Context要使用Activity的。如果要使用Application的Context,那必须要指定Window的type为系统级。


    Toast的Window创建过程

    • Toast属于系统的Window,内部视图由两种方式指定:系统默认、通过setView方法指定一个自定义View。它们都对应Toast的一个View内部成员mNextView。
    • Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。
    1. Toast提供了show和cancel用于显示和隐藏View,内部是一个IPC过程。
    /**
     * Show the view for the specified duration.
     */
    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
        }
    }
    
    /**
     * Close the view if it's showing, or don't show it if it isn't showing yet.
     * You do not normally have to call this.  Normally view will disappear on its own
     * after the appropriate duration.
     */
    public void cancel() {
        mTN.cancel();
    }
    

    可以看到show方法中调用了NMS的enqueueToast方法,参数为包名、远程回调、时长。enqueueToast首先将Toast请求封装为ToastRecord对象并添加到一个名为mToastQueue的队列中。这个队列对于非系统应用来说最多同事存在50个。

    @Override
    public void enqueueToast(String pkg, ITransientNotification callback, int duration){
        ...
        Binder token = new Binder();
        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
        record = new ToastRecord(callingPid, pkg, callback, duration, token);
        mToastQueue.add(record);
        ...
    }
    

    当TosatRecord被添加到mToastQueue中后,NMS就会通过showNextToastLocked方法来显示当前Toast。

    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            try {
                record.callback.show(record.token);//这里显示当前Toast
                scheduleTimeoutLocked(record);///延时隐藏Toast
                return;
            } catch (RemoteException e) {
                ...
            }
        }
    }
    
    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);
    }
    

    这里的record.callback实际上是Toast中的TN对象的远程Binder,这需要跨进程来完成。scheduleTimeoutLocked方法发送一个延时消息来隐藏Toast,只有2个延迟时间,LONG_DELAY为3.5秒,SHORT_DELAY为2秒。移除后,如果队列中还有其他Toast则继续显示其他Toast。

    void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();//隐藏Toast这里也是IPC过程
        } catch (RemoteException e) {
            ...
        }
        ...
        if (mToastQueue.size() > 0) {
            showNextToastLocked();//继续显示Toast
        }
    }
    

    通过分析,知道Toast的显示和隐藏实际上是由TN来实现的,内部有show和hide两个方法,使用了Handler来切换到Toast的线程。

    @Override
    public void show(IBinder windowToken) {
        mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
    }
    
    @Override
    public void hide() {
        mHandler.obtainMessage(HIDE).sendToTarget();
    }
    

    而最终调用了handleShow和handleHide方法。

    public void handleShow(IBinder windowToken) {
        ...
        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        mWM.addView(mView, mParams);
        ...
    }
    
    public void handleHide() {
        ...
        mWM.removeViewImmediate(mView);
        ...
    }
    

    一张图解释Toast的添加和移除:


    Toast.jpg

    相关文章

      网友评论

          本文标题:理解Window和WindowManager(三)

          本文链接:https://www.haomeiwen.com/subject/kbmogftx.html