三、Activity的Window创建过程
1. Window的创建
Activty的启动最终会在ActivtyThread的performLaunchActivty()中完成。
ActivtiyThread # performLaunchActivity()
在performLaunchActivty()中:
- 反射创建了一个Activty实例。
- 调用它的attach()方法关联运行过程中依赖的上下文变量。
- 对它进行初始化。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// .....
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
// 获取类加载器
java.lang.ClassLoader cl = appContext.getClassLoader();
// 反射获取一个Activty实例
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// ......
try {
// ......
if (activity != null) {
// ......
// 调用attach()给activty关联变量
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);
// ......
return activity;
}
能new的为什么要用反射?
反射机制增加程序的灵活性,避免将程序写死到代码里,实例化一个对象,不使用反射,如果想变成实例化其他类,那么必须修改源代码,并重新编译。使用反射,可以将类描述可以写到配置文件中,这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。
Activity # attach()
在这个方法中,会创建Activty视图内容所需的Window。
- 创建Window对象。
- 设置Window有关监听。
- 初始化Window。
- 赋值Activty的各种参数。
final void attach(/*一系列的参数,太长了......*/) {
// ......
// 创建Window对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 传入Window有关的监听,Activty实现的
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
// ......初始化Window中的部分内容
// ......赋值各种参数
// 设置WMS
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
// 看是否有父Window依赖
mWindow.setContainer(mParent.getWindow());
}
// 赋值参数
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
2. 初始视图
从setContentView()开始,这是为Activty设置视图的起点。
Activity # setContentView()
直接调用了Window的setContentView()。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow # setContentView()
在这里会调用installDecor()去创建DecorView,在完成后,mContentParent加载传入的Activity布局。
- 创造初始DecorView。
- mContentParent加载参数布局。
- 调用监听接口。
@Override
public void setContentView(int layoutResID) {
// 初始化DecorView
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 如果已经创建过DecorView结构,已经有了content,就移除
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// ......
} else {
// mContentParent中加载传入的布局资源
mLayoutInflater.inflate(layoutResID, mContentParent);
}
// ......
if (cb != null && !isDestroyed()) {
// 回调通知Activity视图已经改变
cb.onContentChanged();
}
// ......
}
PhoneWindow # installDecor()
在这个方法里,先创建了DecorView,再创建了DecorView的子结构mContentParent,这个就是ViewGroup就是用来装setContentView()布局的。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 如果还没有DecorView,调用generateDecor()创建
mDecor = generateDecor(-1);
// ......
} else {
// 如果有DecorView对象了,就关联此Window
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 赋值mContentParent
mContentParent = generateLayout(mDecor);
// 初始化布局......
}
}
PhoneWindow # generateDecor()
在这个方法中
- 创建了DecorView对象,传入了该Window对象。
- 在DecorView的构造方法中将DecorView和Window绑定了。
protected DecorView generateDecor(int featureId) {
// ......创建context参数
return new DecorView(context, featureId, this, getAttributes());
}
PhoneWindow # generateLayout()
- DecorView加载布局。
- contentParent的findView后返回给mContentParent。在前面讲到的PhoneWindow的setContentView()中加载布局。
protected ViewGroup generateLayout(DecorView decor) {
// 根据Window的R.styleable等参数设置Window的flag、params、layoutResource......
mDecor.startChanging();
// DecorView加载布局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ID_ANDROID_CONTENT = com.android.internal.R.id.content
// setContentView()布局加载的ViewGroup
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ......
mDecor.finishChanging();
return contentParent;
}
3. Window的添加
到这时,Window创建完毕,试图也初始化完毕了,但是DecorView还没有正式的被WindowManagerService添加到Window中。
ActivityThread # handleResumeActivity()
在handleResumeActivty()中,会调用Activity的makeVisible()。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// ......
r.activity.makeVisible();
// ......
}
Activity # makeVisible()
- 获取WMS对象,这个对象在attach()时就已经初始化了。
- 调用addView()添加Window。
- 设置DecorView可见。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
四、Dialog的Window创建过程
Dialog的Window的创建过程和Activity非常相似。
1. Window的创建
Dialog # Dialog()
在构造方法中获取了WMS,创建了Window,并初始化。
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
// .....
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
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);
mListenersHandler = new ListenersHandler(this);
}
创建Dialog对象时传入的context不能是Application的context,一般的Dialog需要应用token,应用token是Activity才有的,所以需要传入Activity的context,否则就会报错。
但是如果Dialog的Window设置成系统级别则不需要应用token。
dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR);
2. 初始视图
Dialog # setContentView()
同样是调用了PhoneWindow的setContentView去加载布局。
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
PhoneWindow # setContentView()
这个方法在Activity已经分析过了,和之前的一样,创建并初始化DecorView,mContentParent加载参数的布局。
3. Window的添加
Dialog # show()
在Dialog的show()中添加Window,调用WindowManager的addView()。
public void show() {
// ......
// 监听通知
onStart();
mDecor = mWindow.getDecorView();
// ......
WindowManager.LayoutParams l = mWindow.getAttributes();
// ......
// 添加Window
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
}
4. Window的移除
调用Dialog的dismiss()时,会移除Window。
Dialog # dismiss()
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
Dialog # dismissDialog()
在这里会调用WindowManager的removeViewImmediate()同步移除这个Dialog的Window。
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
// 移除Window
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
// 置空回收
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
五、Toast的Window创建过程
- Toast属于系统Window。
- 内部会有两类IPC过程(除了和WMS的)
- Toast访问NotificationManagerService的过程。
- NotificationManagerService回调Toast里的TN接口。
- 因为Toast的定时取消功能使用了Handler,所以Toast不能在没有Looper的线程中弹出。
从最熟悉show()开始。
1. 调用NMS
Toast # show()
IPC调用NMS,传入了回调TN和Toast时长。
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 {
// 远程调用NMS
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
在NotificationManagerService中,mService是INotificationManager.Stub的匿名对象,实现了Binder接口定义的方法,包括show()中调用的enqueueToast()。
2. NMS处理Toast
NotificationManagerService的mService # enqueueToast()
- 查看是否已经存在可以复用的Toast了。
- 存在就更新Toast内容和存在时间。
- 不存在
- 创建一个Binder对象,调用WMS添加。
- 重新封装一个ToastRecord,放入mToastQueue。
- 调用showNextToastLocked()显示mToastQueue中的第一个Toast。
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration) {
// ......
synchronized (mToastQueue) {
// ......
try {
// Toast的封装对象
ToastRecord record;
int index;
if (!isSystemToast) {
// 如果不是系统应用的Toast,就遍历ToastRecord集合。
// 看是否存在来自于同一个包的Toast。如果存在就返回对应下标,没有就返回-1。
index = indexOfToastPackageLocked(pkg);
} else {
// 如果是系统Toast,除了对比包名还要对比是不是同一个callback,返回下标。
index = indexOfToastLocked(pkg, callback);
}
if (index >= 0) {
// 如果找到了,就直接跟新内容和时间。
record = mToastQueue.get(index);
record.update(duration);
record.update(callback);
} else {
// 创建一个token,显式加入WMS,显式加入就需要显式移除,在后面会看到。
Binder token = new Binder();
mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
// 如果不存在,就新创一个ToastRecord,并加入集合。
record = new ToastRecord(callingPid, pkg, callback, duration, token);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
}
keepProcessAliveIfNeededLocked(callingPid);
// 如果添加前还没有ToastRecord,就开始显示
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
3. 回调TN,显示Toast
NotificationManagerService # showNextToastLocked()
- TN的show()被远程调用。
- 发送一个Toast时长的延时消息。
- 处理显示失败的情况,继续尝试显示队列中的下一个Toast。
void showNextToastLocked() {
// 第一个Toast
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
// 调用TN接口的show(),在应用的Binder线程中执行
record.callback.show(record.token);
// 发送一个延时消息,取决于Toast显示时长
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
// ......
// 处理IPC调用失败,显示失败的情况
int index = mToastQueue.indexOf(record);
if (index >= 0) {
// 如果显示失败的Record还在队列中就移除
mToastQueue.remove(index);
}
keepProcessAliveIfNeededLocked(record.pid);
// 如果队列中还有Record,就继续处理下一个
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
Toast$TN # show()
这个方法是通过NMS以跨进程的方式调用的,运行在应用的Binder线程。因为需要切换线程,所以使用了handler。
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
调用了handleShow方法,参数token是在NotificationManagerService()中构建的和WMS通信的Binder对象。
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
// ......
}
}
Toast$TN # handleShow()
- 判断是否有提交了取消的消息,有就直接返回。
- 移除现在显示的。
- 初始化WindowManager。
- 如果此WM之前有添加过,就先移除之前添加的。
- 调用WM的addView()添加。
mNextView是在makeText()中加载的布局。如果是自定义布局的Toast,就在setView()中赋值的。
public void handleShow(IBinder windowToken) {
// 如果已经有取消的消息了,就不要去处理显示了
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
// 如果现在显示的不是将要显示的
if (mView != mNextView) {
// 移除现在显示的
handleHide();
// 赋值
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
// 获取WindowManager
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// 处理Window的参数
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
// ......
mParams.token = windowToken;
// 如果之前存在过,就先把之前的移除
if (mView.getParent() != null) {
mWM.removeView(mView);
}
try {
// 添加
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}
}
4. 处理后事
NotificationManagerService # scheduleTimeoutLocked()
再来看执行完显示工作后的发送延时消息的方法。先是移除了存在的record的消息,然后延时发送了MESSAGE_TIMEOUT消息。
private void scheduleTimeoutLocked(ToastRecord r) {
// 移除handler中有关此record的所有消息
mHandler.removeCallbacksAndMessages(r);
// 获取一个MESSAGE_TIMEOUT消息
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
// 延时时间为Toast显示时长
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
// 延时发送
mHandler.sendMessageDelayed(m, delay);
}
MESSAGE_TIMEOUT是什么,调用了handleTimeout()。
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
// ......
}
}
NotificationficationManagerService # handleTimeout()
执行了cancelToastLocked(),看起来是去删除Toast的操作。
private void handleTimeout(ToastRecord record) {
if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
Notification # cancelToastLocked()
- 远程调用TN的hide()。
- 从队列中删除此ToastRecord。
- 调用WMS移除token。
- 如果队列中还有Toast,调用showNextToastLocked()显示下一个Toast。
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
// 远程调用TN的hide()
record.callback.hide();
} catch (RemoteException e) {
// ......
}
// 将这个record从Toast队列中移除
ToastRecord lastToast = mToastQueue.remove(index);
// 调用WMS显式移除Window的token,这个token是在enqueueToast()中添加的
mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY);
keepProcessAliveIfNeededLocked(record.pid);
// 如果Toast队列中还有Record,就调用继续执行下一个
if (mToastQueue.size() > 0) {
showNextToastLocked();
}
}
Toast$TN # hide()
和TN的show()一样,也使用handler发送了一个消息,调用了handleHide()。
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.obtainMessage(HIDE).sendToTarget();
}
Toast # handleHide()
调用WM的removeViewImmediate()。
public void handleHide() {
if (mView != null) {
// 如果这个当前Toast的view确实被添加了
if (mView.getParent() != null) {
// 调用WindowManager的removeViewImmediate()同步移除Window
mWM.removeViewImmediate(mView);
}
mView = null;
}
}