纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

App保活技术实现 详解App保活技术实现

lsgxeva   2021-06-09 我要评论
想了解详解App保活技术实现的相关内容吗lsgxeva在本文为您仔细讲解App保活技术实现的相关知识和一些Code实例欢迎阅读和指正我们先划重点:App,保活下面大家一起来学习吧

前言

通过ioctl跟binder驱动交互实现以最快的方式唤醒新的保活服务最大程度防止保活失败同时我也将跟您分享我是怎么做到在不甚了解binder的情况下快速实现ioctl binder这种高级操作

声明:现在这个保活方式在MIUI等定制Android系统中已经不能保活大部分时候只能活在模拟器中了但对与我们的轻量定制的Android系统一些系统级应用的保活这个方案还是有用的

这是一个Android设计的不合理的地方路子可以堵但还是有必要留一个统一的保活接口的这个接口由Google实现也好厂商来实现也好总好过现在很笨拙的系统自启动管理或者是JobScheduler我觉得本质上来说让应用开发者想尽各种办法去做保活这个事情是没有意义的保活的路子被封了但保活还是需要做保活的成本也提高了

黑科技进程保活原理

Gityuan大佬放出了一份分析TIM的黑科技保活的博客史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术(后来不知道什么原因又删除了)顿时间掀起了一阵波澜仿佛让开发者们又看到了应用保活的一丝希望Gityuan大佬通过超强的专业技术分析为我们解开了TIM保活方案的终极奥义

后来为数不多的维术大佬在Gityuan大佬的基础上发布了博客Android 黑科技保活实现原理揭秘又进行了系统进程查杀相关的源码分析为我们带来的结论是Android系统杀应用的时候会去杀进程组循环 40 遍不停地杀进程每次杀完之后等 5ms

总之引用维术的话语原理如下:

1.利用Linux文件锁的原理使用2个进程互相监听各自的文件锁来感知彼此的死亡

2.通过 fork 产生子进程fork 的进程同属一个进程组一个被杀之后会触发另外一个进程被杀从而被文件锁感知

具体来说创建 2 个进程 p1, p2这两个进程通过文件锁互相关联一个被杀之后拉起另外一个同时 p1 经过 2 次 fork 产生孤儿进程 c1p2 经过 2 次 fork 产生孤儿进程 c2c1 和 c2 之间建立文件锁关联这样假设 p1 被杀那么 p2 会立马感知到然后 p1 和 c1 同属一个进程组p1 被杀会触发 c1 被杀c1 死后 c2 立马感受到从而拉起 p1因此这四个进程三三之间形成了铁三角从而保证了存活率

按照维术大佬的理论只要进程我复活的足够快系统它就杀不死我

维术大佬写了一个简单的实现代码在这里:github.com/tiann/Leoric这个方案是当检测到进程被杀时会通过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通信以唤醒新进程

1.Java 层创建 Parcel (含 Intent)拿到 Parcel 对象的 mNativePtr(native peer)传到 Native 层

2.native 层直接把 mNativePtr 强转为结构体指针

3.fork 子进程建立管道准备传输 parcel 数据

4.子进程读管道拿到二进制流重组为 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的源码可以看到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类的地址因此可以强转得到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 网页里找到适合的版本下载相应的固件然后解包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系列文章

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位该值为764位版本该值为8*/
        break;
    }
   return ret;
}

具体内核层的实现我们就不关心了到这里我们了解到Binder在Android系统中会有一个设备节点调用ioctl控制这个节点时实际上会调用到内核态的binder_ioctl方法

为了利用ioctl启动Android Service必然是需要用ioctl向binder驱动写数据而这个控制命令就是BINDER_WRITE_READbinder驱动层的一些细节我们在这里就不关心了那么在什么地方会用ioctl 向binder写数据呢?

IPCThreadState.talkWithDriver

阅读Gityuan的Binder系列6—获取服务(getService)一节在binder模块下IPCThreadState.cpp中有这样的实现(源码目录: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(源码目录: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发数据好不好使

1、打开设备

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));
}

2、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));

3、查看日志

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);

1、获取到IServiceManager Binder Client

2、从ServiceManager中获取到ActivityManager Binder Client

3、调用ActivityManager binder的transact方法传输Service的Parcel数据

通过ioctl启动Service也应该是类似的步骤:

1、获取到ServiceManager的mHandle句柄

2、进行binder调用获取到ActivityManager的mHandle句柄

3、进行binder调用传输启动Service的指令数据

这里有几个问题:

1、不依赖libbinder.so时ndk中没有Parcel类的定义parcel数据哪里来怎么封装?

2、如何获取到BpBinder的mHandle句柄?

如何封装Parcel数据

Parcel类是Binder进程间通信的一个基础的、必不可少的数据结构往Parcel中写入的数据实际上是写入到了一块内部分配的内存上最后把这个内存地址封装到binder_write_read结构体中Parcel作为一个基础的数据结构和Binder相关类是可以解耦的可以直接拿过来使用我们可以根据需要对有耦合性的一些方法进行裁剪

c++ Parcel类路径:frameworks/native/libs/binder/Parcel.cpp

jni Parcel类路径:frameworks/base/core/jni/android_os_Parcel.cpp

如何获取到BpBinder的mHandle句柄

1、获取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

2、获取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;

3、传输启动指定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*/);

4、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类转ParcelComponentName类转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/https://github.com/lcodecorex/KeepAlive

master分支是利用 libbinder.so 与 ActivityManagerService 通信的版本ioctl分支是使用 ioctl 与 binder 驱动通信的版本

当然这个保活的办法虽然很强但现在也只能活在模拟器里了


相关文章

猜您喜欢

网友评论

Copyright 2020 www.fresh-weather.com 【世纪下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式