今天,在做项目的过程中,实现了一个浮动窗口的功能,大致思路是这样的:通过实例化一个ImageButton并给这个Button设置监听,然后将这个Button传递给WindowManager的addView方法,在ListView滑动过程中,通过监听ListView的滑动状态,利用WindowManager的updateViewLayout方法,控制浮动窗口的显示和隐藏。功能实现很简单,但是,一直有一个疑问:android是如何控制窗口的呢?这就得看看Android Window的内部机制
在Android里Window是一个抽象的概念,它是指屏幕上的一个显示区域。系统是通过系统服务WindowManagerService来统一对Window实施管理的。在FrameWork层,提供了WindowManager类来使用WindowManagerService提供的服务,对Window实施操作。在WindowManagerService里,对应一个WindState,这个WindowState就是Window的具体存在。
对于开发者来说,可以看到的window的存在是Android 提供的Window类,这个类是一个抽象类,代表一个用户可以见的window。这个类的唯一实现类是PhoneWindow,该类里封装了一个DecorView对象,这就是用户可见的View。在Activity的时候,会给其实例化一个Window,最后通过WindowManger将DecorView加到WindowManagerService中。
WindowManager是开发者和系统维护的window的入口,WindowManager和WindowManagerService 的交互是一个IPC的过程。
下面就从源码的角度分析一下Android Window的内部实现机制。
Window的内部机制
Window是一个抽象概念,每一个Window都对应一个View和ViewRootImpl,Window和View之间通过ViewRootImpl之间建立联系,Window并不可见,它是以View为存在形式的。
Window由WindowManagerService统一维护,外界需要操作Window必须通过WindowManager提供的接口实现,它提供了Window的添加、更新和删除的操作。下面结合源码,分析一下Window的添加 更新和删除的原理。
Window的添加
Wind的添加时通过WindowManager的addView来完成的。
public interface WindowManager extends ViewManager
它是一个继承自ViewManager的接口
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager的实现类是WindowManagerImpl
public final class WindowManagerImpl implements WindowManager
它对WindowManager三大方法的实现如下:
@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);
}
mGlobal的定义是这样的
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
可以看出,WindowManagerImpl通过桥接模式,利用WindowManagerGlobal 来实现了WindowManager的功能。
WindowManagerGlobal是用来管理Window的全局类,它里面维护了几个全局的列表。
//存储所有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,即Window
private final ArraySet<View> mDyingViews = new ArraySet<View>();
下面分析一下添加WindowManagerGlobal Window的过程
检查参数是否合法,如果是子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) {//如果是子View还得调整参数
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
创建ViewRootImpl,并将其和View添加到列表中
//创建ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//将view添加到list中去 mViews.add(view); mRoots.add(root); mParams.add(wparams);
使用ViewRootImpl更新界面完成window的添加过程
完成以上任务后,由ViewRootImpl通过setView()方法来实现Window的添加。
//由ViewRootImpl实现View的绘制
root.setView(view, wparams, panelParentView);
在setView()内部,会调用requestLayout(),这个方法是一个异步方法,来完成View的绘制工作。这个方法如下:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否是主线程
checkThread();
mLayoutRequested = true;
//绘制View的入口
scheduleTraversals();
}
}
在这个方法里主要执行两个方法,一个是检查当前线程是不是View的创建的线程,另外一个是绘制View。
在执行完requestLayout()后,完成Window的添加过程。
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//通过WindowSession完成Window的添加
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();
}
}
添加的过程是通过mWindowSession.addToDisplay()完成的。mWindowSession的定义如下:
final IWindowSession mWindowSession;
它是一个IWindowSession类型,在Android源码里,被定义为一个AIDL类型,Session 对其功能进行了实现:
final class Session extends IWindowSession.Stub
mWindowSession是在ViewRootImpl实例化的:
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
再看看WindowManagerGlobal.getWindowSession();
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
//获得与WindowMangerService通讯的IBinder接口
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
在看看getWindowManagerService()
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
它获得的其实是与WindowMangerService通讯的IBinder接口,看看WindowMangerService的实现就知道了:
public class WindowManagerService extends IWindowManager.Stub
WindowMangerService就是IWindowManager的实现类,同时它实现了openSession()方法:
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, callback, client, inputContext);
return session;
}
再看看Session的addToDisplay()
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
其实,添加Window的过程就是通过Session和WindowManagerService通讯的过程。由此,添加Window就交给WindowManagerService完成。
Window的删除
Window的删除和添加一样,最终是通过WindowManagerGlobal.removeView()实现的,它的思路很简单:
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
//查找要删除哪个View
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
//删除View
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
删除是通过 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);
}
}
}
下面看看 root.die(immediate)方法:
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;
}
它主要是通过调用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();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
主要是完成一些清理工作。
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.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);
}
}
不难发现,最终也是通过ViewRootImpl来实现的,setLayoutParams()方法最终会调用
scheduleTraversals();
而这个方法,最终会导致View的重新测量、布局和重绘。
到此windw的添加、更新和删除的内部实现机制就分析完毕,不难发现,ViewRootImpl是控制View的测量、布局和重绘的。