在第一讲中我们知道,系统按照Window的type分为:应用窗口、子窗口、系统窗口。我们分别以其中的情况来
在WMS系列第一讲中介绍PhoneWindow的时候,我们有介绍PhoneWindow的创建时机是在ActivityThread中调用attach()方法初始化Activity。
这个时候Activity中就创建了Window对象。接下来就需要在窗口中添加真正的显示元素View或者ViewGroup。熟悉Activity启动流程的话,在调用核心方法performLaunchActivity之后,最终调起Activity的onCreat()方法。
这个方法中我们会最最常用的就是调用setContentView()方法,并传入layoutid。(当然传参并不仅仅有这一种)
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()方法返回的是Window对象。Window类的唯一实现类是PhoneWindow。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
// 1
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 {
// 2
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 3
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
注释1:如果mContentParent为空(mContentParent:放置窗口内容的视图)。会调用installDecor()方法。这个方法为Winodw类安装一个窗口装饰。该方法源码就不附上了,他主要是做几件事情:
A.如果mDecor为空,则调用generateDecor()方法创建一个DecorView对象,并将它赋值给mDecor;
B.在generateLayout()方法中根据用户指定的参数选择不同的窗口装饰,然后通过调用findViewById(ID_ANDROID_CONTENT)获得contentParent对象,并将这个值赋值给mContentParent
注释2:当安装完窗口装饰mDecor后,就可以把用户界面layout.xml文件添加到窗口装饰中。就会调用LayoutInflater的inflate()方法来实现。(可以看到mContentParent对象是作为方法参数传入的)。
注释3:最后调用onContentChanged()回调方法,通知应用窗口内容发送了改变。
当Activity准备好了之后就会去通知AMS,AMS中的流程这里不展开了,经过层层调用最终到Activity的makeVisible()方法。
void makeVisible() {
if (!mWindowAdded) {
// 获得ViewManager对象
ViewManager wm = getWindowManager();
// 调用addView()方法
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
这里关注下addView()方法种的参数:
第一个参数mDecor是一个DecorView对象,也就是一个用户能看见的Activity全部界面内容;
第二个参数getAttributes()方法返回时WindowManager.LayoutParams类型的mWindowAttributes对象,这个对象在Window类初始化代码中赋值的,赋值方法又调用到WindowManager的LayoutParams()构造函数:
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
我们看到默认LayoutParams是MATCH_PARENT,type类型是TYPE_APPLICATION。
而ViewManager的addView()方法的具体实现直接参见WMS第二讲的内容即可。
Toast的创建,一般都是调用一句Toast.makeText().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对象result,初始化一个LayoutInflater对象,并调用inflate()方法赋值给一个View对象,将传入的参数text附在TextView上。然后将这个view赋值给Toast.mNextView变量,将传参duration赋值给Toast.mDuration变量。
这样一个Toast对象就创建成功,然后继续看show()方法:
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;
final int displayId = mContext.getDisplayId();
try {
service.enqueueToast(pkg, tn, mDuration, displayId);
} catch (RemoteException e) {
// Empty
}
}
这里是会调用到INotificationManager的enqueueToast()方法,这是一个IPC的方法。实现是在远端的NotificationManagerService中:
@VisibleForTesting
final IBinder mService = new INotificationManager.Stub() {
// Toasts
// ============================================================================
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration,
int displayId)
{
......
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// If it's already in the queue, we update it in place, we don't
// move it to the end of the queue.
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
// Limit the number of toasts that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
}
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, displayId);
record = new ToastRecord(callingPid, pkg, callback, duration, token,
displayId);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveIfNeededLocked(callingPid);
}
// If it's at index 0, it's the current toast. It doesn't matter if it's
// new or just been updated. Call back and tell it to show itself.
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
接下来就会执行到showNextToastLocked()方法:
@GuardedBy("mToastQueue")
void showNextToastLocked() {
// 取出mToastQueue中的第一个ToastRecord对象
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
// 1
record.callback.show(record.token);
// 2
scheduleDurationReachedLocked(record);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
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;
}
}
}
}
注释1:调用其内部的TN对象的show()方法
注释2:发送一个异步延迟消息,使得Toast显示一定时间后清除该Toast窗口,清除动作是在cancelToastLocked()中实现;
我们再来看Toast的子类TN中的show()方法:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
发送SHOW消息,最终调用其内部的handleShow()方法:
public void handleShow(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
// If a cancel/hide is pending - no need to show - at this point
// the window token is already invalid and no need to do any work.
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
// Since the notification manager service cancels the token right
// after it notifies us to cancel the toast there is an inherent
// race and we may attempt to add a window after the token has been
// invalidated. Let us hedge against that.
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
}
先创建一个WindowManager对象,然后对WindowManager.LayoutParams中的常用变量进行赋值更新。最终调用WM的addView()方法,完成窗口的添加。
Dialog对应的窗口类型的:TYPE_SYSTEM_DIALOG。属于系统窗口。我们先来看Dialog的构造函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5OZHtq14-1583226714351)(H:\突进面试\QQ截图20200302162338.png)]
最终调用到最后一个构造函数上去:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == Resources.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
// 1
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
// 获取WindowManager对象
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 2
final Window w = new PhoneWindow(mContext);
mWindow = w;
// 3
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
// 4
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
// 5
mListenersHandler = new ListenersHandler(this);
}
注释一:创建Dialog内部的Context对象,并将他赋值给mContext;
注释二:创建一个Window对象;
注释三:调用setCallback()方法,指定该Window的消息回调接口就是这个Dialog对象;
注释四:设置Window对象的内部WindowManager对象;
注释五:创建一个回调句柄mListenersHandler,用于在内部发送异步消息
Dialog的构造函数中创建了内部的window对象之后。如果要将Dialog显示出来就要调用其show()方法。来看源码中的实现:
public void show() {
// 1
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
// 2
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
// 3
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
// 4
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
// 5
mWindowManager.addView(mDecor, l);
if (restoreSoftInputMode) {
l.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
}
mShowing = true;
// 6
sendShowMessage();
}
注释1:如果窗口已经存在,则直接调用setVisibility()方法显示;
注释2:如果窗口不存在,则调用dispatchOnCreate()方法,回调Dialog的onCreate()方法
注释3:赋值mDecor;
注释4:设置添加Dialog窗口所使用的LayoutParams参数;
注释5:调用WindowManager的addView()方法添加窗口;
注释6:可以调用setOnShowListener()方法为Dialog添加一个回调接口;