前言
在[007]一次Binder通信最大可以傳輸多大的數(shù)據(jù)?這個(gè)文章,我得到了一個(gè)結(jié)論,就是正常情況下一次Binder通信最大可以傳輸?shù)臄?shù)據(jù)的大小是1MB-8KB。突然想到我們?cè)谕ㄟ^(guò)ContentResolver對(duì)象調(diào)用ContentProvider的調(diào)用query返回Cursor的時(shí)候,本質(zhì)上這是一次Binder通信,那這個(gè)Cursor對(duì)象大小有沒(méi)有限制呢?是不是也要小于1MB-8KB?
Cursor的實(shí)現(xiàn)原理
rameworks/base/libs/androidfw/CursorWindow.cpp
status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
String8 ashmemName("CursorWindow: ");
ashmemName.append(name);
status_t result;
//調(diào)用ashmem_create_region創(chuàng)建一個(gè)共享內(nèi)存
int ashmemFd = ashmem_create_region(ashmemName.string(), size);
if (ashmemFd < 0) {
result = -errno;
} else {
//設(shè)置匿名共享內(nèi)存可讀可寫(xiě)
result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
if (result >= 0) {
//調(diào)用mmap進(jìn)行內(nèi)存映射
void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
if (data == MAP_FAILED) {
result = -errno;
} else {
result = ashmem_set_prot_region(ashmemFd, PROT_READ);
if (result >= 0) {
CursorWindow* window = new CursorWindow(name, ashmemFd,
data, size, false /*readOnly*/);
//省略部分代碼
return result;
}
//序列化只傳遞兩個(gè)數(shù)據(jù)1.mName 2. mAshmemFd
status_t CursorWindow::writeToParcel(Parcel* parcel) {
status_t status = parcel->writeString8(mName);//一個(gè)名字
if (!status) {
status = parcel->writeDupFileDescriptor(mAshmemFd);//匿名共享內(nèi)存的文件描述FD
}
return status;
}
從代碼來(lái)看Cursor的真實(shí)實(shí)現(xiàn)應(yīng)該是CursorWindow.cpp,CursorWindow的Data數(shù)據(jù)真實(shí)實(shí)現(xiàn)是匿名共享內(nèi)存,在序列化到Parcel的時(shí)候,只需要傳遞String和匿名共享內(nèi)存的FD就好了。
這樣子看來(lái)Cursor的大小是不受限制的,不懂匿名共享內(nèi)存的可以先看一下[006]匿名共享內(nèi)存(Ashmem)的使用
其實(shí)在Android Framework中對(duì)此有一定的限制,請(qǐng)注意在CursorWindow::create會(huì)傳遞一個(gè)size的參數(shù)。
frameworks/base/core/java/android/database/CursorWindow.java
public CursorWindow(String name) {
this(name, getCursorWindowSize());
}
public CursorWindow(String name, @BytesLong long windowSizeBytes) {
mStartPos = 0;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
//windowSizeBytes默認(rèn)是getCursorWindowSize的返回值就是2048*1024
mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
windowSizeBytes + " bytes failed. " + printStats());
}
mCloseGuard.open("close");
recordNewWindow(Binder.getCallingPid(), mWindowPtr);
}
private static int getCursorWindowSize() {
if (sCursorWindowSize < 0) {
//<integer name="config_cursorWindowSize">2048</integer>
sCursorWindowSize = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_cursorWindowSize) * 1024;
}
return sCursorWindowSize;
}
總結(jié):
Cursor的Data區(qū)域是基于匿名共享內(nèi)存實(shí)現(xiàn)的,所以Binder進(jìn)程傳遞的Cursor對(duì)象,本質(zhì)上就是一個(gè)String和FD(根本不用擔(dān)心超出Binder的1MB-8KB的限制導(dǎo)致異常),但是這個(gè)匿名共享內(nèi)存的大小是有限制的,安卓系統(tǒng)中Cursor的data匿名共享內(nèi)存的大小限制是2MB。當(dāng)然可以通過(guò)調(diào)用public CursorWindow(String name, @BytesLong long windowSizeBytes)來(lái)設(shè)置Cursor的Data區(qū)域大小
意外發(fā)現(xiàn)Parcel.cpp中的writeBlob方法
frameworks/native/libs/binder/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 (len > INT32_MAX) {
// don't accept size_t values which may have come from an
// inadvertent conversion from a negative int.
return BAD_VALUE;
}
status_t status;
//如果mAllowFds為false或者len小于等于16kb,使用普通的方式保存數(shù)據(jù)
if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
ALOGV("writeBlob: write in place");
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;
}
//使用匿名共享內(nèi)存的方式保存數(shù)據(jù)
ALOGV("writeBlob: write to ashmem");
int fd = ashmem_create_region("Parcel Blob", len);
if (fd < 0) return NO_MEMORY;
//創(chuàng)建可讀可寫(xiě)的匿名共享內(nèi)存fd
int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
if (result < 0) {
status = result;
} else {
//申請(qǐng)物理內(nèi)存
void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
status = -errno;
} else {
if (!mutableCopy) {
result = ashmem_set_prot_region(fd, PROT_READ);
}
if (result < 0) {
status = result;
} else {
//把數(shù)據(jù)寫(xiě)到申請(qǐng)的匿名共享內(nèi)存上
status = writeInt32(mutableCopy ? BLOB_ASHMEM_MUTABLE : BLOB_ASHMEM_IMMUTABLE);
if (!status) {
status = writeFileDescriptor(fd, true /*takeOwnership*/);
if (!status) {
outBlob->init(fd, ptr, len, mutableCopy);
return NO_ERROR;
}
}
}
}
::munmap(ptr, len);
}
::close(fd);
return status;
}
frameworks/base/core/java/android/os/Parcel.java
/** @hide */
public final void restoreAllowFds(boolean lastValue) {
nativeRestoreAllowFds(mNativePtr, lastValue);
}
/**
* Write a blob of data into the parcel at the current {@link #dataPosition},
* growing {@link #dataCapacity} if needed.
* @param b Bytes to place into the parcel.
* {@hide}
* {@SystemApi}
*/
public final void writeBlob(byte[] b) {
writeBlob(b, 0, (b != null) ? b.length : 0);
}
/**
* Write a blob of data into the parcel at the current {@link #dataPosition},
* growing {@link #dataCapacity} if needed.
* @param b Bytes to place into the parcel.
* @param offset Index of first byte to be written.
* @param len Number of bytes to write.
* {@hide}
* {@SystemApi}
*/
public final void writeBlob(byte[] b, int offset, int len) {
if (b == null) {
writeInt(-1);
return;
}
Arrays.checkOffsetAndCount(b.length, offset, len);
nativeWriteBlob(mNativePtr, b, offset, len);
}
發(fā)現(xiàn)其實(shí)Parcel有隱藏接口,可以通過(guò)restoreAllowFds來(lái)讓writeBlob的接口內(nèi)部以匿名共享內(nèi)存的方式存儲(chǔ)數(shù)據(jù)。
猜想:在Intent跳轉(zhuǎn)另一個(gè)頁(yè)面的時(shí)候,能否通過(guò)調(diào)用隱藏接口來(lái)進(jìn)行大數(shù)據(jù)的傳輸?
常規(guī)的操作
private void test1() {
Intent intent = new Intent(this, Main2Activity.class);
byte[] bytes = new byte[1024*1024];//創(chuàng)建一個(gè)1M的數(shù)據(jù)
intent.putExtra("kobe", bytes);
this.startActivity(intent);
}
出現(xiàn)異常
E AndroidRuntime: Caused by: android.os.TransactionTooLargeException: data parcel size 1048944 bytes
E AndroidRuntime: at android.os.BinderProxy.transactNative(Native Method)
E AndroidRuntime: at android.os.BinderProxy.transact(Binder.java:1127)
E AndroidRuntime: at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:3754)
E AndroidRuntime: at android.app.Instrumentation.execStartActivity(Instrumentation.java:1669)
因?yàn)锽inder調(diào)用的限制是1M-8K,我們傳遞一個(gè)1M的數(shù)據(jù),肯定報(bào)錯(cuò)了。
特殊操作:調(diào)用Parcel的隱藏接口
private void test2() {
Intent intent = new Intent(this, Main2Activity.class);
Data data = new Data();
data.bytes = new byte[1024*1024];//創(chuàng)建一個(gè)1M的數(shù)據(jù),并保存在Data中的bytes
intent.putExtra("kobe", data);//把一個(gè)Parcelable對(duì)象傳進(jìn)去
this.startActivity(intent);
}
//Data實(shí)現(xiàn)了Parcelable接口
public class Data implements Parcelable {
public byte[] bytes;
@Override
public int describeContents() {
return 0;
}
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.restoreAllowFds(true);//設(shè)置Parcel的AllowFds為true
dest.writeBlob(bytes);//把數(shù)據(jù)傳遞寫(xiě)到Parcel里面,存儲(chǔ)方式是匿名共享內(nèi)存
}
public Data() {
}
//反序列化
protected Data(Parcel in) {
this.bytes = in.readBlob();//把數(shù)據(jù)從Parcel中讀出來(lái)
}
public static final Parcelable.Creator<Data> CREATOR = new Parcelable.Creator<Data>() {
@Override
public Data createFromParcel(Parcel source) {
return new Data(source);
}
@Override
public Data[] newArray(int size) {
return new Data[size];
}
};
}
寫(xiě)完代碼的我感覺(jué)非常好,程序跑起來(lái),正準(zhǔn)備宣布我的新發(fā)現(xiàn),結(jié)果發(fā)現(xiàn)還是我太年輕了
E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: File descriptors passed in Intent
E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:2009)
E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:1951)
E AndroidRuntime: at android.app.IActivityManager$Stub$Proxy.startActivity(IActivityManager.java:4425)
E AndroidRuntime: at android.app.Instrumentation.execStartActivity(Instrumentation.java:1613)
E AndroidRuntime: at android.app.Activity.startActivityForResult(Activity.java:4501)
E AndroidRuntime: at android.app.Activity.startActivityForResult(Activity.java:4459)
E AndroidRuntime: at android.app.Activity.startActivity(Activity.java:4820)
E AndroidRuntime: at android.app.Activity.startActivity(Activity.java:4788)
E AndroidRuntime: at com.tct.blobtest.MainActivity.test2(MainActivity.java:28)
E AndroidRuntime: at com.tct.blobtest.MainActivity.onCreate(MainActivity.java:14)
E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:7023)
E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:7014)
E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215)
E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2861)