理解Window和WindowManager(一)

##1、背景##

Window表示一个窗口的概念,我们平时直接接触Window的机会并不多,但是在某些特殊需求中比如在桌面上显示一个悬浮的图标时,就需要Window来实现。

Window是一个抽象类,其具体实现类是PhoneWindow。创建一个Window是很简单的事情,只需通过WindowManager即可完成,WindowManager是外界访问Window的入口,而Window的具体实现是在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

Android中所有的视图都是通过Window来呈现的,无论是Activity,Dialog还是Toast,它们的视图实际都是附加在Window上的,因此Window实际是View的直接管理者。需要具体了解Android是如何将视图添加到界面上的,可以参考一下另外一篇文章SetContentView与LayoutInflater源码分析

##2、Window简介##

为了分析Window的工作机制,我们先来了解一下WindowManager如何添加一个Window,下面我们给个示例。

	mButton = new Button(this);
	mButton.setText("button");

	wManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
	
	mParams = new WindowManager.LayoutParams();
    mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;// 系统提示window
    mParams.format = PixelFormat.TRANSLUCENT;// 支持透明
    mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;// 焦点
    mParams.width = 490;//窗口的宽和高
	mParams.x = 100;//窗口位置的偏移量
    mParams.y = 200;
	
	wManager.addView(mButton,mParams);

以上就是将一个Button添加到屏幕坐标(100,200)的位置,而其中的flag和type是两个比较重要的参数。
Flags表示Window的属性值,有很多选项具体可以参考官方文档。
Type表示Window的类型,具体有三种应用Window、子Window、系统Window。其中应用Window对应Activity,子Window不能单独存在需要依附在特殊父Window中(比如常见的Dialog),而系统Window需要声明权限才能创建Window(比如Toast和系统状态栏)。

Window是分层的,层级大的会覆盖在层级小的上面。在三类Window中应用Window的层级范围是1-99,子Window的层级范围是1000-1999,而系统Window的层级范围是2000-2999,这些层级对应着WindowManager.LayoutParams的type属性值。

WindowManager所提供的功能很简单,常用的就三个方法,即addView、removeView、updateView,如果大家对此不是很熟悉的请先于阅读一下我的另外一篇文章WindowManagerService和应用程序的IPC过程
从这三个方法来看,WindowManager操作Window的过程更像是在操作Window中的View,其实我们常见的通过手指拖动Window的效果也很好实现,只需根据手指的位置来设定LayoutParams中的x和y的值即可改变Window的位置,当然需要给View设定OnTouchListener监听。

public boolean onTouch(View v,MotionEvent event){

	int rawX = (int)event.getRawX();
	int rawY = (int)event.getRawY();
	switch(event.getAction()){
		case MotionEvent.ACTION_MOVE:{
			
			mLayoutParams.x = rawX;
			mLayoutParams.y = rawY;
		  mWindowManager.updateViewLayout(mButton,mLayoutParams);
			break;
		}
	}
	return false;
}

##3、Window的操作方法##

之前也提到过操作Window的三个方法即addView,removeView,updateView,为了分析Window的内部机制,我们就从这三个方法入手。

1、Window的addView过程

Window的添加过程需要通过WindowManager的addView来实现,而WindowManager是一个接口,其真正实现者是WindowManagerImpl类,我们进入该类查看这三个方法。

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
   mGlobal.addView(view, params, mDisplay, mParentWindow);
}

public void removeView(View view) {
    mGlobal.removeView(view, false);
}

public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
   mGlobal.updateViewLayout(view, params);
}

可以发现这三个方法都将操作转交给WindowManagerGlobal来实现,我们先来看一下addView的方法。

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");
     }

	//创建ViewRootImpl并将其添加到列表中
	root = new ViewRootImpl(view.getContext(), display);
	view.setLayoutParams(wparams);

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

	//这里紧接着就调用ViewRootImpl的setView方法来更新Window的添加过程
	root.setView(view, wparams, panelParentView);
}

这里我们来看一下WindowManagerGlobal类中的几个列表对象:

//存储所有Window对应的View
private final ArrayList<View> mViews = new ArrayList<View>();

//存储所有Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

//存储所有Window对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();

//存储正在被删除的View对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();

我们紧接着addView方法的最后通过调用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();

	 mOrigWindowType = mWindowAttributes.type;
     mAttachInfo.mRecomputeGlobalAttributes = true;
     collectViewAttributes();
     //通过WindowSession完成Window的添加过程,mWindowSession的类型是IWindowSession,是一个Binder对象,真正的实现着是Session
     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
}

在setView方法中通过requestLayout来完成异步刷新请求,并最终调用Session的addToDisplay方法,我们进入该方法。

final class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
		
	public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,...) {
	
		//这里将操作转给WMS的addWindow方法
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,outContentInsets, outStableInsets, outInputChannel);
    }        
}

好啦,到此Window的添加请求就交给了WMS进行处理了,具体WMS内部是如何添加的我们不去深究。

2、Window的删除过程

Window的删除过程和添加一样,最终都是通过WindowManagerGlobal来实现的,我们来看一下其源码。

public void removeView(View view, boolean immediate) {
   if (view == null) {
       throw new IllegalArgumentException("view must not be null");
   }

   synchronized (mLock) {
	   //找到需要删除View的index
       int index = findViewLocked(view, true);
       View curView = mRoots.get(index).getView();
       //调用removeViewLocked进行删除
       removeViewLocked(index, immediate);
       if (curView == view) {
           return;
       }

       throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);
   }
}

删除操作转交给removeViewLocked方法进行,进入该方法。

 private void removeViewLocked(int index, boolean immediate) {
	//通过索引index获得需要删除View的ViewRootImpl对象
    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对象的die方法,immediate表示同步删除还是异步删除
    boolean deferred = root.die(immediate);
    if (view != null) {
       view.assignParent(null);
       if (deferred) {
	       //由于die并没有立即删除View,所以需要将其添加到mDyingViews列表中,表示待删除View
           mDyingViews.add(view);
       }
    }
}

通过以上分析我们知道,Window的删除操作具体由ViewRootImpl的die方法来完成。

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.
    //如果是同步删除将直接调用doDie()方法
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }
    //如果是异步删除将通过Handler发送消息然后在调用doDie()方法
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

无论是同步还是异步删除,其最终都是调用doDie方法,我们直接分析该方法。

void doDie() {

	dispatchDetachedFromWindow();
	
	//.....
	mWindowSession.finishDrawing(mWindow);

	//.....
	WindowManagerGlobal.getInstance().doRemoveView(this);
}

在doDie内部会调用dispatchDetachedFromWindow方法,真正的删除操作都在dispatchDetachedFromWindow方法内部实现,并在最后调用WindowManagerGlobal的doRemoveView方法来刷新数据,包括mRoots、mParams、mDyingViews,将当前Window从这三个列表中删除。

dispatchDetachedFromWindow()方法我们就不分析源码,其主要做了以下几件事:

  • 垃圾回收,比如清除数据移除回调等;
  • 通过Session的remove删除Window:mWindowSession.remove(mWindow)这是一个IPC过程,其最终调用WMS的removeWindow方法;
  • 调用View的dispatchDetachedFromWindow()方法,该方法在View内部调用onDetachedFromWindow和onDetachedFromWindowInternal方法,对于onDetachedFromWindow相信大家都已有所了解,当View从Window中移除时,这个方法就会被调用,可以在该方法内部做一些回收的工作;

3、Window的更新过程

我们接下来看一下Window的更新过程,我们直接进入WindowManagerGlobal的updateViewLayout方法。

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的LayoutParams
    view.setLayoutParams(wparams);

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        //更新ViewRootImpl的LayoutParams
        root.setLayoutParams(wparams, false);
    }
}

从以上代码我们不难发现,Window的更新操作还是比较简单的,首先更新View的LayoutParams,然后在更新ViewRootImpl的LayoutParams,具体当中的细节我就不一一分析啦。

好啦,到此对Window的添加、删除、更新等操作就分析完毕啦,下一篇我们将分析几种类型的Window的创建过程(Activity,Dialog,Toast)理解Window和WindowManager(二)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪舞飞影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值