欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解App?;罴夹g(shù)實現(xiàn)

 更新時間:2021年06月09日 14:40:24   作者:lsgxeva  
隨著Android陣營的各大手機廠商對于續(xù)航的高度重視,兩三年前的手機發(fā)布會更是把反保活作為一個系統(tǒng)的賣點,不斷提出了各種反保活的方案,導(dǎo)致現(xiàn)在想實現(xiàn)應(yīng)用?;詈喼彪y于上青天,甚至都需要一個團(tuán)隊來專門研究這個事情。本文將詳細(xì)介紹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)界面

    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-08
  • Android 雙進(jìn)程守護(hù)的實現(xiàn)代碼

    Android 雙進(jìn)程守護(hù)的實現(xiàn)代碼

    這篇文章主要介紹了Android 雙進(jìn)程守護(hù)的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • 詳解Android版本適配:9.0 Pie

    詳解Android版本適配:9.0 Pie

    這篇文章主要介紹了Android版本適配9.0 Pie,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Kotlin掛起函數(shù)的詳細(xì)介紹

    Kotlin掛起函數(shù)的詳細(xì)介紹

    掛起函數(shù)用狀態(tài)機以掛起點將協(xié)程的運算邏輯拆分成不同的片段,每次執(zhí)行協(xié)程運行不同的邏輯片段,由此可以知道協(xié)程是運行在線程中的,線程的并發(fā)處理方式也可以用在協(xié)程上
    2022-09-09
  • Android自定義圓形倒計時進(jìn)度條

    Android自定義圓形倒計時進(jìn)度條

    這篇文章主要為大家詳細(xì)介紹了Android自定義圓形倒計時進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • Android自定義ViewGroup實現(xiàn)受邊界限制的滾動操作(3)

    Android自定義ViewGroup實現(xiàn)受邊界限制的滾動操作(3)

    這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup實現(xiàn)受邊界限制的滾動操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • android中實現(xiàn)完全退出程序方法(退出所有activity)

    android中實現(xiàn)完全退出程序方法(退出所有activity)

    這篇文章主要介紹了android中實現(xiàn)完全退出程序方法(退出所有activity),本文方法是博主個人使用的一個方法,據(jù)說效果非常好,需要的朋友可以參考下
    2015-05-05
  • Flutter自定義年月日倒計時

    Flutter自定義年月日倒計時

    這篇文章主要為大家詳細(xì)介紹了Flutter自定義年月日倒計時,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • android從資源文件中讀取文件流并顯示的方法

    android從資源文件中讀取文件流并顯示的方法

    這篇文章主要介紹了android從資源文件中讀取文件流并顯示的方法,涉及Android針對文件的讀取及顯示技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • Android context源碼詳解及深入分析

    Android context源碼詳解及深入分析

    這篇文章主要介紹了Android context源碼詳解及深入分析的相關(guān)資料,這里對Android Context 如何使用進(jìn)行了詳細(xì)介紹,需要的朋友可以參考下
    2017-01-01

最新評論