天天看點

Android跨程序傳大圖思考及實作——附上原理分析1.抛一個問題2.問題定位分析3.提出疑問4.解答疑問5.Intent設定Bitmap發生了什麼?6.跨程序傳大圖

1.抛一個問題

這一天,法海想鍛煉小青的定力,由于Bitmap也是一個Parcelable類型的資料,

法海

想通過

Intent

小青

傳個特别大的圖檔

intent.putExtra("myBitmap",fhBitmap)
           

如果

“法海”

(Activity)使用Intent去傳遞一個

大的Bitmap

“小青”

(Activity),如果你的圖檔夠大,會出現類似下面這樣的錯誤,請繼續往下看:

Caused by: android.os.TransactionTooLargeException: data parcel size 8294952 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:535)
        at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)
           

至于是什麼樣的大圖,這個隻有法海知道了

(小青:好羞澀啊)

🙈🙈🙈

Android跨程式傳大圖思考及實作——附上原理分析1.抛一個問題2.問題定位分析3.提出疑問4.解答疑問5.Intent設定Bitmap發生了什麼?6.跨程式傳大圖

是以TransactionTooLargeException這玩意爆出來的地方在哪呢?

2.問題定位分析

我們可以看到錯誤的日志資訊裡面看到調用了

BinderProxy.transactNative

,這個transactNative是一個native方法

//android.os.BinderProxy
/**
 * Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
           

在Android Code Search,全局搜尋一下:android_os_BinderProxy_transact

//frameworks/base/core/jni/android_util_Binder.cpp

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ......
    status_t err = target->transact(code, *data, reply, flags);
    ......
    if (err == NO_ERROR) { 
        //如果比對成功直接攔截不往下面執行了
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }
    signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
    return JNI_FALSE;
}
           

我們打開signalExceptionForError方法看看裡面的内容

//frameworks/base/core/jni/android_util_Binder.cpp
//處理異常的方法
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
        bool canThrowRemoteException, int parcelSize)
{
    switch (err) {
        //其他異常,大家可以自行閱讀了解;
        //如:沒有權限異常,檔案太大,錯誤的檔案描述符,等等;
        ........
        case FAILED_TRANSACTION: {
            const char* exceptionToThrow;
            char msg[128];
            //官方在FIXME中寫道:事務過大是FAILED_TRANSACTION最常見的原因
            //但它不是唯一的原因,Binder驅動可以傳回 BR_FAILED_REPLY
            //也有其他原因可能是:事務格式不正确,檔案描述符FD已經被關閉等等

            //parcelSize大于200K就會報錯,canThrowRemoteException傳遞進來的是true
            if (canThrowRemoteException && parcelSize > 200*1024) {
                // bona fide large payload
                exceptionToThrow = "android/os/TransactionTooLargeException";
                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
            } else {
                ..........
            }
            //使用指定的類和消息内容抛出異常
            jniThrowException(env, exceptionToThrow, msg);
        } break;
        ........
    }
}
           

此時我們看到: parcelSize大于200K就會報錯,難道一定是200K以内?先别着急着下結論,繼續往下看👇👇

3.提出疑問

法海:我有個疑問,我看到文檔寫的1M大小啊;

許仙:别急,妹夫,來先看一下文檔的解釋,看一下使用說明:

官方TransactionTooLargeException的文檔中描述到:

Binder 事務緩沖區有一個有限的固定大小,目前為 1MB,由程序所有正在進行的事務共享

可以看到寫的是:共享事務的緩沖區

如來佛祖:汝等别急,我們簡單測試一下,Intent傳遞

201*1024

個位元組數組,我們發現可以正常傳遞過去,Logcat僅僅輸出了一個Error提示的日志資訊,還是可以正常傳遞的

E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0
           

我們再測試一個值,intent傳遞

800*1024

個位元組數組,我們發現會崩潰

android.os.TransactionTooLargeException: data parcel size 821976 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)
        at android.app.servertransaction.ClientTransaction.schedule(ClientTransaction.java:136)
           

不要着急,我們繼續往下看分析

4.解答疑問

我們來看一下,下面兩行代碼

//frameworks/base/core/jni/android_util_Binder.cpp
//這個方法android_os_BinderProxy_transact裡面的
IBinder* target = getBPNativeData(env, obj)->mObject.get();
status_t err = target->transact(code, *data, reply, flags);
           

從上面的分析和測試結果,我們從

target->transact

這裡來找

err傳回值

, 先根據頭檔案,搜尋對應的cpp類,我們看一下這幾個cpp類:BpBinder.cpp、 IPCThreadState.cpp、ProcessState.cpp

//frameworks/native/libs/binder/ProcessState.cpp

// (1 * 1024 * 1024) - (4096 *2)
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define DEFAULT_MAX_BINDER_THREADS 15

//下面兩個注釋
//引用自官方文檔:https://source.android.google.cn/devices/architecture/hidl/binder-ipc
#ifdef __ANDROID_VNDK__
//供應商/供應商程序之間的IPC,使用 AIDL 接口
const char* kDefaultDriver = "/dev/vndbinder";
#else
// "/dev/binder" 裝置節點成為架構程序的專有節點
const char* kDefaultDriver = "/dev/binder";
#endif

//構造函數:初始化一些變量,Binder最大線程數等
ProcessState::ProcessState(const char* driver)
      : mDriverName(String8(driver)),
        mDriverFD(-1),
        mVMStart(MAP_FAILED),
        ......
        mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
        mStarvationStartTimeMs(0),
        mThreadPoolStarted(false),
        mThreadPoolSeq(1),
        mCallRestriction(CallRestriction::NONE) {
    ......
    //打開驅動
    base::Result<int> opened = open_driver(driver);
    if (opened.ok()) {
        //映射(1M-8k)的mmap空間
        mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
                        opened.value(), 0);
        ......
    }
    ......
}
           

點選檢視sysconf.cpp

getauxval(AT_PAGESZ) = 4096,可以得出Binder記憶體限制,BINDER_VM_SIZE = 1M-8kb

這裡為什麼不是1M,而是1M-8K?

最開始的時候,官方寫的是1M,後來他們内部自己優化了;

來看這裡👉👉官方送出的ProcessState.cpp送出的log日志:允許核心更有效地利用其虛拟位址空間

我們知道:

微信的MMKV

美團的Logan的日志元件

,都是

基于mmap

來實作的;

binder驅動的注冊邏輯在Binder.c中,我們看一下

binder_mmap

方法

//kernel/msm/drivers/android/binder.c
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	if (proc->tsk != current->group_leader)
		return -EINVAL;
        //這裡可以看到:映射空間最多4M
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
	......
        //初始化指定的空間vma用于配置設定綁定緩沖區
	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
        ......
}
           

這裡能看到映射空間最多4M,我們再來看一下binder_alloc_mmap_handler這個方法,點選檢視binder_alloc.c

//kernel/msm/drivers/android/binder_alloc.c
//由binder_mmap()調用來初始化指定的空間vma用于配置設定綁定緩沖區
int binder_alloc_mmap_handler(struct binder_alloc *alloc,
			      struct vm_area_struct *vma)
{
     ......
     //buffer_size最大4M
     alloc->buffer_size = vma->vm_end - vma->vm_start;
     ......
     //異步事務的空閑緩沖區大小最大2M
     alloc->free_async_space = alloc->buffer_size / 2;
     ......
}
           

從上面的分析得出結論:

1.Binder驅動給每個程序最多配置設定4M的buffer空間大小;

2.異步事務的空閑緩沖區空間大小最多為2M;

3.Binder核心記憶體上限為1M-8k;

4.異步事務緩沖區空間大小等于buffer_size/2,具體值取決于buffer_size;

同步、異步是定義在AIDL檔案中的,我們看上面測試的兩個例子,其中有一個傳了

800*1024

個位元組數組崩潰如下:

android.os.TransactionTooLargeException: data parcel size 821976 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)
           

點選檢視IApplicationThread.aidl 檢視AIDL裡面的内容,我們看到scheduleTransaction是一個異步的方法;

因為oneway修飾在interface之前,會讓interface内所有的方法都隐式地帶上oneway;

由于oneway異步調用,我們這個時候修改一下,傳遞

(1M-8k)/2

大小之内的資料測試一下

// ((1024 * 1024 - 8 * 1024)/2)-1

 E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 520236, icicle size: 0

Exception when starting activity com.melody.test/.SecondActivity
    android.os.TransactionTooLargeException: data parcel size 522968 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)
           

可以看到還是會報錯,說明異步事務的可用空間不夠,仔細看一下為什麼不夠,細心的同學可能發現了:

警告的日志列印:

extras size: 520236

崩潰的日志列印:

data parcel size: 522968

大小相差:

2732

約等于

2.7k

如果這個時候我們用Intent傳遞一個ByteArray,比之前的大小

減去3k

ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024)

startActivity(Intent(this,SecondActivity::class.java).apply {
            putExtra("KEY",ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024))
})
           

這個時候發現

(1M-8k)/2 -3k

,可以成功傳遞資料,說明有其他資料占用了這部分空間。

我們上面寫了,不要忘記:共享事務的緩沖區,

這裡減去3k僅測試用的

,我們繼續往下分析;

找一下:異步事務的空閑緩沖區空間大小比較的地方,打開binder_alloc.c,找到

binder_alloc_new_buf

方法

//kernel/msm/drivers/android/binder_alloc.c
//配置設定一個新緩沖區
struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
					   size_t data_size,
					   size_t offsets_size,
					   size_t extra_buffers_size,
					   int is_async,
					   int pid)
{
	......
	buffer = binder_alloc_new_buf_locked(alloc, data_size, offsets_size,extra_buffers_size, is_async, pid);
        .......
}
           

我們來看一下binder_alloc_new_buf_locked方法

//kernel/msm/drivers/android/binder_alloc.c
static struct binder_buffer *binder_alloc_new_buf_locked(	
	struct binder_alloc *alloc,
	size_t data_size,
	size_t offsets_size,
	size_t extra_buffers_size,
	int is_async,
	int pid)
{
    ......
    //如果是異步事務,檢查所需的大小是否在異步事務的空閑緩沖區區間内
    if (is_async &&
	alloc->free_async_space < size + sizeof(struct binder_buffer)) {
            return ERR_PTR(-ENOSPC);
	}
}
           

分析了這麼多,不論是同步還是異步,都是共享事務的緩沖區,如果有大量資料需要通過Activity的Intent傳遞,資料大小最好維持在200k以内;

上面測試的時候,超出200k資料傳遞的時候,LogCat已經給我們列印提示

“Transaction too large”

了,但是

隻要

沒有超出異步事務空閑的緩沖區大小,

就不會崩潰

如果Intent傳遞大量的資料完全可以使用别的方式方法;

5.Intent設定Bitmap發生了什麼?

5.1-Intent.writeToParcel

Intent資料寫入到parcel中,在writeToParcel方法裡面,Intent把Bundle寫入到Parcel中

//android.content.Intent

public void writeToParcel(Parcel out, int flags) {
    ......
    //把Bundle寫入到Parcel中
    out.writeBundle(mExtras);
}
           

打開

out.writeBundle

方法

//android.os.Parcel#writeBundle
public final void writeBundle(@Nullable Bundle val) {
     if (val == null) {
         writeInt(-1);
         return;
     }
     //執行Bundle自身的writeToParcel方法
     val.writeToParcel(this, 0);
}
           

5.2-Bundle.writeToParcel

//android.os.Bundle

public void writeToParcel(Parcel parcel, int flags) {
     final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
     try {
        //這裡官方注釋已經寫的很詳細了:
        //将Bundle内容寫入Parcel,通常是為了讓它通過IBinder連接配接傳遞
        super.writeToParcelInner(parcel, flags);
     } finally {
        //把mAllowFds值設定回來
        parcel.restoreAllowFds(oldAllowFds);
     }
}
           

點選檢視Parcel.cpp,我們看一下裡面的

pushAllowFds

方法

//frameworks/native/libs/binder/Parcel.cpp
bool Parcel::pushAllowFds(bool allowFds)
{
    const bool origValue = mAllowFds;
    if (!allowFds) {
        mAllowFds = false;
    }
    return origValue;
}
           

如果Bundle設定了不允許帶描述符,當調用pushAllowFds之後Parcel中的内容也不帶描述符;

在文章開頭,我們舉的例子中:通過Intent去傳遞一個Bitmap,在執行到

Instrumentation#execStartActivity

的時候,我們發現Intent有個

prepareToLeaveProcess

方法,在此方法裡面調用了

Bundle#setAllowFds(false)

//android.app.Instrumentation
public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        try {
            ......
            intent.prepareToLeaveProcess(who);
            ......
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
           

5.3-Parcel.writeArrayMapInternal

剛剛上面Bundle.writeToParcel方法裡面

super.writeToParcelInner

觸發下面方法

//android.os.BaseBundle
void writeToParcelInner(Parcel parcel, int flags) {
       ......
       parcel.writeArrayMapInternal(map);
       ......
}
           

我們看一下

writeArrayMapInternal

方法

void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
        ......
        for (int i=0; i<N; i++) {
            writeString(val.keyAt(i));
            //根據不同資料類型調用不同的write方法
            writeValue(val.valueAt(i));
        }
    }
           

5.4-writeValue

文章一開頭我們使用的是

intent.putExtra("bmp",法海bitmap)

//android.os.Parcel
public final void writeValue(@Nullable Object v) {
    ......
    if (v instanceof Parcelable) {
        writeInt(VAL_PARCELABLE);
        writeParcelable((Parcelable) v, 0);
    } 
    ......
}
public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
    ......
    writeParcelableCreator(p);
    p.writeToParcel(this, parcelableFlags);
}
           

因為傳入的是Bitmap,我們看

Bitmap.writeToParcel

5.5-Bitmap.writeToParcel

//android.graphics.Bitmap
public void writeToParcel(Parcel p, int flags) {
    noteHardwareBitmapSlowCall();
    //打開Bitmap.cpp找對應的native方法
    if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
        throw new RuntimeException("native writeToParcel failed");
    }
}
           

點選打開Bitmap.cpp,檢視

Bitmap_writeToParcel

方法

//frameworks/base/libs/hwui/jni/Bitmap.cpp

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
                                     jlong bitmapHandle, jint density, jobject parcel) {
    ......
    //獲得Native層的對象
    android::Parcel* p = parcelForJavaObject(env, parcel);
    SkBitmap bitmap;
    auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
    //擷取SkBitmap
    bitmapWrapper->getSkBitmap(&bitmap);
    //寫入parcel
    p->writeInt32(!bitmap.isImmutable());
    ......
    p->writeInt32(bitmap.width());
    p->writeInt32(bitmap.height());
    p->writeInt32(bitmap.rowBytes());
    p->writeInt32(density);

    // Transfer the underlying ashmem region if we have one and it's immutable.
    android::status_t status;
    int fd = bitmapWrapper->bitmap().getAshmemFd();
    if (fd >= 0 && bitmap.isImmutable() && p->allowFds()) {
        //AshmemFd大于等于0 && bitmap不可變 && parcel允許帶Fd
        //符合上述條件,将fd寫入到parcel中
        status = p->writeDupImmutableBlobFileDescriptor(fd);
        if (status) {
            doThrowRE(env, "Could not write bitmap blob file descriptor.");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }

    //mutableCopy=true:表示bitmap是可變的
    const bool mutableCopy = !bitmap.isImmutable();
    //傳回像素存儲所需的最小記憶體
    size_t size = bitmap.computeByteSize();
    android::Parcel::WritableBlob blob;
    //擷取到一塊blob緩沖區,往下翻有代碼分析
    status = p->writeBlob(size, mutableCopy, &blob);
    ......
}
           

我們來看看writeBlob裡面做了什麼事情

5.6-Parcel::writeBlob

//frameworks/native/libs/binder/Parcel.cpp

static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;  // 16k

status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
    status_t status;
    if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
        //如果不允許帶FD 或者 資料小于等于16k,則直接将圖檔寫入到parcel中
        status = writeInt32(BLOB_INPLACE);
        if (status) return status;
        void* ptr = writeInplace(len);
        if (!ptr) return NO_MEMORY;
        outBlob->init(-1, ptr, len, false);
        return NO_ERROR;
    }
    //不滿足上面的條件,即(允許Fd && len > 16k):
    //建立一個新的ashmem區域并傳回檔案描述符FD
    //ashmem-dev.cpp裡面有注釋說明:
    //https://cs.android.com/android/platform/superproject/+/master:system/core/libcutils/ashmem-dev.cpp
    int fd = ashmem_create_region("Parcel Blob", len);
    if (fd < 0) return NO_MEMORY;
    //設定ashmem這塊區域是“可讀可寫”
    int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
    if (result < 0) {
        status = result;
    } else {
         //根據fd,映射 “len大小” 的mmap的空間
         void* ptr = ::mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
         ......
         if (!status) {
           //将fd寫入到parcel中
           status = writeFileDescriptor(fd, true /*takeOwnership*/);
            if (!status) {
                outBlob->init(fd, ptr, len, mutableCopy);
                return NO_ERROR;
             }
        }
        ......
    }
    ......
}
           

看到這裡,大家應該知道我們為什麼先分析Intent傳遞資料大小的上限了吧;

在目錄5下面的 5.2-Bundle.writeToParcel已經說明清楚了,Intent啟動Activity的時候,禁用掉了檔案描述符;

是以: 在執行writeBlob方法隻能執行到第一個分支,

直接将圖檔寫入到parcel中

,我們在目錄4給出Intent傳遞資料大小限制的結論;

那麼如何不受Intent禁用檔案描述符和資料大小的限制?

6.跨程序傳大圖

在Parcel類中看到writeValue方法裡面有個分支,判斷目前value是不是IBinder,如果是IBinder類型的會調用writeStrongBinder把這個對象寫入到Parcel中;

是以我們可以使用Bundle的putBinder來把

IBinder對象

寫入到Parcel中,通過

putBinder

不會受Intent禁用檔案描述符的影響,資料大小也沒有限制,Bitmap寫入到parcel中預設是true,可以使用匿名共享記憶體(Ashmem);

6.1-單程序下putBinder用法

//定義一個IntentBinder,此方法僅在『同一個程序』下有效哦,切記切記!!!!
class IntentBinder(val imageBmp:Bitmap? = null): Binder()

//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
        putBinder("myBinder",IntentBinder(bitmap))
}))

//------------------------擷取Bitmap并顯示如下--------------------------//
//com.xxx.xxx.SecondActivity
val bundle: Bundle? = intent.extras
val imageBinder:IntentBinder? = bundle?.getBinder("myBinder") as IntentBinder?
//拿到Binder中的Bitmap
val bitmap = imageBinder?.imageBmp
//自行壓縮後顯示到ImageView上.....
           

注意: 這個用法

不能

跨程序,喜歡動手的同學,可以試一試,給SecondActivity配置一個

android:process=":remote"

,你會發現會報一個

強制轉換的異常錯誤

//錯誤的用在多程序場景下,報錯如下:
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.xxx.xxx.IntentBinder
           

🤔為什麼可以通過這種方式傳遞對象?

Binder會為我們的對象建立一個全局的JNI引用,點選檢視android_util_Binder.cpp

//frameworks/base/core/jni/android_util_Binder.cpp
......
static struct bindernative_offsets_t
{
    // Class state.
    jclass mClass;
    jmethodID mExecTransact;
    jmethodID mGetInterfaceDescriptor;

    // Object state.
    jfieldID mObject;

} gBinderOffsets;
......
static const JNINativeMethod gBinderMethods[] = {
     /* name, signature, funcPtr */
    // @CriticalNative
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
    // @CriticalNative
    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
    ......
    { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
    { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
};

const char* const kBinderPathName = "android/os/Binder";

//調用下面這個方法,完成Binder類的注冊
static int int_register_android_os_Binder(JNIEnv* env)
{
    //擷取Binder的class對象
    jclass clazz = FindClassOrDie(env, kBinderPathName);

    //内部建立全局引用,并将clazz儲存到全局變量中
    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);

    //擷取Java層的Binder的成員方法execTransact
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");

    //擷取Java層的Binder的成員方法getInterfaceDescriptor
    gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor",
        "()Ljava/lang/String;");

    //擷取Java層的Binder的成員變量mObject
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");

    //注冊gBinderMethods中定義的函數
    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}
......
           

6.2-多程序下putBinder用法

//先定義一個IGetBitmapService.aidl
package com.xxx.aidl;
interface IGetBitmapService {
    Bitmap getIntentBitmap();
}

//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity      👉程序A
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
    putBinder("myBinder",object: IGetBitmapService.Stub() {
        override fun getIntentBitmap(): Bitmap {
            return bitmap
        }
    })
}))

//------------------------擷取Bitmap并顯示如下--------------------------//
//com.xxx.xxx.SecondActivity      👉程序B
val bundle: Bundle? = intent.extras
//傳回IGetBitmapService類型
val getBitmapService = IGetBitmapService.Stub.asInterface(bundle?.getBinder("myBinder"))
val bitmap = getBitmapService.intentBitmap
//自行壓縮後顯示到ImageView上.....