面試官:跨進程傳遞大圖,你能想到哪些方案呢?

面試官提了一個問題,跨進程傳遞大圖,你能想到哪些方案呢??

我們來看看 A、B和 C 三位同學的表現如何吧??

A同學自認為無所不知,水平已達應用開發(fā)天花板,目前月薪 10k?

面試官:如何跨進程傳遞大圖?

A:很簡單,把圖片存到 SD 卡,然后把路徑傳過去,在別的進程讀出來這不就完事了嘛。

面試官:這個需要文件操作,效率不行,有別的方法嗎?

A:Bitmap 實現了 Parcelable 接口,可以通過 Intent.putExtra(String name, Parcelable value) 方法直接放 Intent 里面?zhèn)鬟f。?

?面試官:那這個方法有什么缺點??

?A:Bitmap 太大就會拋異常,所以我更喜歡傳路徑

?面試官:為什么會拋異常??

?A:.....?

?面試官:好的,回去等通知吧?

B同學業(yè)余時間經常打游戲、追劇、熬夜,目前月薪 15k

面試官:Intent 直接傳 Bitmap 會有什么問題??

?B:Bitmap 太大會拋 TransactionTooLargeException 異常,原因是:底層判斷只要 Binder Transaction 失敗,且 Intent 的數據大于 200k 就會拋這個異常了。(見:android_util_Binder.cpp 文件 signalExceptionForError 方法)

面試官:為什么 Intent 傳值會有大小限制。?

?B:應用進程在啟動 Binder 機制時會映射一塊 1M 大小的內存,所有正在進行的 Binder 事務共享這 1M 的緩沖區(qū)。當使用 Intent 進行 IPC 時申請的緩存超過 1M - 其他事務占用的內存時,就會申請失敗拋 TransactionTooLargeException 異常了。(哼,不會像上次一樣答不出來了。見:“談談你對 binder 的理解?”)?

?面試官:如何繞開這個限制呢?

B:通過 AIDL 使用 Binder 進行 IPC 就不受這個限制,具體代碼如下:?

Bundle bundle = new Bundle();

bundle.putBinder("binder", new IRemoteGetBitmap.Stub() {

? ? @Override

? ? public Bitmap getBitMap() throws RemoteException {

? ? ? ? return mBitmap;

? ? }

});

intent.putExtras(bundle);

面試官:這是什么原理呢??

?B:還沒去細看?

?面試官:好的,回去等通知吧?

C 同學堅持每天學習、不斷的提升自己,目前月薪 30k?

面試官:為什么通過 putBinder 的方式傳 Bitmap 不會拋 TransactionTooLargeException 異常?

C:這個問題,我們先來看下,底層在 IPC 時是怎么把 Bitmap 寫進 Parcel 的。

Android - 28 Bitmap.cpp

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, ...) {

? ? // 拿到 Native 的 Bitmap? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

? ? auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);

? ? // 拿到其對應的 SkBitmap, 用于獲取 Bitmap 的像素信息

? ? bitmapWrapper->getSkBitmap(&bitmap);

? ? int fd = bitmapWrapper->bitmap().getAshmemFd();

? ? if (fd >= 0 && !isMutable && p->allowFds()) {

? ? ? ? // Bitmap 帶了 ashmemFd && Bitmap 不可修改 && Parcel 允許帶 fd

? ? ? ? // 就直接把 FD 寫到 Parcel 里,結束。

? ? ? ? status = p->writeDupImmutableBlobFileDescriptor(fd);

? ? ? ? return JNI_TRUE;

? ? }

? ? // 不滿足上面的條件就要把 Bitmap 拷貝到一塊新的緩沖區(qū)

? ? android::Parcel::WritableBlob blob;

? ? // 通過 writeBlob 拿到一塊緩沖區(qū) blob

? ? status = p->writeBlob(size, mutableCopy, &blob);

? ? // 獲取像素信息并寫到前面拿到緩沖區(qū) blob

? ? const void* pSrc =? bitmap.getPixels();

? ? if (pSrc == NULL) {

? ? ? ? memset(blob.data(), 0, size);

? ? } else {

? ? ? ? memcpy(blob.data(), pSrc, size);

? ? }

}

接下來我們看一下 writeBlob 是怎么獲取緩沖區(qū)的(注意雖然方法名寫著 write , 但是實際往緩沖區(qū)寫數據是在 writeBlob 方法執(zhí)行之后處理的)

Android - 28 Parcel.cpp // Maximum size of a blob to transfer in-place. static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;

status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob) {

? ? if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {

? ? ? ? // 如果不允許帶 fd ,或者這個數據小于 16K

? ? ? ? // 就直接在 Parcel 的緩沖區(qū)里分配一塊空間來保存這個數據

? ? ? ? status = writeInt32(BLOB_INPLACE);

? ? ? ? void* ptr = writeInplace(len);

? ? ? ? outBlob->init(-1, ptr, len, false);

? ? ? ? return NO_ERROR;

? ? }

? ? // 另外開辟一個 ashmem,映射出一塊內存,后續(xù)數據將保存在 ashmem 的內存里

? ? int fd = ashmem_create_region("Parcel Blob", len);

? ? void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

? ? ...

? ? // parcel 里只寫個 fd 就好了,這樣就算數據量很大,parcel 自己的緩沖區(qū)也不用很大

? ? status = writeFileDescriptor(fd, true /*takeOwnership*/);

? ? outBlob->init(fd, ptr, len, mutableCopy);

? ? return status; }

通過上面的分析,我們可以看出,同一個 Bitmap 寫入到 Parcel 所占的緩沖區(qū)大小和 Pacel 的 allowFds 有關。

直接通過 Intent 傳 Bitmap 容易拋 TransactionTooLargeException 異常,就是因為 Parcel 的 allowFds = false,直接把 Bitmap 寫入緩沖區(qū)占用了較大的內存。?

接下來,我們來看一下,allowFds 是什么時候被設置成 false 的呢:?

// 啟動 Activity 執(zhí)行到 Instrumentation.java 的這個方法

public ActivityResult execStartActivity(..., Intent intent, ...){

? ...

? intent.prepareToLeaveProcess(who);

? ActivityManager.getService().startActivity(...,intent,...)

}

// Intent.java

public void prepareToLeaveProcess(boolean leavingPackage) {

// 這邊一層層傳遞到最后設置 Parcel 的 allowfds

? setAllowFds(false);

? ....

}

?面試官:太多了,你懂的。?

?C:總結一下:較大的 bitmap 直接通過 Intent 傳遞容易拋異常是因為 Intent 啟動組件時,系統禁掉了文件描述符 fd 機制 , bitmap 無法利用共享內存,只能拷貝到 Binder 映射的緩沖區(qū),導致緩沖區(qū)超限, 觸發(fā)異常; 而通過 putBinder 的方式,避免了 Intent 禁用描述符的影響,bitmap 寫 parcel 時的 allowFds 默認是 true , 可以利用共享內存,所以能高效傳輸圖片。?

面試官:可以,我們再來聊聊別的。?

看完了這三位同學的面試表現,你有什么感想呢??

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容