您的位置:首页 > 移动开发 > Android开发

第八章理解Window和WindowManager(Android开发艺术探索)

2017-03-20 11:09 435 查看
8.1、window和windowManager

为了分析window的工作机制,我们需要先了解如何使用windowManager添加一个window

代码如下:

WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width,height,type,flags,format);
manager.addView(btn, layoutParams);


是不是很简单的说?

LayoutParams中type和flags比较重要

flags有很多选项,来控制window的显示特性,我们看几个常用的

public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
表示此window不需要获取焦点,不接收各种输入事件,此标记是会同时启用FLAG_NOT_TOUCH_MODAL
最终事件会直接传递给下层具有焦点的window
public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
自己window区域内的事件,自己处理;自己区域外的事件传递给底层window处理;
一般这个都会默认开启,不然其他window无法收到事件
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
可以让此window显示在锁屏上


type表示window的类型,window有3种类型,应用window,子window,系统window

window是分层的,层级大的view会覆盖在层级小的view上面

应用window的层级范围:1~99

子window的层级范围:1000~1999

系统window的层级范围:2000~2999

这些层级返回对应type参数,如果想要window至于最顶层,采用较大的层级即可

同时如果是系统类型的window是需要在menifest中配置权限的:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />


下面我们来看看windowManager:

它是一个接口,继承ViewManager:

public interface WindowManager extends ViewManager {

ViewManager中只有3个方法:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}


根据方法名称就可以知道是对view的增加、修改、删除

8.2、window的内部机制

每个window都对应一个view和viewrootimpl

8.2.1、window的添加过程

window的添加是通过windowManager来实现的

windowManager是一个接口,它的实现类是:windowManagerImpl

我们来看看WindowManagerImpl这个类:

找到了我们需要的方法:

public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}

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

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


我们可以看到它是利用了工厂类WindowManaerGlobal来实现的

WindowManaerGlobal实现addview分如下几步:

1、检查参数是否合法,如果是子window,那么还需要调整一些布局参数

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;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}


2、创建viewRootImpl并将view添加到列表中

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

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


3、通过ViewRootImpl来更新界面,并完成window的添加

先通过:

root.setView(view, wparams, panelParentView);
调用:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}


然后通过windowSession来完成添加,其实是一次IPC的调用:

try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, 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();
}
}


最后Session内部会通过WindowManagerService来实现添加。

8.2.2、Window的删除过程

window的删除过程和添加过程一样,都是通过WindowManagerGlobal实现的

代码如下:

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


首先,通过findViewLocked来查找待删除的view的索引

然后执行removeViewLocked通过ViewRootImpl来进一步删除

代码如下:

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


WindowManagerImpl提供了2中删除方式:

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

@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}


一个是异步删除,一个是同步删除(removeViewImmediate),我们一般不用同步删除

我们来说下异步删除:

首先我们看这行代码:

boolean deferred = root.die(immediate);


ViewRootImpl先调用die方法发送一个删除的handle消息就返回了

最终添加到mDyingViews中,mDyingViews表示待删除列表

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.
//如果是同步,直接执行删除
if (immediate && !mIsInTraversal) {
doDie();
return false;
}

if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
"  window=" + this + ", title=" + mWindowAttributes.getTitle());
}
//如果是异步则发送消息
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}


这里只做了一个简单的判断,如果是异步那么只发送消息,然后交给handle,由handle来调用doDie();

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DIE:
doDie();


同步就直接调用doDie();

下面我们来看看doDie()方法

void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
}


着重看dispatchDetachedFromWindow方法,它是真正的删除方法

我们来看看它的实现:

void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
if (mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.validate();
}
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
//2、资源回收的工作,比如终止动画,停止线程等。
mView.dispatchDetachedFromWindow();
}

mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
//1、垃圾回收的工作,比如清楚数据和消息
removeSendWindowContentChangedCallback();

destroyHardwareRenderer();

setAccessibilityFocus(null, null);

mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mAttachInfo.mSurface = null;

mSurface.release();

if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
}
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
//3、通过session的remove方法删除window
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;
}

unscheduleTraversals();
}


它主要做4件事:

1、垃圾回收的工作,比如清楚数据和消息

2、调用mView.dispatchDetachedFromWindow();,内部会调用onDetachedFromWindow();onDetachedFromWindowIntenal();

onDetachedFromWindow方法大家一定不陌生,当view从window中移除的时候,这个方法就会被调用,可以在这个方法里面做资源回收的工作,比如终止动画,停止线程等。

3、通过session的remove方法删除window

mWindowSession.remove(mWindow);


这同样是一个IPC操作,最终会调用WindowManagerService的removeWindow方法

public void removeWindow(Session session, IWindow client) {
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return;
}
removeWindowLocked(session, win);
}
}


4、调用windowManagerGlobal的doRemoveView方法刷新数据

需要将当前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);
}
}
}


8.2.3、Window的更新过程

废话不多说,直接看代码:

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

然后更新ViewRootImpl的LayoutParams

这个过程最终是由WindowManagerService的relayoutWindow来具体实现的,同样是一个IPC过程

8.3、window的创建过程

View是Andorid视图的呈现方式,但是它不能单独存在,必须依附在window上面,因此,有视图的地方就有window;android中的activity,dialog,toast都对应一个window

8.3.1、Activity的Window创建过程

想了解Activity的window创建过程,得先了解Activity的启动过程。

Activity的启动过程很复杂,最终会由ActivityThread中的performLunchActivity()通过类加载器创建activity的实例,并调用attach方法

在attach方法中,系统会创建activity所属的window对象并为它设置回调接口,因为activity实现了window的callback接口,因此当window接收到外界的状态改变就会回调activity的方法,下面我们来看看callback里面的几个常用方法:

onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent


onAttachedToWindow、onDetachedFromWindow

这两个方法是view对自己的被add , 被remove 的监视。

onAttachedToWindow 是view 本身的回调,用于初始化一些东西相当于onstart 。当view 被添加到window中,被绘制之前的回调。如addview(this view);

onDetachedFromWindow 是view 本身的回调,用于销毁一些东西onstop,当view被从window中删除时的回调。如 removeview(this view);

dispatchTouchEvent :事件分发机制

下面回到正题,Activity的window是怎么创建的:

其实activity的window是通过PolicyManager的一个工厂方法makeNewWindow来创建的,window的具体实现的phoneWindow

从activity的setContentView可以看出,activity将具体实现交给了window处理,window交给了phoneWindow处理,所以只需要看phoneWindow的处理步骤:

1、如果没有DecorView就创建它

phoneWindow需要通过generateLayout方法来加载布局文件到DecorView中

2、将View添加到DecorView的mContentParent中

3、回调activity的onContentChanged方法并通知activity视图已经发生改变

经过上面3个步骤,DecorView已经初始化完毕,

在Activity的handleResumeActivity方法中,首先会调用activity的onResume方法,接着调用makeVisible,在makeVisible方法中真正完成添加和显示的过程,到这里activity的视图才能被用户看到。

8.3.2、Dialog的Window创建过程

和activity的window创建类似:

1、创建window

dialog的window创建同样是通过PolicyManager的makeNewWindow创建的,创建后的对象就是phoneWindow

2、初始化DecorView并将Dialog视图添加到DecorView中去,这个也和activity类似

3、将Decorview添加到Window中并显示

在dialog的show方法中会通过WindowManager将DecorView添加到window中去

mWindowManager.addView(mDecorView, 1);
mShowing = true;


当dialog关闭时,会通过WindowManager来移除DecorView

mWindowManager.removeViewImmediate(mDecorView);


拓展:

普通的dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Applicaiton的context就会报错:没有应用token,而应用token只有activity才有。

8.3.3、Toast的Window创建过程

Toast和dialog不同,它的工作过程就比较复杂,Toast有定时取消的功能,所以系统采用了handle

Toast属于系统window,它内部的视图由两种方式指定:系统默认和自定义view,Toast提供了show和cancle方法:

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 {
//参数1:包名,参数2:远程回调,参数3:toast时长
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}


public void cancel() {
mTN.hide();

try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}


我们来看enqueueToast方法,enqueueToast首先将Toast请求封装成ToastRecord对象并添加到mToastQueue队列中,

mToastQueue是一个arrayList,最多存50个,这样做的目的是为了防止DOS

(Denial of Service),即拒绝服务。

添加到mToastQueue中后,NMS就会通过showNextToastLocked来显示Toast,

然后NMS通过scheduleTimeOutLocked发送一个延时消息(long:3.5s,short:2s),

之后NMS通过cancelToastLocked方法来隐藏Toast,并从mToastQueue移除,这个时候如果mToastQueue中海油其他toast,那么就继续显示其他toast。

Toast的显示和隐藏其实是TN中的show和hide方法

这两个方法是被NMS跨进程调用的,它们运行在Binder线程池中,所以内部使用了Handle进行切换线程。

private static class TN extends ITransientNotification.Stub {
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}

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


然后由handleShow方法和handleHide方法接收,从window中添加和删除

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


public void handleHide() {
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
mView = null;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: