前言
文章Activity中的Window的setContentView嫉拐、遇見LayoutInflater&Factory、ViewRootImpl的獨(dú)白魁兼,我不是一個(gè)View(布局篇) 分別講述了Activity的setContentView添加View
婉徘、LayoutInflater布局解析
以及添加Window
。文章內(nèi)容都是站在Activity的角度來(lái)進(jìn)行代碼解析的咐汞,因此我們不再對(duì)Dialog和Toast與Activity做具體分析盖呼,主要來(lái)看看它們與Activity有什么不同之處源碼:android-22
。
Dialog
Dialog的構(gòu)造
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{
//只有Activity的Context可以啟動(dòng)Dialog化撕,因?yàn)镈ialog展示的時(shí)候需要主題資源也就是ContextThemeWrapper几晤。
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (theme == 0) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
outValue, true);
theme = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, theme);
} else {
mContext = context;
}
//因?yàn)槊總€(gè)上下文環(huán)境獲取的系統(tǒng)服務(wù)都是相同的實(shí)例,這里獲取的WindowManager是Activity的WindowManager侯谁。
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//創(chuàng)建Dialog的PhoneWindow對(duì)象锌仅。
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
//Handler中的Looper默認(rèn)為當(dāng)前線程的Looper
mListenersHandler = new ListenersHandler(this);
}
}
Dialog添加View
和Activity相同通過(guò)setContentView
初始化 Window
中的 DecorView
章钾,并對(duì)頁(yè)面 View
進(jìn)行add墙贱。詳細(xì)講述請(qǐng)移動(dòng)到Activity中的Window的setContentView
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{
/**
* Set the screen content from a layout resource. The resource will be
* inflated, adding all top-level views to the screen.
*
* @param layoutResID Resource ID to be inflated.
*/
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
}
Dialog的展現(xiàn)
Dialog
的展現(xiàn)和 Activity
不同是因?yàn)閮烧叩穆暶髦芷诓煌?code>Activity 的聲明周期是有 AMS
調(diào)用而 Dialog
是應(yīng)用程序自己調(diào)用的。ViewRootImpl
的初始化在 Activity
會(huì)在onResume()
方法之后贱傀,而是 Dialog
被調(diào)用show
方法時(shí)觸發(fā)的惨撇。
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener,Window.OnWindowDismissedCallback{
/**
* Start the dialog and display it on screen. The window is placed in the
* application layer and opaque. Note that you should not override this
* method to do initialization when the dialog is shown, instead implement
* that in {@link #onStart}.
*/
public void show() {
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;
//判斷是否調(diào)用onCreate方法
if (!mCreated) {
dispatchOnCreate(null);
}
//調(diào)用onStart方法
onStart();
//獲取DecorView對(duì)象實(shí)例
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);
}
//更新Window屬性參數(shù)
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
//Windowmanger添加Window、ViewRootImpl初始化并綁定Window
mWindowManager.addView(mDecor, l);
mShowing = true;
//OnShowListener監(jiān)聽回調(diào)
sendShowMessage();
} finally {
}
}
}
Toast
Toast的構(gòu)造
public class Toast {
final Context mContext;
final TN mTN;//
int mDuration;//展示時(shí)間
View mNextView;//所展示的View
/**
* Construct an empty Toast object. You must call {@link #setView} before you
* can call {@link #show}.
*
* @param context The context to use. Usually your {@link android.app.Application}
* or {@link android.app.Activity} object.
*/
//Context可以為Application也可以為Activity府寒,
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
//NotificationManagerService的客戶端IBinder對(duì)
private static INotificationManager sService;
private static class TN extends ITransientNotification.Stub {
/***部分代碼省略***/
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
//Handler中的Looper默認(rèn)為當(dāng)前線程的Looper
final Handler mHandler = new Handler();
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
//設(shè)置Window類型為Toast
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
}
}
transient_notification.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?android:attr/toastFrameBackground">
<TextView
android:id="@android:id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_horizontal"
android:textAppearance="@style/TextAppearance.Toast"
android:textColor="@color/bright_foreground_dark"
android:shadowColor="#BB000000"
android:shadowRadius="2.75"
/>
</LinearLayout>
Toast添加View
從Toast的調(diào)用我們開始分析Toast.makeText(MainActivity.this , "Hello World" , Toast.LENGTH_SHORT);
我們主要看makeText
方法魁衙。
public class Toast {
/**
* Make a standard toast that just contains a text view.
*
* @param context The context to use. Usually your {@link android.app.Application}
* or {@link android.app.Activity} object.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
*
*/
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);
//獲取布局解析器
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//解析transient_notification.xml生成對(duì)應(yīng)的View
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
//找到View中的id為message的TextView
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
//對(duì)Textview進(jìn)行文字賦值
tv.setText(text);
//展示的Toast所用的View
result.mNextView = v;
//設(shè)置間隔時(shí)間
result.mDuration = duration;
return result;
}
}
主要是對(duì)Toast內(nèi)部成員變量mNextView
和mDuration
進(jìn)行初始化报腔。
Toast的展示
將 Toast
內(nèi)部的 TN
( ITransientNotification
客戶端對(duì)象)加入到 INotificationManager
服務(wù)端的 Binder
兌現(xiàn)的 mToastQueue
隊(duì)列中。再由服務(wù)端循環(huán)遍歷 mToastQueue
隊(duì)列中ToastRecord
對(duì)象剖淀,處理一個(gè)移除一個(gè)纯蛾,每次處理的都是 List
的第一個(gè)ToastRecord
對(duì)象。
public class Toast {
//INotificationManager的客戶端的Binder對(duì)象
private static INotificationManager sService;
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
//獲取INotificationManager的客戶端的Binder對(duì)象
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
/**
* Show the view for the specified duration.
*/
public void show() {
//mNextView不能為空
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
//service初始哈
INotificationManager service = getService();
//獲取當(dāng)前Context對(duì)應(yīng)的包名
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
//將TN加入INotificationManager中的mToastQueue隊(duì)列
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
}
NotificationManagerService
在服務(wù)端處理ITransientNotification
客戶端傳過(guò)來(lái)的enqueueToast
事件纵隔。
public class NotificationManagerService extends SystemService {
//是否是系統(tǒng)調(diào)用
private static boolean isCallerSystem() {
return isUidSystem(Binder.getCallingUid());
}
private final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration){
if (DBG) {
Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
+ " duration=" + duration);
}
if (pkg == null || callback == null) {
Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
return ;
}
//判斷是否是系統(tǒng)調(diào)動(dòng)或者是Android系統(tǒng)應(yīng)用程序進(jìn)行調(diào)用
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
//Toast或者通知權(quán)限被禁用
if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
if (!isSystemToast) {
Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
return;
}
}
//mToastQueue加鎖
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
//尋找當(dāng)前callback在mToastQueue中的索引翻诉,沒找到則返回-1
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.
//index>=0表示mToastQueue中有該callback的索引,record進(jìn)行更新展示時(shí)間
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.
//不是系統(tǒng)的Toast
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
final ToastRecord r = mToastQueue.get(i);
//判斷當(dāng)前的Toast是不是同一個(gè)包發(fā)出的
if (r.pkg.equals(pkg)) {
count++;
//當(dāng)前包的需要展示的Toast緩存數(shù)量>=50
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " toasts. Not showing more. Package=" + pkg);
return;
}
}
}
}
//根據(jù)callback等信息構(gòu)造ToastRecord對(duì)象
record = new ToastRecord(callingPid, pkg, callback, duration);
//將新的ToastRecord對(duì)象加入到隊(duì)列總
mToastQueue.add(record);
//加入之后當(dāng)前的索引是lenth-1
index = mToastQueue.size() - 1;
//將當(dāng)前包對(duì)應(yīng)的線程切換為前臺(tái)線程
keepProcessAliveLocked(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.
//如果之前隊(duì)列中沒有正在處理的消息捌刮,那么處理當(dāng)前這個(gè)ToastRecord
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
}
}
NotificationManagerService
使用先進(jìn)先出(FIFO
)的方式處理 mToastQueue
隊(duì)列中的消息碰煌。
- 服務(wù)端的處理
public class NotificationManagerService extends SystemService {
void showNextToastLocked() {
//獲取隊(duì)列第一個(gè)ToastRecord
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
//調(diào)用客戶端Binder對(duì)應(yīng)的TN.show方法。
record.callback.show();
scheduleTimeoutLocked(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
//當(dāng)前Toast客戶端Binder方法調(diào)用拋出異常
//移除當(dāng)前ToastRecord
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
//切換當(dāng)前ToastRecord進(jìn)程
keepProcessAliveLocked(record.pid);
//遍歷對(duì)象變?yōu)榱斜硐乱粋€(gè)oastRecord對(duì)象
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
}
- 客戶端的處理
private static class TN extends ITransientNotification.Stub {
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);//利用Handler執(zhí)行mShow
}
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
//展示Toast
public void handleShow() {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
//判斷mNextView是否展示過(guò)
if (mView != mNextView) {
// remove the old view if necessary
//移除當(dāng)前展示的Toast
handleHide();
mView = mNextView;
//獲取當(dāng)前的應(yīng)用程序的上下文環(huán)境
Context context = mView.getContext().getApplicationContext();
//獲取當(dāng)前包名
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
//獲取上下文環(huán)境的WindowManagerImpl
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());
//設(shè)置參數(shù)的重力防線
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;
}
//設(shè)置參數(shù)的坐標(biāo)和偏移量
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
//如果mView添加過(guò)绅作,那么先把mView從WindowManager中移除芦圾。
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);
//把需要展示的View添加在WindowManager中
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
}
Toast的消失
系統(tǒng)的 Toast
的 hide
都是在 INotificationManager
的服務(wù)端 Binder
中發(fā)起的,但最終的執(zhí)行都是在 INotificationManager
的客戶端 Binder
中執(zhí)行的俄认。
- 服務(wù)端
public class NotificationManagerService extends SystemService {
private final class WorkerHandler extends Handler{
@Override
public void handleMessage(Message msg){
switch (msg.what){
case MESSAGE_TIMEOUT:
//調(diào)用當(dāng)前的Toast的hide
handleTimeout((ToastRecord)msg.obj);
break;
case MESSAGE_SAVE_POLICY_FILE:
handleSavePolicyFile();
break;
case MESSAGE_SEND_RANKING_UPDATE:
handleSendRankingUpdate();
break;
case MESSAGE_LISTENER_HINTS_CHANGED:
handleListenerHintsChanged(msg.arg1);
break;
case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:
handleListenerInterruptionFilterChanged(msg.arg1);
break;
}
}
}
//讓當(dāng)前Toast展示一段時(shí)間后消失
private void scheduleTimeoutLocked(ToastRecord r){
//移除mHandler關(guān)于這個(gè)TaostRecord的所有Message
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
//發(fā)送一個(gè)delayed=duration的MESSAGE_TIMEOUT事件
mHandler.sendMessageDelayed(m, delay);
}
//使Toast消失
private void handleTimeout(ToastRecord record){
if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
synchronized (mToastQueue) {
//找當(dāng)前ToastRecord在mToastQueue隊(duì)列中的索引
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
//調(diào)用當(dāng)前索引=index的ToastRecord.callback.hide
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
////調(diào)用客戶端Binder對(duì)應(yīng)的TN.hide方法个少。
record.callback.hide();
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ " in package " + record.pkg);
// don't worry about this, we're about to remove it from
// the list anyway
}
//移除處理完的ToastRecord
mToastQueue.remove(index);
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
// Show the next one. If the callback fails, this will remove
// it from the list, so don't assume that the list hasn't changed
// after this point.
//處理隊(duì)列中的下一個(gè)ToastRecord
showNextToastLocked();
}
}
}
- 客戶端
private static class TN extends ITransientNotification.Stub {
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);//利用Handler執(zhí)行mHide
}
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
// note: checking parent() just to make sure the view has
// been added... i have seen cases where we get here when
// the view isn't yet added, so let's try not to crash.
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
//調(diào)用WindowManager的removeView移除mView
mWM.removeView(mView);
}
mView = null;
}
}
}
Dialog和Toast在異步線程的展現(xiàn)
ViewRootImpl的獨(dú)白,我不是一個(gè)View(布局篇) 這篇文章說(shuō)明了為什么我們一般禁止在非 UI線程
中刷新 View
眯杏,以及怎么安全的在異步線程操作UI稍算。
發(fā)生了對(duì)任務(wù)執(zhí)行線程的校驗(yàn),而且當(dāng)前執(zhí)行任務(wù)的線程與創(chuàng)建
ViewRootImpl
的線程不一樣役拴;糊探。
那么 Toast
、 Dialog
和 View
的異步展現(xiàn)河闰,與異步操作UI是否一致呢科平?
首先測(cè)試一下異步展現(xiàn) Dialog
和 Toast
:
//Toast展現(xiàn)
new Thread(new Runnable() {
@Override
public void run() {
//Looper.prepare();
Toast.makeText(TestActivity.this, "test", Toast.LENGTH_SHORT).show();
//Looper.loop();
}
}).start();
//Dialog的展現(xiàn)
new Thread(new Runnable() {
@Override
public void run() {
//Looper.prepare();
new MyDialog(TestActivity.this, "test").show();
//Looper.loop();
}
}).start();
崩潰日志:
//Toast崩潰日志
17:30:04.211#[androidcode@]#30824#E#AndroidRuntime #FATAL EXCEPTION: Thread-2
Process: com.tzx.androidcode, PID: 30513
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
at android.widget.Toast$TN.<init>(Toast.java:394)
at android.widget.Toast.<init>(Toast.java:114)
at android.widget.Toast.makeText(Toast.java:277)
at android.widget.Toast.makeText(Toast.java:267)
at com.tzx.androidcode.activity.TestActivity$1.run(TestActivity.java:72)
at java.lang.Thread.run(Thread.java:764)
//Dialog的崩潰日志
17:33:07.961#[androidcode@]#31514#E#AndroidRuntime #FATAL EXCEPTION: Thread-2
Process: com.tzx.androidcode, PID: 31438
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:203)
at android.os.Handler.<init>(Handler.java:117)
at android.app.Dialog.<init>(Dialog.java:123)
at android.app.Dialog.<init>(Dialog.java:149)
at com.tzx.rollaction.test.BaseDailog.<init>(BaseDailog.java:23)
at com.tzx.rollaction.test.MyDialog.<init>(MyDialog.java:20)
at com.tzx.androidcode.activity.TestActivity$2.run(TestActivity.java:86)
at java.lang.Thread.run(Thread.java:764)
我們可以看到都是提示 當(dāng)前的Handler的Looper沒有調(diào)用prepare
。
我們?cè)谏厦孢M(jìn)行源碼閱讀的時(shí)候都看到了 Toast.TN
和 Dialog
構(gòu)造的時(shí)候的 Handler
都是默認(rèn)當(dāng)前線程的 Looper
姜性。
如果當(dāng)前線程的 Looper
沒有 prepare
那么必定會(huì)拋異常瞪慧,如果僅僅執(zhí)行了 prepare
那么崩潰不會(huì)產(chǎn)生了,但是依舊不展示部念。因?yàn)檎麄€(gè) Looper
還沒有開始弃酌,里面的 Message
都未進(jìn)行處理。最后我們將代碼中注釋的 Looper.prepare();
和 Looper.loop();
打開就可以正常在異步線程進(jìn)行 Toast
和 Dialog
的展現(xiàn)儡炼。
所以 Toast
和 Dialog
的異步展現(xiàn)其實(shí)主要是與其線程的 Looper
隊(duì)列有關(guān)妓湘。 Toast
和 Dialog
展示的時(shí)候進(jìn)行的 ViewRootImpl
的創(chuàng)建,這個(gè)執(zhí)行UI操作的也是這個(gè)線程乌询,所以展現(xiàn)不會(huì)發(fā)現(xiàn)異常榜贴。如果對(duì) Dialog
進(jìn)行異步刷新UI ,那么他的限制和 View
的異步刷新是相同的妹田。
總結(jié)
通過(guò)分析Activity
唬党、Dialog
鹃共、Toast
通過(guò)對(duì) ViewRootImpl
的更細(xì)節(jié)的分析,所有添加在窗口上的 View
都有一個(gè) ViewRootImpl
作為它的 Parent
驶拱,處理View的布局霜浴、事件處理等。
文章到這里就全部講述完啦蓝纲,若有其他需要交流的可以留言哦~坷随!~!