Window和Windowmanager

Window叫做窗口,本质上就是承载view的载体
参考资料 Android艺术探索

 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a Window.
  这个抽象类唯一的实现类是phoneWindow,当你需要实例化window的时候。
 */
public abstract class Window {

但是phoneWindow是隐藏类,无法直接调用。

我们的应用层,都是通过WindowManager来对UI进行操作。
Windowmanager是一个接口,继承自 ViewManager
ViewManager的三个方法,addView,updateViewLayout,removeView并没有在WindowManager中调用,而是在WindowManagerService通过binder来实现

然而其底层实际上是通过WindowManagerService。

 final Button floatingButton = new Button(this);
 floatingButton.setText("button");
 final WindowManager.LayoutParams layoutParams = 
       new WindowManager.LayoutParams(
           WindowManager.LayoutParams.WRAP_CONTENT,
           WindowManager.LayoutParams.WRAP_CONTENT,
           0, 0,
           PixelFormat.TRANSPARENT);

   // flag 设置 Window 属性
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
           | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
           | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;

   // type 设置 Window 类别(层级)
   layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;

   //相当于设置原点中心位置
   layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
   //如果想要设置滚动效果的话 ,只能把原点设置为top和left
   //设置top和right或者center都不行

   layoutParams.x = 100;
   //距离原点x轴的位置大小
   layoutParams.y = 300;
   //距离原点y轴的位置大小
   final WindowManager windowManager = getWindowManager();

   windowManager.addView(floatingButton, layoutParams);

   floatingButton.setOnTouchListener(new View.OnTouchListener() {
       @Override
       public boolean onTouch(View v, MotionEvent event) {
           Log.i(TAG, "onTouch: ");
           int rawX = (int) event.getRawX();
           int rawY = (int) event.getRawY();
           int startX = 0;

           switch (event.getAction()) {
               case MotionEvent.ACTION_DOWN:
                   startX = (int) event.getX();
                   break;
               case MotionEvent.ACTION_MOVE:

                   if (Math.abs(startX - event.getX()) > ViewConfiguration.get(MainActivity.this).getScaledTouchSlop()) {
                       layoutParams.x = rawX;
                       layoutParams.y = rawY;
                       windowManager.updateViewLayout(floatingButton, layoutParams);
                   }
           }
           return true;
       }
   });

同样的 WindowManagerService也是个隐藏类

/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
    implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

从这里可以看出,WindowManagerService和WindowManager的交互是一个IPC的过程。所有的视图都是通过Window来呈现的。

2.Window 分类

2.1 系统window

Toast 和系统状态栏都是系统 Window。
但调用Toast,不需要权限,
系统 Window是需要声明权限才能创建的 Window

2.2 子window

子Window不能单独存在,它通常依附在父window上
需要依附在特定的父 Window 中,如Dialog

2.3 应用 Window

例如 Acitivity

3.Window的添加移除更新的过程

3.1 Window的添加

window是需要通过windowmanager来进行操作,而windowmanager的实现类则是windowmanagerimpl。而在windowmanagerimpl的addview的操作,则是在WindowManagerGlobal中进行操作。

目前也就是需要分析WindowManagerGlobal的addview源码操作。

//核心代码
root = new ViewRootImpl(view.getContext(), display);
 view.setLayoutParams(wparams);
 //view的数组中添加view
 mViews.add(view);
 //数组中添加viewrootimpl.
 mRoots.add(root);
 //windowmanagerLayoutparam的数组中添加params
 mParams.add(wparams);
  //则实际上是通过viewrootimpl来完成view的更新
 root.setView(view, wparams, panelParentView);

ViewRootImpl的核心代码在于

 // 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();==>scheduleTraversals();
   try {
        mOrigWindowType = mWindowAttributes.type;
        mAttachInfo.mRecomputeGlobalAttributes = true;
        collectViewAttributes();
        //核心代码
        res = mWindowSession.addToDisplay(mWindow, mSeq
                        , mWindowAttributes,getHostVisibility(),   

       mDisplay.getDisplayId(),mAttachInfo.mContentInsets,
       mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);

因为是framework层,所以看不到代码

这里就需要借助在线源码阅读

查看到addToDisplay方法的调用

则是在/frameworks/base/services/core/java/com/android/server/wm/路径下的Session类,通过binder的方式

这里 梳理一下整个流程
window通过windowmanager来操作。其实现类是
WindowmanagerImpl
而具体的addView则是在WindowManagerGlobal的addView中

WindowManagerGlobal的addView内部实际上就是通过ViewrootImpl调用SetView,而setView的核心就是两点

  1. requestLayout() 异步刷新请求
    2.Session的addToDisplay()的底层实际上就是WMS与WM的binder交互

3.2 Window的移除过程

public void removeView(View view, boolean immediate) {
    synchronized (mLock) {
        // 找到lockedview的角标
        int index = findViewLocked(view, true);
        //mRoots是ViewRootImpl的数组,根据角标来获取view
        View curView = mRoots.get(index).getView();
        //核心代码:移除掉view
        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());
        }
    }
    //核心代码,也就是说,移除工作还是通过viewrootImpl来完成
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
         //已经被移除的view
            mDyingViews.add(view);
        }
    }
}
 //分析die方法
 boolean die(boolean immediate) {
    if (immediate && !mIsInTraversal) {
        //核心移除代码
        doDie();
        return false;
    }

    if (!mIsDrawing) {
        destroyHardwareRenderer();
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

//分析ViewrootImpl中的doDie方法
void doDie() {
    checkThread();
    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) {                     
                    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);
}

则实际上还是调用了Session的移除,底层也是wms与wm的binder交互

3.3 Window的更新

这个就比较简单了
直接看WindowmanagerGlobal源码 updateViewLayout方法

  public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
   view.setLayoutParams(wparams);
   synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        //实际上就是从layoutparams集合中,移除掉原先的params,新增新的params。
        mParams.remove(index);
        mParams.add(index, wparams);
        //然后通过viewrootImpl去setLayoutparams
        //核心代码
        root.setLayoutParams(wparams, false);
    }
}

viewrootImpl中的setlayoutparams

void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
    synchronized (this) {
      //核心代码就这两句,也就是view的绘制过程。
       if (newView) {
            requestLayout();
        }
        scheduleTraversals();
}

除了这里之外,还会调用WindowSession来更新window的视图,内部还是通过wms的relayoutWindow()来实现,也是一个ipc的过程

4 Window的创建过程

1
2
有视图的地方就有window,无论是Activity,dialog,
popupWindow,view不能单独存在,必须要依附在window上。

4.1 Activity的window创建过程

Activity的启动过程
直接从performLaunchActivity()

核心代码

 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方法。

final void attach(省略参数) {
    //phoneWindow是唯一的实例
    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);
    }
        mWindow.setContainer(mParent.getWindow());
    }
    mWindow.setColorMode(info.colorMode);
}

这里我们可以发现,在APP启动的时候,就已经创建了window,并且给window设置了一系列的参数,并将Activity和window相绑定。

但是这些都只是初始化逻辑,那么Activity的视图和window的视图又是如何相连接的
入口:Activity的setContentView()

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

因为window的实现类是属于phoneWindow

那么这里就需要看PhoneWindow的setContentView方法

@Override
public void setContentView(int layoutResID) {
    // 注意:可以在安装窗口的过程中设置FEATURE_CONTENT_TRANSITIONS
    // 不要检查特性,当主题属性实例化发生之前
    if (mContentParent == null) {
        //没有DecorView的话,就初始化一个
        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);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        //代表content已经改变的callback回调,在Activity中是个空的实现
        cb.onContentChanged();
    }
}

 private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
      //初始化DecorView
        mDecor = generateDecor(-1);  
        //DEcorView设置window
        mDecor.setWindow(this);
      if (mContentParent == null) {
      //初始化layout
      mContentParent = generateLayout(mDecor);

以上步骤则代表,window的初始化,window和Activity的绑定以及DecorView和Content视图的初始化。均在Activity的onCreate中完成

真正显示的代码,则是在ActivityThread的handleResumeActivity()中进行。

//首先要调用performResumeActivity(),调用Activity的onResume()
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
ViewRootImpl impl = decor.getViewRootImpl();
impl.notifyChildRebuilt();
//添加view
wm.addView(decor, l);
WindowManager.LayoutParams l = r.window.getAttributes();
    if (r.activity.mVisibleFromClient) {
        ViewManager wm = a.getWindowManager();
        View decor = r.window.getDecorView();
        //更新view
        wm.updateViewLayout(decor, l);
       }
     }
   if (r.activity.mVisibleFromClient) {
        //显示
        r.activity.makeVisible();
        }
      }

实际上是Activity调用makeVisible方法。

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

整个流程 实际上分三个部分
1.数据的初始化和attach部分
ActivityThread的performLaunchActivity()==>内部的attach就是绑定Activity和window相互绑定
2.视图层部分

4.2 Dialog的window创建过程

入口直接从new dialog开始
dialog构造中的方法

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//直接在dialog的构造中new phoneWindow,并且设置各种回调
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);

而setContentView的过程,实际上就是回到了phoneWindow的setContentView。这个过程和Activity的一致,这里就不描述了。
由此可以看到,有的手机dialog是有默认的title,而高版本的没有,这里Decorview是根据版本来进行设置的

当dialog关闭的时候,会走dismissDialog方法,会通过windowmanager来移除DecorView,同样是wms的IPC过程。并且将mDecor置空,并执行onStop()

void dismissDialog() {
        mWindowManager.removeViewImmediate(mDecor);
        mDecor = null;
        mWindow.closeAllPanels();
        onStop();
        mShowing = false;
        sendDismissMessage();
    }
}

应用层的dialog,必须要Activity的context,因为应用token只有Activity拥有。而如果需要系统级的dialog,则需要将window的type,设置系统级,并且给与对应浮窗权限。

4.3 Toast的window创建过程

Toast因为有定时取消的功能,所以内部加入了Handler。

我们一般使用toast的时候都是这样

Toast.makeText(this, "1", 1).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构造方法
public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    //核心代码,TN是一个Binder类。
    mTN = new TN(context.getPackageName(), looper);
    //初始化数据,加载自带的dimen值
    mTN.mY = context.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.toast_y_offset);
    mTN.mGravity = context.getResources().getInteger(
            com.android.internal.R.integer.config_toastDefaultGravity);
}

  //TN构造方法源码分析
TN(String packageName, @Nullable Looper looper) {
        final WindowManager.LayoutParams params = mParams;     
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mPackageName = packageName;
         //params的初始化代码,省略.......
        if (looper == null) {
            looper = Looper.myLooper();
        }
        //这里是最关键的处理代码
        mHandler = new Handler(looper, null) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case SHOW: {
                        IBinder token = (IBinder) msg.obj;
                        handleShow(token);
                        break;
                    }
                    case HIDE: {
                        handleHide();
                        mNextView = null;
                        break;
                    }
                    case CANCEL: {
                        handleHide();
                        mNextView = null;
                        try {
                            getService().cancelToast(mPackageName, TN.this);
                        } catch (RemoteException e) {
                        }
                        break;
                    }
    }

 @Override
    public void show(IBinder windowToken) {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
    }

    /**
     * schedule handleHide into the right thread
     */
    @Override
    public void hide() {
        if (localLOGV) Log.v(TAG, "HIDE: " + this);
        mHandler.obtainMessage(HIDE).sendToTarget();
    }

    public void cancel() {
        if (localLOGV) Log.v(TAG, "CANCEL: " + this);
        mHandler.obtainMessage(CANCEL).sendToTarget();
    }

而这个类自身就有sendMessage

我们再来看看Toast的show方法在做什么

public void show() {
    //很显然Toast的show,是一个IPC的过程。
    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    try {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
    }
}

//NotificationManagerService的源码分析。
//这里是通过一个Toast的queue队列,来处理ToastRecord
//从队列里面获取或者回收到队列中。
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
    {
        synchronized (mToastQueue) {
            int callingPid = Binder.getCallingPid();
            long callingId = Binder.clearCallingIdentity();
            try {
                ToastRecord record;
                int index;
              //判断是否是系统toast
                if (!isSystemToast) {
                    index = indexOfToastPackageLocked(pkg);
                } else {
                    index = indexOfToastLocked(pkg, callback);
                }
                if (index >= 0) {
                    record = mToastQueue.get(index);
                    record.update(duration);
                    record.update(callback);
                } else {
                    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 (index == 0) {
                    //当Toast添加完之后,就显示toast
                    showNextToastLocked();
                }
            } finally {
                Binder.restoreCallingIdentity(callingId);
            }
        }
    }


@GuardedBy("mToastQueue")
void showNextToastLocked() {
    ToastRecord record = mToastQueue.get(0);
    while (record != null) { 
        try {
            record.callback.show(record.token);
            //核心代码
            scheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {   
            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;
            }
        }
    }
}

 @GuardedBy("mToastQueue")
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);
}

也就是说,完成一个toast,调用了两种ipc的过程
分别是toast.makeText()调用了Toast内部的TN过程
还有show方法里面的NotificationManagerService的IPC过程

4 最后的问题 一个应用中有多少个 Window?

我们可以从Activity的源码开始分析
Activity是属于应用级的window

因为window是个抽象类,实现类就是phoneWindow,所以这里就代表了 多少个Activity就有多少应用级的window

toast是属于系统window

PopupWindow和Activity是共用一个window

dialog是属于应用window,说明多少个dialog对应多少个window

有多少dialogFragment也有多少window

很明显 dialogfragment是继承自Fragment的,Fragment的context是属于Activity,但是内部是有dialog的

1
2
3
4
5
6
7
8
很显然,我们不能简单的按照什么Activity,dialog  dialogfragment来算。
因为如果以后Google新建一大堆组件的话,那答案岂不是没个标准了??

综上,具体有多少window,这个是要看window到底是属于子window
还是属于系统window还是应用window
应用window的话,创建多少就有多少
系统window就只有一个
子window的话,创建多少就有多少
igding wechat
2018 依计行事 持续精进
坚持原创技术分享,您的支持将鼓励我继续创作!