你們好,我是老玩童。今天來跟你們分享 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 驅動通訊
方式一讓我嚐到了一點甜頭,實現了大佬的思路,不由讓鄙人浮想聯翩,感慨萬千,鄙人的造詣已經如此之深,不久就會人在美國,剛下飛機,迎娶白富美,走向人生巔峯矣......
![](http://static.javashuo.com/static/loading.gif)
咳咳。不由想到 ioctl 的方式我也能夠嘗試着實現一下。ioctl 是一個 linux 標準方法,那麼咱們就直奔主題看看,binder 是什麼,ioctl 怎麼跟 binder driver 通訊。
Binder 介紹
Binder 是 Android 系統提供的一種 IPC 機制。每一個 Android 的進程,均可以有一塊用戶空間和內核空間。用戶空間在不一樣進程間不能共享,內核空間能夠共享。Binder 就是一個利用能夠共享的內核空間,完成高性能的進程間通訊的方案。
Binder 通訊採用 C/S 架構,從組件視角來講,包含 Client、Server、ServiceManager 以及 binder 驅動,其中 ServiceManager 用於管理系統中的各類服務。如圖:
![](http://static.javashuo.com/static/loading.gif)
能夠看到,註冊服務、獲取服務、使用服務,都是須要通過 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 調用的時序圖:
![](http://static.javashuo.com/static/loading.gif)
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 基本原理:
![](http://static.javashuo.com/static/loading.gif)
Binder 的 Client 端和 Server 端位於不一樣的進程,它們的用戶空間是相互隔離。而內核空間由 Linux 內核進程來維護,在安全性上是有保障的。因此,Binder 的精髓就是在內核態開闢了一塊共享內存。
![](http://static.javashuo.com/static/loading.gif)
數據發送方寫數據時,內核態經過 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。