preface
Review, review, review
Do you know anything about Binder mechanics?
Binder is so good, so why Zygote’s IPC mechanism uses sockets instead of Binder?
Why are Binders safe?
Why does an Intent fail to upload a large image across processes?
What is the difference between AIDL’s oneWay and non-OneWay?
This paper will analyze the above problems
directory
I. IPC mechanism
Inter-process Communication refers to inter-process Communication. Android and Liunx both have their own IPC mechanisms. While Android is inherited from Linux, the IPC communication mechanism is not completely inherited. If you were to design a set of inter-process communication, how would you design a simple flow of data?
Linux
-
The pipe
Inherited from Unix, it was an important communication mechanism in the early days of Unix. The main idea is to create a shared file in memory so that communication parties can use this shared file to pass information. This file is essentially a kernel buffer (4K), with a pipe system function call to get the descriptor to create a pipe, one for reading and one for writing. The communication mode is half-duplex. Data cannot be read and written by itself. After data is read, it is not in the pipeline and cannot be read repeatedly. The model is as follows:
-
signal
For example, when we press Ctrl + C on a computer, we interrupt the program. This is the signal mechanism that stops the program. Signals are a form of asynchronous communication that can be used by the kernel to notify user-space processes of system events, but not for information exchange. Each signal has a name and number, and the name usually starts with SIG. For example, the kill process method in Android.
-
A semaphore
Semaphore is a counter, is used to control access to Shared resources, multiple processes it as a locking mechanism, prevent a process is access to a Shared resource, the other process to access the resources, mainly between and within the same process as a process synchronization methods between threads, by controlling the allocation of such resources to implement mutual exclusion and synchronization.
-
The message queue
Double-linked list structure, existing in memory, by one or more processes read and write information, information will be copied twice, for frequent, large amount of information communication should not use message queue.
-
The Shared memory
A block of memory that multiple processes can read and write directly. The advantage is that a block of memory is reserved in the kernel space and can be mapped to its own private address space by processes that need to access it. In this way, processes can read and write data directly without copying data.
-
The socket
Sockets can be used to communicate between processes on different machines. For example, sockets are full duplex, can be read or written, and can be used between two unrelated processes.
Android
-
AIDL
Android Interface Definition Language (Android Interface Definition Language) is designed to handle interprocess communication
-
Messenger
If a large number of messages are sent to the server, it is not appropriate for the server to process them one by one. Besides, it can only be used for data transfer between processes and cannot support method calls like AIDL.
-
Bundle
The Bundle implements the Parcelable interface, so it can be easily transferred between different processes. Activities, services, and receives are all delivered in intEnts via bundles.
-
File sharing
-
ContentProvider
ContentProvider provides a unified interface for storing and retrieving data. It can share data between different applications and is itself suitable for inter-process communication. The underlying implementation of ContentProvider is also Binder. Such as address book, audio and video, etc., these operations themselves are cross-process communication.
-
Binder
For message queues, sockets, and pipes, data is copied from the sender’s cache to the kernel’s cache, and then from the kernel’s cache to the receiver’s cache, twice in total, as shown in the following figure
With a Binder, data is copied from the sender’s cache to the kernel’s cache, and the receiver’s cache and the kernel’s cache are mapped to a physical address (in effect, the descriptor is mapped to the space where one process writes and the other process reads). The following figure
Binder performs better than Socket and saves one copy of data. What is the security of Binder?
First Socket or named pipe, others know the IP address or pipe name can be connected to it, write data, here is easy to be used maliciously, there is no verification of the user’s information. This kind of information can only be added in kernel mode by IPC mechanisms such as Binder, which do this. In Android, this identity identifier is actually a UID.
Binders do not transfer large amounts of data. However, memoryFiles are inefficient to transfer data across processes through files. Memoryfiles are the high-performance file classes in Android’s anonymous shared memory.
-
Scoket
The Zygote process forks the Zygote process, executes Zygote’s main function, internally registering a local Scoket, and enters a loop to process the data.
2. Binder drive
1. Communication process with Binder when Android starts
-
After the Android underlying Linux is started, the init process is the first process started in user space
-
The init process loads the init.rc configuration and starts the system services defined in the configuration file through fork + exec, including Zygote, ServiceManager, and SurfaceFlinger
-
The Zygote process started (frameworks/base/ CMDS /app_process/app_main.cpp). After the VM is started and the JNI function is registered, the SystemServer process continues to start, that is, the main function of the SystemServer process is executed
com.android.server.SystemServer.java public static void main(String[] args) { new SystemServer().run(); Private void run() {..... // Boot services, core services, other services startBootstrapServices(); startCoreServices(); startOtherServices(); . }Copy the code
Using AMS as an example, the following methods are called after an AMS instance is created
// 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)); }... }Copy the code
You can see that AMS and some other services are registered with the ServiceManager
Let’s move on to how do you register
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 {// Get a Native BpBinder with BinderInternal And then converted to Java layer BinderProxy object 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); }Copy the code
Take BpBinder from the bottom, turn it into a BinderProxy object, call addService, and put it into a ServiceManager to manage. The addService method corresponding to the native layer frameworks/native/libs/IServicManager CPP in the addService method, back analysis of SurfaceFlinger registration service will this method.
-
The ServiceManager process is started. The main function opens the Binder driver, maps memory, and opens a Binder Loop waiting for Client and Service requests.
frameworks/native/cmds/sedrvicemanager/service_manager.c int main(int argc, char** argv) { .... if (argc > 1) { driver = argv[1]; } else { driver = "/dev/binder"; }... Bs = binder_open(driver, 128*1024); . // 2\. Binder becomes the context manager, telling Binder drivers that I am a ServiceManager, If (binder_become_context_manager(bs)) {ALOGE("cannot become context Manager (%s)\n", strerror(errno)); return -1; }... 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. The for loop reads the data from the binder driver, and binder_parse processes the request 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); . }}Copy the code
For clarification, binder_open maps 128 K of memory through Mmap memory, but only in the ServiceManager process. BINDER_VM_SIZE ((1 * 1024 * 1024) – sysconf(SC_PAGE_SIZE) * 2)
Memory space,SC_PAGE_SIZE is the size of a page, estimated to be 1m-8K.
Binder has a memory limit of 1M-8K and a maximum transfer of 507K data in one call.
This is because the default Binder thread pool is 15 (not counting the Binder main thread created by a particular instruction, otherwise 16), and 15 threads share so much memory that the actual transfer size is not that large.
-
The SurfaceFlinger process is started
frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp int main(int, char**) { startHidlServices(); signal(SIGPIPE, SIG_IGN); // When SF is launched in its own process, limit the number of // binder threads to 4. ProcessState::self()->setThreadPoolMaxThreadCount(4); // start the thread pool sp<ProcessState> ps(ProcessState::self); ps->startThreadPool(); // 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\. // Publish SurfaceFlinger 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; }Copy the code
You can see the fourth comment in the code above, which will publish the SurfaceFlinger service to the ServiceManager, so you might be wondering, why does the SurfaceFlinger process get the Service before the ServiceManager process is started?
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)); If (gDefaultServiceManager == NULL) sleep(1); } } return gDefaultServiceManager; }Copy the code
As you can see, fetching gDefaultServiceManager uses a while loop and will not stop until it gets it
Let’s look again at the addService code at comment 4 (similar to 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\. Write services to Parcele data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1:0); Status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err; }Copy the code
Transact at comment 2 calls code in IPCThreadState, which is where the real underlying interaction takes place
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) Copy the code
-
Finally, post an Android startup flowchart
2, Binder communication layered architecture
-
For BinderProxy, we through the section on the Zygote process started ServiceManagerNative. AsInsterface method as you can see, to get the remote Binder entity, tend to wrap a layer, For example, the ServiceManagerProxy returned by the asInsterface method is a BinderProxy object
-
BpBinder is similar to BinderProxy, a Native layer and a Java layer. BpBinder holds a binder handle handler inside. We look directly at the getContextObject method with Binder in defaultServiceManager in the SurfaceFlinger process startup section
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; }Copy the code
-
ProcessState is a process singleton, one process at a time, that opens Binder drivers, maps memory, and handles MMAP calls. Here you might ask where was the ProcessState created? When was it created?
After Zygote process is started, it receives SystemServer and APP process information through Socket. When you want to create SystemServer and APP process, you will execute onZygoteInit method in the new process to start the thread pool
virtual void onZygoteInit() { sp<ProcessState> proc = ProcessState::self(); ALOGV("App process: starting thread pool.\n"); proc->startThreadPool(); } Copy the code
For system services, such as the start time of ProcessState in SurfaceFlinger, see the simple analysis of the process in the previous section. All Binder thread pools are set to size 4
-
IPCThreadState thread singleton, responsible for communication between binder drivers and other commands. The SurfaceFlinger process analyzed in the previous section starts to publish the service to the ServiceManager using IPCThreadState, and let’s look at its Transact method
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"); 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; }Copy the code
-
The whole process is that the Proxy makes a Transact call, packages the data into a Parcel, and then calls BpBinder down to BpBinder, where the Transact method of IPCThreadState holds the handle value, IPCThreadState is responsible for executing Binder specific instructions. The Server receives the request to the Client through IPCThread, then layers up and finally calls back to the Stub onTransact method
3. Data transfer process with Binder
The oneway
oneway
-
When writing code in AIDL, if the interface is marked oneway, it means that the Server side serializes processing (taking message instructions out of the asynchronous queue and distributing them one by one) and calls asynchronously. This keyword is primarily used to modify the behavior of remote calls, as shown in the two figures above. For AIDL classes without the oneway keyword, the client needs to suspend the thread to wait for Sleep, which is equivalent to calling the Sleep function. Related system Binder calls such as WMS and AMS are oneway.
-
The simple differences between Oneway and non-Oneway AIDL files are as follows. After you practice with Android Studio, take a look at the generated file and see the call chain.
// 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); . }Copy the code
-
The key difference between oneway and non-OneWay is the last parameter, which continues with IPCThreadState’s transact method
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); 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; }Copy the code
If the flags is 0, which is not oneway, waitForResponse(reply) will be called, otherwise waitForResponse(NULL,NULL) will be called. The latter is no need to wait for the other party to return the result.
If you look closely, you can also see from the code above that the message sent to the driver is BC_TRANSATION, CMD, and if you look at waitForResponse, you’ll see that the received message starts with BR. That is, all instructions sent to Binder drivers start with BC_ and all instructions sent by Binder drivers start with 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 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; }Copy the code
3. Binder cross-process transfer scheme
In the introduction to Android development, may directly pass through Intent Bitmap, and found that TransactionTooLargetException anomalies. Why is that? So how to solve it?
-
Problem analysis
Bundle bundle = new Bundle(); b.putParcelable("bitmap",mBitmap); intent.putExtras(b); startActivity(intent); Copy the code
Our analysis of the cause abnormal code, throw an exception where is BinderProxy transactNative (Native Method) 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; }Copy the code
Let’s continue with the signalExceptionForError method
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]; 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; }}}Copy the code
Note 1 states that if transact fails and parcelSize is greater than 200 K, this exception is most likely thrown. You may ask when this FAILED_TRANSACTION was thrown.
We can look at the waitForResponse method called within the Transact method in the IPCThreadState analysis earlier
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) { ..... Ioctl if ((err=talkWithDriver()) < NO_ERROR) break; . cmd = (uint32_t)mIn.readInt32(); . switch (cmd) { ...... case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; . }Copy the code
Binder drivers only set ERR to FAILED_TRANSACTION when they return BR_FAILED_REPLY. When do Binder drivers send BR_FAILED_REPLY?
Note 1 is the communication with binder, which calls the ioctl function internally. The second argument is specified as BINDER_WRITE_READ, which stands for reading and writing data to the driver, both at the same time.
The binder_transaction method is called inside the IOCtl function
/ drivers/stagine/android/binder. C / / pay attention to this file and ServerMnager there call 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; }... }Copy the code
We can see that if the buffer request of data_size fails, the BR_FAILED_REPLY error code will be sent to the client.
-
Problem solution
As for the solution, we need to look at all the cross-process delivery methods.
The use of file format to save images is not considered here, because disk operation and cross-process operation, no file lock, performance and data instability. Other ways do not work, can be from the memory copy times, memory leaks.
Go directly to the subject and use AIDL to make IPC calls through Binder to pass the image.
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)Copy the code
So why, you ask, can this scheme achieve big picture transfer?
Let’s look directly at the serialization process in 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); }Copy the code
Let’s look again at the Bundle’s writeToParcel method
@Override public void writeToParcel(Parcel parcel, Int flags) {// comment 1 Final Boolean oldAllowFds = parcel. PushAllowFds ((mFlags & FLAG_ALLOW_FDS)! = 0); try { super.writeToParcelInner(parcel, flags); } finally { parcel.restoreAllowFds(oldAllowFds); }}Copy the code
The implication of comment 1 is whether passing descriptors is allowed
WirteToParcelInner calls the super.wirteToParcelInner method, which stores the data to the map, writes to the Parcel again, and eventually calls Bitmap’s wirteToParcel method (part of the call chain is omitted). The Bitmap method internally calls the native method
nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p) static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, ...). {// Take Native Bitmap auto bitmapWrapper = reinterpret_cast< bitmapWrapper *>(bitmapHandle); . Int fd = bitmapWrapper-> Bitmap ().getashmemfd (); If (FD >= 0 &&! isMutable && p->allowFds()) { status = p->writeDupImmutableBlobFileDescriptor(fd); return JNI_TRUE; Android:} / / does not meet the above conditions: Parcel: : WritableBlob blob. Status = p->writeBlob(size, mutableCopy, &blob); Const void* pSrc = bitmap.getPixels(); // Copy data to buffer memcpy(blob.data(), pSrc, size); }Copy the code
WriteBlob function (analysis written directly in code)
Status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob) { Or this data is less than BLOB_INPLACE_LIMIT = 16K if (! MAllowFds | | len < = BLOB_INPLACE_LIMIT) {/ / is directly to the image in the Parcel status = writeInt32 (BLOB_INPLACE); //Parcel's buffer finds an offset void* PTR = writeInplace(len); outBlob->init(-1, ptr, len, false); return NO_ERROR; Int fd = ashmem_create_region("Parcel Blob", len); / / memory mapped space void * PTR = : : mmap (NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0). . // Write fd to Parcel status = writeFileDescriptor(fd, true /*takeOwnership*/); outBlob->init(fd, ptr, len, mutableCopy); return status; }Copy the code
As you can see from the above, allowFds will open an anonymous shared memory space for storing bitmaps if the image is larger than 16K, and no exceptions will be caused.
Why can’t you just deliver it with an Intent?
Everyone knows that the execStartActivity method of the Instrumentation will be called after startActivity
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); . }Copy the code
We have a look at the intent. PrepareToLeaveProcess method
public void prepareToLeaveProcess(boolean leavingPackage) { setAllowFds(false); } public void setAllowFds(boolean allowFds) { if (mExtras != null) { mExtras.setAllowFds(allowFds); } } Copy the code
AllowFds is set to false and the Bitmap is written directly to the Parcel buffer, too large is a problem.
Iv. Problem solving
1. Do you know Binder mechanics?
According to the Binder communication architecture diagram in Section 2, some of the processes in the startup process from the phone are illustrated
Binder layered architecture diagram
Binder’s Oneway and Non-Oneway data transfer diagrams
Advantages and disadvantages of Binder: performance (copy once), safety (check UID, PID)
2. Binder is so good, so why Zygote IPC communication mechanism uses sockets instead of Binder?
With Binder, Zygote starts with binder mechanisms, opens binder drivers, gets descriptors, mMAP process memory maps, registers binder threads, and creates a Binder object to register with serviceManager. In addition, if AMS wants zygote to initiate the application process request, it needs to query the Binder object of Zygote from serviceManager, and then initiate the binder call, which is very tedious.
In contrast, Zygote and SystemServer are the parent and child relationship, for simple message communication, using pipes or sockets is very convenient.
If Zygote were to fork systemServer with binder, systemServer would inherit zygote’s descriptor and mapped memory, and the two processes would share the same data structure in the Binder driver layer, which would not work. You’d have to turn off the old descriptors and restart the Binder mechanism again, asking for trouble.
3. Why are binders safe?
During data transmission, identity verification is performed by UID and PID
4. Why does an Intent fail to upload a large image across processes?
Regular intents pass data, set the Bundle’s allowFds to false in startActivity, and write the Bitmap directly to the Parcel buffer. If bitmaps are passed as bundle.putBinder, a block shared anonymous memory is created to store Bitmap data, while a Parcel buffer stores just FDS.
5. What is the difference between AIDL’s oneWay and non-OneWay?
Architecture diagram of Oneway and non-Oneway, Oneway server side is serial processing, asynchronous call, Client side does not sleep to wait for the driver to return data.