前言
年末了,得加紧复习、复习、再复习。
如果有和我一样打算明年春招跳槽的小伙伴可以关注我的【Github】,里面有我从Android 大V 那里收集整理的众多一线互联网大厂的 Android 核心面试知识点。欢迎大家的阅读,如果觉得赞的话,可以在我的Github中点个Star哦!
Github 地址:https://github.com/733gh/xiongfan
⚠️干货预警,前方高能!!!
- 你是否了解Binder机制?
- Binder这么好用,那为什么Zygote的IPC通信机制用Socket而不用Binder?
- 为什么说Binder是安全的?
- Intent跨进程传大图为什么会崩溃?
- AIDL的oneWay和非oneway有什么区别?
- 本文将针对以上问题进行原理分析
一、IPC机制
Inter-Process Communication 简称 IPC ,即为进程间通信。Android与Liunx都有自己的IPC机制。虽然说Android是继承自Linux的,但是IPC通信机制并不是完全继承的。如果要你设计一套进程间通信,你会如何设计,数据简单传递流程是如何的?
imageLinux
-
管道
继承自Unix,它是 Unix早期的一个重要通信机制。主要思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。这个文件本质是内核缓冲区( 4k ),通过pipe系统函数调用即可得到描述符,创建管道,一个用来读,一个用来写。通信方式为半双工,数据自己读不能写,数据读走后,便不在管道中,不能反复读取。模型如下:
-
信号
举个例子,我们在电脑上按 Ctrl + C 会中断程序,这是通过信号机制来停止了程序。信号就是一种异步通信方式,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,但是不适用于信息交换。每个信号都有一个名字和编号,名字一般都以SIG开头。例如Android里面的杀掉进程方法。
-
信号量
信号量是一个计数器,用来控制多个进程对共享资源的访问,它作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源,主要作为进程间以及同一进程内不同线程之间的同步手段,通过控制这种资源的分配来实现互斥和同步的。
-
消息队列
双链表结构,存在内存中,由一个或多个进程读写信息,信息会复制两次,对于频繁、信息量大的通信不宜使用消息队列。
-
共享内存
多个进程可以直接读写的一块内存,优点在于内核空间专门留出了一块内存可以由需要访问的进程映射到自己的私有地址空间,进程就可以不需要进行数据拷贝,而直接读写数据。
-
套接字
套接字可用于不同机器之间的进程通信,例如Socket,全双工的,即可读又可写,可以用在两个无亲缘关系的进程间。
Android
-
AIDL
Android interface definition Language 即 Android 接口定义语言,专门用来处理进程间通信
-
Messenger
以串行的方式来处理客户端发来的消息,如果有大量消息发送到服务端,服务端仍然一个一个的处理再相应客户端显然不合适,并且在进程间只能用于数据传递,不能像AIDL一样,支持方法调用。
-
Bundle
Bundle实现了Parcelable 接口,所以它可以方便的在不同进程间传输。Activity、Service、Receive都是在Intent中通过Bundle来进行数据传递的。
-
文件共享
-
ContentProvider
ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder。例如通讯录,音视频等,这些操作本身就是跨进程进行通信。
-
Binder
对于消息队列、Socket、管道而言,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共拷贝两次,如下图
对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到一块物理地址的(其实是拿到描述符后同时映射到两个进程的空间,一个进程写,另一个进程就能读到)。如下图
这个就体现出了Binder的性能比Socket好,少了一次数据拷贝,那Binder的安全体现在哪呢?
首先Socket或命名管道,别人知道IP地址或者管道名就可以和它进行连接,写数据,这里容易被恶意使用,没有对使用者的信息进行验证。这种信息要想不出问题,只能由IPC机制本身在内核态添加,例如Binder,它就是这么做的,Android里面这种身份信息标识其实就是UID。
这里要留意一下,一般Binder不会传递太大的数据,然而通过文件跨进程传递数据,效率有点低,作为内存优化手段可以选择使用MemoryFile,这个类是Android匿名共享内存里面的高性能文件类。
-
Scoket
常见于Zygote 和 AMS通信,Zygote接受AMS的请求就是用的Scoket,init进程 fork 出 zygote进程后,会执行Zygote的main函数,内部会注册一个本地Scoket,然后进入循环来处理数据。
二、Binder驱动
1、Android 启动时 Binder 通信流程
image-
Android 底层 Linux启动之后,用户空间启动的第一个进程为 init 进程
-
init 进程 加载 init.rc 配置,通过 fork + exec 系统启动配置文件定义的系统服务,包括 Zygote进程、ServiceManager进程、SurfaceFlinger进程等
-
Zygote进程启动 (
frameworks/base/cmds/app_process/app_main.cpp
)。 启动完虚拟机、注册好JNI 函数后,会继续启动SystemServer
进程,也就是说会执行SystemServer
进程的main函数com.android.server.SystemServer.java public static void main(String[] args) { new SystemServer().run(); } //这里就会启动 AMS WMS PMS 等服务 private void run() { ..... // 开机引导服务、核心服务、其他服务 startBootstrapServices(); startCoreServices(); startOtherServices(); ..... }
这里以
AMS
为例,创建好AMS实例后会调用它的以下方法// ActivityManagerService extends IActivityManager.Stub public void setSystemProcess() { try { ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true); ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats); ServiceManager.addService("meminfo", new MemBinder(this)); ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); ServiceManager.addService("dbinfo", new DbBinder(this)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(this)); } ..... }
可以看到这里会将
AMS
和一些其它服务注册到ServiceManager
里面我们继续看一下是如何注册的
android.os.ServiceManager.java //addService public static void addService(String name, IBinder service) { try { getIServiceManager().addService(name, service, false); } catch (RemoteException var3) { Log.e("ServiceManager", "error in addService", var3); } } //getService private static IServiceManager getIServiceManager() { if (sServiceManager != null) { return sServiceManager; } else { //重点 这里通过 BinderInternal 拿到一个Native的 BpBinder,然后转换成Java层的BinderProxy对象 sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); return sServiceManager; } } //ServiceManagerNative.asInsterface static public IServiceManager asInterface(IBinder obj) { if (obj == null) { return null; } IServiceManager in = (IServiceManager)obj.queryLocalInterface(descriptor); if (in != null) { return in; } return new ServiceManagerProxy(obj); }
看到这里,大概就清楚了,从底层拿到BpBinder,然后转换成BinderProxy对象,调用
addService
方法,放入到ServiceManager 中管理。addService 方法对应于 native 层的frameworks/native/libs/IServicManager.cpp
中的 addService 方法,后面在对SurfaceFlinger
注册服务 会分析此方法。 -
ServiceManager进程启动。我们直接看它的main函数,里面会打开Binder驱动、映射内存,开启
Binder Loop
循环,等待Client 和 Service 的请求 (这里说的Service是指系统服务)frameworks/native/cmds/sedrvicemanager/service_manager.c int main(int argc, char** argv) { .... if (argc > 1) { driver = argv[1]; } else { driver = "/dev/binder"; } .... //1.打开 Binder 驱动 映射内存 128 k bs = binder_open(driver, 128*1024); ..... // 2\. Binder 成为了上下文的管理者,告诉Binder驱动,我就是ServiceManager,注册、查询都找我 if (binder_become_context_manager(bs)) { ALOGE("cannot become context manager (%s)\n", strerror(errno)); return -1; } .... //3.开启binder loop循环 处理消息 binder_loop(bs, svcmgr_handler); return 0; } frameworks/native/cmds/sedrvicemanager/binder.c void binder_loop(struct binder_state *bs, binder_handler func) { ...... readbuf[0] = BC_ENTER_LOOPER; binder_write(bs, readbuf, sizeof(uint32_t)); //4.for 循环 把 binder驱动发来的数据读过来 ,然后 binder_parse 处理请求 for (;;) { bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (uintptr_t) readbuf; res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); if (res < 0) { ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno)); break; } res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func); ....... } }
这里说明一下,
binder_open
中会通过mmap
内存映射 128 k 的内存空间,但是这仅仅在ServiceManager
进程中,其它Binder 服务进程BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(SC_PAGE_SIZE) * 2)
的内存空间,
SC_PAGE_SIZE
为 一个page
页大小, 估摸着为1M - 8K的样子。那为什么网上有人说Binder内存限制是1M - 8K,而一次调用最大传输数据只有 507 K呢?
这是因为 Binder 线程池数量默认是 15 个 (不计 某个指令创建的 Binder主线程,否则为 16 个),15 个线程共享这么多内存空间,所以实际传输大小并没有这么大。
-
SurfaceFlinger 进程启动
frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp int main(int, char**) { startHidlServices(); signal(SIGPIPE, SIG_IGN); //1\. Binder线程池数量设置为了4 // When SF is launched in its own process, limit the number of // binder threads to 4. ProcessState::self()->setThreadPoolMaxThreadCount(4); //2.启动线程池 // start the thread pool sp<ProcessState> ps(ProcessState::self()); ps->startThreadPool(); //3.初始化 SurfaceFlinger // instantiate surfaceflinger sp<SurfaceFlinger> flinger = new SurfaceFlinger(); setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY); set_sched_policy(0, SP_FOREGROUND); ...... // initialize before clients can connect flinger->init(); //4\. 重点来了,发布 SurfaceFlinger到 ServiceManager 中 // publish surface flinger sp<IServiceManager> sm(defaultServiceManager()); sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false); // publish GpuService sp<GpuService> gpuservice = new GpuService(); sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false); ....... // run surface flinger in this thread flinger->run(); return 0; }
我们可以看到上述代码的第 4 处 注释,它会发布
SurfaceFlinger
服务到ServiceManager
中,那你有可能会想,ServiceManager
进程还没有启动完成,SurfaceFlinger 进程就来获取呢?frameworks/native/libs/IServiceManager.cpp sp<IServiceManager> defaultServiceManager() { if (gDefaultServiceManager != NULL) return gDefaultServiceManager; { AutoMutex _l(gDefaultServiceManagerLock); while (gDefaultServiceManager == NULL) { gDefaultServiceManager = interface_cast<IServiceManager>( ProcessState::self()->getContextObject(NULL)); // 1\. 这里拿到的是一个BpBinder对象 if (gDefaultServiceManager == NULL) sleep(1); } } return gDefaultServiceManager; }
可以看到获取
gDefaultServiceManager
利用了 while循环,会知道获取到后才会停止我们再看看注释 4 处的
addService
代码 (getService
类似)virtual status_t addService(const String16& name, const sp<IBinder>& service, bool allowIsolated) { Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); //1\. 把服务写到 Parcele 里了 data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1 : 0); //2\. 发送请求 status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err; }
注释 2 处 的
transact
调用的是IPCThreadState
中的代码,真正底层交互还是通过IPCThreadState
来的status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-
最后贴一个Android系统启动流程图
2、Binder通信分层架构
-
对于
BinderProxy
,我们通过上一小节中Zygote
进程启动 的ServiceManagerNative.asInsterface
方法可以看到,拿到远程Binder
实体后,往往会封装一层,比如该asInsterface
方法返回的ServiceManagerProxy
,这就是一个BinderProxy
对象 -
BpBinder
和BinderProxy
差不多,一个是Native 层,一个是Java 层的,BpBinder 内部持有了一个 binder 句柄 handler,我们直接看上一小节 分析的 SurfaceFlinger 进程启动中 的defaultServiceManager
中的 拿 Binder 的getContextObject
方法sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/) { return getStrongProxyForHandle(0); } sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle) { sp<IBinder> result; AutoMutex _l(mLock); handle_entry* e = lookupHandleLocked(handle); if (e != NULL) { IBinder* b = e->binder; if (b == NULL || !e->refs->attemptIncWeak(this)) { if (handle == 0) { Parcel data; status_t status = IPCThreadState::self()->transact( 0, IBinder::PING_TRANSACTION, data, NULL, 0); if (status == DEAD_OBJECT) return NULL; } // BpBinder b = new BpBinder(handle); e->binder = b; if (b) e->refs = b->getWeakRefs(); result = b; ...... } return result; }
-
ProcessState
是进程单例,一个进程只有一个,主要是负责打开Binder驱动,映射内存,负责mmap
调用。这里你可能会问ProcessState
是在哪里创建的?什么时候创建的?Zygote
进程启动后,都是通过Socket
接受SystemServer
和APP进程的信息,在要创建SystemServer
和APP
进程时,都会在新进程执行onZygoteInit
方法,启动线程池virtual void onZygoteInit() { sp<ProcessState> proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); }
至于系统服务,例如
SurfaceFlinger
中ProcessState
启动时机,可以看该上一小节对该进程的简单分析。而且所有的Binder线程池都设定了大小为 4 -
IPCThreadState
线程单例,主要是负责binder驱动与其它命令的通信。在 上一小节分析的SurfaceFlinger
进程启动,把服务发布到ServiceManager
中,就利用了IPCThreadState
,我们看一下它的transact
方法status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { status_t err = data.errorCheck(); flags |= TF_ACCEPT_FDS; IF_LOG_TRANSACTIONS() { TextOutput::Bundle _b(alog); alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand " << handle << " / code " << TypeCode(code) << ": " << indent << data << dedent << endl; } if (err == NO_ERROR) { LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(), (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY"); //重点 BC_TRANSACTION 指令 err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); } if (err != NO_ERROR) { if (reply) reply->setError(err); return (mLastError = err); } if ((flags & TF_ONE_WAY) == 0) { ..... if (reply) { err = waitForResponse(reply); } else { Parcel fakeReply; err = waitForResponse(&fakeReply); } ..... } else { err = waitForResponse(NULL, NULL); } return err; }
-
整体流程就是
Proxy
发起transact
调用,然后把数据打包到Parcel
中,然后会向下调用BpBinder
,在BpBinder
中调用IPCThreadState
的transact
方法,持有句柄值,IPCThreadState
负责执行具体Binder指令。Server端通过IPCThread
接受到Client的请求,然后层层向上,最后回调到Stub的onTransact
方法
3、Binder数据传递流程
非oneway
imageoneway
image-
在AIDL中写代码时,如果接口标记了oneway,表示Server端串行化处理(从异步队列中拿出消息指令一个个分发)、异步调用。这个关键字主要是用于修改远程调用的行为,就如上面的两个图一样。非
oneway
关键字的AIDL
类,客户端需要挂起线程等待休眠,相当于调用了Sleep函数。例如WMS
、AMS
等相关系统Binder
调用都是oneway
的。 -
oneway
和非oneway的
AIDL文件简单区别如下,可以用Android Studio 实践后,看一下生成的文件,查看调用链。//非oneway interface BookCaller{ void initBook(ICallback callback); } --->>>> public void initBook(Icallback callback){ .... mRemote.transact(Stub.TRANSATION_initbook,_data,_reply,0); .... } //onway oneway interface BookCaller{ void initBook(ICallback callback); } ---->>>> public void initBook(Icallback callback){ .... mRemote.transact(Stub.TRANSATION_initbook,_data,null,IBinder.FLAG_ONEWAY); .... }
-
从上面得知
oneway
和非oneway
的区别最关键的在于最后一个参数,继续看IPCThreadState
的transact
方法status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { status_t err = data.errorCheck(); ..... if (err == NO_ERROR) { LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(), (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY"); // 1\. cmd = BC_TRANSACTION err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); } if (err != NO_ERROR) { if (reply) reply->setError(err); return (mLastError = err); } // 2\. 判断是否oneway if ((flags & TF_ONE_WAY) == 0) { #if 0 if (code == 4) { // relayout ALOGI(">>>>>> CALLING transaction 4"); } else { ALOGI(">>>>>> CALLING transaction %d", code); } #endif if (reply) { err = waitForResponse(reply); } else { Parcel fakeReply; err = waitForResponse(&fakeReply); } #if 0 if (code == 4) { // relayout ALOGI("<<<<<< RETURNING transaction 4"); } else { ALOGI("<<<<<< RETURNING transaction %d", code); } #endif IF_LOG_TRANSACTIONS() { TextOutput::Bundle _b(alog); alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand " << handle << ": "; if (reply) alog << indent << *reply << dedent << endl; else alog << "(none requested)" << endl; } } else { err = waitForResponse(NULL, NULL); } return err; }
我们看注释 2 处,如果 flags 是 0 也就是 非oneway,会调用
waitForResponse``(reply)
,否则会调用waitForResponse(NULL,NULL),这两个函数的区别就是,前者需要等待对方返回结果,后者是不需要等待对方返回结果。仔细查看,你还可以通过上面代码知道向驱动发送的消息
cmd
是BC_TRANSATION
,如果你再看看waitForResponse
方法里,你会发现收消息,都是BR开头的。也就是说向Binder 驱动发送消息的指令都是以BC_
开头,由Binder驱动向外发送的指令都是以BR_
开头status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) { uint32_t cmd; int32_t err; while (1) { ..... cmd = (uint32_t)mIn.readInt32(); ... switch (cmd) { //oneway 的话 就直接 goto finish了,因为参数为NULL case BR_TRANSACTION_COMPLETE: if (!reply && !acquireResult) goto finish; break; ..... case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; case BR_ACQUIRE_RESULT: { ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT"); const int32_t result = mIn.readInt32(); if (!acquireResult) continue; *acquireResult = result ? NO_ERROR : INVALID_OPERATION; } goto finish; case BR_REPLY: { binder_transaction_data tr; err = mIn.read(&tr, sizeof(tr)); ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY"); if (err != NO_ERROR) goto finish; if (reply) { ....... } else { freeBuffer(NULL, reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets), tr.offsets_size/sizeof(binder_size_t), this); continue; } } goto finish; default: err = executeCommand(cmd); if (err != NO_ERROR) goto finish; break; } } finish: if (err != NO_ERROR) { if (acquireResult) *acquireResult = err; if (reply) reply->setError(err); mLastError = err; } return err; }
三、Binder跨进程传递大图方案
在刚入门Android开发时,可能会直接通过Intent传递Bitmap,然后发现TransactionTooLargetException
异常。这是为什么呢?那又该如何解决呢?
-
问题分析
Bundle bundle = new Bundle(); b.putParcelable("bitmap",mBitmap); intent.putExtras(b); startActivity(intent);
我们对这段造成异常的代码进行分析,抛出异常的地方是
BinderProxy.transactNative(Native Method)
方法frameworks/base/core/jni/android_util_Binder.cpp {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact} static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException { ...... Parcel* data = parcelForJavaObject(env, dataObj); if (data == NULL) { return JNI_FALSE; } ....... status_t err = target->transact(code, *data, reply, flags); ......... signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize()); return JNI_FALSE; }
我们继续看一下
signalExceptionForError
方法void signalExceptionForError(JNIEnv* env, jobject obj, status_t err, bool canThrowRemoteException, int parcelSize) { switch (err) { case UNKNOWN_ERROR: jniThrowException(env, "java/lang/RuntimeException", "Unknown error"); break; case NO_MEMORY: jniThrowException(env, "java/lang/OutOfMemoryError", NULL); break; ....... case PERMISSION_DENIED: jniThrowException(env, "java/lang/SecurityException", NULL); break; ......... case FAILED_TRANSACTION: { ALOGE("!!! FAILED BINDER TRANSACTION !!! (parcel size = %d)", parcelSize); const char* exceptionToThrow; char msg[128]; //注释 1 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 { exceptionToThrow = (canThrowRemoteException) ? "android/os/DeadObjectException" : "java/lang/RuntimeException"; snprintf(msg, sizeof(msg)-1, "Transaction failed on small parcel; remote process probably died"); } jniThrowException(env, exceptionToThrow, msg); } break; ......... default: jniThrowException(env, canThrowRemoteException ? "android/os/RemoteException" : "java/lang/RuntimeException", msg.string()); break; } } }
注释 1 处 说明了 如果
transact
失败 并且parcelSize
大于 200 k 的话,大概率会抛这个异常。你可能会问这个FAILED_TRANSACTION
是什么时候抛出来的?我们可以看一下之前分析
IPCThreadState
中的transact
方法内调用的waitForResponse
方法status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) { ..... while (1) { //注释 1 与 binder 进行通信 ioctl if ((err=talkWithDriver()) < NO_ERROR) break; .... cmd = (uint32_t)mIn.readInt32(); .... switch (cmd) { ...... case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; ....... }
可以看到当 Binder驱动 返回
BR_FAILED_REPLY
时,才会设置 err 为FAILED_TRANSACTION
, 那什么时候 Binder驱动会发送BR_FAILED_REPLY
指令呢?注释 1 处 是与 binder 的通信,内部调用了
ioctl
函数,第二个参数指定为了BINDER_WRITE_READ
,代表向驱动 读取和写入数据,可同时读写。ioctl
函数内调用了binder_transaction
方法/drivers/stagine/android/binder.c //注意这个文件和ServerMnager 那里调用的binder.c不一样,这个是内核的 static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) { .... t->buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offsets_size, !reply && (t->flags & TF_ONE_WAY)); if (t->buffer == NULL) { return_error = BR_FAILED_REPLY; goto err_binder_alloc_buf_failed; } ..... }
我们可以看到如果
data_size
大小的buffer
申请失败,则会将BR_FAILED_REPLY
错误码发送到客户端。 -
问题解决方案
至于解决方案,我们需要从所有跨进程传递的方式里去找。
这里不考虑使用文件形式来保存图片,因为要进行磁盘操作,而且跨进程操作,没加文件锁,性能和数据都不稳定。其它方式也不行,可以从内存拷贝次数、内存泄漏方面进行阐述。
直接进入主题,使用AIDL通过
Binder
进行IPC调用来传递图片。Bundle bundle = new Bundle(); bundle.putBinder("binder",new IRemoteCaller.Stub(){ @Override public Bitmap getBitmap() throw RemoteException{ return mBitmp; } }); //注 putBinder(@Nullable String key, @Nullable IBinder value)
那么你会问,为什么这种方案可以实现大图传递呢?
-
我们直接看
startActivity
中序列化过程int startActivity(...){ Parcel data = Parcel.obtain(); ... intent.writeToParcel(data,0); ... mRemote.transact(START_ACTIVITY_TRANSACTION,data,reply,0); ... } intent.wirteToParcel(data,0) -->> public void writeToParcel(Parcel out,int flags){ out.writeBundle(mExtras); .... } writeBundle() -->> public final void wirteBundle(Bundle val){ val.writeToParcel(this,0); }
我们再分析一下`Bundle`的 `writeToParcel` 方法
```
@Override
public void writeToParcel(Parcel parcel, int flags) {
//注释 1
final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
try {
super.writeToParcelInner(parcel, flags);
} finally {
parcel.restoreAllowFds(oldAllowFds);
}
}
```
注释 1 处的 含义是 是否允许传递 描述符
上面这个方法最终调用的是 `super.wirteToParcelInner` 方法,该方法会将数据存到map里,然后再一次写入到Parcel,最终是会调用 Bitmap 的 wirteToParcel 方法的(这里省略了部分调用链),而Bitmap的这个方法内部调用了这个native方法
```
nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p)
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, ...) {
// 拿到 Native 的 Bitmap
auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
.....
// 往parcel里写 Bitmap 的各种配置参数
int fd = bitmapWrapper->bitmap().getAshmemFd();
//图像不能更改 并且 Parcel 允许带 FD,就将fd写到Parcel中
if (fd >= 0 && !isMutable && p->allowFds()) {
status = p->writeDupImmutableBlobFileDescriptor(fd);
return JNI_TRUE;
}
// 不满足上面的条件
android::Parcel::WritableBlob blob;
// 得到一块缓冲区
status = p->writeBlob(size, mutableCopy, &blob);
//获取像素数据
const void* pSrc = bitmap.getPixels();
//将数据拷贝到缓冲区
memcpy(blob.data(), pSrc, size);
}
```
`writeBlob` 函数 (分析直接在代码里写)
```
status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
// 如果不允许带 fd ,或者这个数据小于 BLOB_INPLACE_LIMIT = 16K
if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
// 就直接将图片存在Parcel 中
status = writeInt32(BLOB_INPLACE);
//Parcel的buffer中找一块偏移
void* ptr = writeInplace(len);
outBlob->init(-1, ptr, len, false);
return NO_ERROR;
}
//允许带描述符情况
//开辟一个匿名共享内存
int fd = ashmem_create_region("Parcel Blob", len);
//映射到内存空间
void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
...
//将fd写到Parcel中
status = writeFileDescriptor(fd, true /*takeOwnership*/);
outBlob->init(fd, ptr, len, mutableCopy);
return status;
}
```
从上面可以看出 `allowFds`打开后,如果图片大于**16k** ,将会开辟一个匿名共享内存空间 用来存 `Bitmap`,然后就不会导致异常。
那为什么直接用`Intent`传递不可以呢?
大家都知道 `startActivity` 后都会调用 `Instrumentation 的 execStartActivity` 方法
```
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
....
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target, requestCode, 0, null, options);
.....
}
```
我们看一下 `intent.prepareToLeaveProcess` 方法
```
public void prepareToLeaveProcess(boolean leavingPackage) {
setAllowFds(false);
}
public void setAllowFds(boolean allowFds) {
if (mExtras != null) {
mExtras.setAllowFds(allowFds);
}
}
```
这里将 `allowFds` 设置成了 `false`, 然后就会将 `Bitmap` 直接写到 `Parcel` 缓冲区,太大就出问题了。
四、问题解答
1、你是否了解Binder机制?
根据第二节的Binder通信架构图,从手机启动过程中的一些进程阐述
Binder分层架构图
Binder的oneway和非oneway数据传递图
Binder的优缺点: 性能(拷贝一次)、安全(校验UID、PID)
2、Binder这么好用,那为什么Zygote的IPC通信机制用Socket而不用Binder?
如果用了binder,zygote要先启动binder机制,打开binder驱动,获得描述符,mmap进程内存映射,注册binder线程,还要创建一个binder对象注册到serviceManager,另外AMS要想zygote发起创建应用进程请求的话,要先从serviceManager查询zygote的binder对象,再发起binder调用,非常繁琐。
相比之下,zygote和systemserver本就是父子关系,对于简单的消息通信,用管道或者socket非常方便。
如果zygote用了binder机制,再fork systemServer,那systemServer就继承了zygote的描述符和映射的内存,这两个进程在binder驱动层就会共用一套数据结构,这肯定是不行的。那还得把旧的描述符关掉,再重新启动一遍binder机制,自找麻烦。
3、为什么说Binder是安全的?
在数据传输过程中有身份的校验,通过UID、PID进行校验
4、Intent跨进程传大图为什么会崩溃?
常规的intent传递数据,在startActivity时将Bundle的 allowFds 设置成了false, 然后就会将 Bitmap直接写到 Parcel 缓冲区。如果通过 bundle.putBinder形式传递Bitmap,会开辟一个块共享匿名内存用来存Bitmap的数据,而Parcel 缓冲区只是存储 FD 。
5、AIDL的oneWay和非oneway有什么区别?
oneway和非oneway的架构图,oneway server端是串行处理,异步调用,Client端不用休眠等待驱动返回数据。
Github 地址:https://github.com/733gh/xiongfan
网友评论