Android知识进阶树——Window & ViewRootImpl

栏目: IOS · Android · 发布时间: 6年前

内容简介:Window是一个抽象概念,它是以View的形式存在,每个Window都对应着View和ViewRootImpl,Window和View之间通过ViewRootImpl建立联系Window的整个添加过程可分为两部分执行:View的添加是从调用windowManager.addView()开始的,其实点开windowManager只是一个继承ViewManager的接口,在活动中真正执行任务的是它的实现类WindowMangerImpl,因此方法会执行到WindowMangerImpl.addView(),但
val layoutParams = WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE shl WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL shl  WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
windowManager.addView(imageView,layoutParams)
复制代码
  • Window类型
  1. 系统级WIndow:系统级别的Window需要声明权限才能创建,如Toast
  2. 应用级Window:系统的活动窗口,如:Activity
  3. 子Window:附属在父Window中,如:Dialog
  • FLAG
  1. FLAG_NOT_FOCUSABLE:表示不获取焦点,事件最终会传递到底层的Window
  2. FLAG_NOT_TOUCH_MODEL:只处理自己区域内的点击事件,区域之外的传递给底层Window
  3. FLAG_SHOW_WHEN_LOCKED:开启此模式可以显示在锁屏上

2、Window工作过程

Window是一个抽象概念,它是以View的形式存在,每个Window都对应着View和ViewRootImpl,Window和View之间通过ViewRootImpl建立联系

2.1、Window的添加过程

Window的整个添加过程可分为两部分执行:

  • WindowManager
  • WindowManagerService

View的添加是从调用windowManager.addView()开始的,其实点开windowManager只是一个继承ViewManager的接口,在活动中真正执行任务的是它的实现类WindowMangerImpl,因此方法会执行到WindowMangerImpl.addView(),但WindowMangerImpl 是个聪明的类,在addView()中除了验证设置LayoutParams的合法性之外,它又将所有的工作都桥接给WindowManagerGlobal执行:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);//验证params的合法性
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); // 直接交给WindowManagerGlobal处理
}
复制代码
  • WindowManagerGlobal

在具体执行方法前先介绍下WindowManagerGlobal中的各个集合的作用(见下面注释),在Window工作的整个过程他们时刻保存着Window和View的运行状态

private final ArrayList<View> mViews = new ArrayList<View>();  // 保存所有Window对应的View
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();  // 保存所有Window对应的ViewRootImpl
private final ArrayList<WindowManager.LayoutParams> mParams =  // 保存所有Window对应的WindowManager.LayoutParams
        new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>(); // 保存正在被删除的View

复制代码
  • WindowManagerGlobal.addView()
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view); 
mRoots.add(root); 
mParams.add(wparams);
root.setView(view, wparams, panelParentView);  // View的绘制
复制代码

上面是addView()中的部分代码,它执行了以下几个操作:

  1. 创建ViewRootImpl的实例
  2. 设置View的布局参数
  3. 分别在集合中保存view、root和params

在保存了相关数据后,View真正的执行就是setView()这一句开始,下面看看ViewRootImpl中是如何实现View的测量绘制的

  • ViewRootImpl.setView()

ViewRootImpl是View中的最高层级,属于所有View的根(但ViewRootImpl不是View,只是实现了ViewParent接口),实现了View和WindowManager之间的通信协议

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        ......
          requestLayout();//对View进行第一次测量和绘制

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); //调用WindowSession的addTodiaplay()添加窗口
        		}
}
复制代码

requestLayout()内调用scheduleTraversals(),scheduleTraversals()中 会获取主线程的Handler然后发送消息执行TraversalRunnable实例,TraversalRunnable是Runnable的实现类,在run()方法中执行oTraversal() ,然后方法会执行到performTraversals()

  • performTraversals()
//调用performMeasure完成Window内视图的测量
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

                    if (measureAgain) {
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
   ......
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight); //完成View的布局Layout
            }
......
 performDraw();//对View的绘制
复制代码

performTraversals方法中,依次调用了performMeasure、performLayout、performDraw三个方法,这三个方法中又分别调用View或ViewGroupde的measure、layout和draw方法,完成了View的测量、布局和绘制;

  • WindowSession使用Binder机制调用IWindowSession接口,内部调用WindowManagerService.addWindow()添加,到此所有的操作就执行到了WindowManagerService中,关于WindowManagerService的工作过程请参考Android窗口管理分析
@Override
 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);
 }
复制代码

2.2、Window删除过程

删除过程和添加一样最后执行任务的都是WindowManagerGlobal,先看下WindowManagerGlobal的removeView()方法:

int index = findViewLocked(view, true); 
View curView = mRoots.get(index).getView(); 
removeViewLocked(index, immediate);
复制代码

removeView()中主要执行三个步骤:

  1. 获取当前操作View的index
  2. 获取mRoots中保存的ViewRootImpl实例
  3. 调用removeViewLocked执行删除
  • removeViewLocked():获取要删除的View执行删除操作
ViewRootImpl root = mRoots.get(index);
View view = root.getView();   // 获取ViewRootImpl保存的View

boolean deferred = root.die(immediate);  // 调用die()执行删除View
mDyingViews.add(view);  // 将要删除的View添加到mDyingViews
复制代码
  • die():发送删除消息
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
    doDie(); 
    return false;
}
    mHandler.sendEmptyMessage(MSG_DIE); 
    return true;
}
复制代码

在die()方法中根据传入的immediate执行同步或异步删除:

  1. 同步删除:直接调用doDie()方法执行删除
  2. 异步删除:发送Handler消息调用doDie()
  • doDie():真正执行View的删除
mView.dispatchDetachedFromWindow();  
mWindowSession.remove(mWindow); 
mView.onDetachedFromWindow();
WindowManagerGlobal.getInstance().doRemoveView(this);  
复制代码

doDie是真正发起删除的地方,执行操作如下:

  1. 调用用mWindowSession最终调用WindowMangerService.removeWindow()
  2. 调用View的onDetachedFromWindow()执行View的移除操作
  3. 移除mRoots、mParams、mDyingView中保存的View信息

2.3、Window更新过程

  • WindowManagerGlobal.updateViewLayout()
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);  // 设置新的LyaoutParams

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);  //更新root,mParams集合中的数据
        mParams.remove(index);
        mParams.add(index, wparams);//替换mParams中保存的wparams
        root.setLayoutParams(wparams, false); // 更新View
    }
}
复制代码

执行过程见上面注释,在 root.setLayoutParams中会触发ViewRootImpl的scheduleTraversals实现View的测量、布局、绘制;

3、实例

3.1、Activity中Window的添加

Android知识进阶树——Window & ViewRootImpl

借用网络上的一幅图展示Activity的层次关系:

  1. PhoneWindow:Activity的活动窗口
  2. DecorView:所有视图的根View,其中包含标题栏和content
  3. ContentView:布局容器设置的layout文件被加载到其中
  • window的创建
  1. 在Activity.attach()方法中使用PolicyManager.makeNewWindow()创建PhoneWIndow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
复制代码
  • window设置视图
//Activity中
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);  //调用PhoneWindow的setContentView()
    initWindowDecorActionBar();
}
复制代码

此处的getWindow()得到的就是前面创建的PhoneWindow ,所以setContentView()最终是在PhoneWindow中执行的

public void setContentView(int layoutResID) {
      if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
}
复制代码

setContentView()方法中,首先判断contentParent是否空,如果为空则执行installDecor(),installDecor()中有两处代码比较明显,分别是初始化DecorView和mContentParent,下面分别看看这两个方法

  • generateDecor():创建DecorView实例
protected DecorView generateDecor(int featureId) {
    return new DecorView(context, featureId, this, getAttributes()); //初始化DecorView,此时只是一个FrameLayout
}
复制代码
  • generateLayout():加载布局文件并初始化mContentParent
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//根据加载后的布局查找content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  // 加载DecorView布局中的content容器
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;  //content的id

//mDecor.onResourcesLoaded()
final View root = inflater.inflate(layoutResource, null);  //加载原始布局文件:包含标题栏和content
addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));  //将加载的布局添加到DecorView中
复制代码

在generateLayout中完成了布局layout文件的加载,具体细节如下:

  1. 加载getWindowStyle中的属性值
  2. 根据设置的style初始化Layout的WindowManager.LayoutParams 和选择系统的布局资源layoutResource
  3. 设置DecorView的背景、标题、颜色等状态
  4. 然后调用mDecor.onResourcesLoaded()加载layoutResource到DecorView中
  5. 根据资源id获取布局中的contentParent容器
  • 将View添加到DecorView的contentParent容器中
@Override
public void setContentView(int layoutResID) {
     mLayoutInflater.inflate(layoutResID, mContentParent); //加载布局到mContentParent中
}
复制代码
  • 加载完布局后回调onContentChange(),通知Activity加载完毕
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
    cb.onContentChanged();
}
复制代码

到此DecorView和contentParent初始化已经完成,DecorView中加载了一个具有TitleView和ContentView的布局,并且加载的layoutResID也已加载到ContentView中,所以关于DecorView内部的工作已经完成,但DecorView未被添加到Window中,所以此时界面仍是不可见

  • DecorView添加到Window()

ActivityThread的handleResumeActivity()中调用Activity的makeVisible()方法,makeVisible中调用WindowManager.addView()将DecorView添加到PhoneWindow中,到此布局资源展示在屏幕上

//handleResumeActivity
if (r.activity.mVisibleFromClient) {
    r.activity.makeVisible();
}

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());  //将DecorView添加到PhoneWindow中
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);  //设置DecorView可见
}
复制代码

3.2、Dialog中Window的添加

  • Dialog使用
val dialog = Dialog(this,R.style.Base_ThemeOverlay_AppCompat_Dialog)
dialog.setContentView(R.layout.dialog)
dialog.show()
dialog.cancel()
复制代码
  • 创建Window

从上面使用可以看出,dialog设置布局时和Activity都是使用setContentView,所以其执行初始化的过程和Activity一致,只是在将DecorView添加到Window时有所不同

  • 将DecorView添加到Window
public void show() { //在Dialog显示时添加到Window中
  mWindowManager.addView(mDecor, l); // 添加DecorView
}
复制代码
  • dialog关闭时通过WindowManager移除DectorView
mWindowManager.removeViewImmediate(mDecor);
复制代码

3.3、Toast中Window的创建

  • 使用
Toast.makeText(this,"Toast",Toast.LENGTH_SHORT).show()
复制代码
  • makeText():makeText执行了Toast的文件加载和和设置
Toast result = new Toast(context, looper); //创建Toast实例,并传入队列Loop
LayoutInflater inflate = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);  //加载Toast布局并设置View
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text); //设置Toast的信息
result.mNextView = v; // 复制给mNextView
result.mDuration = duration;  //设置Toast的弹出时长
return result;
复制代码
  • Toast的显示
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;

service.enqueueToast(pkg, tn, mDuration);
复制代码

针对上面方法中做几点说明:

  1. service是INotificationManager的代理类,此处是IPC通信;
  2. TN 是ITransientNotification的代理类
  3. mNextView是本次Toast加载的View
  4. service.enqueueToast()将Toast加入消息队列

Toast最终回调TN中的show方法,show()中发送Message到Handle,然后调用handleShow()

public void handleShow(IBinder windowToken) {
handleHide();
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;

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
}
复制代码

handleShow()中执行以下操作:

  1. 调用handleHide()隐藏前一个Toast
  2. 设置Toast的mParams参数,如:坐标、mDuration
  3. 调用WindowManager的addView()添加View

WindowManagerService是如何执行Window的添加和操作的?


以上所述就是小编给大家介绍的《Android知识进阶树——Window & ViewRootImpl》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Clean Architecture

Clean Architecture

Robert C. Martin / Prentice Hall / 2017-9-20 / USD 34.99

Practical Software Architecture Solutions from the Legendary Robert C. Martin (“Uncle Bob”) By applying universal rules of software architecture, you can dramatically improve developer producti......一起来看看 《Clean Architecture》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具