第八章理解Window和WindowManager(Android开发艺术探索)
2017-03-20 11:09
435 查看
8.1、window和windowManager
为了分析window的工作机制,我们需要先了解如何使用windowManager添加一个window
代码如下:
是不是很简单的说?
LayoutParams中type和flags比较重要
flags有很多选项,来控制window的显示特性,我们看几个常用的
type表示window的类型,window有3种类型,应用window,子window,系统window
window是分层的,层级大的view会覆盖在层级小的view上面
应用window的层级范围:1~99
子window的层级范围:1000~1999
系统window的层级范围:2000~2999
这些层级返回对应type参数,如果想要window至于最顶层,采用较大的层级即可
同时如果是系统类型的window是需要在menifest中配置权限的:
下面我们来看看windowManager:
它是一个接口,继承ViewManager:
根据方法名称就可以知道是对view的增加、修改、删除
8.2、window的内部机制
每个window都对应一个view和viewrootimpl
8.2.1、window的添加过程
window的添加是通过windowManager来实现的
windowManager是一个接口,它的实现类是:windowManagerImpl
我们来看看WindowManagerImpl这个类:
找到了我们需要的方法:
我们可以看到它是利用了工厂类WindowManaerGlobal来实现的
WindowManaerGlobal实现addview分如下几步:
1、检查参数是否合法,如果是子window,那么还需要调整一些布局参数
2、创建viewRootImpl并将view添加到列表中
3、通过ViewRootImpl来更新界面,并完成window的添加
先通过:
然后通过windowSession来完成添加,其实是一次IPC的调用:
最后Session内部会通过WindowManagerService来实现添加。
8.2.2、Window的删除过程
window的删除过程和添加过程一样,都是通过WindowManagerGlobal实现的
代码如下:
首先,通过findViewLocked来查找待删除的view的索引
然后执行removeViewLocked通过ViewRootImpl来进一步删除
代码如下:
WindowManagerImpl提供了2中删除方式:
一个是异步删除,一个是同步删除(removeViewImmediate),我们一般不用同步删除
我们来说下异步删除:
首先我们看这行代码:
ViewRootImpl先调用die方法发送一个删除的handle消息就返回了
最终添加到mDyingViews中,mDyingViews表示待删除列表
die方法如下:
这里只做了一个简单的判断,如果是异步那么只发送消息,然后交给handle,由handle来调用doDie();
同步就直接调用doDie();
下面我们来看看doDie()方法
着重看dispatchDetachedFromWindow方法,它是真正的删除方法
我们来看看它的实现:
它主要做4件事:
1、垃圾回收的工作,比如清楚数据和消息
2、调用mView.dispatchDetachedFromWindow();,内部会调用onDetachedFromWindow();onDetachedFromWindowIntenal();
onDetachedFromWindow方法大家一定不陌生,当view从window中移除的时候,这个方法就会被调用,可以在这个方法里面做资源回收的工作,比如终止动画,停止线程等。
3、通过session的remove方法删除window
这同样是一个IPC操作,最终会调用WindowManagerService的removeWindow方法
4、调用windowManagerGlobal的doRemoveView方法刷新数据
需要将当前window所关联的这三类对象从列表中删除。
8.2.3、Window的更新过程
废话不多说,直接看代码:
首先更新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
这两个方法是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中去
当dialog关闭时,会通过WindowManager来移除DecorView
拓展:
普通的dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Applicaiton的context就会报错:没有应用token,而应用token只有activity才有。
8.3.3、Toast的Window创建过程
Toast和dialog不同,它的工作过程就比较复杂,Toast有定时取消的功能,所以系统采用了handle
Toast属于系统window,它内部的视图由两种方式指定:系统默认和自定义view,Toast提供了show和cancle方法:
我们来看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进行切换线程。
然后由handleShow方法和handleHide方法接收,从window中添加和删除
为了分析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; }
相关文章推荐
- Android开发艺术探索——第八章:理解Window和WindowManager
- 读书笔记-Android开发艺术探索-第8章-理解Window和WindowManager
- 《Android 开发艺术探索》笔记——(8)Window 和 WindowManager
- android艺术开发探索之window和windowmanager
- 利用Window和WindowManager实现悬浮窗效果——Android开发艺术探索笔记
- Android开发艺术探索 第5章 理解RemoteViews 读书笔记
- Android 开发艺术探索笔记 第五章 理解RemoteViews
- Android开发艺术探索第五章——理解RemoteViews
- Android开发艺术探索第五章——理解RemoteViews
- Android开发——带你彻底理解 Window 和 WindowManager
- 开发艺术探索-- Window及WindowManager
- Android开发艺术探索_第八章
- Android开发艺术探索_理解RemoteViews(五)
- 第五章理解RemoteViews(Android开发艺术探索)
- Android开发艺术探索<Notification使用>
- 读书笔记:Android开发艺术探索之第4章 View的工作原理
- 读后总结--Android开发艺术探索
- Android开发艺术探索学习-老生常谈Activity生命周期
- Android艺术开发探索——第二章:IPC机制(下)
- Android开发艺术探索读书(三)-View的事件体系