Android跨程序傳大圖思考及實現——附上原理分析

語言: CN / TW / HK

highlight: androidstudio theme: arknights


1.拋一個問題

這一天,法海想鍛鍊小青的定力,由於Bitmap也是一個Parcelable型別的資料,法海想通過Intent小青傳個特別大的圖片 kotlin intent.putExtra("myBitmap",fhBitmap) 如果“法海”(Activity)使用Intent去傳遞一個大的Bitmap“小青”(Activity),如果你的圖片夠大,會出現類似下面這樣的錯誤,請繼續往下看: text 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) 至於是什麼樣的大圖,這個只有法海知道了(小青:好羞澀啊)🙈🙈🙈

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

2.問題定位分析

我們可以看到錯誤的日誌資訊裡面看到呼叫了BinderProxy.transactNative,這個transactNative是一個native方法

java //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 ```c++ //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方法看看裡面的內容

```c++ //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提示的日誌資訊,還是可以正常傳遞的 text E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0 我們再測試一個值,intent傳遞800*1024個位元組陣列,我們發現會崩潰 text 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.解答疑問

我們來看一下,下面兩行程式碼 c++ //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.cppIPCThreadState.cppProcessState.cpp
```c++ //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

//下面兩個註釋 //引用自官方文件:http://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 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方法

c++ //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

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個位元組陣列崩潰如下: text 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大小之內的資料測試一下 ```text // ((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) kotlin 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方法 c++ //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方法

c++ //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中

```java //android.content.Intent

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

java //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

```java //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**](http://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/Parcel.cpp),我們看一下里面的pushAllowFds`方法

c++ //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)
java //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觸發下面方法 java //android.os.BaseBundle void writeToParcelInner(Parcel parcel, int flags) { ...... parcel.writeArrayMapInternal(map); ...... } 我們看一下writeArrayMapInternal方法

java 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) java //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

java //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方法

```c++ //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(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

```java //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裡面有註釋說明: //http://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**](#heading-4)下面的 [**5.2-Bundle.writeToParcel**](#heading-6)已經說明清楚了,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用法

```kotlin //定義一個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"`,你會發現會報一個`強制轉換的異常錯誤`text //錯誤的用在多程序場景下,報錯如下: java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.xxx.xxx.IntentBinder ``` 🤔為什麼可以通過這種方式傳遞物件?
Binder會為我們的物件建立一個全域性的JNI引用,點選檢視android_util_Binder.cpp

```c++ //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用法

```kotlin //先定義一個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上..... ```

7.參考

1.Binder kernel 基礎方法
2.Android Binder 魅族核心團隊
3.Android系統匿名共享記憶體Ashmem驅動程式原始碼分析
4.Android 匿名共享記憶體的使用


往期文章推薦:
1.Jetpack Compose UI建立佈局繪製流程+原理 —— 內含概念詳解(滿滿乾貨)
2.Jetpack App Startup如何使用及原理分析
3.Jetpack Compose - Accompanist 元件庫
4.原始碼分析 | ThreadedRenderer空指標問題,順便把Choreographer認識一下
5.原始碼分析 | 事件是怎麼傳遞到Activity的?
6.聊聊CountDownLatch 原始碼
7.Android正確的保活方案,不要掉進保活需求死迴圈陷進