面試官提了一個問題,跨進程傳遞大圖,你能想到哪些方案呢??
我們來看看 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 , 可以利用共享內存,所以能高效傳輸圖片。?
面試官:可以,我們再來聊聊別的。?
看完了這三位同學的面試表現,你有什么感想呢??