Android跨进程传大图思考及实现——附上原理分析_android微信 跨进程打开图片(3)

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上…

注意: 这个用法不能跨进程,喜欢动手的同学,可以试一试,给SecondActivity配置一个android:process=":remote",你会发现会报一个强制转换的异常错误

//错误的用在多进程场景下,报错如下:
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.xxx.xxx.IntentBinder

🤔为什么可以通过这种方式传递对象?
Binder会为我们的对象创建一个全局的JNI引用,点击查看android_util_Binder.cpp

//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用法

//先定义一个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上…

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
img

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

6)]
[外链图片转存中…(img-jDSwSv4y-1712648774107)]
[外链图片转存中…(img-20ifgqC3-1712648774107)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上HarmonyOS鸿蒙开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-N1zgrhuw-1712648774107)]

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

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值