最新Android跨进程传输大图思考及实现_aidl bitmap,2024年最新字节面试官职级

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

接下来是资料清单:(敲黑板!!!


1.数据结构和算法

2.设计模式

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

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

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

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

因为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$Stub$Proxy.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);
    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(...)


## 最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年**腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题**,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

>![](https://img-blog.csdnimg.cn/img_convert/b79dbc8d5aaac7fbeef2332127879272.webp?x-oss-process=image/format,png)

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。



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

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

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

p = BitmapFactory.decodeStream(...)


## 最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年**腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题**,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

>[外链图片转存中...(img-zh5bdXBj-1715370696483)]

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。



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

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值