【玩转Android无障碍】之布局节点速查器

上一篇介绍了的这个系列的背景【玩转Android无障碍】之序言接下来就开始一步一步实现吧

工欲善其事必先利其器

  • 接触Android无障碍(AccessibilityService)功能开发首先遇到的问题就是如何获取页面上的元素信息,只有拿到元素唯一标识信息(比如元素id或者text)才能通过调用系统的findAccessibilityNodeInfosByViewId()findAccessibilityNodeInfosByText()去获取这个元素,然后对这个元素进行相关操作,比如常见的点击某个元素、获取某个元素的内容、滑动一下屏幕等类似的操作,通过这些操作就能组合成一条完整的业务流程。

如何获取元素

  • 常规获取页面元素的方式是使用Android Studio的LayoutInspector进行查看页面元素信息,但是这种方式有一个致命的问题就是APP要开启debug模式才可以获取到页面元素,所以要想分析其他市场上的APP是行不通的(不考虑模拟器或者root机),当然在历史的长河中还存在过Android Device MonitorUI Automator Viewer,这两个工具也可以获取到节点元素信息,不过基本上算是被淘汰了,这里就不展开讲了,感兴趣的可以网上搜搜有很多现成的资料

  • 本文要介绍的是直接通过代码获取当前屏幕窗口的根节点,然后一层一层的遍历,最后打印出自己需要的相对重要的节点信息即可,有了这么一套工具之后就可以对你想处理的页面可以很方便的获取页面元素,让自己只专注于具体的业务流程开发就可以了,可以节约大部分查找页面元素的时间。

具体实现

实现思路

  • AccessibilityService类中有个getRootInActiveWindow()方法,他可以获取到当前活动窗口的根节点,返回的是AccessibilityNodeInfo对象,然后可以拿到它的childCount,通过循环遍历就可以查找到所有的子节点了,节点包含的信息很多,我们一般需要是classNametextviewIdResourceNamedescriptionisClickableisScrollableisEditable这些基础数据,为了方便打印出我们需要的数据,首先定义一个包装类NodeWrapper,重写它的toString()方法,这样就可以按照既定的格式输出一些我们需要的信息了
kotlin 复制代码
data class NodeWrapper(
    var className: String,
    var text: String? = null,
    var id: String? = null,
    var description: String? = null,
    var isClickable: Boolean = false,
    var isScrollable: Boolean = false,
    var isEditable: Boolean = false,
    var nodeInfo: AccessibilityNodeInfo? = null
) {
    override fun toString() = "className = $className → text = $text → id = $id → description = $description → isClickable = $isClickable → isScrollable = $isScrollable → isEditable = $isEditable"

}

代码设计

  • 我们把这个打印节点信息的方法定义为printNodeInfo(),因为rootInActiveWindow()返回是AccessibilityNodeInfo对象,所以为了方便调用我们定义一个AccessibilityNodeInfo类的扩展方法,大致内容如下:
kotlin 复制代码
fun AccessibilityNodeInfo?.printNodeInfo() {
    val node = this ?: return
    //选择我们需要的信息包装起来,便于打印
    val nodeWrapper = NodeWrapper(
        className = node.className.default(),
        text = node.text.default(),
        id = node.viewIdResourceName.default(),
        description = node.contentDescription.default(),
        isClickable = node.isClickable,
        isScrollable = node.isScrollable,
        isEditable = node.isEditable,
        nodeInfo = node
    )
    //打印我们需要的信息
    Log.d("printNodeInfo", nodeWrapper.toString())
    val size = node.childCount
    if (size > 0) {
        //通过递归调用去打印子节点的信息
        for (index in 0 until size) {
            node.getChild(index).printNodeInfo()
        }
    }
}

如何使用

  • 首先需要定义一个继承AccessibilityService类的子类TestAccessibilityService类,重写onCreate()方法,在里边进行初始化赋值给我们定义的全局变量testAccessibilityService = this
kotlin 复制代码
    companion object {
        var testAccessibilityService: AccessibilityService? = null
    }

    override fun onCreate() {
        super.onCreate()
        testAccessibilityService = this
    }

    override fun onDestroy() {
        testAccessibilityService = null
        super.onDestroy()
    }
  • 然后在需要的地方就可以调用testAccessibilityService?.rootInActiveWindow?.printNodeInfo()进行打印节点信息了。先来看下效果吧
ini 复制代码
className = android.widget.FrameLayout → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.LinearLayout → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.FrameLayout → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.LinearLayout → text =  → id = com.android.wechat.tools:id/action_bar_root → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.FrameLayout → text =  → id = android:id/content → description =  → clickable = false → scrollable = false → editable = false
className = android.view.ViewGroup → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.LinearLayout → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.view.ViewGroup → text =  → id = com.android.wechat.tools:id/toolbar → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.TextView → text = 微信自动化工具 → id =  → description =  → clickable = false → scrollable = false → editable = false
className = androidx.appcompat.widget.LinearLayoutCompat → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.view.ViewGroup → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.FrameLayout → text =  → id = com.android.wechat.tools:id/nav_host_fragment_content_main → description =  → clickable = false → scrollable = false → editable = false
className = android.view.ViewGroup → text =  → id =  → description =  → clickable = false → scrollable = false → editable = false
className = android.widget.Button → text = 无障碍服务已开启 → id = com.android.wechat.tools:id/btn_open_service → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.Button → text = 页面元素检测工具 → id = com.android.wechat.tools:id/btn_print_node → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.Button → text = 获取微信好友列表 → id = com.android.wechat.tools:id/btn_get_friend_list → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.Button → text = 一键检测《通过假转账方式》 → id = com.android.wechat.tools:id/btn_check → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.Button → text = 一键检测《通过拉群方式》 → id = com.android.wechat.tools:id/btn_check_by_group → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.Button → text = 微信自动抢红包 → id = com.android.wechat.tools:id/btn_wx_auto_hb → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.Button → text = 微信自动回复消息 → id = com.android.wechat.tools:id/btn_wx_auto_reply → description =  → clickable = true → scrollable = false → editable = false
className = android.widget.TextView → text =  → id = com.android.wechat.tools:id/tv_task_des → description =  → clickable = false → scrollable = false → editable = false
className = androidx.recyclerview.widget.RecyclerView → text =  → id = com.android.wechat.tools:id/recycler_view → description =  → clickable = false → scrollable = false → editable = false
className = android.view.View → text =  → id = android:id/statusBarBackground → description =  → clickable = false → scrollable = false → editable = false
  • 可以看到当前窗口的元素都已经被详细的打印出来了,不过这样打印出来看起来怪怪的,总感觉是哪里有问题,看了半天发现,输出的节点都是同一级的,看不出来父子元素的关系,这样就会出现我们想要查看某个元素的父节点的时候就特别费劲,如果页面简单还好,稍微复杂点的页面能把人看懵逼。那么该怎么去展示父子节点的关系呐,突然想到我们gradle中有个dependenciestask,这个task可以打印出出我们所依赖库中的具体依赖关系,也就是我们需要的父子关系,我们可以先看一下它输出的是什么样的。
lua 复制代码
+--- androidx.core:core-ktx:1.7.0
|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.31 -> 1.7.10 (*)
|    +--- androidx.annotation:annotation:1.1.0 -> 1.3.0
|    \--- androidx.core:core:1.7.0
|         +--- androidx.annotation:annotation:1.2.0 -> 1.3.0
|         +--- androidx.annotation:annotation-experimental:1.1.0
|         +--- androidx.lifecycle:lifecycle-runtime:2.3.1
|         |    +--- androidx.lifecycle:lifecycle-common:2.3.1
|         |    |    \--- androidx.annotation:annotation:1.1.0 -> 1.3.0
|         |    +--- androidx.arch.core:core-common:2.1.0
|         |    |    \--- androidx.annotation:annotation:1.1.0 -> 1.3.0
|         |    \--- androidx.annotation:annotation:1.1.0 -> 1.3.0
|         \--- androidx.versionedparcelable:versionedparcelable:1.1.1
|              +--- androidx.annotation:annotation:1.1.0 -> 1.3.0
|              \--- androidx.collection:collection:1.0.0 -> 1.1.0
|                   \--- androidx.annotation:annotation:1.1.0 -> 1.3.0

可以清晰的看出层级关系,这不这个是我们需要的吗,先来分析一下他的结构欧,根节点+--- 开始标记,每个子节点前加个|区分,然后加几个空格让他出现父子关系的样子,如果子节点是最后一个再加个\---表示当前是层级的最后一个节点,以此类推就出现上边展示的输出格式了,接下来看看具体代码吧

ini 复制代码
fun AccessibilityNodeInfo?.printNodeInfo(prefix: String = "", isLast: Boolean = false) {
    val node = this ?: return
    val nodeWrapper = NodeWrapper(
        text = node.text.default(),
        id = node.viewIdResourceName.default(),
        className = node.className.default(),
        description = node.contentDescription.default(),
        isClickable = node.isClickable,
        isScrollable = node.isScrollable,
        isEditable = node.isEditable,
        nodeInfo = node
    )
    val marker = if (isLast) """\--- """ else "+--- "
    val currentPrefix = "$prefix$marker"
    Log.d("printNodeInfo", currentPrefix + nodeWrapper.toString())

    val size = node.childCount
    if (size > 0) {
        val childPrefix = prefix + if (isLast) "  " else "|  "
        val lastChildIndex = size - 1
        for (index in 0 until size) {
            val isLastChild = index == lastChildIndex
            node.getChild(index).printNodeInfo(childPrefix, isLastChild)
        }
    }
}

代码不复杂,只是在原来代码的基础上多了几个前缀字符的标记而已,现在再打印一下同一个页面信息看看效果吧

ini 复制代码
+--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |  \--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |    \--- className = android.widget.LinearLayout → text =  → id = com.android.wechat.tools:id/action_bar_root → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |      \--- className = android.widget.FrameLayout → text =  → id = android:id/content → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |        \--- className = android.view.ViewGroup → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |          +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |          |  \--- className = android.view.ViewGroup → text =  → id = com.android.wechat.tools:id/toolbar → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |          |    +--- className = android.widget.TextView → text = 微信自动化工具 → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |          |    \--- className = androidx.appcompat.widget.LinearLayoutCompat → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |          \--- className = android.view.ViewGroup → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |            \--- className = android.widget.FrameLayout → text =  → id = com.android.wechat.tools:id/nav_host_fragment_content_main → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |              \--- className = android.view.ViewGroup → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 无障碍服务已开启 → id = com.android.wechat.tools:id/btn_open_service → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 页面元素检测工具 → id = com.android.wechat.tools:id/btn_print_node → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 获取微信好友列表 → id = com.android.wechat.tools:id/btn_get_friend_list → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 一键检测《通过假转账方式》 → id = com.android.wechat.tools:id/btn_check → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 一键检测《通过拉群方式》 → id = com.android.wechat.tools:id/btn_check_by_group → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 微信自动抢红包 → id = com.android.wechat.tools:id/btn_wx_auto_hb → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.Button → text = 微信自动回复消息 → id = com.android.wechat.tools:id/btn_wx_auto_reply → description =  → isClickable = true → isScrollable = false → isEditable = false
|  |                +--- className = android.widget.TextView → text =  → id = com.android.wechat.tools:id/tv_task_des → description =  → isClickable = false → isScrollable = false → isEditable = false
|  |                \--- className = androidx.recyclerview.widget.RecyclerView → text =  → id = com.android.wechat.tools:id/recycler_view → description =  → isClickable = false → isScrollable = false → isEditable = false
|  \--- className = android.view.View → text =  → id = android:id/statusBarBackground → description =  → isClickable = false → isScrollable = false → isEditable = false

哈哈,是不是瞬间好看多了,各个父子关系可以清晰的看出来,同级元素页可以一眼看出来了

  • 因为getRootInActiveWindow()是在AccessibilityService类中才有的方法,每次调用需要写一些重复性的代码,所有我们再定义一个AccessibilityService类的扩展方法,以后直接调用xxx.printNodeInfo()就可以了
kotlin 复制代码
fun AccessibilityService?.printNodeInfo() {
    this ?: return
    rootInActiveWindow.printNodeInfo()
}

拓展

  • 虽然打印的代码部分完成了,这个时候又引出一个问题,比如我们想在微信里边看某个页面的节点信息怎么办呐,总不能每次都编译一遍代码去执行打印节点的方法吧,我们期望的是在任何页面都可以随时随地的打印,如何在微信页面触发我们的printNodeInfo()方法呐,自然而然的想到用悬浮窗去实现了,悬浮窗可以浮在任何页面之上,我们只需点一下悬浮窗,然它获取AccessibilityService对象并调一下printNodeInfo()方法就可以了,关于Android悬浮窗这里就不做过多讲解了,因为不是本文的重点,在网上找了优秀的开源库直接使用就可以了。我们看一下微信的页面信息吧
ini 复制代码
+--- className = android.widget.FrameLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|  \--- className = android.widget.FrameLayout → text =  → id = com.tencent.mm:id/fkh → description =  → isClickable = false → isScrollable = false → isEditable = false
|    \--- className = android.widget.RelativeLayout → text =  → id = com.tencent.mm:id/gv5 → description =  → isClickable = false → isScrollable = false → isEditable = false
|      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/gv4 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        +--- className = android.widget.RelativeLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  +--- className = android.widget.ListView → text =  → id = android:id/list → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/l49 → description =  → isClickable = true → isScrollable = false → isEditable = false
|        |  |  |  +--- className = android.widget.ImageView → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |  +--- className = android.widget.TextView → text = 微信 → id = com.tencent.mm:id/b7 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |  \--- className = android.widget.TextView → text = Version 8.0.40 → id = com.tencent.mm:id/b6 → description =  → isClickable = true → isScrollable = false → isEditable = false
|        |  |  +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/iwg → description =  → isClickable = true → isScrollable = false → isEditable = false
|        |  |  |  +--- className = android.view.View → text =  → id = com.tencent.mm:id/krm → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/br8 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/gv6 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/kp3 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        |    \--- className = android.widget.TextView → text = 功能介绍 → id = android:id/title → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/its → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |      \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/isy → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/iwg → description =  → isClickable = true → isScrollable = false → isEditable = false
|        |  |  |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/br8 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |  \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/kp3 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |    \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |      \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        \--- className = android.widget.TextView → text = 投诉 → id = android:id/title → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/its → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |      \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/isy → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/iwg → description =  → isClickable = true → isScrollable = false → isEditable = false
|        |  |  |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    +--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/br8 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/gv6 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |      \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/kp3 → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        |  \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        |    \--- className = android.widget.TextView → text = 检查新版本 → id = android:id/title → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    |        \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |    \--- className = android.widget.LinearLayout → text =  → id = com.tencent.mm:id/its → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  |      \--- className = android.widget.ImageView → text =  → id = com.tencent.mm:id/isy → description =  → isClickable = false → isScrollable = false → isEditable = false
|        |  |  \--- className = android.widget.TextView → text =  → id = android:id/title → description =  → isClickable = true → isScrollable = false → isEditable = false
|        |  \--- className = android.view.View → text =  → id = com.tencent.mm:id/e5c → description =  → isClickable = false → isScrollable = false → isEditable = false
|        \--- className = android.widget.FrameLayout → text =  → id = com.tencent.mm:id/i1f → description =  → isClickable = false → isScrollable = false → isEditable = false
|          \--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|            +--- className = android.widget.TextView → text = 《软件许可及服务协议》 → id = com.tencent.mm:id/khc → description =  → isClickable = true → isScrollable = false → isEditable = false
|            +--- className = android.widget.LinearLayout → text =  → id =  → description =  → isClickable = false → isScrollable = false → isEditable = false
|            |  +--- className = android.widget.TextView → text = 《隐私保护指引摘要》 → id = com.tencent.mm:id/khb → description =  → isClickable = true → isScrollable = false → isEditable = false
|            |  \--- className = android.widget.TextView → text = 《隐私保护指引》 → id = com.tencent.mm:id/kha → description =  → isClickable = true → isScrollable = false → isEditable = false
|            +--- className = android.widget.TextView → text = 客服电话:400 670 0700 → id = com.tencent.mm:id/exh → description =  → isClickable = false → isScrollable = false → isEditable = false
|            \--- className = android.widget.TextView → text = 腾讯公司 版权所有

最后

  • 这样我们的元素检测小工具就算全部写完了,以后想看某个页面查看节点信息就可以直接打开我们的APP,开启无障碍服务后,启动悬浮窗,打开指定APP某个页面,点一下悬浮窗就可以在控制台看到美化后的具体的节点信息了。

  • 后续如何有需求我们可以把打印的节点信息直接在APP中查看,但是看出节点这种事情还是在PC端大屏上看着才舒服,所以暂时就不展示在手机端喽

  • 刀已磨好,接下来就开始在微信中实操吧

预告

下一篇让我们期待一下【玩转Android无障碍】之小试牛刀,通过一个简单的例子带大家实操一下

相关推荐
烬奇小云3 小时前
认识一下Unicorn
android·python·安全·系统安全
顾北川_野15 小时前
Android 进入浏览器下载应用,下载的是bin文件无法安装,应为apk文件
android
CYRUS STUDIO15 小时前
Android 下内联汇编,Android Studio 汇编开发
android·汇编·arm开发·android studio·arm
右手吉他15 小时前
Android ANR分析总结
android
PenguinLetsGo17 小时前
关于 Android15 GKI2407R40 导致梆梆加固软件崩溃
android·linux
杨武博19 小时前
音频格式转换
android·音视频
音视频牛哥21 小时前
Android音视频直播低延迟探究之:WLAN低延迟模式
android·音视频·实时音视频·大牛直播sdk·rtsp播放器·rtmp播放器·android rtmp
ChangYan.21 小时前
CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法
android·conda
二流小码农1 天前
鸿蒙开发:ForEach中为什么键值生成函数很重要
android·ios·harmonyos
夏非夏1 天前
Android 生成并加载PDF文件
android