我正在尝试创建一个可从用户屏幕读取文本的 AccessibilityService,它运行良好,并且我能够从本机 Android 应用程序获取大部分文本。不过,当我用我的示例 Flutter 应用程序尝试它时,我得到的所有文本都为空。
这是从我的服务读取文本的当前代码:
private void listAllTextsInActiveWindow() {
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
if (rootNode != null) {
List<HashMap<String, String>> allTexts = new ArrayList<>();
traverseNodesForText(rootNode, allTexts);
rootNode.recycle();
// Now 'allTexts' contains a list of all texts in the active window
Gson gson = new Gson();
String json = gson.toJson(allTexts);
Log.d("OUTPUT", json);
} else {
Log.d("OUTPUT", "NULL");
}
}
private void traverseNodesForText(AccessibilityNodeInfo node, List<HashMap<String, String>> allTexts) {
if (node == null) return;
if (node.getText() != null && !node.getText().toString().isEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
HashMap data = new HashMap();
data.put("view_class", node.getClassName());
data.put("view_text", node.getText());
data.put("view_description", node.getContentDescription());
data.put("view_complete_info", node.toString());
allTexts.add(data);
}
}
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo childNode = node.getChild(i);
traverseNodesForText(childNode, allTexts);
}
}
我得到一个空数组作为输出。
当我试图让它工作时,我的前辈告诉我有一个名为“Automate”的应用程序,当我尝试使用该应用程序及其 Inspect 布局时,我能够获得包含所有文本的层次结构 XML。
它的输出是:
<hierarchy rotation="0">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.view.View" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.view.View" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.view.View" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.view.View" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1465]">
<node index="0" text="" resource-id="" class="android.view.View" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,154]">
<node index="0" text="" resource-id="" class="android.view.View" package="com.example.apk_installer_demo" content-desc="Download & Install Test APK" checkable="false" checked="false" clickable="false" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[30,75][549,128]"/></node>
<node index="1" text="" resource-id="" class="android.widget.Button" package="com.example.apk_installer_demo" content-desc="Download & Install New Version" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[126,787][594,877]"/></node>
</node>
</node>
</node>
</node>
</node>
</node>
<node index="1" text="" resource-id="android:id/navigationBarBackground" class="android.view.View" package="com.example.apk_installer_demo" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][0,0]"/></node>
</hierarchy>
现在这个输出是可读的,我正在努力实现这样的目标。
让我知道我是否可以做到这一点。
使用 uiautomator 转储进行良好调用!您是否注意到所有节点的文本都是空的?我不确定 Flutter 如何处理按钮上的文本可视文本,但您需要检查行中的内容描述 (
content-desc
)
if (node.getText() != null && !node.getText().toString().isEmpty())
否则你会错过它。尝试一下
if (node.getText() != null && (!node.getText().toString().isEmpty() || node.getContentDescription().toString().isEmpty()))
但这可能不是您问题的结束,因为这也会捕获带有替代文本的图像,因此您可能需要使匹配更加复杂。