Dialog赐劣、Toast的Window和ViewRootImpl

前言

文章Activity中的Window的setContentView嫉拐、遇見LayoutInflater&FactoryViewRootImpl的獨(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)部成員變量mNextViewmDuration進(jìn)行初始化报腔。

Toast的展示

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)的 Toasthide 都是在 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 的線程不一樣役拴;糊探。

那么 ToastDialogView異步展現(xiàn)河闰,與異步操作UI是否一致呢科平?

首先測(cè)試一下異步展現(xiàn) DialogToast :

//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.TNDialog 構(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)行 ToastDialog 的展現(xiàn)儡炼。

所以 ToastDialog 的異步展現(xiàn)其實(shí)主要是與其線程的 Looper 隊(duì)列有關(guān)妓湘。 ToastDialog 展示的時(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的布局霜浴、事件處理等。

文章到這里就全部講述完啦蓝纲,若有其他需要交流的可以留言哦~坷随!~!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驻龟,一起剝皮案震驚了整個(gè)濱河市温眉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翁狐,老刑警劉巖类溢,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異露懒,居然都是意外死亡闯冷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門懈词,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蛇耀,“玉大人,你說(shuō)我怎么就攤上這事坎弯》牡樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵抠忘,是天一觀的道長(zhǎng)撩炊。 經(jīng)常有香客問我,道長(zhǎng)崎脉,這世上最難降的妖魔是什么拧咳? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮囚灼,結(jié)果婚禮上骆膝,老公的妹妹穿的比我還像新娘。我一直安慰自己灶体,他們只是感情好阅签,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赃春,像睡著了一般愉择。 火紅的嫁衣襯著肌膚如雪劫乱。 梳的紋絲不亂的頭發(fā)上织中,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天锥涕,我揣著相機(jī)與錄音,去河邊找鬼狭吼。 笑死层坠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刁笙。 我是一名探鬼主播破花,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼疲吸!你這毒婦竟也來(lái)了座每?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摘悴,失蹤者是張志新(化名)和其女友劉穎峭梳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹂喻,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葱椭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了口四。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孵运。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蔓彩,靈堂內(nèi)的尸體忽然破棺而出治笨,到底是詐尸還是另有隱情,我是刑警寧澤赤嚼,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布大磺,位于F島的核電站,受9級(jí)特大地震影響探膊,放射性物質(zhì)發(fā)生泄漏杠愧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一逞壁、第九天 我趴在偏房一處隱蔽的房頂上張望流济。 院中可真熱鬧,春花似錦腌闯、人聲如沸绳瘟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)糖声。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蘸泻,已是汗流浹背琉苇。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悦施,地道東北人并扇。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抡诞,于是被迫代替她去往敵國(guó)和親穷蛹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容