天天看点

Toast执行流程分析与复用意图分析的问题源码执行流程分析Toast的复用

意图分析的问题

  1. Toast的显示是异步的还是同步。
  2. Toast是否可以在子线程中实例化并调用

    Toast.show

    方法。
  3. Toast对象复用的可行性。

源码执行流程分析

Toast的本地创建和show()方法

实例化Toast对象

在实际使用中我们常使用使用如下代码实例化一个Toast对象。

Toast.makeText(Context, msg, Toast.LENGTH_SHOW);
           

下面看看makeText方法的源码:

/**
     * 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对象
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);

        //注意此处把加载的view的引用给mNextView,该对象其实就是Toast实际显示的View
        //这里实际也告诉我们,可以给mNextView对象设置不同的View(通过setView(view)方法),
        //从而自定义Toast的显示样式和内容。
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }
           

很简单的一个过程,实例化一个对象,加载要显示的View,设置View到Toast对象,返回新建的对象。注意上面的方法中,每次调用时都会新建一个

Toast

对象,看下Toast的构造器源码:

/**
     * 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.
     */
    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);
    }
           

从上面的代码中发现,其构造器实际就是在新创建一个TN对象并初始化它的属性,至于TN对象是什么,后面会讲到。

Toast.show()方法调用过程

show()

方法是最终执行显示Toast消息的地方吗?下面我们来看源码。

/**
     * 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;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
           

可以看到show()方法很简短,没有明显显示Toast的代码,看

INotificationManager service = getService();
           

这行代码中,INotificationManager是个用于Binder机制IPC通信的接口,看

getService()

的源码。

static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }
           

从代码中,可以发现是返回了一个以

"notification"

为标记的远程服务对象的代理。这个远程服务对象就是NotificationManagerService,以下简称NMS,所以在show()方法中的

service.enqueueToast(pkg, tn, mDuration);
           

实际调用的就是是NMS中的

enqueueToast

方法,先来看下传递给该方法的三个参数:

  • pkg 这个不用多说,就是表示应用的包名的字符串。
  • mDuration 也很简单,就是Toast显示的时长。
  • tn 这个参数是实现显示逻辑的核心部分,参数的类型为TN,是Toast类中的一个内部类,看到类定义的继承体系:

我们就知道了,TN实际上也是一个使用Binder机制IPC通信的远程调用的ITransientNotification接口的实现类,该类的具体实现后面再看。到目前为止,可以知道Toast.show()方法中并没有直接显示Toast消息,只是调用了NMS的

enqueueToast

方法,那这个方法中会不会直接通过某种方式显示Toast呢?先来看下NMS这个类的继承结构:

public class NotificationManagerService extends INotificationManager.Stub {
     ......
 }
           

显然NMS就是INotificationManager.Stub这个抽象类的具体实现类,注意在API23中NMS已经改为继承SystemServer类,其内部新增一个mService 对象实现该抽象类:

//API = 23
private final IBinder mService = new INotificationManager.Stub() {
           

NMS中Toast的执行流程

保存到Toast队列过程

上面说到show方法中调用NMS中的

enqueueToast

方法,下面是其关键部分的源码:

public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            ...... //省略部分代码
            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    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.
                    if (index >= ) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {

                        if (!isSystemToast) {
                            int count = ;
                            final int N = mToastQueue.size();
                            for (int i=; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         return;
                                     }
                                 }
                            }
                        }

                        record = new ToastRecord(callingPid, pkg, callback, duration);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - ;
                        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.
                    if (index == ) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }
           

上面这段代码的总体意思是,将新发送过来的Toast消息(实际是Toast对象的一些参数)添加到

mToastQueue

队列中,如果当前没有正在显示的Toast,则直接显示新的Toast消息。

mToastQueue

实际上是一个ArrayList对象,具体逻辑来说:

  • 6~15行代码中,首先根据传递过来的包名和TN binder通讯对象,获取该Toast对象在队列中的位置,如果存在,则更新Toast的显示时间,且并不会改变它在队列中的位置。
  • 32~33行中,其实就是该Toast在队列中不存在时,则创建一个包裹Toast参数的ToastRecord对象,放入队列中。
  • 41~42行,只有当前没有正在显示的Toast或当前更新的Toast正在显示时,才直接调用显示Toast的

    showNextToastLocked()

    方法,这里如果是Toast正在显示这种情况下调用的该方法,则会重置原来的超时计时,以更新的数据重新设置超时计时,这点会在后面分析的代码中体现。

循环获取、显示Toast过程

来看下

showNextToastLocked()

方法的源码:

void showNextToastLocked() {
        ToastRecord record = mToastQueue.get();
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                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
                int index = mToastQueue.indexOf(record);
                if (index >= ) {
                    mToastQueue.remove(index);
                }
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > ) {
                    record = mToastQueue.get();
                } else {
                    record = null;
                }
            }
        }
    }
           

方法的逻辑很简单,不管是正常执行流程还是异常流程,都是获取队列中的第一个ToastRecord对象,然后调用它的callback成员的show方法,这时候发现这里的callback成员就是我们在上边的

enqueueToast

方法中传入的远程TN对象,从这里看出NMS中并没有直接显示Toast,其显示的过程还是通过TN的远程代理对象来实现的,其show方法的具体实现后面再分析。

从上面的分析我们知道

showNextToastLocked()

方法每次只取队列中的第一个元素,那么是怎么实现不同的Toast的显示的呢?注意下第7行的代码:

scheduleTimeoutLocked(record);
           

这个方法是做什么的呢?我们看下源码:

private void scheduleTimeoutLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        mHandler.sendMessageDelayed(m, delay);
    }
           

代码逻辑很简单,其实质就是为每个Toast消息设置一个显示超时的处理器,需要注意的是代码第3行会先移除ToastRecord原来超时处理器(也就是消息),超时处理器的原理就是使用handler的延时消息机制,mHandler发送一个标记为MESSAGE_TIMEOUT的延时消息,延时时长取决于我们已经显示的Toast的显示时长标记,mHandler是一个WorkHandler对象,WorkHandler中收到该消息时,直接调用handleTimeout方法处理,看下该方法的源码:

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 >= ) {
                cancelToastLocked(index);
            }
        }
    }

    // lock on mToastQueue
    int indexOfToastLocked(String pkg, ITransientNotification callback)
    {
        IBinder cbak = callback.asBinder();
        ArrayList<ToastRecord> list = mToastQueue;
        int len = list.size();
        for (int i=; i<len; i++) {
            ToastRecord r = list.get(i);
            if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
                return i;
            }
        }
        return -;
    } 

    void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            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
        }
        mToastQueue.remove(index);
        keepProcessAliveLocked(record.pid);
        if (mToastQueue.size() > ) {
            // 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.
            showNextToastLocked();
        }
    }
           

handleTimeout方法中先调用

indexOfToastLocked(String pkg, ITransientNotification callback)

获取ToastRecord对象在队列中位置,该对象是由msg.obj转换而来的,再根据该下标调用

cancelToastLocked(int index)

去取消已到超时时间的Toast,看

cancelToastLocked(int index)

的实现中:

  • 30行record.callback.hide(),隐藏正在显示的Toast,具体的实现后面再分析。
  • 37~44行代码,先移除队列中该下标的元素,再调用

    showNextToastLocked()

    方法,接下来就是重复上面分析的该方法的执行流程。

到这里总结下Toast在NMS中的执行流程:

  • 每当增加一个Toast的时候,先判断队列中是否存在该Toast,若存在则直接更新显示时长,不改变其在队列中的位置,若不存在则将参数封装到一个ToastRecord对象中,并放入队列中,若新增的Toast在队列的最前端,则直接显示。
  • 当正在显示的Toast的显示时长到达预先设置的显示时间时,清除正在显示的Toast,从队列中移除该Toast,显示队列中的下一条Toast,重复这个过程,直到队列中没有新的Toast需要显示。

到目前我们可以得出结论一:

对于某个具体的Toast的显示是一个异步串行的过程,只要当队列中其前面的Toast全部显示完或取消掉,才能显示该Toast。

Toast的最终显示过程

上面分析了那么多,并没有涉及到Toast的真正的显示过程,这一小节分析的Toast的显示、隐藏过程。

上面说过,显示Toast调用的是TN.show()方法,那show方法的实现是怎样的呢?

public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }
           

show方法直接调用了mHandler.post发送了一个消息,也就是说Toast的显示依赖mHandler来分发,来看下mHandler的定义,和mShow这个Runnable的具体实现:

final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

        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;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();    
           
  • 代码18行,可以看到mHandler的初始化调用的是其默认构造器,也就是mHandler所绑定的线程就是初始化TN对象的线程,而TN对象是在Toast.makeToast方法中初始化的,又Handler的初始化成功的前提是,该线程存在消息循环即:
new Thread(){
        public void run(){
            ......
            Looper.prepare();
            ......
            Looper.loop();
            ......
        }
    }
           
  • 代码第4行,mShow这个Runable作为消息的实际执行体,没有其他额外的操作,直接调用了handleShow()方法,这个方法才是真正最终显示Toast的地方,看其源码:
public void handleShow() {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                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());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                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);
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }
           

看到这个方法的具体实现,一下子就明白,Toast的View的最终显示是使用WindowManager的addView方法添加到Window中的,同样取消正在显示的View实际就是调用WindowManager的removeView方法。到这里可能会有个疑惑,不是说Android中刷新界面只能在UI线程中吗?为什么这里显示View没有对非UI线程做限制呢?这个问题,这里推荐一篇文章:Android子线程真的不能更新UI么,这里可以解决我们的疑惑,View只能在与其关联的ViewRootImpl对象创建时所在线程刷新,否则将抛出异常。所以前面说Toast依赖handler来分发显示,更确认的说它需要mHandler初始化时绑定的线程来提供刷新UI时所需要的线程安全。

所以到这里可以得出第二个结论:

Toast可以在子线程中创建,只需要该线程存在消息循环即可,且调用Toast.makeToast方法的线程就是其所属的线程。

Toast的取消流程与显示的流程实际是一样的,这里就不再额外分析了。

到此Toast的执行流程算是分析结束了,做一个的流程执行总结:

  • Toast.makeToast方法创建一个全新的Toast对象,包括初始化TN对象。
  • Toast.show()方法通过传递TN对象, packageName, 显示时长三个参数给远程调用NMS的

    enqueueToast

    方法,将其加入到显示Toast的队列中。
  • 当上面传递的Toast参数的封装对象位于队列的最前端时,则再通过远程回调TN对象的show方法显示Toast。
  • TN对象在show方法中在初始化mHandler时所绑定的线程中将Toast要显示的View通过WindowManager的addView方法添加到Window中,最终实现显示Toast。
  • 在NMS中若Toast的显示时长到达或远程取消,则远程回调TN的hide方法,清除Toast,如果存在下一条Toast,则继续显示。

附上一张UML时序图:

Toast执行流程分析与复用意图分析的问题源码执行流程分析Toast的复用

Toast的复用

Toast对象复用的实际意义

在某些情况下,在一个应用中可能在一段短时间内,集中弹出几个Toast,有时候我们需要忽略已经显示或还未显示的Toast,直接显示最新的Toast,因为Toast的显示是串行的原因,要达到这一目的,有两种方式:

  • cancel掉要显示Toast之前的所有Toast,让要显示的Toast位于队列的最前端。
  • 重复使用同一个Toast对象,每次要显示新的Toast只需更新该Toast对象的View等参数就可以了。

    它们各有自己的优缺点,这里主要只分析复用带来的问题。

复用带来的问题

使用上述第一种方式,有一个弊端,需要保持每一个Toast的对象,手动去控制它们的生命周期,使原本简单的Toast操作,变的复杂化,如果控制不当,可能造成内存泄露。

使用第二种方法,使用起来确实更方便,全局只保存一个Toast对象,且能更快速的更新Toast消息(如果Toast正在显示,则不需要先移除当前Toast,再显示新的Toast,直接显示最新消息),但是是否就真的可以全局任意使用呢?前面结论一说到Toast可以在子线程中显示调用,先看下面的代码:

//MainThread
        final Toast toast = Toast.makeText(this, "Main Thread show Toast", Toast.LENGTH_LONG);
        toast.show();

        //start a new child thread
        new Thread()
        {
            public void run() {
                Looper.prepare();
                //此处延时1000ms,为了两个目的:
                //1、保证先显示第一条Toast的效果
                //2、保证Toast的TextView在重新设置值以前已经依附到Window中,因为在TextView或者说整个Toast的
                //布局在没有添加到Window(没有创建其对应的ViewRootImpl)之前,setText方法是可以在非UI线程中调用的
                try
                {
                    Thread.sleep();
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                toast.setText("Child Thread show Toast");

                //调用toast.setText方法时,若Toast正在显示,是否调用toast.show()方法会展示两种显示效果:
                //1、不调用toast.show(),只刷新显示的Toast消息,不刷新显示的时长,显示的新消息时间为剩余的显示时长
                //即:显示时长 = Toast设置显示时长 - 已使用时间
                //2、调用toast.show()方法,刷新Toast消息,重置显示时长,从新开始计时,显示时长为最新设置的时长标志位。
                //即:显示时长 >= Toast设置显示时长(>=是因为调用toast.setText方法就会先更新View)
                //注意如果是toast.setDuration(),toast.setView()方法,需要调用toast.show()方法来保证更改的字段生效
                toast.show();
                Looper.loop();
            };

        }.start();
           

上述代码是先在主线程构建Toast对象并显示,再在子线程中直接复用该Toast,设置不同的Text值而已,然而在实际执行中显示完”Main Thread show Toast”后欲显示”Child Thread show Toast”消息时会抛出以下异常:

- ::: E/AndroidRuntime(): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
           

这个异常的描述的大致意思是:只有原来创建了视图层的线程能够控制它的view,这个也是通常在非UI线程中刷新UI元素抛出的异常,这个异常是在ViewRootImpl的checkThread方法中抛出来的,具体原因细节请看Android子线程真的不能更新UI么这篇文章。

上面的代码中

Toast.makeText

方法是在主线程中调用的,从上节的内容分析可知,这也意味着Toast的TN成员对象的mHandler所绑定的消息队列是主线程的消息队列,这也导致最终在第一次调用

WindowManager.addView

方法时创建ViewRootImpl对象初始化时获取的是主线程对象的引用,所以当在子线程中重新刷新View内容时,做线程对象检查不匹配导致异常抛出。

以上实际执行结果说明当使用同一个Toast对象时,在多线程使用时Toast需要满足Android单线程刷新UI这一原则。

如何实现多线程安全复用Toast

无论使用何种方法都必须要符合Android单线程刷新UI这一原则,这里提供两种方法:

- 将所有对Toast的操作放到UI线程中去执行,这样就保证了单一原则,参看以下代码:

public class ToastTestActivity extends Activity
{


    private Button sendToastBtn;

    //handler初始化时绑定到主线程消息循环
    private static Handler mHandler = new Handler(Looper.getMainLooper());

    private static Toast mToast = null;

    public static void showToastToUiThread(final Toast toast, final CharSequence text, final int duration)
    {
        if(toast != null)
        {
            //如果当前线程不是主线程,则将Toast方法的调用放到主线程中去显示,主线程的ID与进程ID一致
            if(Process.myTid() != Process.myPid())
            {
                mHandler.post(new Runnable()
                {

                    @Override
                    public void run()
                    {
                        // TODO Auto-generated method stub
                        toast.setText(text);
                        toast.setDuration(duration);
                        toast.show();
                    }

                });
            }
            else
            {
                toast.setText(text);
                toast.setDuration(duration);
                toast.show();
            }
        }

    }

    /** {@inheritDoc} */

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.toast_test_activity_layout);
        //初始化Toast
        if(mToast == null)
        {
            mToast = Toast.makeText(this, "init toast", Toast.LENGTH_LONG);
            mToast.show();
        }

        sendToastBtn = (Button) findViewById(R.id.sendToastBtn);
        sendToastBtn.setOnClickListener(new OnClickListener()
        {

            @Override
            public void onClick(View v)
            {
                // TODO Auto-generated method stub
                showToastToUiThread(mToast, "first toast msg", Toast.LENGTH_LONG);

                new Thread()
                {
                    public void run() {
                        try
                        {
                            Thread.sleep();
                        }
                        catch (InterruptedException e)
                        {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        showToastToUiThread(mToast, "second toast msg", Toast.LENGTH_LONG);
                    };

                }.start();                              
            }
        });
    }
}
           

使用这种方法可以保证线程安全,使用起来也非常简单,但是这样有一个问题,当主线程业务比较繁忙的时候,可能会导致Toast延时显示,同时如果有大量的Toast集中在某个时间点全部堆积到主线程上去显示,有较小概率会对主线的其他消息响应有影响。

  • 创建一个子线程用于Toast的显示,即将Toast的构造过程放到一个专门的非UI线程中去执行,从而保证线程安全,也防止影响主线程的运行,参考以下示例代码:
public class MyToast 
{

    private static final String TAG = "MyToast";

    private static MyToast myToast = new MyToast();

    private Toast toast;

    /**
     * 使用一个线程用于专门显示Toast
     * */
    private static Thread thread = new Thread()
    {
        public void run() 
        {
            Looper.prepare();
            Log.d(TAG, "init handle and toast!!");
            handle = new HelperHandler();
            synchronized (myToast)
            {
                myToast.toast = Toast.makeText(MyApplication.getInstance(), "", );
                myToast.notifyAll();
            }

            Looper.loop();

        };
    };

    private static Handler handle = null;

    //初始化启动线程
    static 
    {
        thread.start();
    }

    public static  MyToast getDefaultInstance()
    {
        synchronized (myToast)
        {
            if(myToast.toast == null)
            {
                try
                {
                    myToast.wait();
                }
                catch (InterruptedException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return myToast;
    }



    /**
     * 此方法的创建过程是一个同步的,可能会比较耗时
     * 使用此方法创建的Toast对象都是在thread线程中的。
     * */
    public static MyToast newToast(Context context, CharSequence text, int duration)
    {
        Log.d(TAG, "create a new toast");
        MyToast toast = new MyToast();
        synchronized (toast)
        {
            handle.post(new CreateToastRunnable(toast, context, text, duration));
            try
            {
                toast.wait();
                Log.d(TAG, "create finished ");
            }
            catch (InterruptedException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        if(toast.toast == null)
        {
            return null;
        }
        return toast;
    }

    private static class HelperHandler extends Handler
    {

        private static final int QUIT_LOOP = -;
        /** {@inheritDoc} */

        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Override
        public void handleMessage(Message msg)
        {
            // TODO Auto-generated method stub
            switch (msg.what)
            {
            case QUIT_LOOP:
                if(Build.VERSION.SDK_INT >= )
                {
                    Looper.myLooper().quitSafely();
                }
                else
                {
                    Looper.myLooper().quit();
                }
                break;

            default:
                break;
            }
        }
    }

    /**
     * 
     * */
    public void showToastByThread(CharSequence text)
    {
        handle.post(new MyRunnable(this, text));        
    }

    /**
     * 
     * */
    public void showToastByThread()
    {
        Log.d(TAG, "pre to post msg");
        handle.post(new Runnable()
        {

            @Override
            public void run()
            {
                // TODO Auto-generated method stub
                Log.d(TAG, "is read to show toast = " + MyToast.this);
                MyToast.this.toast.show();
            }

        });
    }

    /** {@inheritDoc} */     
    public MyToast setDuration(int duration)
    {
        // TODO Auto-generated method stub
        toast.setDuration(duration);
        return this;
    }

    public MyToast setView(View view)
    {
        toast.setView(view);
        return this;
    }

    private static class MyRunnable implements Runnable
    {

        private CharSequence text;

        private MyToast toasts;

        public MyRunnable(MyToast toasts, CharSequence text)
        {
            // TODO Auto-generated constructor stub
            this.text = text;
            this.toasts = toasts;
        }

        /** {@inheritDoc} */

        @Override
        public void run()
        {
            // TODO Auto-generated method stub
            long startT = System.currentTimeMillis();
            Log.d(TAG, "startTime = " + startT);
            toasts.toast.setText(text);
            toasts.toast.show();

            Log.d(TAG, "totalTime = " + (System.currentTimeMillis() - startT));
        }

    }

    public static void stopToastThread()
    {
        handle.sendEmptyMessage(HelperHandler.QUIT_LOOP);
    }

    /**
     * 用于在thread线程中构建一个Toast对象
     * **/
    private static class CreateToastRunnable implements Runnable
    {

        private MyToast mytoast;

        private Context context;

        private CharSequence text;

        private int duration;

        public CreateToastRunnable(MyToast mytoast, Context context, CharSequence text, int duration)
        {
            // TODO Auto-generated constructor stub
            this.mytoast = mytoast;
            this.context = context;
            this.text = text;
            this.duration = duration;
        }

        /** {@inheritDoc} */

        @Override
        public void run()
        {
            // TODO Auto-generated method stub
            synchronized (mytoast)
            {
                mytoast.toast = Toast.makeText(context, text, duration);
                mytoast.notifyAll();
            }
        }

    }
}

//以下为测试用代码:
     //使用默认创建的单例Toast对象,在不同的线程中复用它
     MyToast.getDefaultInstance().setDuration().showToastByThread("first toast msg");
     new Thread()
     {
         public void run() {
             try
             {
                 Thread.sleep();
             }
             catch (InterruptedException e)
             {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
             MyToast.getDefaultInstance().setDuration().showToastByThread("second toast msg");

             try
             {
                 Thread.sleep();
             }
             catch (InterruptedException e1)
             {
                 // TODO Auto-generated catch block
                 e1.printStackTrace();
             }

             //新建一个Toast对象,在不同的线程中重复使用它
             final MyToast tempToast = MyToast.newToast(ToastTestActivity.this, "third Toast msg", );
             tempToast.showToastByThread();                        
             new Thread()
             {
                 public void run() 
                 {
                     try
                     {
                         Thread.sleep();
                     }
                     catch (InterruptedException e)
                     {
                         // TODO Auto-generated catch block
                         e.printStackTrace();
                     }
                     tempToast.showToastByThread("fourth toast msg");
                 };
             }.start();

         };

     }.start();  
           

上述就是采用自定义的一个线程来实现的Toast多线程安全使用的例程,其实现多线程安全访问的本质就是借用绑定了指定Thread的Handler对象将所有Toast相关操作发送异步消息到指定线程中执行。

可以发现使用这种方法虽然实现了线程安全刷新UI,但是同时也引入了一个运行在后台的线程,且因为消息循环的原因,需要用户自己手动去停止消息循环才能终止线程,这样相比于方法一的简单使用Toast,无疑这种方式在稳定性和简单性上都不如第一种方式,所以这里推荐使用第一种方式。

注意上面不管使用哪种方式的Toast复用,都存在一个问题,当Toast消息还在NMS的队列中未显示时,复用它们都会导致原有的Toast消息被替换掉,比如注释掉上面253-261这几行代码,那么”third Toast msg”这条消息就直接别它后面那条替换掉了。

以上就是所有关于Toast的一点理解,如有不正确之处,欢迎大家指正。