1、Activity的Window创建过程
要分析Activity的Window创建过程就必须了解activity的启动过程,详细的过程在后面分析,大概了解即可。Activity的启动过程很复杂,最终会由ActivityThread中的perfromLaunchActivity()来完成整个启动过程,这个方法内部会通过类加载器创建Activity的实例对象,并且调用其attach方法为其关联运行过程中的所依赖的一系列上下文的变量。代码如下所示:
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
以上在android9.0上并没有找到相关的PolicyManager相关的代码。(书中可能是之前的版本),我在Android4.0上搜索到了相关的代码。
到这里Window已经创建完成了(不同版本代码最终都是创建了PhoneWindow对象),下面分析Activity的视图是怎么附属在Window上面的。由于Activity的视图是由setContentView方法提供的,我们只需要看setContentView的实现就可以:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
看下PhoneWindow中的setContentView方法的代码:
a.如果没有DecorView就去创建它
b.将View添加到DecorView的mContentParent中
c.回调Activity的onCreateChanged方法来通知Activity视图已经发生改变
这个过程就更简单了,由于Activity实现了Window的Callback接口,这里表示Activity的布局文件已经被添加到DecorView的mContentParent中了,于是需要通知Activity,使其可以做相应的处理。Activity的onCreateChanged是个空实现,我们可以在子Activity处理这个回调,这个过程代码如下:
final callback cb = getCallback();
if(cb != null && !isDestroyed()){
cb.onContentChanged();
}
可以看到真正的DecorView的添加时在Activity的makeViewVisible中的。
2、Dialog的Window创建过程
Dialog的Window创建过程和Activity类似,有如下几个步骤:
a.创建Window
在android9.0上的代码和书上的不一样。以我们的为准。代码片段如下:
b.初始化DecorView并将Dialog的视图添加到DecorView中
这个过程也和Activity的类似,都是通过Window去添加指定的布局文件。
c.将DecorView添加到Window并且显示
创建一个Dialog的代码:(MainActivity的onCreate方法中)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Dialog dialog = new Dialog(this);//使用activity的context是没问题的
TextView textView = new TextView(this);
textView.setText("this is a toast");
dialog.setContentView(textView);
dialog.show();
}
效果如下:
如果使用 Dialog dialog = new Dialog(getApplicationContext()); 就会报如下异常:
2020-04-04 23:18:06.428 22385-22385/com.example.testgesturedetector E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testgesturedetector, PID: 22385
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.testgesturedetector/com.example.testgesturedetector.MainActivity}: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3194)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3302)
at android.app.ActivityThread.-wrap12(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
at android.os.Handler.dispatchMessage(Handler.java:108)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7425)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:884)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:372)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:128)
at android.app.Dialog.show(Dialog.java:454)
at com.example.testgesturedetector.MainActivity.onCreate(MainActivity.java:24)
at android.app.Activity.performCreate(Activity.java:7372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3147)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3302)
at android.app.ActivityThread.-wrap12(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
at android.os.Handler.dispatchMessage(Handler.java:108)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7425)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
在android9.0上实际测试是能正常弹出来的。
3、Toast的Window创建过程
/**
* 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;
final int displayId = mContext.getDisplayId();
try {
service.enqueueToast(pkg, tn, mDuration, displayId);
} 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();
}
首先来看下Toast的显示过程,它调用了NMS中的enqueueToast方法:
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
@GuardedBy("mToastQueue")
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
record.callback.show(record.token);
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;
}
}
}
}
Toast隐藏和 显示工作过程是类似的。这里就不具体分析。