Window的创建过程
有View的地方必须要有Window才能显示,比如Activity、Dialog、Toast。
Activity的Window创建过程
Activity启动过程是通过ActivityThread中的performLaunchActivity()完成,内部会通过类加载器创建Activity实例对象,并调用
attch()。
- 通过查看attch()源码发现,api level为23以下的是通过PolicyManager的makeNewWindow方法创建:
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
而PolicyManager实现的几个工厂方法都在IPolicy中,PolicyManager的真正实现类是Policy。
public interface IPolicy {
public Window makeNewWindow(Context context);
public LayoutInflater makeNewLayoutInflater(Context context);
public WindowManagerPolicy makeNewWindowManager();
public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
public class Policy implements IPolicy {
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
public LayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}
public WindowManagerPolicy makeNewWindowManager() {
return new PhoneWindowManager();
}
public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
return new PhoneFallbackEventHandler(context);
}
}
- 通过查看attch()源码发现,api level为23以上的是直接创建PhoneWindow对象:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
通过观察发现在api level为23以上,IPolicy接口已经不存在。但可以说明一点的是Window的具体实现就是PhoneWindow。
- 这个时候Window已经创建出来了,接下来看一下Activity的视图是怎么附属在Window上。我们看Activity的setContentView方法。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到实际上是交给了PhoneWindow处理。在PhoneWindow中,setContentView()大致做了如下步骤:
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) {
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);//第二步
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();//第三步
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
- 如果没有DecorView,就创建它。
installDecor()内部调用:
generateDecor()创建DecorView。
generateLayout()加载布局。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
protected ViewGroup generateLayout(DecorView decor) {
...
}
- 将View添加到DecorView的mContentParent中
创建完DecorView后直接将Activity的视图添加到mContentParent中即可。
mLayoutInflater.inflate(layoutResID, mContentParent);
- 回调Activity的onContentChanged方法通知Activity视图已经发生改变
由于Activity实现了Callback接口,那么回调onContentChanged即可。在Activity中这个方法是空实现,我们可以自己处理这个回调。
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
- 完成以上三个步骤,DecorView已经创建并将Activity的布局加到DecorView的mContentParent中了,但是这个时候还没被WindowManager正式添加到Window中,还不能显示出来。
最后要通过ActivityThread的handleResumeActivity方法中在调用onResume方法后的makeVisible方法,才会真正添加和显示。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
Dialog的Window创建过程
- 创建Window
这一步与Activity类似,api23为分水岭,一个使用PolicyManager,一个直接new。 - 初始化DecorView并将Dialog的视图添加到DecorView中
这一步也与Activity类似,都是通过Window的setContentView方法添加指定的布局文件。
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
- 将DecorView添加到Window中并显示
在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中。
public void show() {
...
onStart();
mDecor = mWindow.getDecorView();
...
try {
mWindowManager.addView(mDecor, l);
...
} finally {
}
}
当Dialog关闭时,则会通过WindowManager来移除DecorView。
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
void dismissDialog() {
...
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
...
}
}
要注意的是,应用级视图的Context要使用Activity的。如果要使用Application的Context,那必须要指定Window的type为系统级。
Toast的Window创建过程
- Toast属于系统的Window,内部视图由两种方式指定:系统默认、通过setView方法指定一个自定义View。它们都对应Toast的一个View内部成员mNextView。
- Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。
- Toast提供了show和cancel用于显示和隐藏View,内部是一个IPC过程。
/**
* Show the view for the specified duration.
*/
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 {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
/**
* Close the view if it's showing, or don't show it if it isn't showing yet.
* You do not normally have to call this. Normally view will disappear on its own
* after the appropriate duration.
*/
public void cancel() {
mTN.cancel();
}
可以看到show方法中调用了NMS的enqueueToast方法,参数为包名、远程回调、时长。enqueueToast首先将Toast请求封装为ToastRecord对象并添加到一个名为mToastQueue的队列中。这个队列对于非系统应用来说最多同事存在50个。
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration){
...
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
...
}
当TosatRecord被添加到mToastQueue中后,NMS就会通过showNextToastLocked方法来显示当前Toast。
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
try {
record.callback.show(record.token);//这里显示当前Toast
scheduleTimeoutLocked(record);///延时隐藏Toast
return;
} catch (RemoteException e) {
...
}
}
}
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);
}
这里的record.callback实际上是Toast中的TN对象的远程Binder,这需要跨进程来完成。scheduleTimeoutLocked方法发送一个延时消息来隐藏Toast,只有2个延迟时间,LONG_DELAY为3.5秒,SHORT_DELAY为2秒。移除后,如果队列中还有其他Toast则继续显示其他Toast。
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
record.callback.hide();//隐藏Toast这里也是IPC过程
} catch (RemoteException e) {
...
}
...
if (mToastQueue.size() > 0) {
showNextToastLocked();//继续显示Toast
}
}
通过分析,知道Toast的显示和隐藏实际上是由TN来实现的,内部有show和hide两个方法,使用了Handler来切换到Toast的线程。
@Override
public void show(IBinder windowToken) {
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
@Override
public void hide() {
mHandler.obtainMessage(HIDE).sendToTarget();
}
而最终调用了handleShow和handleHide方法。
public void handleShow(IBinder windowToken) {
...
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
...
}
public void handleHide() {
...
mWM.removeViewImmediate(mView);
...
}
一张图解释Toast的添加和移除: