返回介绍

2.3. enqueueToast

发布于 2024-12-23 21:45:12 字数 7329 浏览 0 评论 0 收藏 0

加入队列,用来显示 Toast,队列最大数 50

  @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 ;
    }
    //(1) 判断是否系统的 Toast,如果当前包名是 android 则为系统
    final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
    //判断当前 toast 所属的 pkg 是不是所阻止的
    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;
        //(2) 判断 Toast 是否在队列当中
        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 {
          // Limit the number of toasts that any given package except the android
          // package can enqueue.  Prevents DOS attacks and deals with leaks.
          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++;
                //toasts 最大数 50 个
                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                  Slog.e(TAG, "Package has already posted " + count
                      + " toasts. Not showing more. Package=" + pkg);
                  return;
                }
              }
            }
          }
          //获得 ToastRecord 对象
          record = new ToastRecord(callingPid, pkg, callback, duration);
          //放入 mToastQueue 中             
          mToastQueue.add(record);
          index = mToastQueue.size() - 1;
          //(3) 设置该 Toast 为前台进程
          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 == 0) {
          //(4) 直接显示 Toast
          showNextToastLocked();
        }
      } finally {
        Binder.restoreCallingIdentity(callingId);
      }
    }
  }	

(1) 判断是否系统的 Toast,源码:

  private static boolean isCallerSystem() {
    return isUidSystem(Binder.getCallingUid());
  }
  
  private static boolean isUidSystem(int uid) {
    final int appid = UserHandle.getAppId(uid);
	  // 判断 pid 为系统进程使用的用户 id,值为 1000,或者为系统进程的手机的用户 id,值为 1001
    return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
  }

(2) 判断 Toast 是否在队列当中,源码:

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

(3) 设置该 Toast 为前台进程,源码:

  // lock on mToastQueue
  void keepProcessAliveLocked(int pid)
  {
    // toasts from this pid
    int toastCount = 0; 
    ArrayList<ToastRecord> list = mToastQueue;
    int N = list.size();
    for (int i=0; i<N; i++) {
      ToastRecord r = list.get(i);
      if (r.pid == pid) {
        toastCount++;
      }
    }
    try {
      // 设置该 Toast 为前台进程
      mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
    } catch (RemoteException e) {
      // Shouldn't happen.
    }
  }

(4) 直接显示 Toast,源码:

  void showNextToastLocked() {
	  //直接取第一个
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
      if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
      try {
        //回调 TN 类,显示 Toast
        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 >= 0) {
          mToastQueue.remove(index);
        }
        keepProcessAliveLocked(record.pid);
        if (mToastQueue.size() > 0) {
          record = mToastQueue.get(0);
        } else {
          record = null;
        }
      }
    }
  }
  
  private void scheduleTimeoutLocked(ToastRecord r)
  {
    //移除 ToastRecord
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    //static final int LONG_DELAY = 3500; // 3.5 seconds
    //static final int SHORT_DELAY = 2000; // 2 seconds
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
		//发送 Toast 消失的 message
    mHandler.sendMessageDelayed(m, delay);
  }

从 enqueueToast 方法可知,先判断是不是系统和合法的 Toast,然后判断是否在 ToastQueue(这里解释了很多 Toast,是一个个显示的),如果存在,只需要更新 Toast 显示的时间,如果不在,就直接显示,回调给 TN 类。 到这里,知道了 Toast 是如何显示的。 还没有结束,继续追踪 mHandler,来到 WorkerHandler :

  private final class WorkerHandler extends Handler
  {
    @Override
    public void handleMessage(Message msg)
    {
      switch (msg.what)
      {
        case MESSAGE_TIMEOUT:
          handleTimeout((ToastRecord)msg.obj);
          break;
         //……
      }
    }

  }
  
  private void handleTimeout(ToastRecord record)
  {
    if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
    synchronized (mToastQueue) {
	    //还是判断 Toast 是否在队列当中
      int index = indexOfToastLocked(record.pkg, record.callback);
      if (index >= 0) {
        cancelToastLocked(index);
      }
    }
  }
  
  void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    try {
      //回调 TN 类,Toast 消失
      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 中移除
    mToastQueue.remove(index);
    //设置该 Toast 为前台进程
    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.
      //继续 show 下个 Toast
      showNextToastLocked();
    }
  }

到这里,知道了 Toast 是如何消失的。Toast 核心代码显示和消失源码分析完毕。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文