Bootstrap

Android Toast信息定位分析介绍

Android Toast信息定位分析介绍

一、前言

Android Toast有时候莫名其妙的弹框,需要定位到是哪个应用发出,咋搞?

系统开发的就没啥难度,可以直接在系统源码里面加个打印重新编包验证就行了,
因为Toast需要传入上下文,所以通过上下文是可以获取到应用包名的,和Toast文本信息;

但是如果不是系统源码开发难道就不行了吗,其实也是可以的,
之前安装过一个“Android开发工具箱”的apk应用,开启无障碍模式下就可以监听Toast的信息,包括Toast的包名和文本。

本文介绍一下,上面两种方式定位Toast 的代码实现。

本文不难,有兴趣的可以看看。

二、Toast定位

1、系统源码加日志定位Toast信息

Toast 的简单代码:

Toast.makeText(Context, "Toast Message", Toast.LENGTH_SHORT).show();

(1)Toast 的源码位置:
framework\base\core\java\android\widget\Toast.java

(2)添加打印日志:
public class Toast {
    static final String TAG = "Toast";
    
    ...
    public void show() {
        
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = new WeakReference<>(mNextView);
        final boolean isUiContext = mContext.isUiContext();
        final int displayId = mContext.getDisplayId();

        //下面的代码就打印了:Toast应用的包名和具体弹框的信息
        Log.i(TAG, "mybebug show() pkg = " + pkg + ",mText = " + mText);
    
        ...
    }
}

就是这么简单加一行信息就行。

如果想要知道这个Toast是哪个应用哪行代码执行的,加入栈堆信息就行:

import android.os.SystemProperties;

        boolean isNeedLogToast = SystemProperties.getBoolean("persist.sys.debug.need_log_toast", true);
        if (isNeedLogToast) {
            Log.i(TAG, "mybebug show() pkg = " + pkg + "mText = " + mText);
            Exception e = new Exception("LogToast");
            e.printStackTrace();
        }

上面加入了prop属性控制,防止一直打印堆栈信息,一般有几十行堆栈信息。
部分日志,如下:


11-07 09:18:20.141  7078  7078 I Toast   : mybebug show() pkg = com.debug.factory,menumText = WiFi is not connected. Please connect and try again

130|console:/ # logcat |grep -i System.err                                     
...
11-07 04:35:25.730  2194  2194 W System.err: java.lang.Exception: LogToast
11-07 04:35:25.730  2194  2194 W System.err:    at android.widget.Toast.show(Toast.java:214)
11-07 04:35:25.730  2194  2194 W System.err:    at com.skg.settings.utils.ToastUtils.showToast(ToastUtils.java:36)
11-07 04:35:25.730  2194  2194 W System.err:    at com.skg.settings.utils.ToastUtils.showToast(ToastUtils.java:40)
11-07 04:35:25.730  2194  2194 W System.err:    at com.skg.settings.adapter.WirelessNetworkAdapter$16.run(WirelessNetworkAdapter.java:793)
11-07 04:35:25.731  2194  2194 W System.err:    at android.os.Handler.handleCallback(Handler.java:942)
11-07 04:35:25.731  2194  2194 W System.err:    at android.os.Handler.dispatchMessage(Handler.java:99)
11-07 04:35:25.731  2194  2194 W System.err:    at android.os.Looper.loopOnce(Looper.java:201)
11-07 04:35:25.731  2194  2194 W System.err:    at android.os.Looper.loop(Looper.java:288)
11-07 04:35:25.731  2194  2194 W System.err:    at android.app.ActivityThread.main(ActivityThread.java:7924)
11-07 04:35:25.731  2194  2194 W System.err:    at java.lang.reflect.Method.invoke(Native Method)
11-07 04:35:25.731  2194  2194 W System.err:    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
11-07 04:35:25.731  2194  2194 W System.err:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
11-07 04:35:25.758   937  1777 W FileUtils: android.system.ErrnoException: chmod failed: ENOENT (No such file or directory)

上面就可以看到是哪个应用包名下的哪个类的哪行方法执行的Toast,还是比较详细的。

如果不想编译系统源码监听Toast信息,那么就可以使用下面这种方法了。

2、监听无障碍模式下的Toast信息

写一个apk demo吧,挺简单的,主要是一个服务的监听。具体代码如下:

(1)MyAccessibilityService继承自无障碍服务
package com.liwenzhi.audiodemo;

import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onCreate() {
        super.onCreate();
        LogUtil.debug("");
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        LogUtil.debug("event = " + event);
        // 检查事件类型是否是Toast显示的事件
        // 检查事件是否来自Toast
        if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
            CharSequence packageName = event.getPackageName();
            // 获取Toast的文本内容
            CharSequence toastText = event.getText().toString();
            if (toastText != null) {
                // 处理Toast信息,这里打印一下
                String toastString = toastText.toString();
                LogUtil.debug("packageName = " + packageName + ", toastString = " + toastString);
            }
        }
    }

    @Override
    public void onInterrupt() {
        // 处理中断事件
        LogUtil.debug("");
    }
}

上面的Toast 信息是可以得到打印的,网上有些写法是不太对。
AccessibilityService 也是一个四大组件的Service,所以必须声明。

(2)apk中的AndroidManafest.xml声明
        <service
            android:name=".MyAccessibilityService"
            android:exported="false"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action
                     android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>

            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config" />
        </service>

必须要写上面的permission和 meta-data 信息,否则不会给你显示你可以获取到什么权限。
按照上面的写就行了。

(3)在res/xml文件夹下新建accessible_service_config_test.xml

内容如下:

<?xml version="1.0" encoding="utf-8"?>
<!-- typeAllMask:接收所有事件。
==========窗口事件相关(常用)==========
typeWindowStateChanged:监听窗口状态变化,比如打开一个popupWindow,dialog,Activity切换等等。
typeWindowContentChanged:监听窗口内容改变,比如根布局子view的变化。
typeWindowsChanged:监听屏幕上显示的系统窗口中的事件更改。 此事件类型只应由系统分派。
typeNotificationStateChanged:监听通知变化,比如notifacation和toast。
============View事件相关==========
typeViewClicked:监听view点击事件。
typeViewLongClicked:监听view长按事件。
typeViewFocused:监听view焦点事件。
typeViewSelected:监听AdapterView中的上下文选择事件。
typeViewTextChanged:监听EditText的文本改变事件。
typeViewHoverEnter、typeViewHoverExit:监听view的视图悬停进入和退出事件。
typeViewScrolled:监听view滚动,此类事件通常不直接发送。
typeViewTextSelectionChanged:监听EditText选择改变事件。
typeViewAccessibilityFocused:监听view获得可访问性焦点事件。
typeViewAccessibilityFocusCleared:监听view清除可访问性焦点事件。
============手势事件相关==========
typeGestureDetectionStart、typeGestureDetectionEnd:监听手势开始和结束事件。
typeTouchInteractionStart、typeTouchInteractionEnd:监听用户触摸屏幕事件的开始和结束。
typeTouchExplorationGestureStart、typeTouchExplorationGestureEnd:监听触摸探索手势的开始和结束。
android:description :辅助功能描述,描述该辅助功能用来干嘛的
android:packageNames :指定辅助功能监听的包名,不指定表示监听所有应用
android:accessibilityEventTypes:辅助功能处理事件类型,一般配置为typeAllMask表示接收所有事件
android:accessibilityFlags:辅助功能查找截点方式,一般配置为flagDefault默认方式。
android:accessibilityFeedbackType:操作相应按钮以后辅助功能给用户的反馈类型,包括声音,震动等。
android:notificationTimeout:相应时间设置
android:canRetrieveWindowContent:是否允许辅助功能获得窗口的节点信息,为了能正常实用辅助功能,请务必保持该项配置为true 
-->

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_name"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:accessibilityFlags="flagDefault" />

如果要监听整个系统的Toast信息,就不要写packageNames 标签信息,否则会只监听某个应用的Toast信息;

另外 description 标签信息必须要写成@string 形式,直接写字符串是编译不通过的,随便链接一个@string的字符串都是可以的。

上面是监听所有事件,界面发生改变或者点击到设备屏幕,都会收到相关事件信息。

网上有的说要申请权限BIND_ACCESSIBILITY_SERVICE:

<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />

其实不用也可以,Service 那里声明就可以了。

三、其他

1、Toast分析小结

两种方式可以定位到Toast是哪里发出的;
一种是在系统源码Toast.java里面加打印;
另外一种是监听"无障碍服务"AccessibilityService的动作,获取Toast 的信息。

2、查看当前界面的信息情况 dumpsys window

dumpsys window信息非常多,一般都是grep过滤查看当前焦点的界面信息。

console:/ # //Launcher界面时
console:/ # dumpsys window | grep mFoc
  mFocusedApp=ActivityRecord{329e2a3 u0 com.android.launcher3/.uioverrides.QuickstepLauncher t31}
    mFocusedWindow=Window{94e847d u0 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher}
console:/ # 
console:/ # //打开原生设置界面时
console:/ # dumpsys window | grep mFoc                                         
  mFocusedApp=ActivityRecord{7337aee u0 com.android.settings/.SubSettings t34}
    mFocusedWindow=Window{d562574 u0 com.android.settings/com.android.settings.SubSettings}
console:/ #  //打开谷歌文件管理器界面时
console:/ # dumpsys window | grep mFoc                                         
  mFocusedApp=ActivityRecord{51e0ddd u0 com.google.android.apps.nbu.files/.documentbrowser.filepreview.FilePreviewActivity t33}
    mFocusedWindow=Window{fe3284d u0 com.google.android.apps.nbu.files/com.google.android.apps.nbu.files.documentbrowser.filepreview.FilePreviewActivity}
console:/ # 

上面可以获取到当前应用的哪个Activity界面,但是还无法获取到Fragment界面,
如果要获取到Fragment或者具体控件的信息,也是可以使用无障碍服务获取到的,有需要的自行研究看看。

2、Android 开发工具箱 apk

Android 开发工具箱 apk
2024年11 月下载的,是2024下半年的apk,从网页上也是可以搜索发现: “Android开发工具箱” apk应用
里面主要功能:
系统基本信息显示(分辨率、wifi连接情况),屏幕测距,二维码识别,跳转到系统设置界面、开发者选项界面等,权限统计,
应用信息查看,通知和消息监听,Activity界面监听等功能是免费查看的。
apk反编译、签名需要收费。

https://download.csdn.net/download/wenzhi20102321/89975054

;