詳解App?;罴夹g(shù)實現(xiàn)
前言
通過ioctl跟binder驅(qū)動交互,實現(xiàn)以最快的方式喚醒新的?;罘?wù),最大程度防止?;钍 M瑫r,我也將跟您分享,我是怎么做到在不甚了解binder的情況下,快速實現(xiàn)ioctl binder這種高級操作。
聲明:現(xiàn)在這個?;罘绞皆贛IUI等定制Android系統(tǒng)中已經(jīng)不能?;?,大部分時候只能活在模擬器中了。但對與我們的輕量定制的Android系統(tǒng),一些系統(tǒng)級應(yīng)用的?;?,這個方案還是有用的。
這是一個Android設(shè)計的不合理的地方,路子可以堵,但還是有必要留一個統(tǒng)一的?;罱涌诘?。這個接口由Google實現(xiàn)也好,廠商來實現(xiàn)也好,總好過現(xiàn)在很笨拙的系統(tǒng)自啟動管理或者是JobScheduler。我覺得本質(zhì)上來說,讓應(yīng)用開發(fā)者想盡各種辦法去做?;?,這個事情是沒有意義的,?;畹穆纷颖环饬?,但保活還是需要做,?;畹某杀疽蔡岣吡?。
黑科技進(jìn)程?;钤?/h2>
Gityuan大佬放出了一份分析TIM的黑科技保活的博客史上最強Android?;钏悸罚荷钊肫饰鲵v訊TIM的進(jìn)程永生技術(shù)(后來不知道什么原因又刪除了),頓時間掀起了一陣波瀾,仿佛讓開發(fā)者們又看到了應(yīng)用保活的一絲希望。Gityuan大佬通過超強的專業(yè)技術(shù)分析,為我們解開了TIM?;罘桨傅慕K極奧義。
后來,為數(shù)不多的維術(shù)大佬在Gityuan大佬的基礎(chǔ)上,發(fā)布了博客Android 黑科技?;顚崿F(xiàn)原理揭秘又進(jìn)行了系統(tǒng)進(jìn)程查殺相關(guān)的源碼分析。為我們帶來的結(jié)論是,Android系統(tǒng)殺應(yīng)用的時候,會去殺進(jìn)程組,循環(huán) 40 遍不停地殺進(jìn)程,每次殺完之后等 5ms。
總之,引用維術(shù)的話語,原理如下:
1.利用Linux文件鎖的原理,使用2個進(jìn)程互相監(jiān)聽各自的文件鎖,來感知彼此的死亡。
2.通過 fork 產(chǎn)生子進(jìn)程,fork 的進(jìn)程同屬一個進(jìn)程組,一個被殺之后會觸發(fā)另外一個進(jìn)程被殺,從而被文件鎖感知。
具體來說,創(chuàng)建 2 個進(jìn)程 p1, p2,這兩個進(jìn)程通過文件鎖互相關(guān)聯(lián),一個被殺之后拉起另外一個;同時 p1 經(jīng)過 2 次 fork 產(chǎn)生孤兒進(jìn)程 c1,p2 經(jīng)過 2 次 fork 產(chǎn)生孤兒進(jìn)程 c2,c1 和 c2 之間建立文件鎖關(guān)聯(lián)。這樣假設(shè) p1 被殺,那么 p2 會立馬感知到,然后 p1 和 c1 同屬一個進(jìn)程組,p1 被殺會觸發(fā) c1 被殺,c1 死后 c2 立馬感受到從而拉起 p1,因此這四個進(jìn)程三三之間形成了鐵三角,從而保證了存活率。
按照維術(shù)大佬的理論,只要進(jìn)程我復(fù)活的足夠快,系統(tǒng)它就殺不死我。
維術(shù)大佬寫了一個簡單的實現(xiàn),代碼在這里:github.com/tiann/Leoric,這個方案是當(dāng)檢測到進(jìn)程被殺時,會通過JNI的方式,調(diào)用Java層的方法來復(fù)活進(jìn)程。為了實現(xiàn)穩(wěn)定的保活,尤其是系統(tǒng)殺進(jìn)程只給了5ms復(fù)活的機會,使用JNI這種方式復(fù)活進(jìn)程現(xiàn)在達(dá)不到最優(yōu)的效果。
Java 層復(fù)活進(jìn)程
復(fù)活進(jìn)程,其實就是啟動指定的Service。當(dāng)native層檢測到有進(jìn)程被殺時,為了能夠快速啟動新Service。我們可以通過反射,拿到ActivityManager的remote binder,直接通過這個binder發(fā)送數(shù)據(jù),即可實現(xiàn)快速啟動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 層進(jìn)行 binder 通信
在Java層做進(jìn)程復(fù)活的工作,這個方式是比較低效的,最好的方式是在 native 層使用純 C/C++來復(fù)活進(jìn)程。方案有兩個。
其一,維術(shù)大佬給出的方案是利用libbinder.so, 利用Android提供的C++接口,跟ActivityManagerService通信,以喚醒新進(jìn)程。
1.Java 層創(chuàng)建 Parcel (含 Intent),拿到 Parcel 對象的 mNativePtr(native peer),傳到 Native 層。
2.native 層直接把 mNativePtr 強轉(zhuǎn)為結(jié)構(gòu)體指針。
3.fork 子進(jìn)程,建立管道,準(zhǔn)備傳輸 parcel 數(shù)據(jù)。
4.子進(jìn)程讀管道,拿到二進(jìn)制流,重組為 parcel。
其二,Gityuan大佬則認(rèn)為使用 ioctl 直接給 binder 驅(qū)動發(fā)送數(shù)據(jù)以喚醒進(jìn)程,才是更高效的做法。然而,這個方法,大佬們并沒有提供思路。
那么今天,我們就來實現(xiàn)這兩種在 native 層進(jìn)行 Binder 調(diào)用的操作。
方式一 利用 libbinder.so 與 ActivityManagerService 通信
上面在Java層復(fù)活進(jìn)程一節(jié)中,是向ActivityManagerService發(fā)送特定的封裝了Intent的Parcel包來實現(xiàn)喚醒進(jìn)程。而在native層,沒有Intent這個類。所以就需要在Java層創(chuàng)建好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; }
這個變量對應(yīng)了C++中Parcel類的地址,因此可以強轉(zhuǎn)得到Parcel指針:
Parcel *parcel = (Parcel *) parcel_ptr;
然而,NDK中并沒有提供binder這個模塊,我們只能從Android源碼中扒到binder相關(guān)的源碼,再編譯出libbinder.so。騰訊TIM應(yīng)該就是魔改了binder相關(guān)的源碼。
提取libbinder.so
為了避免libbinder的版本兼容問題,這里我們可以采用一個更簡單的方式,拿到binder相關(guān)的頭文件,再從系統(tǒng)中拿到libbinder.so,當(dāng)然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版本,不同架構(gòu)的系統(tǒng)so庫,可以在 Google Factory Images 網(wǎng)頁里找到適合的版本,下載相應(yīng)的固件,然后解包system.img(需要在windows或linux中操作),提取出目標(biāo)so。
binder_libs
├── arm64-v8a
│ ├── libbinder.so
│ ├── libc.so
│ ├── libcutils.so
│ └── libutils.so
├── armeabi-v7a
│ ├── ...
├── x86
│ ├── ...
└── x86_64
├── ...
為了避免兼容問題,我這里只讓這些so參與了binder相關(guān)的頭文件的鏈接,而沒有實際使用這些so。這是利用了so的加載機制,如果應(yīng)用lib目錄沒有相應(yīng)的so,則會到system/lib目錄下查找。
SDK24以上,系統(tǒng)禁止了從system中加載so的方式,所以使用這個方法務(wù)必保證targetApi <24。
否則,將會報找不到so的錯誤??梢园焉厦娴膕o放到j(luò)niLibs目錄解決這個問題,但這樣就會有兼容問題了。
CMake修改:
# 鏈接binder_libs目錄下的所有so庫 link_directories(binder_libs/${CMAKE_ANDROID_ARCH_ABI}) # 引入binder相關(guān)的頭文件 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)
進(jìn)程間傳輸Parcel對象
C++里面還能傳輸對象?不存在的。好在Parcel能直接拿到數(shù)據(jù)地址,并提供了構(gòu)造方法。所以我們可以通過管道把Parcel數(shù)據(jù)傳輸?shù)狡渌M(jìn)程。
Parcel *parcel = (Parcel *) parcel_ptr; size_t data_size = parcel->dataSize(); int fd[2]; // 創(chuàng)建管道 if (pipe(fd) < 0) {return;} pid_t pid; // 創(chuàng)建子進(jìn)程 if ((pid = fork()) < 0) { exit(-1); } else if (pid == 0) {//第一個子進(jìn)程 if ((pid = fork()) < 0) { exit(-1); } else if (pid > 0) { // 托孤 exit(0); } uint8_t data[data_size]; // 托孤的子進(jìn)程,讀取管道中的數(shù)據(jù) int result = read(fd[0], data, data_size); } // 父進(jìn)程向管道中寫數(shù)據(jù) int result = write(fd[1], parcel->data(), data_size);
重新創(chuàng)建Parcel:
Parcel parcel; parcel.setData(data, data_size);
傳輸Parcel數(shù)據(jù)
// 獲取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 驅(qū)動通信
方式一讓我嘗到了一點甜頭,實現(xiàn)了大佬的思路。
不禁想到ioctl的方式我也可以嘗試著實現(xiàn)一下。ioctl是一個linux標(biāo)準(zhǔn)方法,那么我們就直奔主題看看,binder是什么,ioctl怎么跟binder driver通信。
Binder介紹
Binder是Android系統(tǒng)提供的一種IPC機制。每個Android的進(jìn)程,都可以有一塊用戶空間和內(nèi)核空間。用戶空間在不同進(jìn)程間不能共享,內(nèi)核空間可以共享。Binder就是一個利用可以共享的內(nèi)核空間,完成高性能的進(jìn)程間通信的方案。
Binder通信采用C/S架構(gòu),從組件視角來說,包含Client、Server、ServiceManager以及binder驅(qū)動,其中ServiceManager用于管理系統(tǒng)中的各種服務(wù)。如圖:
可以看到,注冊服務(wù)、獲取服務(wù)、使用服務(wù),都是需要經(jīng)過binder通信的。
- Server通過注冊服務(wù)的Binder通信把自己托管到ServiceManager
- Client端可以通過ServiceManager獲取到Server
- Client端獲取到Server后就可以使用Server的接口了
Binder通信的代表類是BpBinder(客戶端)和BBinder(服務(wù)端)。
ps:有關(guān)binder的詳細(xì)知識,大家可以查看Gityuan大佬的Binder系列文章。
ioctl函數(shù)
ioctl(input/output control)是一個專用于設(shè)備輸入輸出操作的系統(tǒng)調(diào)用,它誕生在這樣一個背景下:
操作一個設(shè)備的IO的傳統(tǒng)做法,是在設(shè)備驅(qū)動程序中實現(xiàn)write的時候檢查一下是否有特殊約定的數(shù)據(jù)流通過,如果有的話,后面就跟著控制命令(socket編程中常常這樣做)。但是這樣做的話,會導(dǎo)致代碼分工不明,程序結(jié)構(gòu)混亂。所以就有了ioctl函數(shù),專門向驅(qū)動層發(fā)送或接收指令。
Linux操作系統(tǒng)分為了兩層,用戶層和內(nèi)核層。我們的普通應(yīng)用程序處于用戶層,系統(tǒng)底層程序,比如網(wǎng)絡(luò)棧、設(shè)備驅(qū)動程序,處于內(nèi)核層。為了保證安全,操作系統(tǒng)要阻止用戶態(tài)的程序直接訪問內(nèi)核資源。一個Ioctl接口是一個獨立的系統(tǒng)調(diào)用,通過它用戶空間可以跟設(shè)備驅(qū)動溝通了。函數(shù)原型:
int ioctl(int fd, int request, …);
作用:通過IOCTL函數(shù)實現(xiàn)指令的傳遞
- fd 是用戶程序打開設(shè)備時使用open函數(shù)返回的文件描述符
- request是用戶程序?qū)υO(shè)備的控制命令
- 后面的省略號是一些補充參數(shù),和cmd的意義相關(guān)
應(yīng)用程序在調(diào)用ioctl進(jìn)行設(shè)備控制時,最后會調(diào)用到設(shè)備注冊struct file_operations結(jié)構(gòu)體對象時的unlocked_ioctl或者compat_ioctl兩個鉤子上,例如Binder驅(qū)動的這兩個鉤子是掛到了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, };
它的實現(xiàn)如下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { /*根據(jù)不同的命令,調(diào)用不同的處理函數(shù)進(jìn)行處理*/ switch (cmd) { case BINDER_WRITE_READ: /*讀寫命令,數(shù)據(jù)傳輸,binder IPC通信的核心邏輯*/ ret = **binder_ioctl_write_read**(filp, cmd, arg, thread); break; case BINDER_SET_MAX_THREADS: /*設(shè)置最大線程數(shù),直接將值設(shè)置到proc結(jié)構(gòu)的max_threads域中。*/ break; case BINDER_SET_CONTEXT_MGR: /*設(shè)置Context manager,即將自己設(shè)置為ServiceManager,詳見3.3*/ break; case BINDER_THREAD_EXIT: /*binder線程退出命令,釋放相關(guān)資源*/ break; case BINDER_VERSION: { /*獲取binder驅(qū)動版本號,在kernel4.4版本中,32位該值為7,64位版本該值為8*/ break; } return ret; }
具體內(nèi)核層的實現(xiàn),我們就不關(guān)心了。到這里我們了解到,Binder在Android系統(tǒng)中會有一個設(shè)備節(jié)點,調(diào)用ioctl控制這個節(jié)點時,實際上會調(diào)用到內(nèi)核態(tài)的binder_ioctl方法。
為了利用ioctl啟動Android Service,必然是需要用ioctl向binder驅(qū)動寫數(shù)據(jù),而這個控制命令就是BINDER_WRITE_READ。binder驅(qū)動層的一些細(xì)節(jié)我們在這里就不關(guān)心了。那么在什么地方會用ioctl 向binder寫數(shù)據(jù)呢?
IPCThreadState.talkWithDriver
閱讀Gityuan的Binder系列6—獲取服務(wù)(getService)一節(jié),在binder模塊下IPCThreadState.cpp中有這樣的實現(xiàn)(源碼目錄: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進(jìn)行通信 if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; ... } while (err == -EINTR); //當(dāng)被中斷,則繼續(xù)執(zhí)行 ... return err; }
可以看到ioctl跟binder driver交互很簡單,一個參數(shù)是mProcess->mDriverFD,一個參數(shù)是BINDER_WRITE_READ,另一個參數(shù)是binder_write_read結(jié)構(gòu)體,很幸運的是,NDK中提供了linux/android/binder.h這個頭文件,里面就有binder_write_read這個結(jié)構(gòu)體,以及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)
這意味著,這些結(jié)構(gòu)體和宏定義很可能是版本兼容的。
那我們只需要到時候把數(shù)據(jù)揌到binder_write_read結(jié)構(gòu)體里面,就可以進(jìn)行ioctl系統(tǒng)調(diào)用了!
/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的構(gòu)造函數(shù)中得知,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是一個全局單例對象,這意味著,在當(dāng)前進(jìn)程中,open_driver只會執(zhí)行一次,得到的 mDriverFD 會一直被使用。
const char* kDefaultDriver = "/dev/binder";
而open函數(shù)操作的這個設(shè)備節(jié)點就是/dev/binder。
納尼?在應(yīng)用層直接操作設(shè)備節(jié)點?Gityuan大佬不會騙我吧?一般來說,Android系統(tǒng)在集成SELinux的安全機制之后,普通應(yīng)用甚至是系統(tǒng)應(yīng)用,都不能直接操作一些設(shè)備節(jié)點,除非有SELinux規(guī)則,給應(yīng)用所屬的域或者角色賦予了那樣的權(quán)限。
看看文件權(quán)限:
➜ ~ adb shell
chiron:/ $ ls -l /dev/binder
crw-rw-rw- 1 root root 10, 49 1972-07-03 18:46 /dev/binder
可以看到,/dev/binder設(shè)備對所有用戶可讀可寫。
再看看,SELinux權(quán)限:
chiron:/ $ ls -Z /dev/binder
u:object_r:binder_device:s0 /dev/binder
查看源碼中對binder_device角色的SELinux規(guī)則描述:
allow domain binder_device:chr_file rw_file_perms;
也就是所有domain對binder的字符設(shè)備有讀寫權(quán)限,而普通應(yīng)用屬于domain。
寫個Demo試一下
驗證一下上面的想法,看看ioctl給binder driver發(fā)數(shù)據(jù)好不好使。
1、打開設(shè)備
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
打開設(shè)備節(jié)點成功了,耶✌️!但是ioctl失敗了🤔,失敗原因是Invalid argument,也就是說可以通信,但是Parcel數(shù)據(jù)有問題。來看看數(shù)據(jù)應(yīng)該是什么樣的。
binder_write_read結(jié)構(gòu)體數(shù)據(jù)封裝
IPCThreadState.talkWithDriver方法中,bwr.write_buffer指針指向了mOut.data(),顯然mOut是一個Parcel對象。
binder_write_read bwr; bwr.write_buffer = (uintptr_t)mOut.data();
再來看看什么時候會向mOut中寫數(shù)據(jù):
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結(jié)構(gòu)體數(shù)據(jù),binder_transaction_data結(jié)構(gòu)體中又包含了作為參數(shù)傳進(jìn)來的data Parcel對象。
writeTransactionData方法會被transact方法調(diào)用:
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { status_t err = data.errorCheck(); // 數(shù)據(jù)錯誤檢查 flags |= TF_ACCEPT_FDS; if (err == NO_ERROR) { // 傳輸數(shù)據(jù) err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); } ... // 默認(rèn)情況下,都是采用非oneway的方式, 也就是需要等待服務(wù)端的返回結(jié)果 if ((flags & TF_ONE_WAY) == 0) { if (reply) { //等待回應(yīng)事件 err = waitForResponse(reply); }else { Parcel fakeReply; err = waitForResponse(&fakeReply); } } else { err = waitForResponse(NULL, NULL); } return err; }
IPCThreadState是跟binder driver真正進(jìn)行交互的類。每個線程都有一個IPCThreadState,每個IPCThreadState中都有一個mIn、一個mOut。成員變量mProcess保存了ProcessState變量(每個進(jìn)程只有一個)。
接著看一下一次Binder調(diào)用的時序圖:
Binder介紹一節(jié)中說過,BpBinder是Binder Client,上層想進(jìn)行進(jìn)程間Binder通信時,會調(diào)用到BpBinder的transact方法,進(jìn)而調(diào)用到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這幾個參數(shù)都是調(diào)用的地方傳過來的,現(xiàn)在唯一不知道的就是mHandle是什么東西。mHandle是BpBinder(也就是Binder Client)的一個int類型的局部變量(句柄),只要拿到了這個handle就相當(dāng)于拿到了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、調(diào)用ActivityManager binder的transact方法傳輸Service的Parcel數(shù)據(jù)。
通過ioctl啟動Service也應(yīng)該是類似的步驟:
1、獲取到ServiceManager的mHandle句柄;
2、進(jìn)行binder調(diào)用獲取到ActivityManager的mHandle句柄;
3、進(jìn)行binder調(diào)用傳輸啟動Service的指令數(shù)據(jù)。
這里有幾個問題:
1、不依賴libbinder.so時,ndk中沒有Parcel類的定義,parcel數(shù)據(jù)哪里來,怎么封裝?
2、如何獲取到BpBinder的mHandle句柄?
如何封裝Parcel數(shù)據(jù)
Parcel類是Binder進(jìn)程間通信的一個基礎(chǔ)的、必不可少的數(shù)據(jù)結(jié)構(gòu),往Parcel中寫入的數(shù)據(jù)實際上是寫入到了一塊內(nèi)部分配的內(nèi)存上,最后把這個內(nèi)存地址封裝到binder_write_read結(jié)構(gòu)體中。Parcel作為一個基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),和Binder相關(guān)類是可以解耦的,可以直接拿過來使用,我們可以根據(jù)需要對有耦合性的一些方法進(jìn)行裁剪。
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; }
可以發(fā)現(xiàn),getStrongProxyForHandle是一個根據(jù)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()); //寫入服務(wù)名 data.writeString16(name); remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply); return reply.readStrongBinder(); }
可以看到,CHECK_SERVICE_TRANSACTION這個binder調(diào)用是有返回值的,返回值會寫到reply中,通過reply.readStrongBinder()方法,即可從reply這個Parcel對象中讀取到ActivityManager的IBinder。每個Binder對象必須要有它自己的mHandle句柄,不然,transact操作是沒辦法進(jìn)行的。所以,很有可能,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); }
調(diào)用到了Parcel::unflattenBinder方法,顧名思義,函數(shù)最終想要得到的是一個Binder對象,而Parcel中存放的是二進(jìn)制的數(shù)據(jù),unflattenBinder很可能是把Parcel中的一個結(jié)構(gòu)體數(shù)據(jù)給轉(zhuǎn)成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結(jié)構(gòu)體,這個結(jié)構(gòu)體重有一個handle變量,這個變量就是BpBinder中的mHandle句柄。
因此,在不依賴libbinder.so的情況下,我們可以自己組裝數(shù)據(jù)發(fā)送給ServiceManager,進(jìn)而獲取到ActivityManager的mHandle句柄。
IPCThreadState是一個被Binder依賴的類,它是可以從源碼中抽離出來為我們所用的。上一節(jié)中說到,Parcel類也是可以從源碼中抽離出來的。
通過如下的操作,我們就可以實現(xiàn)ioctl獲取到ActivityManager對應(yīng)的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結(jié)構(gòu)體的Parcel對象,再經(jīng)過如下的操作就可以得到ActivityManager的mHandle句柄:
const flat_binder_object* flat = reply->readObject(false); return flat->handle;
3、傳輸啟動指定Service的Parcel數(shù)據(jù)
上一步已經(jīng)拿到ActivityManger的mHandle句柄,比如值為1。這一步的過程和上一步類似,自己封裝Parcel,然后調(diào)用IPCThreadState::transact方法傳輸數(shù)據(jù),偽代碼如下:
Parcel data; // 把Service相關(guān)信息寫到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類轉(zhuǎn)Parcel,ComponentName類轉(zhuǎn)Parcel,這些類在c++中是沒有對應(yīng)的類的。所以需要我們參考intent.writeToParcel/ComponentName.writeToParcel等方法的源碼的實現(xiàn),自行封裝數(shù)據(jù)。下面這段代碼就是把啟動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); }
繼續(xù)寫Demo試一下
上面已經(jīng)知道了怎么通過ioctl獲取到ActivityManager,可以寫demo試一下:
// 打開binder設(shè)備 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加上一些日志,打印結(jié)果如下:
D/KeepAlive: BR_DEAD_REPLY
D/KeepAlive: write_transact failed, error=-32
reply中始終讀不到數(shù)據(jù)。這是為什么?現(xiàn)在已經(jīng)不報Invalid argument的錯誤了,說明Parcel數(shù)據(jù)格式可能沒問題了。但是不能成功把數(shù)據(jù)寫給ServiceManager,或者ServiceManager返回的數(shù)據(jù)不能成功寫回來。
想到Binder是基于內(nèi)存的一種IPC機制,數(shù)據(jù)都是對的,那問題就出在內(nèi)存上了。這就要說到Binder基本原理以及Binder內(nèi)存轉(zhuǎn)移關(guān)系。
Binder基本原理:
Binder的Client端和Server端位于不同的進(jìn)程,它們的用戶空間是相互隔離。而內(nèi)核空間由Linux內(nèi)核進(jìn)程來維護(hù),在安全性上是有保障的。所以,Binder的精髓就是在內(nèi)核態(tài)開辟了一塊共享內(nèi)存。
數(shù)據(jù)發(fā)送方寫數(shù)據(jù)時,內(nèi)核態(tài)通過copy_from_user()方法把它的數(shù)據(jù)拷貝到數(shù)據(jù)接收方映射(mmap)到內(nèi)核空間的地址上。這樣,只需要一次數(shù)據(jù)拷貝過程,就可以完成進(jìn)程間通信。
由此可知,沒有這塊內(nèi)核空間是沒辦法完成IPC通信的。Demo失敗的原因就是缺少了一個mmap過程,以映射一塊內(nèi)存到內(nèi)核空間。修改如下:
#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
最后
相關(guān)的代碼我已經(jīng)發(fā)布到Github(lcodecorex/,https://github.com/lcodecorex/KeepAlive
master分支是利用 libbinder.so 與 ActivityManagerService 通信的版本,ioctl分支是使用 ioctl 與 binder 驅(qū)動通信的版本。
當(dāng)然,這個?;畹霓k法雖然很強,但現(xiàn)在也只能活在模擬器里了。
以上就是詳解App?;罴夹g(shù)實現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于App?;罴夹g(shù)實現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android UI設(shè)計與開發(fā)之仿人人網(wǎng)V5.9.2最新版引導(dǎo)界面
這篇文章主要為大家詳細(xì)介紹了Android UI設(shè)計與開發(fā)之仿人人網(wǎng)V5.9.2最新版引導(dǎo)界面,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08Android 雙進(jìn)程守護(hù)的實現(xiàn)代碼
這篇文章主要介紹了Android 雙進(jìn)程守護(hù)的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08Android自定義ViewGroup實現(xiàn)受邊界限制的滾動操作(3)
這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup實現(xiàn)受邊界限制的滾動操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12android中實現(xiàn)完全退出程序方法(退出所有activity)
這篇文章主要介紹了android中實現(xiàn)完全退出程序方法(退出所有activity),本文方法是博主個人使用的一個方法,據(jù)說效果非常好,需要的朋友可以參考下2015-05-05