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