面试官:跨进程如何传递大图?又蒙圈了......

共 12048字,需浏览 25分钟

 ·

2021-11-08 15:41

 BATcoder技术群,让一部分人先进大厂

大家,我是刘望舒,腾讯TVP,著有三本业内知名畅销书,连续四年蝉联电子工业出版社年度优秀作者,百度百科收录的资深技术专家。

前华为架构师,现独角兽技术负责人


想要加入 BATcoder技术群,公号回复BAT 即可。



作者:Halifax

https://juejin.cn/post/7011276367308750879


1、抛一个问题

这一天,法海想锻炼小青的定力,由于Bitmap也是一个Parcelable类型的数据,法海想通过Intent给小青传个特别大的图片。

intent.putExtra("myBitmap",fhBitmap)

如果“法海”(Activity)使用Intent去传递一个大的Bitmap给“小青”(Activity),如果你的图片够大,会出现类似下面这样的错误,请继续往下看:

Caused by: android.os.TransactionTooLargeException: data parcel size 8294952 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:535)
        at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)

至于是什么样的大图,这个只有法海知道了🙈🙈🙈

所以TransactionTooLargeException这玩意爆出来的地方在哪呢?

2、问题定位分析

我们可以看到错误的日志信息里面看到调用了BinderProxy.transactNative,这个transactNative是一个native方法。

//android.os.BinderProxy
/**
 * Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;

在Android Code Search,全局搜索一下:android_os_BinderProxy_transact。

https://cs.android.com/

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_util_Binder.cpp

//frameworks/base/core/jni/android_util_Binder.cpp

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ......
    status_t err = target->transact(code, *data, reply, flags);
    ......
    if (err == NO_ERROR) { 
        //如果匹配成功直接拦截不往下面执行了
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }
    signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
    return JNI_FALSE;
}

我们打开signalExceptionForError方法看看里面的内容。

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_util_Binder.cpp;drc=master;l=824

//frameworks/base/core/jni/android_util_Binder.cpp
//处理异常的方法
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
        bool canThrowRemoteException, int parcelSize)
{
    switch (err) {
        //其他异常,大家可以自行阅读了解;
        //如:没有权限异常,文件太大,错误的文件描述符,等等;
        ........
        case FAILED_TRANSACTION: {
            const char* exceptionToThrow;
            char msg[128];
            //官方在FIXME中写道:事务过大是FAILED_TRANSACTION最常见的原因
            //但它不是唯一的原因,Binder驱动可以返回 BR_FAILED_REPLY
            //也有其他原因可能是:事务格式不正确,文件描述符FD已经被关闭等等

            //parcelSize大于200K就会报错,canThrowRemoteException传递进来的是true
            if (canThrowRemoteException && parcelSize > 200*1024) {
                // bona fide large payload
                exceptionToThrow = "android/os/TransactionTooLargeException";
                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
            } else {
                ..........
            }
            //使用指定的类和消息内容抛出异常
            jniThrowException(env, exceptionToThrow, msg);
        } break;
        ........
    }
}

此时我们看到: parcelSize大于200K就会报错,难道一定是200K以内?先别着急着下结论,继续往下看。

3、提出疑问

法海:我有个疑问,我看到文档写的1M大小啊;

许仙:别急,妹夫,来先看一下文档的解释,看一下使用说明:

官方TransactionTooLargeException的文档中描述到:Binder 事务缓冲区有一个有限的固定大小,目前为 1MB,由进程所有正在进行的事务共享。

https://developer.android.com/reference/android/os/TransactionTooLargeException

可以看到写的是:共享事务的缓冲区。

如来佛祖:汝等别急,我们简单测试一下,Intent传递201*1024个字节数组,我们发现可以正常传递过去,Logcat仅仅输出了一个Error提示的日志信息,还是可以正常传递的。

E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0

我们再测试一个值,intent传递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$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)
        at android.app.servertransaction.ClientTransaction.schedule(ClientTransaction.java:136)

不要着急,我们继续往下看分析。

4、解答疑问

我们来看一下,下面两行代码。

//frameworks/base/core/jni/android_util_Binder.cpp
//这个方法android_os_BinderProxy_transact里面的
IBinder* target = getBPNativeData(env, obj)->mObject.get();
status_t err = target->transact(code, *data, reply, flags);

从上面的分析和测试结果,我们从target->transact这里来找err返回值, 先根据头文件,搜索对应的cpp类,我们看一下这几个cpp类:BpBinder.cpp、 IPCThreadState.cpp、ProcessState.cpp。

https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/BpBinder.cpp

https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/IPCThreadState.cpp

https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/ProcessState.cpp

//frameworks/native/libs/binder/ProcessState.cpp
// (1 * 1024 * 1024) - (4096 *2)
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define DEFAULT_MAX_BINDER_THREADS 15
//下面两个注释
//引用自官方文档:https://source.android.google.cn/devices/architecture/hidl/binder-ipc
#ifdef __ANDROID_VNDK__
//供应商/供应商进程之间的IPC,使用 AIDL 接口
const char* kDefaultDriver = "/dev/vndbinder";
#else
// "/dev/binder" 设备节点成为框架进程的专有节点
const char* kDefaultDriver = "/dev/binder";
#endif
//构造函数:初始化一些变量,Binder最大线程数等
ProcessState::ProcessState(const char* driver)      
   : mDriverName(String8(driver)),        
   mDriverFD(-1),        
   mVMStart(MAP_FAILED),        
   ...... 
   mMaxThreads(DEFAULT_MAX_BINDER_THREADS),        
   mStarvationStartTimeMs(0),       
   mThreadPoolStarted(false),        
   mThreadPoolSeq(1),        
   mCallRestriction(CallRestriction::NONE) {   
   ......    
   //打开驱动    
   base::Result opened = open_driver(driver);    
   if (opened.ok()) {        
      //映射(1M-8k)的mmap空间       
      mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,                      
              opened.value(), 0);     
              ......   
          }   
          ......
      }

getauxval(AT_PAGESZ) = 4096,可以得出Binder内存限制,BINDER_VM_SIZE = 1M-8kb

这里为什么不是1M,而是1M-8K?

最开始的时候,官方写的是1M,后来他们内部自己优化了;

来看官方提交的ProcessState.cpp提交的log日志:允许内核更有效地利用其虚拟地址空间。

https://android.googlesource.com/platform/frameworks/native/+/c0c1092183ceb38dd4d70d2732dd3a743fefd567

我们知道:微信的MMKV、美团的Logan的日志组件,都是基于mmap来实现的;

binder驱动的注册逻辑在Binder.c中,我们看一下binder_mmap方法。

https://android.googlesource.com/kernel/msm/+/refs/tags/android-11.0.0_r0.105/drivers/android/binder.c

//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。

https://android.googlesource.com/kernel/msm/+/refs/tags/android-11.0.0_r0.105/drivers/android/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$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)

点击查看IApplicationThread.aidl 查看AIDL里面的内容,我们看到scheduleTransaction是一个异步的方法;

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/IApplicationThread.aidl

因为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方法。

https://android.googlesource.com/kernel/msm/+/refs/tags/android-11.0.0_r0.105/drivers/android/binder_alloc.c

//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.cstatic 
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.Intentpublic 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方法。

https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/Parcel.cpp

//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)。

//frameworks/native/libs/binder/Parcel.cpp
bool Parcel::pushAllowFds(bool allowFds)
{   
const bool origValue = mAllowFds;   
  if (!allowFds) {      
  mAllowFds = false;   
}   
return origValue;
}

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 val) {       
  ......      
  for (int i=0; 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方法。

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/jni/Bitmap.cpp

//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(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。

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/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上.....

参考

Binder kernel 基础方法

https://www.jianshu.com/p/04e04e38136a

Android Binder 魅族内核团队

http://kernel.meizu.com/android-binder.html

Android系统匿名共享内存Ashmem驱动程序源代码分析

https://blog.csdn.net/luoshengyang/article/details/6664554

Android 匿名共享内存的使用

https://zhuanlan.zhihu.com/p/92769131



·················END·················

推荐阅读

耗时2年,Android进阶三部曲第三部《Android进阶指北》出版!

『BATcoder』做了多年安卓还没编译过源码?一个视频带你玩转!

『BATcoder』我去!安装Ubuntu还有坑?

重生!进阶三部曲第一部《Android进阶之光》第2版 出版!

为了防止失联,欢迎关注我的小号

  微信改了推送机制,真爱请星标本公号👇
浏览 73
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐