Android跨进程传输大图思考及实现_aidl bitmap

}


**点击查看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 S t u b Stub StubProxy.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 S t u b Stub StubProxy.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.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上…

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

跨平台开发:Flutter.png

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。**

[外链图片转存中…(img-EusKYaBn-1714458771702)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值