使用ADB获取安卓任务缩略图

前言

参考的文章

通过 ADB 获取安卓任务截图,这篇文章可能是 2022 年唯一能用的方案,目前测试了安卓12和安卓11,根据 shell.apk 这个系统 apk 自带的权限,想在任何版本中获取到后台任务的缩略图,应该都是可行的。

我加了可能哈

起初不太想写有关这类的文章,但竟没想到实现这样一个简单的东西竟如此坎坷,正当我海量浏览 Google 来的资料时,学校啪的一下给我寝室电断了。

感觉翻了整个google,唯一有用的文章是

首先 AcitvityManager,DisplayManager 这些都是安卓的 services,通过 context 的 getSystemService 传入 key 即可获取,而这一系列的 manager 其实都是 wrapper,在设计模式中叫装饰器模式,内部其实都是调用的对应的 ManagerServices,而这个 Services,可以反射出来,这部分的知识来自 scrcpy 的 server,代码参考ServiceManager.java,所以在没有 app 运行的 dex 中,我们可以反射出我们想要使用的 Services。

安卓任务的缩略图保存在 ,但是文件及文件夹的上下文都是 system,即使是 shell,也没法直接通过文件读写获取到缩略图。

开始分析

为了检测在 java runtime 中反射出的对象,是否有我们需要的函数,写了一个简单的打印类的工具,如下

文中所指向 getTaskSnapshot 这个方法是来自ActivityManager.getService() 内的,但是,随着安卓的更新,这个函数换!位!置!了!但是这篇文章给到我很大的启示。
IActivityManager.java中还有 getTaskThumbnail 方法,后来也没了。

再想一下,在我们的安卓设备中,哪一个app是能够获取到后台任务缩略图的?

那就是系统自带任务管理,这个任务管理无非权限高一点,它依然是一个独立的安卓 app,只要它能获取到,我们在 shell 上下文的加持下,也一样能获取到。
后来直接从安卓源码入手,基于以前的反编译经验,可以很快知道安卓的后台管理页面都在 SystemUI.apk 中 ,在安卓中对应的包就是SystemUI

通过 github 的代码搜索,我们也能很快能在ActivityManagerWrapper.java 找到 getTaskThumbnail 函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @return the task snapshot for the given {@param taskId}.
*/
public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
ActivityManager.TaskSnapshot snapshot = null;
try {
snapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, isLowResolution);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve task snapshot", e);
}
if (snapshot != null) {
return new ThumbnailData(snapshot);
} else {
return new ThumbnailData();
}
}

关键代码如下

1
snapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, isLowResolution);

我们发现 getTaskSnapshot 这个函数了来自 ActivityTaskManager 的 Services ,通过源代码ActivityTaskManager.java 我们可以找到 getService() 函数,是一个静态函数,也就是说我们可以直接反射来用。

如下

1
2
3
Class<?> cls = Class.forName("android.app.ActivityTaskManager");
java.lang.reflect.Method services = cls.getDeclaredMethod("getService");
Object itm = services.invoke(null);

这个 itm 其实就是一个 IActivityTaskManager。而我们再通过反射工具类来看一下它的函数

的确是有 getTaskSnapshot 函数的,接着我们获取 TaskSnapshot 对象

如下

研究到这儿我以为已经结束了,但我拿到的是一个 TaskSnapshot 对象,源码定义在TaskSnapshot.java,那我们怎么获取图片字节流呢?

构造Bitmap

我们顺利的通过反射得到 TaskSnapshot 后,但是如何借助 ADB 将这个玩意传出去呢,看了它所有的属性,好像都用不上,于是我开始尝试将它转换为 bitmap 再获取字节流。经过上面文章的指引,我开始尝试构造一个bitmap,当我把文中的这行代码放到我的IDE后

1
Bitmap bmp = Bitmap.createHardwareBitmap(buffer);

很不意外的报错了,有可能是 SDK 版本导致的 api 的 break change,但是最后发现 Bitmap 有一个 wrapHardwareBuffer 函数,原型如下

1
public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer, @Nullable ColorSpace colorSpace);

于是我开始找 TaskSnapshot 中可用的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@UnsupportedAppUsage
public GraphicBuffer getSnapshot() {
return GraphicBuffer.createFromHardwareBuffer(mSnapshot);
}
/**
* @return The hardware buffer representing the screenshot.
*/
public HardwareBuffer getHardwareBuffer() {
return mSnapshot;
}
/**
* @return The color space of hardware buffer representing the screenshot.
*/
public ColorSpace getColorSpace() {
return mColorSpace;
}

其中 getHardwareBuffer 函数在安卓11没有安卓12有。
为了兼容,使用一下代码反射出对应的对象

1
2
3
Object buffer = snapshot.getClass().getMethod("getSnapshot").invoke(snapshot);
Object hardBuffer = Class.forName("android.hardware.HardwareBuffer").getMethod("createFromGraphicBuffer", buffer.getClass()).invoke(null, buffer);
Object colorSpace = snapshot.getClass().getMethod("getColorSpace").invoke(snapshot);

最后再构造一个 bitmap

创建服务端

我们能够很轻松的通过 adb 建立端口的转发。
最后基于 HTTP 实现,使用NanoHttp这个微型框架,来提供可以获取缩略图的服务端。
将 java 代码编译成 dex 后,push 到设备上再用 app_process 执行,服务就启动了,用浏览器就能直接获取任务缩略图。
如图

最后,一个分布式的任务管理器不就来了吗。
在电脑上管理手机应用
在手机上管理另一台手机的应用

之前看到过的写得很好的一句话
“这就是所谓的劣币驱逐良币罢。写一篇好的技术文章需要大量的成本取找资料去实
践,而且写得越深入,门槛就越高,受众就越小。
然而水一篇晒工资,晒房子,感情八卦的文章却没
什么难度,而且最能戳中每个人心中的 g 点,或
意淫幻想,或愤愤不平。
然后这种事情如同滚雪球一样,越滚越大,然后吸
引一些臭味相投的人。
这是个娱乐至死的年代,一个逐利的社区为了流量,自然需要这样的内容来充实自己的运营数据。掘金
如此,知乎亦如此。”

技术不应该只是为了流量,技术的价值在于技术本身~

作者

梦魇兽

发布于

2022-03-16

更新于

2023-03-12

许可协议

评论