[Android][DevTips]定位Toast的所有者

前言

DevTips是一个新系列,旨在记录一些开发过程中的小技巧,没有太大的技术含量,更多的作用是备忘;

Tip

因此先上结论:

  1. Toast显示过程中,可通过adb shell dumpsys notification查看:
    Current Notification Manager state:
      Toast Queue:
        ToastRecord{ea7f229 pkg=com.xxx callback=android.app.ITransientNotification$Stub$Proxy@a550c4e duration=0
    
  2. 如果没有办法抓到现场,从日志的NotificationService也会有相应记录:
    06-10 10:50:06.551  1364  2522 I NotificationService: enqueueToast pkg=com.xxx  callback=android.app.ITransientNotification$Stub$Proxy@a550c4e duration=0
    06-10 10:50:06.575  1364  2522 I NotificationService: cancelToast pkg=com.xxx callback=android.app.ITransientNotification$Stub$Proxy@790df67
    06-10 10:50:06.575  1364  2522 W NotificationService: Toast already cancelled. pkg=com.xxx callback=android.app.ITransientNotification$Stub$Proxy@790df67
    06-10 10:50:06.575  1364  2522 I NotificationService: cancelToast pkg=com.xxx callback=android.app.ITransientNotification$Stub$Proxy@d6ab114
    06-10 10:50:06.575  1364  2522 W NotificationService: Toast already cancelled. pkg=com.xxx callback=android.app.ITransientNotification$Stub$Proxy@d6ab114
    06-10 10:50:08.581  1364  7015 W NotificationService: Toast already killed. pkg=com.xxx callback=android.app.ITransientNotification$Stub$Proxy@cbb990d
    

溯源

为什么NotificationService(NotificationManagerService)记录了Toast的记录?

  1. 首先查看Toast.javashow()方法实现:
        /**
         * Show the view for the specified duration.
         */
        public void show() {
            if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                checkState(mNextView != null || mText != null, "You must either set a text or a view");
            } else {
                if (mNextView == null) {
                    throw new RuntimeException("setView must have been called");
                }
            }
    
            INotificationManager service = getService();
            String pkg = mContext.getOpPackageName();
            TN tn = mTN;
            tn.mNextView = mNextView;
            final int displayId = mContext.getDisplayId();
    
            try {
                if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                    if (mNextView != null) {
                        // It's a custom toast
                        service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
                    } else {
                        // It's a text toast
                        ITransientNotificationCallback callback =
                                new CallbackBinder(mCallbacks, mHandler);
                        service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
                    }
                } else {
                    service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
                }
            } catch (RemoteException e) {
                // Empty
            }
        }
    
  2. 可见Toast.show()的实现,实际上会调用到INotificationManager.enqueueTextToast()/enqueueToast()中;
  3. NotificationManagerService.mService作为INotificationManager的实现,其enqueueTextToast()/enqueueToast()实现如下:
        @Override
        public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration,
                int displayId, @Nullable ITransientNotificationCallback callback) {
            enqueueToast(pkg, token, text, null, duration, displayId, callback);
        }
    
        @Override
        public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
                int duration, int displayId) {
            enqueueToast(pkg, token, null, callback, duration, displayId, null);
        }
    
        private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text,
                @Nullable ITransientNotification callback, int duration, int displayId,
                @Nullable ITransientNotificationCallback textCallback) {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " token=" + token
                        + " duration=" + duration + " displayId=" + displayId);
            }
    
            if (pkg == null || (text == null && callback == null)
                    || (text != null && callback != null) || token == null) {
                Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " text=" + text + " callback="
                        + " token=" + token);
                return;
            }
    
            final int callingUid = Binder.getCallingUid();
            checkCallerIsSameApp(pkg);
            final boolean isSystemToast = isCallerSystemOrPhone()
                    || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
            boolean isAppRenderedToast = (callback != null);
            if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) {
                return;
            }
    
            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                final long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, token);
                    // 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 can enqueue.
                        // Prevents DOS attacks and deals with leaks.
                        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_TOASTS) {
                                    Slog.e(TAG, "Package has already queued " + count
                                            + " toasts. Not showing more. Package=" + pkg);
                                    return;
                                }
                            }
                        }
    
                        Binder windowToken = new Binder();
                        mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId,
                                null /* options */);
                        record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,
                                text, callback, duration, windowToken, displayId, textCallback);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveForToastIfNeededLocked(callingPid);
                    }
                    // If it's at index 0, it's the current toast.  It doesn't matter if it's
                    // new or just been updated, show it.
                    // If the callback fails, this will remove it from the list, so don't
                    // assume that it's valid after this.
                    if (index == 0) {
                        showNextToastLocked(false);
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }
    
  4. 无论是enqueueTextToast()还是enqueueToast()其最终都会有enqueueToast的日志输出,且会调用到mToastQueue.add(),而后者则会在dump()时输出;
        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
            final DumpFilter filter = DumpFilter.parseFromArguments(args);
            final long token = Binder.clearCallingIdentity();
            try {
                if (filter.stats) {
                    dumpJson(pw, filter);
                } else if (filter.rvStats) {
                    dumpRemoteViewStats(pw, filter);
                } else if (filter.proto) {
                    dumpProto(fd, filter);
                } else if (filter.criticalPriority) {
                    dumpNotificationRecords(pw, filter);
                } else {
                    dumpImpl(pw, filter);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

	void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) {
        pw.print("Current Notification Manager state");
        if (filter.filtered) {
            pw.print(" (filtered to "); pw.print(filter); pw.print(")");
        }
        pw.println(':');
        int N;
        final boolean zenOnly = filter.filtered && filter.zen;

        if (!zenOnly) {
            synchronized (mToastQueue) {
                N = mToastQueue.size();
                if (N > 0) {
                    pw.println("  Toast Queue:");
                    for (int i=0; i<N; i++) {
                        mToastQueue.get(i).dump(pw, "    ", filter);
                    }
                    pw.println("  ");
                }
            }
        }
        ...
	}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值