你们好,我是老玩童。今天来跟你们分享 TIM 最强保活思路的几种实现方法。这篇文章我将经过 ioctl 跟 binder 驱动交互,实现以最快的方式唤醒新的保活服务,最大程度防止保活失败。同时,我也将跟您分享,我是怎么作到在不甚了解 binder 的状况下,快速实现 ioctl binder 这种高级操做。php
声明:如今这个保活方式在 MIUI 等定制 Android 系统中已经不能保活,大部分时候只能活在模拟器中了。但对于咱们的轻量定制的 Android 系统,一些系统级应用的保活,这个方案仍是有用的。java
随着 Android 阵营的各大手机厂商对于续航的高度重视,两三年前的手机发布会更是把反保活做为一个系统的卖点,不断提出了各类反保活的方案,致使如今想实现应用保活简直难于上青天,甚至都须要一个团队来专门研究这个事情。连微信这种超级 APP,也要拜倒在反保活的石榴裙下,容许后台启动太费电,不容许后台启动就收不到消息。。Android 发现了一个保活野路子就堵一条,然而不少场景是有保活的强需求的,有木有考虑过咱们开发者的感觉,本身人何须为难本身人😭。linux
我以为这是一个 Android 设计的不合理的地方,路子能够堵,但仍是有必要留一个统一的保活接口的。这个接口由 Google 实现也好,厂商来实现也好,总好过如今很笨拙的系统自启动管理或者是 JobScheduler。我以为本质上来讲,让应用开发者想尽各类办法去作保活,这个事情是没有意义的,保活的路子被封了,但保活仍是须要作,保活的成本也提升了,简直浪费生命。Android 的锅。(仅表明我的观点)android
黑科技进程保活原理
大概 2 个月前,Gityuan 大佬放出了一份分析 TIM 的黑科技保活的博客史上最强 Android 保活思路:深刻剖析腾讯 TIM 的进程永生技术 [1](后来不知道什么缘由又删除了),顿时间掀起了一阵波澜,仿佛让开发者们又看到了应用保活的一丝但愿。Gityuan 大佬经过超强的专业技术分析,为咱们解开了 TIM 保活方案的终极奥义。c++
后来,为数很少的维术大佬在 Gityuan 大佬的基础上,发布了博客 Android 黑科技保活实现原理揭秘 [2] 又进行了系统进程查杀相关的源码分析。为咱们带来的结论是,Android 系统杀应用的时候,会去杀进程组,循环 40 遍不停地杀进程,每次杀完以后等 5ms。git
总之,引用维术的话语,原理以下:github
-
利用 Linux 文件锁的原理,使用 2 个进程互相监听各自的文件锁,来感知彼此的死亡。 -
经过 fork 产生子进程,fork 的进程同属一个进程组,一个被杀以后会触发另一个进程被杀,从而被文件锁感知。
具体来讲,建立 2 个进程 p1, p2,这两个进程经过文件锁互相关联,一个被杀以后拉起另一个;同时 p1 通过 2 次 fork 产生孤儿进程 c1,p2 通过 2 次 fork 产生孤儿进程 c2,c1 和 c2 之间创建文件锁关联。这样假设 p1 被杀,那么 p2 会立马感知到,而后 p1 和 c1 同属一个进程组,p1 被杀会触发 c1 被杀,c1 死后 c2 立马感觉到从而拉起 p1,所以这四个进程三三之间造成了铁三角,从而保证了存活率。web
按照维术大佬的理论,只要进程我复活的足够快,系统它就杀不死我,嘿嘿。shell
维术大佬写了一个简单的实现,代码在这里:github.com/tiann/Leoric[3],这个方案是当检测到进程被杀时,会经过 JNI 的方式,调用 Java 层的方法来复活进程。为了实现稳定的保活,尤为是系统杀进程只给了 5ms 复活的机会,使用 JNI 这种方式复活进程如今达不到最优的效果。编程
Java 层复活进程
复活进程,其实就是启动指定的 Service。当 native 层检测到有进程被杀时,为了可以快速启动新 Service。咱们能够经过反射,拿到 ActivityManager 的 remote binder,直接经过这个 binder 发送数据,便可实现快速启动 Service。
Class<?> amnCls = Class.forName("android.app.ActivityManagerNative");
amn = activityManagerNative.getMethod("getDefault").invoke(amnCls);
Field mRemoteField = amn.getClass().getDeclaredField("mRemote");
mRemoteField.setAccessible(true);
mRemote = (IBinder) mRemoteField.get(amn);
启动 Service 的 Intent:
Intent intent = new Intent();
ComponentName component = new ComponentName(context.getPackageName(), serviceName);
intent.setComponent(component);
封装启动 Service 的 Parcel:
Parcel mServiceData = Parcel.obtain();
mServiceData.writeInterfaceToken("android.app.IActivityManager");
mServiceData.writeStrongBinder(null);
mServiceData.writeInt(1);
intent.writeToParcel(mServiceData, 0);
mServiceData.writeString(null); // resolvedType
mServiceData.writeInt(0);
mServiceData.writeString(context.getPackageName()); // callingPackage
mServiceData.writeInt(0);
启动 Service:
mRemote.transact(transactCode, mServiceData, null, 1);
在 native 层进行 binder 通讯
在 Java 层作进程复活的工做,这个方式是比较低效的,最好的方式是在 native 层使用纯 C/C++来复活进程。方案有两个。
其一,维术大佬给出的方案是利用 libbinder.so, 利用 Android 提供的 C++接口,跟 ActivityManagerService 通讯,以唤醒新进程。
-
Java 层建立 Parcel (含 Intent),拿到 Parcel 对象的 mNativePtr(native peer),传到 Native 层。 -
native 层直接把 mNativePtr 强转为结构体指针。 -
fork 子进程,创建管道,准备传输 parcel 数据。 -
子进程读管道,拿到二进制流,重组为 parcel。
其二,Gityuan 大佬则认为使用 ioctl 直接给 binder 驱动发送数据以唤醒进程,才是更高效的作法。然而,这个方法,大佬们并无提供思路。
那么今天,咱们就来实现这两种在 native 层进行 Binder 调用的骚操做。
方式一 利用 libbinder.so 与 ActivityManagerService 通讯
上面在 Java 层复活进程一节中,是向 ActivityManagerService 发送特定的封装了 Intent 的 Parcel 包来实现唤醒进程。而在 native 层,没有 Intent 这个类。因此就须要在 Java 层建立好 Intent,而后写到 Parcel 里,再传到 Native 层。
Parcel mServiceData = Parcel.obtain();
mServiceData.writeInterfaceToken("android.app.IActivityManager");
mServiceData.writeStrongBinder(null);
mServiceData.writeInt(1);
intent.writeToParcel(mServiceData, 0);
mServiceData.writeString(null); // resolvedType
mServiceData.writeInt(0);
mServiceData.writeString(context.getPackageName()); // callingPackage
mServiceData.writeInt(0);
查看Parcel 的源码 [4] 能够看到,Parcel 类有一个 mNativePtr 变量:
private long mNativePtr; // used by native code
// android4.4 mNativePtr 是 int 类型
能够经过反射获得这个变量:
private static long getNativePtr(Parcel parcel) {
try {
Field ptrField = parcel.getClass().getDeclaredField("mNativePtr");
ptrField.setAccessible(true);
return (long) ptrField.get(parcel);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
这个变量对应了 C++中Parcel 类 [5] 的地址,所以能够强转获得 Parcel 指针:
Parcel *parcel = (Parcel *) parcel_ptr;
然而,NDK 中并无提供 binder 这个模块,咱们只能从 Android 源码中扒到 binder 相关的源码,再编译出 libbinder.so。腾讯 TIM 应该就是魔改了 binder 相关的源码。
提取 libbinder.so
为了不 libbinder 的版本兼容问题,这里咱们能够采用一个更简单的方式,拿到 binder 相关的头文件,再从系统中拿到 libbinder.so,固然 binder 模块还依赖了其它的几个 so,要一块儿拿到,否则编译的时候会报连接错误。
adb pull /system/lib/libbinder.so ./
adb pull /system/lib/libcutils.so ./
adb pull /system/lib/libc.so ./
adb pull /system/lib/libutils.so ./
若是须要不一样 SDK 版本,不一样架构的系统 so 库,能够在 Google Factory Images[6] 网页里找到适合的版本,下载相应的固件,而后解包 system.img(须要在 windows 或 linux 中操做),提取出目标 so。
binder_libs
├── arm64-v8a
│ ├── libbinder.so
│ ├── libc.so
│ ├── libcutils.so
│ └── libutils.so
├── armeabi-v7a
│ ├── ...
├── x86
│ ├── ...
└── x86_64
├── ...
为了不兼容问题,我这里只让这些 so 参与了 binder 相关的头文件的连接,而没有实际使用这些 so。这是利用了 so 的加载机制,若是应用 lib 目录没有相应的 so,则会到 system/lib 目录下查找。
SDK24 以上,系统禁止了从 system 中加载 so 的方式,因此使用这个方法务必保证 targetApi <24。
不然,将会报找不到 so 的错误。能够把上面的 so 放到 jniLibs 目录解决这个问题,但这样就会有兼容问题了。
CMake 修改:
# 连接 binder_libs 目录下的全部 so 库
link_directories(binder_libs/${CMAKE_ANDROID_ARCH_ABI})
# 引入 binder 相关的头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/)
# libbinder.so libcutils.so libutils.so libc.so 等库连接到 libkeep_alive.so
target_link_libraries(
keep_alive
${log-lib} binder cutils utils c)
进程间传输 Parcel 对象
C++里面还能传输对象?不存在的。好在 Parcel 能直接拿到数据地址,并提供了构造方法。因此咱们能够经过管道把 Parcel 数据传输到其它进程。
Parcel *parcel = (Parcel *) parcel_ptr;
size_t data_size = parcel->dataSize();
int fd[2];
// 建立管道
if (pipe(fd) < 0) {return;}
pid_t pid;
// 建立子进程
if ((pid = fork()) < 0) {
exit(-1);
} else if (pid == 0) {//第一个子进程
if ((pid = fork()) < 0) {
exit(-1);
} else if (pid > 0) {
// 托孤
exit(0);
}
uint8_t data[data_size];
// 托孤的子进程,读取管道中的数据
int result = read(fd[0], data, data_size);
}
// 父进程向管道中写数据
int result = write(fd[1], parcel->data(), data_size);
从新建立 Parcel:
Parcel parcel;
parcel.setData(data, data_size);
传输 Parcel 数据
// 获取 ServiceManager
sp<IServiceManager> sm = defaultServiceManager();
// 获取 ActivityManager binder
sp<IBinder> binder = sm->getService(String16("activity"));
// 传输 parcel
int result = binder.get()->transact(code, parcel, NULL, 0);
方式二 使用 ioctl 与 binder 驱动通讯
方式一让我尝到了一点甜头,实现了大佬的思路,不由让鄙人浮想联翩,感慨万千,鄙人的造诣已经如此之深,不久就会人在美国,刚下飞机,迎娶白富美,走向人生巅峰矣......

咳咳。不由想到 ioctl 的方式我也能够尝试着实现一下。ioctl 是一个 linux 标准方法,那么咱们就直奔主题看看,binder 是什么,ioctl 怎么跟 binder driver 通讯。
Binder 介绍
Binder 是 Android 系统提供的一种 IPC 机制。每一个 Android 的进程,均可以有一块用户空间和内核空间。用户空间在不一样进程间不能共享,内核空间能够共享。Binder 就是一个利用能够共享的内核空间,完成高性能的进程间通讯的方案。
Binder 通讯采用 C/S 架构,从组件视角来讲,包含 Client、Server、ServiceManager 以及 binder 驱动,其中 ServiceManager 用于管理系统中的各类服务。如图:

能够看到,注册服务、获取服务、使用服务,都是须要通过 binder 通讯的。
-
Server 经过注册服务的 Binder 通讯把本身托管到 ServiceManager -
Client 端能够经过 ServiceManager 获取到 Server -
Client 端获取到 Server 后就可使用 Server 的接口了
Binder 通讯的表明类是 BpBinder(客户端) 和 BBinder(服务端)。
ps:有关 binder 的详细知识,你们能够查看 Gityuan 大佬的Binder 系列 [7] 文章。
ioctl 函数
ioctl(input/output control) 是一个专用于设备输入输出操做的系统调用,它诞生在这样一个背景下:
操做一个设备的 IO 的传统作法,是在设备驱动程序中实现 write 的时候检查一下是否有特殊约定的数据流经过,若是有的话,后面就跟着控制命令(socket 编程中经常这样作)。可是这样作的话,会致使代码分工不明,程序结构混乱。因此就有了 ioctl 函数,专门向驱动层发送或接收指令。
Linux 操做系统分为了两层,用户层和内核层。咱们的普通应用程序处于用户层,系统底层程序,好比网络栈、设备驱动程序,处于内核层。为了保证安全,操做系统要阻止用户态的程序直接访问内核资源。一个Ioctl接口是一个独立的系统调用,经过它用户空间能够跟设备驱动沟通了。函数原型:
int ioctl(int fd, int request, …);
做用:经过 IOCTL 函数实现指令的传递
-
fd 是用户程序打开设备时使用 open 函数返回的文件描述符 -
request 是用户程序对设备的控制命令 -
后面的省略号是一些补充参数,和 cmd 的意义相关
应用程序在调用ioctl
进行设备控制时,最后会调用到设备注册struct file_operations
结构体对象时的unlocked_ioctl
或者compat_ioctl
两个钩子上,例如 Binder 驱动的这两个钩子是挂到了 binder_ioctl 方法上:
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
它的实现以下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
/*根据不一样的命令,调用不一样的处理函数进行处理*/
switch (cmd) {
case BINDER_WRITE_READ:
/*读写命令,数据传输,binder IPC 通讯的核心逻辑*/
ret = **binder_ioctl_write_read**(filp, cmd, arg, thread);
break;
case BINDER_SET_MAX_THREADS:
/*设置最大线程数,直接将值设置到 proc 结构的 max_threads 域中。*/
break;
case BINDER_SET_CONTEXT_MGR:
/*设置 Context manager,即将本身设置为 ServiceManager,详见 3.3*/
break;
case BINDER_THREAD_EXIT:
/*binder 线程退出命令,释放相关资源*/
break;
case BINDER_VERSION: {
/*获取 binder 驱动版本号,在 kernel4.4 版本中,32 位该值为 7,64 位版本该值为 8*/
break;
}
return ret;
}
具体内核层的实现,咱们就不关心了。到这里咱们了解到,Binder 在 Android 系统中会有一个设备节点,调用 ioctl 控制这个节点时,实际上会调用到内核态的 binder_ioctl 方法。
为了利用 ioctl 启动 Android Service,必然是须要用 ioctl 向 binder 驱动写数据,而这个控制命令就是BINDER_WRITE_READ
。binder 驱动层的一些细节咱们在这里就不关心了。那么在什么地方会用 ioctl 向 binder 写数据呢?
IPCThreadState.talkWithDriver
阅读 Gityuan 的Binder 系列 6—获取服务 (getService)[8] 一节,在 binder 模块下IPCThreadState.cpp[9] 中有这样的实现 (源码目录:frameworks/native/libs/binder/IPCThreadState.cpp):
status_t IPCThreadState::talkWithDriver(bool doReceive) {
...
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
status_t err;
do {
//经过 ioctl 不停的读写操做,跟 Binder Driver 进行通讯
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
...
} while (err == -EINTR); //当被中断,则继续执行
...
return err;
}
能够看到 ioctl 跟 binder driver 交互很简单,一个参数是 mProcess->mDriverFD,一个参数是 BINDER_WRITE_READ,另外一个参数是 binder_write_read 结构体,很幸运的是,NDK 中提供了linux/android/binder.h
这个头文件,里面就有 binder_write_read 这个结构体,以及 BINDER_WRITE_READ 常量的定义。
[惊不惊喜意不意外 ]
#include<linux/android/binder.h>
struct binder_write_read {
binder_size_t write_size;
binder_size_t write_consumed;
binder_uintptr_t write_buffer;
binder_size_t read_size;
binder_size_t read_consumed;
binder_uintptr_t read_buffer;
};
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
这意味着,这些结构体和宏定义极可能是版本兼容的。
那咱们只须要到时候把数据揌到 binder_write_read 结构体里面,就能够进行 ioctl 系统调用了!
/dev/binder
再来看看 mProcess->mDriverFD 是什么东西。mProcess 也就是ProcessState.cpp[10](源码目录:frameworks/native/libs/binder/ProcessState.cpp):
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver))
, ...
{}
从 ProcessState 的构造函数中得知,mDriverFD 由 open_driver 方法初始化。
static int open_driver(const char *driver) {
int fd = open(driver, O_RDWR | O_CLOEXEC);
if (fd >= 0) {
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers);
}
return fd;
}
ProcessState 在哪里实例化呢?
sp<ProcessState> ProcessState::self() {
if (gProcess != nullptr) {
return gProcess;
}
gProcess = new ProcessState(kDefaultDriver);
return gProcess;
}
能够看到,ProcessState 的 gProcess 是一个全局单例对象,这意味着,在当前进程中,open_driver 只会执行一次,获得的 mDriverFD 会一直被使用。
const char* kDefaultDriver = "/dev/binder";
而 open 函数操做的这个设备节点就是/dev/binder。
纳尼?在应用层直接操做设备节点?Gityuan 大佬不会骗我吧?通常来讲,Android 系统在集成 SELinux 的安全机制以后,普通应用甚至是系统应用,都不能直接操做一些设备节点,除非有 SELinux 规则,给应用所属的域或者角色赋予了那样的权限。
看看文件权限:
➜ ~ adb shell
chiron:/ $ ls -l /dev/binder
crw-rw-rw- 1 root root 10, 49 1972-07-03 18:46 /dev/binder
能够看到,/dev/binder 设备对全部用户可读可写。
再看看,SELinux 权限:
chiron:/ $ ls -Z /dev/binder
u:object_r:binder_device:s0 /dev/binder
查看源码中对 binder_device 角色的 SELinux 规则描述:
allow domain binder_device:chr_file rw_file_perms;
也就是全部 domain 对 binder 的字符设备有读写权限,而普通应用属于 domain。
既然这样,肝它!
写个 Demo 试一下
验证一下上面的想法,看看 ioctl 给 binder driver 发数据好很差使。
一、打开设备
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
if (fd < 0) {
LOGE("Opening '%s' failed: %s\n", "/dev/binder", strerror(errno));
} else {
LOGD("Opening '%s' success %d: %s\n", "/dev/binder", fd, strerror(errno));
}
二、ioctl
Parcel *parcel = new Parcel;
parcel->writeString16(String16("test"));
binder_write_read bwr;
bwr.write_size = parcel->dataSize();
bwr.write_buffer = (binder_uintptr_t) parcel->data();
int ret = ioctl(fd, BINDER_WRITE_READ, bwr);
LOGD("ioctl result is %d: %s\n", ret, strerror(errno));
三、查看日志
D/KeepAlive: Opening '/dev/binder' success, fd is 35
D/KeepAlive: ioctl result is -1: Invalid argument
打开设备节点成功了,耶✌️!可是 ioctl 失败了🤔,失败缘由是Invalid argument
,也就是说能够通讯,可是 Parcel 数据有问题。来看看数据应该是什么样的。
binder_write_read 结构体数据封装
IPCThreadState.talkWithDriver 方法中,bwr.write_buffer 指针指向了 mOut.data(),显然 mOut 是一个 Parcel 对象。
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
再来看看何时会向 mOut 中写数据:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.data.ptr.buffer = data.ipcData();
...
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
writeTransactionData 方法中,会往 mOut 中写入一个 binder_transaction_data 结构体数据,binder_transaction_data 结构体中又包含了做为参数传进来的 data Parcel 对象。
writeTransactionData 方法会被 transact 方法调用:
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags) {
status_t err = data.errorCheck(); // 数据错误检查
flags |= TF_ACCEPT_FDS;
if (err == NO_ERROR) {
// 传输数据
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
...
// 默认状况下,都是采用非 oneway 的方式, 也就是须要等待服务端的返回结果
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
//等待回应事件
err = waitForResponse(reply);
}else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
err = waitForResponse(NULL, NULL);
}
return err;
}
IPCThreadState 是跟 binder driver 真正进行交互的类。每一个线程都有一个IPCThreadState
,每一个IPCThreadState
中都有一个 mIn、一个 mOut。成员变量 mProcess 保存了 ProcessState 变量 (每一个进程只有一个)。
接着看一下一次 Binder 调用的时序图:

Binder 介绍一节中说过,BpBinder 是 Binder Client,上层想进行进程间 Binder 通讯时,会调用到 BpBinder 的 transact 方法,进而调用到 IPCThreadState 的 transact 方法。来看看 BpBinder 的 transact 方法的定义:
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
if (mAlive) {
status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
BpBinder::transact 方法的 code/data/reply/flags 这几个参数都是调用的地方传过来的,如今惟一不知道的就是 mHandle 是什么东西。mHandle 是 BpBinder(也就是 Binder Client) 的一个 int 类型的局部变量(句柄),只要拿到了这个 handle 就至关于拿到了 BpBinder。
ioctl 启动 Service 分几步?
下面是在依赖 libbinder.so 时,启动 Service 的步骤:
// 获取 ServiceManager
sp<IServiceManager> sm = defaultServiceManager();
// 获取 ActivityManager binder
sp<IBinder> binder = sm->getService(String16("activity"));
// 传输 parcel
int result = binder.get()->transact(code, parcel, NULL, 0);
一、获取到 IServiceManager Binder Client;
二、从 ServiceManager 中获取到 ActivityManager Binder Client;
三、调用 ActivityManager binder 的 transact 方法传输 Service 的 Parcel 数据。
经过 ioctl 启动 Service 也应该是相似的步骤:
一、获取到 ServiceManager 的 mHandle 句柄;
二、进行 binder 调用获取到 ActivityManager 的 mHandle 句柄;
三、进行 binder 调用传输启动 Service 的指令数据。
这里有几个问题:
一、不依赖 libbinder.so 时,ndk 中没有 Parcel 类的定义,parcel 数据哪里来,怎么封装?
二、如何获取到 BpBinder 的 mHandle 句柄?
如何封装 Parcel 数据
Parcel 类是 Binder 进程间通讯的一个基础的、必不可少的数据结构,往 Parcel 中写入的数据其实是写入到了一块内部分配的内存上,最后把这个内存地址封装到 binder_write_read 结构体中。Parcel 做为一个基础的数据结构,和 Binder 相关类是能够解耦的,能够直接拿过来使用,咱们能够根据须要对有耦合性的一些方法进行裁剪。
c++ Parcel 类路径:frameworks[11]/native[12]/libs[13]/binder[14]/Parcel.cpp[15]
jni Parcel 类路径:frameworks[16]/base[17]/core[18]/jni[19]/android_os_Parcel.cpp[20]
如何获取到 BpBinder 的 mHandle 句柄
具体流程参考Binder 系列 4—获取 ServiceManager[21]。
一、获取 ServiceManager 的 mHandle 句柄
defaultServiceManager() 方法用来获取gDefaultServiceManager
对象,gDefaultServiceManager 是 ServiceManager 的单例。
sp<IServiceManager> defaultServiceManager() {
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
while (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}
getContextObject 方法用来获取 BpServiceManager 对象(BpBinder),查看其定义:
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/) {
sp<IBinder> context = getStrongProxyForHandle(0);
return context;
}
能够发现,getStrongProxyForHandle 是一个根据 handle 获取 IBinder 对象的方法,而这里 handle 的值为 0,能够得知,ServiceManager 的 mHandle 恒为 0。
二、获取 ActivityManager 的 mHandle 句柄
获取 ActivityManager 的 c++方法是:
sp<IBinder> binder = serviceManager->getService(String16("activity"));
BpServiceManager.getService:
virtual sp<IBinder> getService(const String16& name) const {
sp<IBinder> svc = checkService(name);
if (svc != NULL) return svc;
return NULL;
}
BpServiceManager.checkService:
virtual sp<IBinder> checkService( const String16& name) const {
Parcel data, reply;
//写入 RPC 头
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
//写入服务名
data.writeString16(name);
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
return reply.readStrongBinder();
}
能够看到,CHECK_SERVICE_TRANSACTION 这个 binder 调用是有返回值的,返回值会写到 reply 中,经过 reply.readStrongBinder() 方法,便可从 reply 这个 Parcel 对象中读取到 ActivityManager 的 IBinder。每一个 Binder 对象必需要有它本身的 mHandle 句柄,否则,transact 操做是没办法进行的。因此,颇有可能,Binder 的 mHandle 的值是写到 reply 这个 Parcel 里面的。
看看 reply.readStrongBinder() 方法搞了什么鬼:
sp<IBinder> Parcel::readStrongBinder() const {
sp<IBinder> val;
readNullableStrongBinder(&val);
return val;
}
status_t Parcel::readNullableStrongBinder(sp<IBinder>* val) const {
return unflattenBinder(val);
}
调用到了 Parcel::unflattenBinder 方法,顾名思义,函数最终想要获得的是一个 Binder 对象,而 Parcel 中存放的是二进制的数据,unflattenBinder 极可能是把 Parcel 中的一个结构体数据给转成 Binder 对象。
看看 Parcel::unflattenBinder 方法的定义:
status_t Parcel::unflattenBinder(sp<IBinder>* out) const {
const flat_binder_object* flat = readObject(false);
if (flat) {
...
sp<IBinder> binder =
ProcessState::self()->getStrongProxyForHandle(flat->handle);
}
return BAD_TYPE;
}
果真如此,从 Parcel 中能够获得一个 flat_binder_object 结构体,这个结构体重有一个 handle 变量,这个变量就是 BpBinder 中的 mHandle 句柄。
所以,在不依赖 libbinder.so 的状况下,咱们能够本身组装数据发送给 ServiceManager,进而获取到 ActivityManager 的 mHandle 句柄。
IPCThreadState 是一个被 Binder 依赖的类,它是能够从源码中抽离出来为咱们所用的。上一节中说到,Parcel 类也是能够从源码中抽离出来的。
经过以下的操做,咱们就能够实现 ioctl 获取到 ActivityManager 对应的 Parcel 对象 reply:
Parcel data, reply;
// data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
// IServiceManager::getInterfaceDescriptor() 的值是 android.app.IActivityManager
data.writeInterfaceToken(String16("android.app.IActivityManager"));
data.writeString16(String16("activity"));
IPCThreadState::self()->transact(
0/*ServiceManger 的 mHandle 句柄恒为 0*/,
CHECK_SERVICE_TRANSACTION, data, reply, 0);
reply 变量也就是咱们想要的包含了 flat_binder_object 结构体的 Parcel 对象,再通过以下的操做就能够获得 ActivityManager 的 mHandle 句柄:
const flat_binder_object* flat = reply->readObject(false);
return flat->handle;
三、传输启动指定 Service 的 Parcel 数据
上一步已经拿到 ActivityManger 的 mHandle 句柄,好比值为 1。这一步的过程和上一步相似,本身封装 Parcel,而后调用 IPCThreadState::transact 方法传输数据,伪代码以下:
Parcel data;
// 把 Service 相关信息写到 parcel 中
writeService(data, packageName, serviceName, sdk_version);
IPCThreadState::self()->transact(
1/*上一步获取的 ActivityManger 的 mHandle 句柄值是 1*/,
CHECK_SERVICE_TRANSACTION, data, reply,
1/*TF_ONE_WAY*/);
四、writeService 方法须要作什么事情?
下面这段代码是 Java 中封装 Parcel 对象的方法:
Intent intent = new Intent();
ComponentName component = new ComponentName(context.getPackageName(), serviceName);
intent.setComponent(component);
Parcel mServiceData = Parcel.obtain();
mServiceData.writeInterfaceToken("android.app.IActivityManager");
mServiceData.writeStrongBinder(null);
mServiceData.writeInt(1);
intent.writeToParcel(mServiceData, 0);
mServiceData.writeString(null); // resolvedType
mServiceData.writeInt(0);
mServiceData.writeString(context.getPackageName()); // callingPackage
mServiceData.writeInt(0);
能够看到,有 Intent 类转 Parcel,ComponentName 类转 Parcel,这些类在 c++中是没有对应的类的。因此须要咱们参考intent.writeToParcel
/ComponentName.writeToParcel
等方法的源码的实现,自行封装数据。下面这段代码就是把启动 Service 的 Intent 写到 Parcel 中的方法:
void writeIntent(Parcel &out, const char *mPackage, const char *mClass) {
// mAction
out.writeString16(NULL, 0);
// uri mData
out.writeInt32(0);
// mType
out.writeString16(NULL, 0);
// // mIdentifier
out.writeString16(NULL, 0);
// mFlags
out.writeInt32(0);
// mPackage
out.writeString16(NULL, 0);
// mComponent
out.writeString16(String16(mPackage));
out.writeString16(String16(mClass));
// mSourceBounds
out.writeInt32(0);
// mCategories
out.writeInt32(0);
// mSelector
out.writeInt32(0);
// mClipData
out.writeInt32(0);
// mContentUserHint
out.writeInt32(-2);
// mExtras
out.writeInt32(-1);
}
继续写 Demo 试一下
上面已经知道了怎么经过 ioctl 获取到 ActivityManager,能够写 demo 试一下:
// 打开 binder 设备
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
Parcel data, reply;
// data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
// IServiceManager::getInterfaceDescriptor() 的值是 android.app.IActivityManager
data.writeInterfaceToken(String16("android.app.IActivityManager"));
data.writeString16(String16("activity"));
IPCThreadState::self()->transact(
0/*ServiceManger 的 mHandle 句柄恒为 0*/,
CHECK_SERVICE_TRANSACTION, data, reply, 0);
const flat_binder_object *flat = reply->readObject(false);
if (flat) {
LOGD("write_transact handle is:%llu", flat->handle);
}else {
LOGD("write_transact failed, error=%d", status);
}
给 IPCThreadState::transact 加上一些日志,打印结果以下:
D/KeepAlive: BR_DEAD_REPLY
D/KeepAlive: write_transact failed, error=-32
reply 中始终读不到数据。这是为何?如今已经不报Invalid argument
的错误了,说明 Parcel 数据格式可能没问题了。可是不能成功把数据写给 ServiceManager,或者 ServiceManager 返回的数据不能成功写回来。
想到 Binder 是基于内存的一种 IPC 机制,数据都是对的,那问题就出在内存上了。这就要说到 Binder 基本原理以及 Binder 内存转移关系。
Binder 基本原理:

Binder 的 Client 端和 Server 端位于不一样的进程,它们的用户空间是相互隔离。而内核空间由 Linux 内核进程来维护,在安全性上是有保障的。因此,Binder 的精髓就是在内核态开辟了一块共享内存。

数据发送方写数据时,内核态经过 copy_from_user() 方法把它的数据拷贝到数据接收方映射 (mmap) 到内核空间的地址上。这样,只须要一次数据拷贝过程,就能够完成进程间通讯。
由此可知,没有这块内核空间是没办法完成 IPC 通讯的。Demo 失败的缘由就是缺乏了一个 mmap 过程,以映射一块内存到内核空间。修改以下:
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
int mDriverFD = open("/dev/binder", O_RDWR | O_CLOEXEC);
mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
日志:
D/KeepAlive: BR_REPLY
D/KeepAlive: write_transact handle is:1
搞定!
最后
相关的代码我已经发布到 Github(lcodecorex[22]/KeepAlive[23]),master
分支是利用 libbinder.so 与 ActivityManagerService 通讯
的版本,ioctl
分支是使用 ioctl 与 binder 驱动通讯
的版本。
固然,这个保活的办法虽然很强,但如今也只能活在模拟器里了。
说一下个人方法论。
一、肯定问题和目标。
研究一个比较复杂的东西的时候,咱们比较难有一个大局观。这个时候,就须要明确本身须要什么?有问题,才能推进本身学习,而后顺腾摸瓜,最后弄清本身的模块在系统中的位置。
这篇文章,咱们肯定了目标是直接经过 ioctl 进行 Binder 通讯,进而肯定 Binder 通讯的关键是拿到 mHandle 句柄。同时也理清了 Binder 通讯的一个基本流程。
二、时序图很重要。
大佬们画的时序图,可快帮助咱们快速理清框架的思路。
三、实践出真知。
纸上得来终觉浅,绝知此事要躬行。我一直践行的一个学习方式是学以至用,能够及时写 Demo 帮助咱们巩固知识以及分析问题。
鸣谢
Gityuan 大佬的Binder 系列 [24]
Android 黑科技保活实现原理揭秘 [25]
binder Driver (binder IPC) 功能介绍与分析 [26]
Binder 驱动之设备控制`binder_ioctl`[27]
Google 官方 Android 源码浏览网站 [28]
做者:小玩童
连接:https://juejin.im/post/5e820b61e51d45470652e7b8
参考资料
史上最强 Android 保活思路:深刻剖析腾讯 TIM 的进程永生技术: http://www.52im.net/forum.php?mod=viewthread&tid=2893&highlight=%B1%A3%BB%EE
[2]Android 黑科技保活实现原理揭秘: http://weishu.me/2020/01/16/a-keep-alive-method-on-android/
[3]github.com/tiann/Leoric: https://github.com/tiann/Leoric
[4]Parcel 的源码: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=336
[5]Parcel 类: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/Parcel.cpp
[6]Google Factory Images: https://developers.google.cn/android/images?h1=zh=cn#angler
[7]Binder 系列: http://gityuan.com/2015/10/31/binder-prepare/
[8]Binder 系列 6—获取服务 (getService): http://gityuan.com/2015/11/15/binder-get-service/
[9]IPCThreadState.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/IPCThreadState.cpp
[10]ProcessState.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/ProcessState.cpp
[11]frameworks: https://cs.android.com/android/platform/superproject/+/master:frameworks/
[12]native: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/
[13]libs: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/
[14]binder: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/
[15]Parcel.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/Parcel.cpp
[16]frameworks: https://cs.android.com/android/platform/superproject/+/master:frameworks/
[17]base: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/
[18]core: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/
[19]jni: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/
[20]android_os_Parcel.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_os_Parcel.cpp
[21]Binder 系列 4—获取 ServiceManager: http://gityuan.com/2015/11/08/binder-get-sm/
[22]lcodecorex: https://github.com/lcodecorex
[23]KeepAlive: https://github.com/lcodecorex/KeepAlive
[24]Binder 系列: http://gityuan.com/2015/10/31/binder-prepare/
[25]Android 黑科技保活实现原理揭秘: http://weishu.me/2020/01/16/a-keep-alive-method-on-android/
[26]binder Driver (binder IPC) 功能介绍与分析: https://blog.csdn.net/vshuang/article/details/88823044
[27]Binder 驱动之设备控制binder_ioctl
: https://www.jianshu.com/p/49830c3473b7
Google 官方 Android 源码浏览网站: https://cs.android.com/
本文分享自微信公众号 - 贾小昆(zywudev)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。