Window叫做窗口,本质上就是承载view的载体
参考资料 Android艺术探索
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a Window.
这个抽象类唯一的实现类是phoneWindow,当你需要实例化window的时候。
*/
public abstract class Window {
但是phoneWindow是隐藏类,无法直接调用。
我们的应用层,都是通过WindowManager来对UI进行操作。
Windowmanager是一个接口,继承自 ViewManager
ViewManager的三个方法,addView,updateViewLayout,removeView并没有在WindowManager中调用,而是在WindowManagerService通过binder来实现
然而其底层实际上是通过WindowManagerService。
final Button floatingButton = new Button(this);
floatingButton.setText("button");
final WindowManager.LayoutParams layoutParams =
new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT);
// flag 设置 Window 属性
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
// type 设置 Window 类别(层级)
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
//相当于设置原点中心位置
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
//如果想要设置滚动效果的话 ,只能把原点设置为top和left
//设置top和right或者center都不行
layoutParams.x = 100;
//距离原点x轴的位置大小
layoutParams.y = 300;
//距离原点y轴的位置大小
final WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
floatingButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "onTouch: ");
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
int startX = 0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(startX - event.getX()) > ViewConfiguration.get(MainActivity.this).getScaledTouchSlop()) {
layoutParams.x = rawX;
layoutParams.y = rawY;
windowManager.updateViewLayout(floatingButton, layoutParams);
}
}
return true;
}
});
同样的 WindowManagerService也是个隐藏类
/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
从这里可以看出,WindowManagerService和WindowManager的交互是一个IPC的过程。所有的视图都是通过Window来呈现的。
2.Window 分类
2.1 系统window
Toast 和系统状态栏都是系统 Window。
但调用Toast,不需要权限,
系统 Window是需要声明权限才能创建的 Window
2.2 子window
子Window不能单独存在,它通常依附在父window上
需要依附在特定的父 Window 中,如Dialog
2.3 应用 Window
例如 Acitivity
3.Window的添加移除更新的过程
3.1 Window的添加
window是需要通过windowmanager来进行操作,而windowmanager的实现类则是windowmanagerimpl。而在windowmanagerimpl的addview的操作,则是在WindowManagerGlobal中进行操作。
目前也就是需要分析WindowManagerGlobal的addview源码操作。
//核心代码
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//view的数组中添加view
mViews.add(view);
//数组中添加viewrootimpl.
mRoots.add(root);
//windowmanagerLayoutparam的数组中添加params
mParams.add(wparams);
//则实际上是通过viewrootimpl来完成view的更新
root.setView(view, wparams, panelParentView);
ViewRootImpl的核心代码在于
// 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();==>scheduleTraversals();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//核心代码
res = mWindowSession.addToDisplay(mWindow, mSeq
, mWindowAttributes,getHostVisibility(),
mDisplay.getDisplayId(),mAttachInfo.mContentInsets,
mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);
这里就需要借助在线源码阅读
查看到addToDisplay方法的调用
则是在/frameworks/base/services/core/java/com/android/server/wm/路径下的Session类,通过binder的方式
这里 梳理一下整个流程
window通过windowmanager来操作。其实现类是
WindowmanagerImpl
而具体的addView则是在WindowManagerGlobal的addView中
WindowManagerGlobal的addView内部实际上就是通过ViewrootImpl调用SetView,而setView的核心就是两点
- requestLayout() 异步刷新请求
2.Session的addToDisplay()的底层实际上就是WMS与WM的binder交互
3.2 Window的移除过程
public void removeView(View view, boolean immediate) {
synchronized (mLock) {
// 找到lockedview的角标
int index = findViewLocked(view, true);
//mRoots是ViewRootImpl的数组,根据角标来获取view
View curView = mRoots.get(index).getView();
//核心代码:移除掉view
removeViewLocked(index, immediate);
}
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());
}
}
//核心代码,也就是说,移除工作还是通过viewrootImpl来完成
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
//已经被移除的view
mDyingViews.add(view);
}
}
}
//分析die方法
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
//核心移除代码
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
//分析ViewrootImpl中的doDie方法
void doDie() {
checkThread();
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) {
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);
}
则实际上还是调用了Session的移除,底层也是wms与wm的binder交互
3.3 Window的更新
这个就比较简单了
直接看WindowmanagerGlobal源码 updateViewLayout方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
//实际上就是从layoutparams集合中,移除掉原先的params,新增新的params。
mParams.remove(index);
mParams.add(index, wparams);
//然后通过viewrootImpl去setLayoutparams
//核心代码
root.setLayoutParams(wparams, false);
}
}
viewrootImpl中的setlayoutparams
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
//核心代码就这两句,也就是view的绘制过程。
if (newView) {
requestLayout();
}
scheduleTraversals();
}
除了这里之外,还会调用WindowSession来更新window的视图,内部还是通过wms的relayoutWindow()来实现,也是一个ipc的过程
4 Window的创建过程
1 | 有视图的地方就有window,无论是Activity,dialog, |
4.1 Activity的window创建过程
Activity的启动过程
直接从performLaunchActivity()
核心代码
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
//核心代码
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
进入到Activity的attach方法。
final void attach(省略参数) {
//phoneWindow是唯一的实例
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//设置各种回调
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mWindow.setContainer(mParent.getWindow());
}
mWindow.setColorMode(info.colorMode);
}
这里我们可以发现,在APP启动的时候,就已经创建了window,并且给window设置了一系列的参数,并将Activity和window相绑定。
但是这些都只是初始化逻辑,那么Activity的视图和window的视图又是如何相连接的
入口:Activity的setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
因为window的实现类是属于phoneWindow
那么这里就需要看PhoneWindow的setContentView方法
@Override
public void setContentView(int layoutResID) {
// 注意:可以在安装窗口的过程中设置FEATURE_CONTENT_TRANSITIONS
// 不要检查特性,当主题属性实例化发生之前
if (mContentParent == null) {
//没有DecorView的话,就初始化一个
installDecor();//核心代码
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//核心代码
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//代表content已经改变的callback回调,在Activity中是个空的实现
cb.onContentChanged();
}
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//初始化DecorView
mDecor = generateDecor(-1);
//DEcorView设置window
mDecor.setWindow(this);
if (mContentParent == null) {
//初始化layout
mContentParent = generateLayout(mDecor);
以上步骤则代表,window的初始化,window和Activity的绑定以及DecorView和Content视图的初始化。均在Activity的onCreate中完成
真正显示的代码,则是在ActivityThread的handleResumeActivity()中进行。
//首先要调用performResumeActivity(),调用Activity的onResume()
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
ViewRootImpl impl = decor.getViewRootImpl();
impl.notifyChildRebuilt();
//添加view
wm.addView(decor, l);
WindowManager.LayoutParams l = r.window.getAttributes();
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
//更新view
wm.updateViewLayout(decor, l);
}
}
if (r.activity.mVisibleFromClient) {
//显示
r.activity.makeVisible();
}
}
实际上是Activity调用makeVisible方法。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
整个流程 实际上分三个部分
1.数据的初始化和attach部分
ActivityThread的performLaunchActivity()==>内部的attach就是绑定Activity和window相互绑定
2.视图层部分
4.2 Dialog的window创建过程
入口直接从new dialog开始
dialog构造中的方法
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//直接在dialog的构造中new phoneWindow,并且设置各种回调
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
而setContentView的过程,实际上就是回到了phoneWindow的setContentView。这个过程和Activity的一致,这里就不描述了。
由此可以看到,有的手机dialog是有默认的title,而高版本的没有,这里Decorview是根据版本来进行设置的
当dialog关闭的时候,会走dismissDialog方法,会通过windowmanager来移除DecorView,同样是wms的IPC过程。并且将mDecor置空,并执行onStop()
void dismissDialog() {
mWindowManager.removeViewImmediate(mDecor);
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
应用层的dialog,必须要Activity的context,因为应用token只有Activity拥有。而如果需要系统级的dialog,则需要将window的type,设置系统级,并且给与对应浮窗权限。
4.3 Toast的window创建过程
Toast因为有定时取消的功能,所以内部加入了Handler。
我们一般使用toast的时候都是这样
Toast.makeText(this, "1", 1).show();
那么这里就从makeText作为入口进行分析。
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
//核心代码
Toast result = new Toast(context, looper);
//通过布局加载器,加载一个默认的布局。并且初始化数据。
LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
//Toast构造方法
public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
//核心代码,TN是一个Binder类。
mTN = new TN(context.getPackageName(), looper);
//初始化数据,加载自带的dimen值
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
//TN构造方法源码分析
TN(String packageName, @Nullable Looper looper) {
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
mPackageName = packageName;
//params的初始化代码,省略.......
if (looper == null) {
looper = Looper.myLooper();
}
//这里是最关键的处理代码
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
mNextView = null;
break;
}
case CANCEL: {
handleHide();
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break;
}
}
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.obtainMessage(HIDE).sendToTarget();
}
public void cancel() {
if (localLOGV) Log.v(TAG, "CANCEL: " + this);
mHandler.obtainMessage(CANCEL).sendToTarget();
}
而这个类自身就有sendMessage
我们再来看看Toast的show方法在做什么
public void show() {
//很显然Toast的show,是一个IPC的过程。
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
}
}
//NotificationManagerService的源码分析。
//这里是通过一个Toast的queue队列,来处理ToastRecord
//从队列里面获取或者回收到队列中。
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index;
//判断是否是系统toast
if (!isSystemToast) {
index = indexOfToastPackageLocked(pkg);
} else {
index = indexOfToastLocked(pkg, callback);
}
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
record.update(callback);
} else {
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
}
keepProcessAliveIfNeededLocked(callingPid);
if (index == 0) {
//当Toast添加完之后,就显示toast
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
@GuardedBy("mToastQueue")
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
try {
record.callback.show(record.token);
//核心代码
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
@GuardedBy("mToastQueue")
private void scheduleTimeoutLocked(ToastRecord r){
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
//这里就会开始做一个延迟加载。再去进行消息的传递。
mHandler.sendMessageDelayed(m, delay);
}
也就是说,完成一个toast,调用了两种ipc的过程
分别是toast.makeText()调用了Toast内部的TN过程
还有show方法里面的NotificationManagerService的IPC过程
4 最后的问题 一个应用中有多少个 Window?
我们可以从Activity的源码开始分析
Activity是属于应用级的window
因为window是个抽象类,实现类就是phoneWindow,所以这里就代表了 多少个Activity就有多少应用级的window
toast是属于系统window
PopupWindow和Activity是共用一个window
dialog是属于应用window,说明多少个dialog对应多少个window
有多少dialogFragment也有多少window
很明显 dialogfragment是继承自Fragment的,Fragment的context是属于Activity,但是内部是有dialog的
1 | 很显然,我们不能简单的按照什么Activity,dialog dialogfragment来算。 |