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隱藏和 顯示工作過程是類似的。這裏就不具體分析。