问一下,利用在线 DeepSeek 等 API 服务实现一个答题 APP

news/2025/2/12 4:11:33/文章来源:https://www.cnblogs.com/puzhiwei/p/18706422

简介

这是一个利用 Android 无障碍功能 + 悬浮窗 + 大模型的搜题应用

原理就是利用无障碍读取屏幕内容,然后通过悬浮窗来显示答案

众所周知我是一个学渣,所以在搜答案方面颇有成就

大概是在 4 年前,我写了这样一个脚本

GitHub:截图OCR识别后搜索题目获取答案

码云:截图OCR识别后搜索题目获取答案

利用 ADB 对屏幕截图后进行 OCR 识别,然后将识别到的结果用搜索引擎和本地题库进行搜索,然后快速获取答案

前几天,看着我手机里面的李跳跳和 DeepSeek,我突然发现,我可以利用无障碍读取屏幕数据,将读取到的题目发送给 DeepSeek 等大模型进行解答,利用 Android 悬浮窗 来显示答案

说干就干,感觉代码不是特别多,于是就有了这个项目

因为没有做历史记录,提问每次只能问一下,所以这个 APP 就叫问一下了

运行展示

https://www.bilibili.com/video/BV1PrNweHEDX

源码

GitHub:https://github.com/PuZhiweizuishuai/ScanSearch

码云:https://gitee.com/puzhiweizuishuai/ScanSearch

注意:无障碍权限属于敏感权限,请确认软件来源后再安装或者自己编译安装,避免造成损失

技术实现

无障碍

首先到 AndroidManifest.xml 配置无障碍服务

        <!-- 注册无障碍服务 --><service android:name=".service.ScreenReaderService"android:exported="true"android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"><intent-filter><action android:name="android.accessibilityservice.AccessibilityService" /></intent-filter><meta-dataandroid:name="android.accessibilityservice"android:resource="@xml/accessibility_service_config" /></service>

创建 app/src/main/res/xml/accessibility_service_config.xml 无障碍配置文件

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeAllMask"android:accessibilityFlags="flagDefault"android:accessibilityFeedbackType="feedbackGeneric"android:canRetrieveWindowContent="true"android:notificationTimeout="100" />

编写 无障碍服务代码

package com.buguagaoshu.scan.search.service
import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.buguagaoshu.scan.search.config.StaticVariableConfig
import com.buguagaoshu.scan.search.data.ScanSearchData
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuidclass ScreenReaderService : AccessibilityService() {// 指定按钮的包名和类名,需要根据实际情况修改private val targetButtonPackageName = "com.buguagaoshu.scan.search"private val targetButtonClassName = "androidx.compose.material3.Button"// 指定按钮的文本内容,需要根据实际情况修改private val targetButtonText = "读取屏幕"override fun onAccessibilityEvent(event: AccessibilityEvent) {if (!StaticVariableConfig.openScan) {return}if (event.packageName == targetButtonPackageName) {return}// 监听滑动事件if (event.eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED || event.eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {// 判断事件类型是否为点击事件val source = event.source// 检查点击的节点是否符合目标按钮的条件if (source != null) {// TODO 增加指定包名过滤配置println(source.packageName)}// 获取根节点信息val rootNode = rootInActiveWindowif (rootNode != null) {// 清除之前的数据StaticVariableConfig.screenTextList.clear();// 遍历节点并读取内容traverseNodeLoop(rootNode)}}}@OptIn(ExperimentalUuidApi::class)private fun traverseNodeLoop(node: AccessibilityNodeInfo) {val stack = mutableListOf<AccessibilityNodeInfo>()stack.add(node)while (stack.isNotEmpty()) {val currentNode = stack.removeAt(stack.size - 1)// 读取节点的文本内容val text = currentNode.textif (text != null && text.isNotEmpty()) {// 存储数据StaticVariableConfig.screenTextList.add(ScanSearchData(Uuid.random().toString(), text.toString()))}// 遍历子节点并将它们添加到栈中for (i in currentNode.childCount - 1 downTo 0) {val child = currentNode.getChild(i)if (child != null) {stack.add(child)}}}}private fun traverseNode(node: AccessibilityNodeInfo) {// 读取节点的文本内容val text = node.textif (text != null && text.isNotEmpty()) {println(text)}// 遍历子节点for (i in 0 until node.childCount) {val child = node.getChild(i)if (child != null) {traverseNode(child)}}}override fun onInterrupt() {// 服务中断时的处理}
}

监控滑动事件,跳过对自身 APP 的监控,将读取到的屏幕数据保存到缓存中,方便后期读取加载

流式响应

因为目前兼容 Open API 的服务都支持流式输出,提升用户体验,避免出现长时间的等待

所以在使用 okhttp 发送请求的时候不能使用 call.execute(),而需要使用 client.newCall(req).enqueue

完整代码实现如下

    fun sendStream(sendData: SendData,url: String,key: String,onChunkReceived: (String) -> Unit,onComplete: () -> Unit,onError: (String) -> Unit) {CoroutineScope(Dispatchers.IO).launch {val body = Json.encodeToString(sendData).toRequestBody(contentType)// Authorization: Bearer $DASHSCOPE_API_KEYval req = Request.Builder().url(url).post(body).addHeader("Authorization", "Bearer $key").build()client.newCall(req).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {e.message?.let { onError(it) }}override fun onResponse(call: Call, response: Response) {response.use {if (!response.isSuccessful) {return}val reader = response.body.charStream()reader.forEachLine { line ->if (line.isNotBlank()) {onChunkReceived(line)}}onComplete()}}})}}

其它

UI实现大部分都是通过AI写的,也没有什么要注意的了

使用指南

一、配置无障碍权限与悬浮窗权限

第一次使用会弹出无障碍权限配置菜单

在已下载应用内点击问一下

给予问一下无障碍权限

无障碍权限

二、配置 API 服务商

首先你需要配置好你的 AI 服务商,当前你也可以不用配置这个,直接使用悬浮窗内打开网页进行搜索,不过由于 Android Webview 控件我不太会用,显示的效果有问题

由于 DeepSeek 的服务目前用不了,暂时用阿里通义的 API 替代

API申请地址:https://platform.deepseek.com/usage

登陆后点击侧边栏 API keys 生成一个 API_KEY

申请API keys

然后到 APP 内填写你需要调用的大模型名称、 API 地址、和 API-KEY 即可使用

填写配置

其它可以白嫖的API服务地址

字节火山:https://www.volcengine.com/product/doubao

目前免费送 50 万 TOKE,支持满血 DeepSeek R1 模型

阿里通义:https://www.aliyun.com/minisite/goods?userCode=4i6gwidx

免费送 100 万 TOKEN

三、开始使用

打开悬浮窗后,进入你要搜索的应用

由于只监听了滑动事件,所以进入应用后请先在屏幕上划两下,然后再点击加载数据

这是应该就可以读取到屏幕上显示内容了

进入应用

将你要搜索的题目进行勾选

点击确定

这样题目就会自动出现再搜索框

搜索框

⚠️注意:如果需要对题目进行编辑,请先点击打开 ⌨️ 键盘获取焦点,不然无法输入,修改完成后请点击关闭键盘,读取屏幕数据会无法读取到当前屏幕信息

为避免滑动事件冲突,如果需要挪动窗口位置,请先点击右上角的锁,挪动完成后,再点击一下锁就可以再次滑动显示内容

最后点击提问即可获取答案

展示答案

四、其它功能

点击打开网页可以调用 秘塔AI搜索,不过由于显示界面太小,所以显示会有一些问题

web AI搜索

点击最小化按钮可以缩小悬浮窗,这时你可以利用手机系统自带的 AI 功能对屏幕进行识别,获取问题信息

版权

本文首发于:https://www.buguagaoshu.com/archives/wen-yi-xia
转载请注明出处

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/881330.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Window逆向之x86 ShellCode入门

免责声明: 由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!前言 不少人对于ShellCode的认知是很浅的,只知道它是一…

【字符串、栈】string转double

stod函数 将string转为double string t = s.substr(i, j - i); double num = stod(t);例题:货币单位换算样例1 输入 2 20CNY53fen 53HKD87cents输出 6432说明: 20元53分+53港元87港分,换算成人民币分后汇总,为6432 样例2 输入 1 100CNY输出 10000说明: 100CNY转换后是1000…

ceph 16.2.15(Pacific)编译

目录获取ceph源码编译拉取submodule网络问题安装依赖do_cmake.sh编译vstart启动问题编译dashboard安装nodejs方法一 下载编译好的源码包方法二 nvm安装node(推荐)编译nodeenv其他boost下载慢总结以下流程在ubuntu22.04 和 openEuler20.03 都实际操作过获取ceph源码 从https:/…

【第四期书生大模型实战营】第2关 L0G2000 Python 基础知识

任务 任务概览任务类型 任务内容 预计耗时闯关任务 Leetcode 383(笔记中提交代码与leetcode提交通过截图) 20mins闯关任务 Vscode连接InternStudio debug笔记 10mins可选任务 pip安装到指定目录 10mins作业总共分为三个任务,两个闯关任务均完成视作闯关成功。 请将作业发布到知…

Redis 持久化策略及其优缺点

原文:Redis 有哪 2 种持久化方式?分别的优缺点是什么?,补充了 Redis 默认的持久化配置Redis 的读写操作都是在内存中,所以 Redis 性能才会高,但是当 Redis 重启后,内存中的数据就会丢失,那为了保证内存中的数据不会丢失,Redis 实现了数据持久化的机制,这个机制会把数…

Innotop:一款MySQL监控工具

在现代数据库管理中,MySQL作为广泛应用的开源关系数据库,已成为各类企业、开发者和数据库管理员(DBA)日常工作中不可或缺的工具。然而,随着数据库规模的增大和查询量的增加,MySQL服务器的性能监控变得尤为重要。为了确保数据库的高效运行和及时排除潜在问题,DBA们需要依…

Windows本地部署deepseek(小白向)

下载Ollama官网下载:ollama.com 点击Download选中Windows版本,点击Download for Windows下载是需要跳转github的,如果无法访问,需要尝试使用github加速器 推荐使用加速器FastGithub(免费,开源) 这里提供国内清华镜像下载 https://cloud.tsinghua.edu.cn/d/df482a15afb64…

SSE、EventSource了解

EventSource接口是web内容和服务器发送事件通信的接口 一个EventSource实例会对HTTP服务器开启一个持久化的连接,以text/event-stream格式发送事件 该连接会一直保持开启直到调用EventSource.close()关闭EventSource是一种Web API,用于建立和服务器之间的【单向持久化】连接 …

基于GD32的简易示波器

基于GD32的简易示波器项目学习 根据立创训练营项目:[简易数字示波器设计(入门版) - 立创开源硬件平台(https://oshwhub.com/course-examples/yi-qi-yi-biao-jian-yi-shu-zi-shi-bo-qi-she-ji-cha-jian-ban) 技术点:原理图绘制,PCB设计,打板,焊接。外部中断,定时器中断,…

DeepSeek本地化部署(Windows)

DeepSeek本地化部署(Windows) 安装 Ollama https://ollama.com/download/windowsCMD部署deepseek模型 https://ollama.com/ 网址中,在网页上方搜索框中输入 Deepseek-r1 1.5b: 需要1.1GB显存 7b: 需要4.7GB显存 8b: 需要4.9GB显存 14b: 需要9.0GB显存 32b: 需要20GB显存 70b…

第三章典型园区网的业务部署

第三章典型园区网的业务部署STP阻塞冗余的链路来避免环路形成,然后当链路故障时,STP可以快速启用被阻塞的链路,恢复连通性。

网站集成微信公众号(订阅号)登录

前一阵子,想着给我的站点集成一个微信登录,因为我之前从未有过微信相关的开发,所以我自己跟着网上的资料,一步一步的慢慢的摸索,过程不免的遇到了许多坑,才把我的网站微信登录集成完成,所以这里分享一下我的摸索的过程。因为我的是订阅号,所以一下的内容均针对订阅号而…