原文地址 http://blog.csdn.net/qq_25806863/article/details/66530372
通过前面的分析可以知道,Android系统中,所有的界面内容显示都是通过Window来实现的,包括Activity,Dialog,Toast等。
先初步分析一下Window,WindowManager,WindowManagerService的关系。
简书地址
从Android设计模式(一)-单例模式中后面的内容可以看到,系统在启动的时候就注册了许多服务。其中就有这样的代码:
package android.app;
final class SystemServiceRegistry {
......
static {
......
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
}
}
这样就创建了一个WindowManager,而且也能看出来WindowManager的实现类是WindowManagerImpl。
同理,要获得一个WindowManager也很简单了:
WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
从头开始分析,先看AlertDialog的构造方法:
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = AlertController.create(getContext(), this, getWindow());
}
调用了父类Dialog的构造方法:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
......
//获取WindowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建一个PhonwWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
//设置回调
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
//设置WindowManager
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
Window通过调用他的w.setWindowManager(mWindowManager, null, null)将WindowManager和Window联系起来。:
package android.view;
public abstract class Window {
......
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
......
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
}
最后一句代码很重要mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);,调用了WindowManagerImpl的createLocalWindowManager方法,这个方法里就一行代码
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
这里重新创建了一个WindowManagerImpl,但是参数比系统注册的时候多了一个构造参数parentWindow。这说明这个WindowManagerImpl是跟一个具体的Window绑定起来的。
package android.view;
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
......
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
......
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
......
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
......
@Override
public Display getDefaultDisplay() {
return mContext.getDisplay();
}
}
从中 很明显的可以看出真正实现一些关键方法如addview 、remoteView等方法的也不是WindowManagerImpl,而是在内部交给了WindowManagerGlobal,由WindowManagerGlobal来真正实现。
然后看一下真正的addView吧:
package android.view;
public final class WindowManagerGlobal {
private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList mParams =
new ArrayList();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
......
//创建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//添加到列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// 最后执行这个,将View显示在手机屏幕上
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
......
}
}
}
上面的方法中先是new了一个ViewRootImpl,然后调用他的setView来显示布局。
Android中的View都是通过ViewRootImpl来完成绘制的。
书中说这个类继承Handler ,可能是以前是这样吧,我看的源码不是这样的
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
public ViewRootImpl(Context context, Display display) {
mContext = context;
//获取IWindowSession,与WindowManagerService建立连接
mWindowSession = WindowManagerGlobal.getWindowSession();
......
//这里保存当前线程
mThread = Thread.currentThread();
......
}
}
继续追踪WindowManagerGlobal.getWindowSession():
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
//获取WindowManagerService
IWindowManager windowManager = getWindowManagerService();
//与系统的WindowManagerService建立一个IWindowSession
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
//这里返回的是IBinder对象,进行IPC通信
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
......
}
return sWindowManagerService;
}
}
继续看 sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService(“window”));这行:
package android.os;
public final class ServiceManager {
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
}
所以IWindowManager.Stub.asInterface(
ServiceManager.getService(“window”));得到的是一个IBinder对象。
到这里,在ServiceManager的getService方法中通过getIServiceManager().getService(“window”)获取到一个IBinder,与WMS建立初步连接。
然后通过IWindowManager.Stub.asInterface方法将IBinder转换成IWindowManager对象。
通过这个对象调用openSession打开一个Session,实现通话。
但是,WMS只负责管理View的z-order,也就是管理当前那个View在最上层显示,并不管理绘制。
既然addView就肯定要把view显示在屏幕上,那么绘制View的任务就在ViewRootImpl的setView方法中。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
//请求绘制View
requestLayout();
......
int res; /* = WindowManagerImpl.ADD_OKAY; */
try {
......
//请求WindowManagerService,让WMS实现Window的添加。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
......
}
}
setView主要做了两件事:
(1)requestLayout();
(2)向WMS请求添加Window;
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
这里我另外注意一下checkThread()这个方法。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
是不是看到了熟悉的异常提示?这个方法要判断当前更新UI 的线程是不是创建ViewRootImpl时的线程,只有在创建ViewRootImpl的线程中更新对应的UI才不会报错。所以不能在子线程中更新UI也是这个原因。
回到requestLayout(),查看scheduleTraversals();
void scheduleTraversals() {
if (!mTraversalScheduled) {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
这里的mTraversalRunnable是个Runnable对象,mChoreographer.postCallback最终会通过一个handler把这个任务发送出去:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
看doTraversal();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
performTraversals();
......
}
}
看performTraversals();
这个方法里将近一千行代码,大致是四个过程:
private void performTraversals(){
// 1 获取Surface对象,用于图形绘制
//2 测量整个视图树中各个View的大小,用performMeasure方法
//3 布局整个视图树,用performLayout方法
//4 绘制整个视图树,用performDraw方法
}
在第四步中,performDraw方法里会调用ViewRootImpl的draw()方法。
draw()中获取到绘制表面Surface,里面最后调用ViewRootImpl的drawSoftware方法,调用GPU绘图。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
//获取canvas
canvas = mSurface.lockCanvas(dirty);
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
try {
......
//从这里开始绘制整个视图树,从DecorView开始
mView.draw(canvas);
} finally {
......
}
} finally {
try {
//解锁canvas,并通知SurfaceFlinger更新这块区域。
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
......
}
}
return true;
}
综上所述,视图树的绘制主要有以下步骤:
(1)判断使用CPU还是GPU绘制
(2)获取绘制表面Surface对象
(3)通过Surface对象获取并锁住绘图对象
(4)从DecorView开始绘制整个视图树
(5)解锁Canvas,并通知SurfaceFlinger更新这块区域。
在创建AlertDialog时,如果传入的Context是ApplicationContext而不是Activity的Context,那么抛出一个异常:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:583)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:313)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:86)
看出出错的地方是ViewRootImpl中的setView方法中。在这个方法中,调用mWindowSession.addToDisplay发送添加View的请求后会返回一个数值。
然后对这个数值进行判断
int res;
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- another window of type "
+ mWindowAttributes.type + " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
通过异常提示可以看出,异常的原因是应用没有token造成的。而token一般只有activity才有,所以要传入activity的context才行。
有一个特殊的对话框不需要传入activity,那就是系统对话框。不传入activity的context也能正常弹出系统对话框。
使用过程:
- 在AndroidManifest.xml中加上权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
系统的type有很多都可以用,TYPE_SYSTEM_ERROR,TYPE_SYSTEM_OVERLAY都可以。