[Discussion] Porting BAAS to Android host platform#400
Conversation
|
感谢你的pr
ps: 我仅有在安卓开发java app的经验,有关在安卓进行python和c++开发需要先了解一下才能更进一步讨论 |
关于第2点,正在开发中的新ui的前端是基于react的,并且准备完全分离前后端进行开发,打包方面可以考虑react-native或者其他更有利的框架;关于桌面端则考虑tauri和electron择一,个人偏好体积较小的前者,但这一点有待社区讨论。 |
|
确实采用 Web 方案对移动端的兼容性更好,p4a 已有对 Webview 的支持,可以很轻松的打包。至于 React Native,刚才看了一下,理论上可以整合到 p4a 中,不过需要比较多的修改与自定义。如果 React Native 相对 Webview 没有很大的性能优势,我还是偏向于采用简单的 Webview 方案。 对于兼容 u2,u2 的依赖中不兼容的应该只有 adbutils,因此我会先尝试为 adbutils 做一个兼容层,让 adbutils 底层调用 Shizuku 而不是 adb。这样不仅 u2 能正常使用, BAAS 中其他调用 adbutils 的代码也可以正常执行。 另外非常感谢作者能够配合修改 C++ 部分的代码。 |
|
有关ocr图像数据传递, 虽然共享内存无法使用, 但是ocr_server还提供了直接将图片附在http请求file中的方法, 另外我发现安卓直接运行c++编译后的程序十分困难, 最终应该会用ndk把ocr_server编译为so库使用 |
|
多进程应该只有 OCR 部分用到,现在改用 so 库,所以现在不再需要多进程了。 psutil 只有 installer 和模拟器控制部分用到,这两个在 Android 上都不需要,因此 psutil 也不需要了,只要在代码里条件性 import 即可。 |
|
我成功使用ndk编译了onnxruntime 1.22.1 在x86_64 和 arm64-v8a 两个abi下的动态库, armeabi-v7a, x86 无法成功编译, 不知道影响大不大, 后续会测试动态库可用性 |
|
等 C++ 部分准备好了我就开始研究怎么用 Python 调库🤗。 adb 部分,目前已经给 adbutils 的基本功能写了兼容层,足够支持 u2 setup,并且测试可以正常使用 u2。 测试了截图速度,为 ~0.6s,感觉有点慢。 直接访问 RPC 大概在 0.5s 左右。 不过有个地方有点奇怪。如果我使用 baas 的 u2 initer,那么滑动和点击会无法正常使用。没有任何错误,但是也没有效果。 import uiautomator2 as u2
from core.device.uiautomator2_client import BAAS_U2_Initer
d = u2.connect('baas') # 'baas' 是模拟设备的 serial id
init = BAAS_U2_Initer(d._adb_device, logger)
init.install()
print('Swipe')
d.swipe(0.5, 0, 0.5, 0.7, 3)
print('Swipe End')日志: 如果改用 u2 自己的,那么就没有问题。 import uiautomator2 as u2
from core.device.uiautomator2_client import BAAS_U2_Initer
d = u2.connect('baas')
d._force_reset_uiautomator_v2()
print('Swipe')
d.swipe(0.5, 0, 0.5, 0.7, 3)
print('Swipe End')目前还没搞明白是什么原因。不知道是不是调用方式不对? |
|
我已经成功在原始的C++代码上使用NDK编译构建了jni库 相关代码在此分支 |
应该没必要单独一个 apk,这样分发和管理都不太方便。一个 app 内可以同时包含 activity 与 service,把 ocr 做成 service,service 能以独立进程运行,这样一个 app 也可以把主体和 ocr 隔离执行。 service 有两种:
现在算是有三种方案:
1 无论如何都是最简单的。可以考虑用 1 或 2,3 实现起来有点复杂。 如果需要内存共享
|
|
抱歉最近有事没有空关注此pr, 后续会跟进 |
This reverts commit dfd37a0.
Java RapidOCR integration is temporary. This will be replaced after C++ ocr_server for Android is ready.
1. ui minor changes // hard coded 2. logging improvement 3. adb server route fixes 4. seperate android main from general main
|
@XcantloadX 我写了一个action自动构建apk 可以从这里下载最近构建的apk, ,我应该按照正确的流程构建apk了,但是程序在模拟器里报错了,可以帮我看一下是什么原因吗 |
|
@pur1fying |
which adds x86_64 support for pyjnius in qt bootstrap
|
@pur1fying 我改了一下 recipe,现在在 AVD 里测 x86_64 的包可以正常运行了 |
I've already written them before in my memory, where is the code ??? TAT
…message ) dlopen failed: cannot locate symbol "__cxa_init_primary_exception" referenced by "/data/data/top.qwq123.boa/files/bin/lib/x86_64/libBAAS_ocr_server.so"...
遇到了点问题,libBAAS_ocr_server.so加载会出现缺少符号 另外我写的另一个demo按同样的写法可以正常运行 |
|
@pur1fying AI 说可能是 NDK 版本兼容问题。如果有必要,可以改改这个文件,用其他的 libc++_shared.so。 |
另外我比较关心的一个问题是安卓平台怎么更新, 能不能通过直接改变files/app中源码进行更新 |
|
Python 文件应该可以直接通过改 files 来热更新,不过 p4a 貌似多加了些元数据,暂时不清楚这个有什么影响。 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ce48dae3d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| from core.exception import SharedMemoryError | ||
|
|
||
| if host_platform_is_android(): | ||
| shared_memory = None |
There was a problem hiding this comment.
Use a non-shared-memory OCR path on Android
On Android this sets shared_memory to None, but the local OCR client is still treated as non-remote (Baas_thread.set_ocr chooses pass_method 0), so startup reaches check_resolution() → create_shared_memory() → SharedMemory.get(), where _init() dereferences shared_memory.SharedMemory. As soon as the Android OCR server responds 200, this raises AttributeError and prevents the script from starting; switch Android to pass_method 1 or provide an Android shared-memory implementation.
Useful? React with 👍 / 👎.
| logger = Logger(None) | ||
| shizuku = ShizukuClient(logger) | ||
| request_permission() | ||
| shizuku.connect() |
There was a problem hiding this comment.
Wait for Shizuku permission before binding
When the app does not already have Shizuku permission (fresh install or revoked permission), request_permission() returns immediately after MainActivity.requestShizukuPermission() registers an async listener, so this connect() runs before the user can grant the permission and returns False; the result is ignored and the ADB shim starts with i_user_service unset. After the user grants the prompt nothing reconnects, so all shell/screencap operations fail until restart.
Useful? React with 👍 / 👎.
| last_rank = friend_data['last_total_assault_rank'] | ||
| _min = self.config.clear_friend_last_total_assault_rank_limit | ||
| if _min != -1 and last_rank is not None: | ||
| if last_rank <= _min: |
There was a problem hiding this comment.
Delete ranks worse than the configured limit
With clear_friend_last_total_assault_rank_limit enabled, this comparison deletes friends whose rank number is better than or equal to the limit, while keeping worse ranks. For example a limit of 50000 would delete rank 1000 and retain rank 80000, which is the opposite of the documented “outside the limit” cleanup and can remove high-value friends.
Useful? React with 👍 / 👎.
| for box, index in zip(boxes, indexes): | ||
| self.logger.info(f"Index [ {index} ], Type {box[1]}, X = {box[0]}") | ||
|
|
||
| _d_min, _d_max = indexes[0], indexes[0] # detect minimum and maximum |
There was a problem hiding this comment.
Retry when OCR yields no usable stage indexes
When OCR finds stage boxes but every recognized number is filtered out by _min <= number <= _max (for example OCR returns 0 or a number from the wrong page), indexes is empty and this line raises IndexError instead of retrying or swiping. That makes the new final-restriction task abort on a normal OCR miss; handle the empty list the same way as no boxes.
Useful? React with 👍 / 👎.
| self.check_serial() | ||
| self.detect_device() | ||
| # HACK: | ||
| self.serial = 'baas' |
There was a problem hiding this comment.
Preserve configured ADB serials off Android
This unconditional overwrite replaces the user's detected USB/emulator serial with the Android-host mock serial baas for every Android-device run, including normal desktop hosts where that mock ADB server does not exist. After this point package detection and all subsequent ADB calls target baas, so existing PC/emulator usage fails unless the Android shim is running; gate this assignment on host_platform_is_android().
Useful? React with 👍 / 👎.
| ocr_region, | ||
| "en-us", | ||
| "Final Restriction Rls Battle Next Open Time", | ||
| "0123456789/" |
There was a problem hiding this comment.
Allow hyphens in closed-event date OCR
In the closed-state path for Global_zh-tw and Global_ko-kr, the format selected below expects a date like %m-%d, but this OCR call excludes - from the candidate set. Dates such as 06-27 are therefore recognized without the separator or rejected, and datetime.strptime(f"{year}/{text.strip()}", fmt) raises instead of updating next_time.
Useful? React with 👍 / 👎.
| if r_min <= self.latest_img_array[int(y * self.ratio)][int(x * self.ratio)][2] <= r_max and \ | ||
| g_min <= self.latest_img_array[int(y * self.ratio)][int(x * self.ratio)][1] <= g_max and \ | ||
| b_min <= self.latest_img_array[int(y * self.ratio)][int(x * self.ratio)][0] <= b_max: | ||
| if self.ratio != 1.0: |
There was a problem hiding this comment.
Cast RGB coordinates before indexing
When self.ratio is exactly 1.0, this branch leaves x and y uncast, unlike the previous implementation. Some call sites still feed dynamically computed medians into RGB features (for example friend cleanup positions), so an even-sized match group can produce 1100.5 and NumPy indexing then raises instead of checking the pixel; cast coordinates regardless of ratio.
Useful? React with 👍 / 👎.
| st.isDir = f.isDirectory(); | ||
| st.size = f.exists() ? f.length() : 0; | ||
| st.mtime = f.exists() ? f.lastModified() : 0; | ||
| st.mode = null; // Cannot get directly |
There was a problem hiding this comment.
Return mode bits from Shizuku stat
On Android-host Global initialization, _get_android_device_ocr_language() pulls DeviceOption via self.u2._adb_device.sync.stat(src) and immediately evaluates src_file_info.mode & 32768; because the Shizuku fsStat response always sends mode = null, the sync layer either cannot serialize a valid stat or returns a None mode that crashes the bit test. Populate file/dir mode bits here so adb sync/stat callers can distinguish regular files.
Useful? React with 👍 / 👎.
| else: | ||
| if np.count_nonzero(state[i] == 0) == FINAL_RESTRICTION_RLS_MAX_CHARACTER_COUNT: # all empty | ||
| st += " <-- Empty" | ||
| ret = -1 |
There was a problem hiding this comment.
Don't let empty rows override valid clear units
When the clear-unit list has a valid row followed by an empty row, this assignment overwrites the previously selected valid index with -1. select_and_copy_clear_unit() treats -1 as “all teams empty” and skips the fight, so accounts with only one or two recorded clear teams cannot use them; only return -1 if no valid row was found.
Useful? React with 👍 / 👎.
| if stat and stat.exists and stat.isDir: | ||
| raise IsADirectoryError(f"Is a directory: {path}") | ||
|
|
||
| # NOTE: shizuku fs_write does not support setting file mode. |
There was a problem hiding this comment.
Apply requested sync modes when pushing executables
The ADB sync layer passes the requested file mode into open_for_write, and first-time Android setup pushes /data/local/tmp/atx-agent with mode=0o755 before executing it. This implementation discards that mode, so files created by fs_write keep the default non-executable permissions and check_atx() cannot start atx-agent after an install or upgrade; chmod the path according to mode after writing.
Useful? React with 👍 / 👎.








Baas on Android
本 PR 将讨论将 BAAS 移植到手机上,完全脱离电脑运行的可能性,以及我目前验证与移植的进度。
(此 PR 目前仅作讨论,暂不合并)
为什么要移植到 Android 上
根本原因是我想为某个游戏编写脚本,但是那个游戏禁止在模拟器运行。一直以来我都在思考将使用 Python 编写的游戏脚本移植到 Android 上的可能性。最近我认为思路已经足够,因此我决定先以 baas 为例开始实验。
移植到 Android 上可能的优点:
可能的缺点:
思路
下面我将从几个方面分别讨论移植的思路与方案。
Python 本体
流行的 python android 方案有两种:
原本打算选择 2,因为更灵活,根据我下面的思路可能需要大量自定义 Java 代码。但是受限于 GUI 方案,这里只能选择 1。
值得注意的是,p4a 默认提供 Python 3.11,而 BAAS 使用 3.9。考虑到时间成本,这里暂定使用 3.11 版本,如果出现问题,后续再考虑能否可以改用 3.9。
GUI
BAAS 的 GUI 为 PyQt5 + pyqt fluent widgets。经过一番搜索,p4a 支持 PySide6,chaquopy 不支持任何 Qt 框架。尽管 PyQt5 似乎也可以部署到 Android 平台,但是资料久远且稀少,因此我决定将 BAAS 的 GUI 先行移植到 PySide6 上,再采用 p4a 方案。
目前使用的方案是:使用 qtpy + monkey patch。
qtpy 是一个 Python Qt 兼容层,抹平了 PyQt5/6 与 PySide6 之间的 API 差异。在引入 qtpy 后,通过 monkey patch,将对 PyQt5 的调用全部转发到 qtpy 上,然后由 qtpy 转发到 PySide6 上。
对于 pyqt fluent widgets,它本身就有 PySide6 版本,因此不需要做太多改动。
具体代码见 patch_window.py。
如果后续有机会,可以考虑将 BAAS 的 GUI 重构到 PySide6 或 qtpy 上。
OpenCV/numpy
p4a 支持 OpenCV 与 numpy。
OCR
BAAS 的 OCR 采用 IPC + 共享内存方案,但是很不幸,多进程与内存共享在 Android 上都不原生可用。如果坚持这种方案,可能需要大量修改 C++ 与 Python 代码,以采用 Android 平台上专有的内存共享方案。
因此,短期来看,我的解决方案是舍弃现有 ocr_server,改用 RapidOCR 的 Android 库,对其进行封装,让 Python 可以调用;长期来看,可以考虑(至少在 Android 上)舍弃 C++ 的多进程通信方案,做成 Python native 扩展。
adb
通常游戏脚本会大量依赖 adb 命令,特别是 adb shell 命令。一般情况下 adb shell 命令只有借助 adb 才能执行,无法在 Android 自身上执行,因为权限不够。
但是借助 Shizuku,普通的 Android APP 也可以拥有 shell 权限,因此脱离电脑执行 adb shell 命令是理论上可行的。
分辨率
目前有两种方案可以在手机上模拟出 1280x720 分辨率:
方案 1 会更简单,而且对现有控制代码的兼容性会更好,因为不涉及多显示器的处理。
但是采用方案 1,脚本执行时 BAAS 会被置于后台,存在被杀后台的风险,而方案 2 不存在这个问题。而且方案 2 理论上可以让用户在挂机的同时,干其他任何事情,只要手机性能允许。
截图
方案 2 在速度与性能上最佳,基本上录屏与投屏类 APP 都使用 MediaProjection API,不存在性能问题。
但是考虑到控制的问题,因此可能暂时会采用方案 3。
控制
方案 1 最简单;方案 2 功能最强大,但是需要用户手动授权,而且授权很容易掉,目前不清楚是否可以通过 shell 完成。
考虑到 #395 (comment) ,方案 3 如果可行,可能是目前的最佳选择。
其他
关于代码修改,根据上面的思路,会有很多地方需要针对平台特判。如果
进度与问题
GUI
目前我已成功移植 GUI,并且大部分功能正常可用,除了:
截图:



adb
目前已成功验证通过 Shizuku 调用 shell 命令可行。
下面的视频演示了 input swipe 命令。可以看到速度还是不错的,点击按钮后几乎瞬间就执行了滑动。
e9ec73c3a3bf925b2a8d4f460ae00018.mp4
分辨率
根据上面提到的 issue,已经有人成功验证了通过 wm size 修改分辨率可行。
控制
根据刚才的演示视频,input 方案可行。