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类中的一个内部类,看到类定义的继承体系:
private static class TN extends ITransientNotification.Stub {
   

我们就知道了,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 >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {

                        if (!isSystemToast) {
                            int count = 0;
                            final int N = mToastQueue.size();
                            for (int i=0; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     if (count >= MAX_PACKAGE_NO
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值