Android API Server 开源分享
前言
好久不见,我是梦魇兽,距离我上一次写文章已经是3年前
这篇会当我回归技术社区的一个开篇吧
其实中间我还一直在自己的博客写文章,虽然频率不高
本人背书:
本科大二收到比较多大厂的面试邀请,大三下滴滴实习,大四上拿到正式 Offer,入职半年 D5 升 D6,毕业后正式工作了一年多后裸辞,前滴滴高级架构
(也许这也是一个噱头,但我会尽可能减少)
如果你是目前正在滴滴工作,可以在内网社区看到我离职的文章
这是一篇一样的在自己博客的文章 再见滴滴研发,你好梦魇兽
简单说,还是因为我感受到这个环境的不公,我厌恶这一切
其中很多想讽刺的内容,写不出来,也是因为所谓的不公
是速享、无界、ADB KIT、Code FA 等软件的开发者
现有开源代码大概在 10W 行+
目前裸辞已经半年多了
今天分享一个开发挺久的一个库,Android API Server
(跟随流量至上的原则,或许是这样的标题,比 Shizuku 还强?历经6年开发,吐血开源!)
背景
这是一个长期在自用的库,但后来发现它的应用越来越广,涉及到我的 FastShare、ADB KIT、Uncon 等
最近几天对整体架构进行了重构
起初这只是一个很小的库,在很早期我需要在 Flutter 侧获得 Android API 的一些信息时,需要编写大量的 MethodChannel
并且随着后来 ADB KIT、Uncon 等在功能上的需求,我需要在 PC 端也能获取到这些信息
并且我希望在使用上,不管是从 PC 端获取,还是安卓本地获取,都能保持高度一致
于是经过比较大量的 Scrcpy 和 Shizuku 这两个我最仰慕的开源项目的学习
才有了 Android API Server(AAS) 这样的解决方案
后来发现对这个库的需求越来越多,例如支持快速集成到我的速享、ADB KIT、Uncon 等
以及支持 Dex 模式等
在今天对整体架构重新进行了设计,正式作为一个对外的开源
也许听起来很复杂,没关系,我们后面会通过简单的示例代码来看一下他的功能
Android API Server 介绍
AAS 是一个为 Android 设备提供 RESTful API 的服务器。它基于 HTTP 协议,可以被任何支持 HTTP 的客户端访问。它设计轻量且易于使用,支持热插拔,你可以通过很简短的代码,来让 AAS 加载你自定义的插件
支持上层框架为 Web 或者 Flutter 或者其他任意不能直接访问 Java 的框架中使用
例如在 Flutter 中,我们几乎需要使用 MethodChannel 来访问安卓的 API
使用 MethodChannel 实现后,无法支持在 Flutter Web 中访问安卓的 MethodChannel
AAS 提供了封装好的开箱即用的 Flutter Plugin,或者你可以根据 API.md 实现任意语言编写的客户端
功能特性
- RESTful: 通过 HTTP 协议,获取安卓 API 相关的信息
- 插件化: 通过简单的代码编写,可实现自定义插件的支持
- 内置 API: 内置开箱即用的在 Dex 中获取 Context、Services 的各种 API
- 内置插件: 内置多个插件,例如获取应用列表、应用图标、创建虚拟显示器等
- Flutter Plugin 支持: 只需要引入 Flutter 依赖,AAS 会随插件的注册而启动,在 Flutter 侧只需要调用 Dart API 即可
- 多种模式支持: 支持 Activity Mode 与 Dex Mode
- 安全: 有一个简单的鉴权,来防止端口扫描恶意调用
架构图
对上层的应用来说,只有 Address 和 Port 的感知,它不在乎对方是哪种模式运行的
你可以在任何地方,任何设备上,通过 HTTP 获取安卓的信息
由于 HTTP 并不安全,所以 AAS 内置了一个简单的接口鉴权,来防止端口扫描恶意调用
从示例代码来理解 AAS
这是 Flutter 编写的示例代码,展示了内置的一些插件和 API 的使用
基于 HTTP 的好处是,你可以通过这样的代码来获取一个 App 的图标(Flutter)
1 | AASClient aasClient = AASClient(); |
AASClient 是多实例,所有的 API 被封装到 AASClient 下
多实例可以让同一个页面加载不同设备的信息,例如 Uncon
获取所有应用信息代码
1 | AppInfos infos = await getAllAppInfos(isSystemApp: false); |
这看起来没什么,但我们可以将这个 Example 编译到 Mac 端
我们只需要更改端口号
1 | AASClient aasClient = AASClient(port: Platform.isMacOS ? 15000 : null); |
这就是在 API 使用上的一致性,我有各种同时运行在 App 内的页面,只需要更改端口号,就可以运行在 PC 端
当然 PC 端需要以 Dex Mode 启动 AAS
并且在这种模式下,我们可以有更多的 API 支持
可以获取到安卓的后台截图,这个功能应该是极少见到的
示例代码中包含了所有的 API 使用方法,示例代码是最好能理解 AAS 的方式
完整代码见 Flutter Example
启动模式介绍
通过这些例子你应该发现了,PC 端加载对应页面的时候,安卓端的服务是怎么启动的?
AAS 有两种启动模式
Activity Mode
这种情况下,AAS 拥有真实的 Activity Context,对于获取应用列表,同普通安卓本身访问 API一样,需要申请权限
Dex Mode
启动脚本在 build_and_run.sh
这种模式,会先将 java 编译成 class,再由 dx 或 d8 工具转换成 dex 文件
通过 adb 运行 app_process 启动 dex
这种模式带来的好处是,我们能使用的权限更多,例如获取后台任务缩略图,创建虚拟显示器(带 Group 的)
所有 java 的权限为 shell(uid 2000),你无需再为获取应用列表,创建虚拟显示器等单独申请权限
我们可以通过为连接到 PC 的设备启动这个服务,再通过 adb forward 获得通信的端口(auto.sh 中带有端口转发)
接下来,你仍然只需要像这样就获得 App的图标
1 | AASClient aasClient = AASClient(port: port); |
你还可以自己实现各种各样的 API ,来获得远超 adb 命令行的功能,例如图标,后台应用截图,adb 命令本身不支持
在 Flutter 中使用
提供 android_api_server_client
来快速的让 Flutter App 拥有这个能力,无需手动启动服务,AAS
随 Flutter Plugin 注册而启动,直接创建 AASClient
则会使用 Flutter Plugin 中启动的端口
1 | dependencies: |
然后直接使用封装好的 Dart API
1 | AASClient aasClient = AASClient(); |
如果你需要在 PC 上访问同样的接口,通过 Dex Mode,你只需要更改端口
1 | AASClient aasClient = AASClient(port: 15000); |
假如我目前有一个 Flutter 编写的展示应用列表的界面是这样
现在我想这个界面在 PC 上展示,亦或者在 Web 中展示
启动 Dex 后,我只需要修改端口号即可
1 | AASClient aasClient = AASClient(port: Platform.isMacOS ? 15000 : null); |
实际上,这样的模式已大量的在无界、速享、ADB KIT中使用
其中的文件管理器、应用列表、任务列表,都是完全的同一份代码,仅仅是端口号不一样
在原生安卓中使用
根据仓库的 Tag 版本,引入对应的依赖
1 | implementation 'com.github.nightmare-space.android_api_server:aas_integrated:v0.1.27' |
启动服务
1 | AASIntegrate aasIntegrate = new AASIntegrate(); |
所以通过这两种模式的了解,在 PC 端获取安卓的信息,通常需要 ADB KIT 或者 Uncon 中一样,需要处理 Dex 的启动流程
详见 ADB KIT
使用 AAS API 以及自定义插件
自定义一个插件
这是获取安卓后台快照的完整插件
1 | public class ActivityTaskManagerPlugin extends AndroidAPIPlugin { |
相关解释
route
方法返回的是这个插件的路由handle
方法是这个插件的处理方法,这里是获取后台任务的缩略图,返回对象参考NanoHTTPD.Response
如果是同一类插件,建议只实现一个 AndroidAPIPlugin
然后增加 param
来区分,例如 action
调用隐藏 API
1 | Object result = ReflectionHelper.invokeHiddenMethod(Object object, String methodName, Object... args); |
获取隐藏字段
1 | Object result = ReflectionHelper.getHiddenField(object, "$name"); |
获取 Context
1 | Context context = ContextStore.getContext(); |
解释一下为什么放在了单例里面,由于 AAS 有两种模式,两种模式下 Context 的获取方式是不一样的,Activity Mode 是直接储存的 Activity Context,Dex Mode 是通过反射获取的 Context
获取 Service
ServiceManager
来自 aas_hidden_api
1 | ServiceManager.getService("activity_task"); |
例如我们要一个 PackageManager 的 Service
IPackageManager
来自 aas_hidden_api,PackageManager
是系统的 API
1 | IPackageManager pms = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); |
这两种方法的选择要根据场景来,如果 IXManager
能实现需求,就用这种,因为在 Dex Mode 中,Context 是不完整的,但是例如 DisplayManager
这种,IDisplayManager
的 API 没有 DisplayManager
好用
更多获取方法详见 SystemServerApi
构建实例
1 | AndroidAPIServer server = new AndroidAPIServer(); |
注册插件
1 | server.registerPlugin(new PackageManagerPlugin()); |
AAS Intergated 代码
1 | public class AASIntegrate { |
开源仓库介绍
- ass: 这是框架本身,是一个壳,不带任何插件,你如果需要使用这样的模式,只需要依赖这个
- aas_hidden_api:一个优雅的可以访问 Android 隐藏 API 的解决方案,通过 compileOnly 依赖到 aas_plugin 和 aas 中
- aas_integrated: 一个集成了 aas_plugin 的库,如果需要现有的一些插件,直接用这个
- aas_plugin: 实现了一些个人项目中会用到的插件,例如 ActivityManagerPlugin、DisplayMnagerPlugin 等
更多场景介绍
文件选择、应用选择(ADB KIT、Uncon、Fast Share)
启动器
可以在 PC 端启动安卓的 App,配合应用流转使用,可实现无需解锁手机,即可在 PC 上运行安卓上的软件
文件预览(Uncon)
视频极速缓冲播放,100G 的文件都能随意拉动进度条
无需安卓安装额外 App,仅需要开启 USB 调试
已有插件介绍
ActivityManagerPlugin
/activity_manager?action=start_activity
: 打开一个 Activity/activity_manager?action=stop_activity
: 关闭 Activity/activity_manager?action=get_app_activities&package=${package}
: 获取 App 的 Activty/activity_manager?action=get_app_detail&package=${package}
: 获取 App 的详细信息/activity_manager?action=get_all_app_info&is_system_app=false
: 获取所有 App 信息/activity_manager?action=get_tasks
: 获取后台的 Task 信息/activity_manager?action=app_main_activity&package=${package}
: 获取 App 的 Main Activity
ActivityTaskManagerPlugin
/task_thumbnail?id=${id}
: 获取 Task 的截图
DisplayManagerPlugin
/display_manager?action=get_displays
: 获取所有的 Display 信息/display_manager?action=create_virtual_display&width=1080&height=1920&dpi=320
: 创建虚拟显示器
PackageManagerPlugin
/package_manager?action=get_permissions&package=${package}
: 获取 App 的权限/package_manager?action=get_icon&package=${package}
: 获取 App 的图标
FilePlugin
/file?action=dir&path=\${dir_path}
: 获取目录下的文件信息/file?action=file&path=\${file_path}
: 获取文件,会直接返回整个文件,用浏览器访问则会预览这个文件,如果是视频则支持极速缓冲播放,支持断点续传
最后
- 没有考虑用 HTTPS,因为有类似于文件的服务,HTTPS 会让访问变慢,增加了一个简单的鉴权,来防止端口扫描恶意调用的情况
- 一点小问题: 小可实力不济,
gradle-8.0
+com.android.tools.build:gradle:8.1.0
组合发布 Jitpack 引入依赖后无法导包,这个组合发到 Jitpack 上,没有 aar,后来用gradle-7.6.4
+com.android.tools.build:gradle:7.4.2
进行的发布
不要给我私信参加任何活动
不要给我任何狗屁创作激励,社区就是被这狗屁激励搞砸的
我也不知道这次回归,能持续多久,也许被某一条评论恶心到,然后再次离开?
世间的变数太多了
几句话与大家共勉
- “偏信则暗,监听则明”
- “道阻且长,行则将至”
- “你看起来很厉害,但一事无成”
我曾一度讨厌任何变成以流量为目的社区
我们编写文章,记录开发历程,分享经验,变成了,我要如何制造噱头,如何编写更有噱头的标题
怎么改文章,才会有更多流量
包括社区各种各样的活动,无一不是在告诉你,你的文章的唯一目的,就是为了更多的流量
其他的技术本身,文章质量,都是狗屁
反驳拉黑
也有一些人说羡慕我现在的生活,但这都是取舍罢了
你想要什么
你准备好失去什么
时间往前,交换进行
世界可总是不公平的,也许你只得到了一点,但你会失去很多
循环往复
下次见
Android API Server 开源分享
http://blog.nightmare.press/2024/12/05/Android-API-Server-开源分享/