• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

1. Ask a question

On this day, Fa Hai wanted to exercise Xiaoqing’s concentration. Since Bitmap is also a Parcelable type of data, Fa Hai wanted to upload a particularly large picture to Xiaoqing through an Intent

intent.putExtra("myBitmap",fhBitmap)
Copy the code

If the Activity uses the Intent to deliver a large Bitmap to the Activity, if your image is large enough, you will get an error like the following:

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)
Copy the code

As for what kind of big picture, only Fahai knows (xiaoqing: so shy)🙈🙈🙈

So TransactionTooLargeException thing came out of this place?

2. Problem location and analysis

We can see the error log information. See the call BinderProxy transactNative, this transactNative is a native method

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

In Android Code Search, Search globally: android_os_BinderProxy_transact

//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) { 
        // If the match is successful, the interception will not be executed below
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }
    signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
    return JNI_FALSE;
}
Copy the code

Let’s open the signalExceptionForError method and see what’s inside

//frameworks/base/core/jni/android_util_Binder.cpp
// How to handle exceptions
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
        bool canThrowRemoteException, int parcelSize)
{
    switch (err) {
        // Other exceptions, you can read for yourself;
        // For example: no permission exception, file too big, wrong file descriptor, etc..case FAILED_TRANSACTION: {
            const char* exceptionToThrow;
            char msg[128];
            // Too large a transaction is the most common cause of FAILED_TRANSACTION
            Binder drivers can return BR_FAILED_REPLY
            // There are other possible reasons: the transaction format is not correct, the file descriptor FD has been turned off, etc
            
            //parcelSize greater than 200K is an error and canThrowRemoteException is passed 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{... }// Throws an exception with the specified class and message content
            jniThrowException(env, exceptionToThrow, msg);
        } break; . }}Copy the code

If the parcelSize is greater than 200K, an error will be reported. Before jumping to conclusions, read on to 👇👇

Ask questions

Fahai: I have a question, I see the document is written in 1M size;

X: don’t worry, her husband, to have a look at the interpretation of the document first, look at the directions for use: official TransactionTooLargeException document description to: Binder transaction buffers have a limited fixed size, currently 1MB, that can be seen by all ongoing transactions shared by the process: shared transaction buffers

The Intent was intended to deliver an array of 201*1024 bytes. Logcat was intended to deliver an Error message. The Intent was intended to deliver an array of 201*1024 bytes

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

When the intent passes an array of 800*1024 bytes, it crashes

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)
Copy the code

Don’t worry, let’s move on to the analysis

4. Answer questions

Let’s look at the next two lines of code

//frameworks/base/core/jni/android_util_Binder.cpp
// This method android_os_BinderProxy_transact
IBinder* target = getBPNativeData(env, obj)->mObject.get(a);status_t err = target->transact(code, *data, reply, flags);
Copy the code

Select * from target->transact; select * from target->transact; select * from err; Bpbinder.cpp, IPCThreadState. CPP, ProcessState

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

// Add two comments
/ / reference since the official document: https://source.android.google.cn/devices/architecture/hidl/binder-ipc
#ifdef __ANDROID_VNDK__
// IPC between vendor/vendor processes, using AIDL interface
const char* kDefaultDriver = "/dev/vndbinder";
#else
// "/dev/binder" device nodes become proprietary nodes for framework processes
const char* kDefaultDriver = "/dev/binder";
#endif

// Constructor: initializes some variables, Binder maximum thread count, etc
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) {
    ......
    // Open the driver
    base::Result<int> opened = open_driver(driver);
    if (opened.ok()) {
        // Map (1m-8K) mmap space
        mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
                        opened.value(), 0); . }... }Copy the code

Binder memory limit: BINDER_VM_SIZE = 1m-8KB

Why is this not 1M, but 1M minus 8K? In the beginning, the official said 1M, but later they optimized it internally; Take a look at 👉👉’s official submission of the processState. CPP log: allowing the kernel to make more efficient use of its virtual address space

We know that wechat MMKV and Meituan Logan log components are based on MMAP.

Binder driver registration logic in binder.c, we look at the binder_mmap method

//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;
        // The mapping space is at most 4M
	if((vma->vm_end - vma->vm_start) > SZ_4M) vma->vm_end = vma->vm_start + SZ_4M; .// Initialize the specified space vMA to allocate the binding buffer
	ret = binder_alloc_mmap_handler(&proc->alloc, vma); . }Copy the code

Here you can see that the mapping space is at most 4M. Let’s look at the binder_alloc_mmap_handler method again. Click binder_alloc

//kernel/msm/drivers/android/binder_alloc.c
// Call binder_mmap() to initialize the specified space vMA used to allocate the binding buffer
int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma)
{.../ / buffer_size maximum 4 malloc->buffer_size = vma->vm_end - vma->vm_start; .// The maximum free buffer size for asynchronous transactions is 2M
     alloc->free_async_space = alloc->buffer_size / 2; . }Copy the code

Conclusions can be drawn from the above analysis: 1.Binder drives to allocate up to 4M buffer space for each process; 2. The maximum size of the free buffer space for asynchronous transactions is 2 MB. 3. Upper limit of memory for Binder kernel is 1M-8K; 4. The buffer size of asynchronous transactions is buffer_size/2, depending on buffer_size;


Synchronous and asynchronous are defined in AIDL files. Let’s look at the two examples above. One of them passed an 800*1024 byte array and crashed as follows:

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)
Copy the code

Click on IApplicationThread. Aidl and check the contents of aiDL. ScheduleTransaction is an asynchronous method. Because the oneway modifier precedes the interface, all methods in the interface implicitly include oneway.

Since oneway calls asynchronously, let’s change it at this time and test it by passing data in (1m-8K)/2 sizes

// ((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)
Copy the code

Extras size: 520236 Data parcel size: 522968 Size difference: 2732 is about 2.7K

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))})Copy the code

At this time, it is found that (1m-8K)/ 2-3K can successfully transfer data, indicating that other data occupies this part of space. So we wrote up here, don’t forget: shared transaction buffers, minus 3k here for testing purposes only, so let’s move on;

Open binder_alloc. C and find the binder_alloc_new_buf method

//kernel/msm/drivers/android/binder_alloc.c
// Allocate a new buffer
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); . }Copy the code

Let’s look at 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 it is an asynchronous transaction, check whether the required size is within the free buffer range of the asynchronous transaction
    if (is_async &&
	alloc->free_async_space < size + sizeof(struct binder_buffer)) {
            return ERR_PTR(-ENOSPC); }}Copy the code

If a large amount of data needs to be transmitted through the Intent of an Activity, it is best to keep the data size within 200K. In the above test, LogCat will print “Transaction too large” for more than 200K of data, but will not crash as long as it does not exceed the size of the buffer free for asynchronous transactions. If an Intent passes a large amount of data, you can use another method entirely.

What happens when the Intent sets the Bitmap?

5.1 Intent. WriteToParcel

Intent data is written to the parcel. In the writeToParcel method, the Intent writes the Bundle to the parcel

//android.content.Intent

public void writeToParcel(Parcel out, int flags) {...// Write the Bundle to a Parcel
    out.writeBundle(mExtras);
}
Copy the code

Open the out.writeBundle method

//android.os.Parcel#writeBundle
public final void writeBundle(@Nullable Bundle val) {
     if (val == null) {
         writeInt(-1);
         return;
     }
     // Execute the Bundle's own writeToParcel method
     val.writeToParcel(this.0);
}
Copy the code

5.2 Bundle. WriteToParcel

//android.os.Bundle

public void writeToParcel(Parcel parcel, int flags) {
     final booleanoldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) ! =0);
     try {
        // The official comment is very detailed:
        // Write Bundle content to a Parcel, usually for delivery via an IBinder connection
        super.writeToParcelInner(parcel, flags);
     } finally {
        // Set the mAllowFds value backparcel.restoreAllowFds(oldAllowFds); }}Copy the code

Click to see Parcel. CPP and take a look at the pushAllowFds method inside

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

If the Bundle does not allow descriptors, the contents of the Parcel will not have descriptors after pushAllowFds is called. In the example we gave at the beginning of the article: Instrumentation#execStartActivity instrument_bitMap Instrumentation#execStartActivity instrument_bitMap Instrumentation#execStartActivity Instrument_bitmap Inside this method Bundle#setAllowFds(false) is called

//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;
    }
Copy the code

5.3 – Parcel. WriteArrayMapInternal

Super. writeToParcelInner just triggered the following method in the bundle. writeToParcel method above

//android.os.BaseBundle
void writeToParcelInner(Parcel parcel, int flags) {... parcel.writeArrayMapInternal(map); . }Copy the code

Let’s look at the writeArrayMapInternal method

void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {...for (int i=0; i<N; i++) {
            writeString(val.keyAt(i));
            Call different write methods according to different data typeswriteValue(val.valueAt(i)); }}Copy the code

5.4-writeValue

We started with intent. PutExtra (” BMP “).

//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);
}
Copy the code

Since we pass in a Bitmap, we look at bitmap.writetoparcel

5.5 Bitmap. WriteToParcel

//android.graphics.Bitmap
public void writeToParcel(Parcel p, int flags) {
    noteHardwareBitmapSlowCall();
    // Open bitmap. CPP to find the corresponding native method
    if(! nativeWriteToParcel(mNativePtr, mDensity, p)) {throw new RuntimeException("native writeToParcel failed"); }}Copy the code

Click open bitmap. CPP to view the Bitmap_writeToParcel method

//frameworks/base/libs/hwui/jni/Bitmap.cpp

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density, jobject parcel) {...// Get Native layer objects
    android::Parcel* p = parcelForJavaObject(env, parcel);
    SkBitmap bitmap;
    auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
    / / get SkBitmap
    bitmapWrapper->getSkBitmap(&bitmap);
    / / write 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(a);if (fd >= 0 && bitmap.isImmutable() && p->allowFds()) {
        //AshmemFd is greater than or equal to 0 && Bitmap is immutable && parcel allows Fd
        // If the above conditions are met, write fd to parcel
        status = p->writeDupImmutableBlobFileDescriptor(fd);
        if (status) {
            doThrowRE(env, "Could not write bitmap blob file descriptor.");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    
    //mutableCopy=true: indicates that bitmap is mutable
    const boolmutableCopy = ! bitmap.isImmutable(a);// Returns the minimum memory required for pixel storage
    size_t size = bitmap.computeByteSize(a); android::Parcel::WritableBlob blob;// Get a bloB buffer, scroll down for code analysis
    status = p->writeBlob(size, mutableCopy, &blob); . }Copy the code

What’s going on in 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) {// If FD is not allowed or data is less than or equal to 16K, write the image to the parcel directly
        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;
    }
    // if Fd &&len > 16k is allowed:
    // Create a new ashmem area and return the file descriptor 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;
    // Set ashmem to readable and writable
    int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
    if (result < 0) {
        status = result;
    } else {
         // Map the space of mmap "len size" according to fd
         void* ptr = ::mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); .if(! status) {// write fd to parcel
           status = writeFileDescriptor(fd, true /*takeOwnership*/);
            if(! status) { outBlob->init(fd, ptr, len, mutableCopy);returnNO_ERROR; }}... }... }Copy the code

You can see why we first analyze the upper limit on the size of an Intent; WriteToParcel disables file descriptors when an Intent starts an Activity. The writeBlob method only goes to the first branch, writing images directly to a parcel. In directory 4, we conclude that the Intent delivers data size limits.

How do you get around intEnts that disable file descriptors and data sizes?

6. Upload the big picture across processes

If the value is IBinder, writeStrongBinder will be called to write the value to the Parcel.

So we can use putBinder to write IBinder objects to a Parcel. PutBinder is not affected by the Intent’s disabled file descriptor, and there is no data size limit. Bitmaps are written to a Parcel by default. You can use anonymous shared memory (Ashmem);

6.1- putBinder usage in a Single process

IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder: IntentBinder
class IntentBinder(val imageBmp:Bitmap? = null): Binder()

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - use the following -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / /
//com.xxx.xxx.MainActivity
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
        putBinder("myBinder",IntentBinder(bitmap))
}))

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- get Bitmap and display the following -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / /
//com.xxx.xxx.SecondActivity
val bundle: Bundle? = intent.extras
valimageBinder:IntentBinder? = bundle? .getBinder("myBinder") as IntentBinder?
// Get Bitmap in Binder
valbitmap = imageBinder? .imageBmp// Compress and display to ImageView.....
Copy the code

Note: This cannot be used across processes. If you like to do it, you can configure The SecondActivity as android: Process =”:remote”, you will find that the cast exception will be reported

/ / error in multi-process scenarios, an error is as follows: Java. Lang. ClassCastException: android. OS. BinderProxy always be cast to com. XXX. XXX. IntentBinderCopy the code

🤔 Why can objects be passed this way? Binder creates a global JNI reference for our object. Check out 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";

// Call the following method to complete the Binder class registration
static int int_register_android_os_Binder(JNIEnv* env)
{
    // Get the Binder class object
    jclass clazz = FindClassOrDie(env, kBinderPathName);
    
    // Internally create a global reference and save clazz to a global variable
    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    
    // Get the Java layer's Binder member method execTransact
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact"."(IJJI)Z");
    
    // Get the Java layer Binder member method getInterfaceDescriptor
    gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor"."()Ljava/lang/String;");
        
    // Get mObject, a member of Binder of the Java layer
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject"."J");
    
    // Register the functions defined in gBinderMethods
    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods)); }...Copy the code

6.2- putBinder usage in Multiple processes

// Define an igetbitmapService.aidl
package com.xxx.aidl;
interface IGetBitmapService {
    Bitmap getIntentBitmap();
}

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - use the following -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / /
/ / com. XXX. XXX. MainActivity 👉 process A
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
    putBinder("myBinder".object: IGetBitmapService.Stub() {
        override fun getIntentBitmap(a): Bitmap {
            return bitmap
        }
    })
}))

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- get Bitmap and display the following -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / /
/ / com. XXX. XXX. SecondActivity 👉 process B
val bundle: Bundle? = intent.extras
// Return the IGetBitmapService type
valgetBitmapService = IGetBitmapService.Stub.asInterface(bundle? .getBinder("myBinder"))
val bitmap = getBitmapService.intentBitmap
// Compress and display to ImageView.....
Copy the code

Reference 7.

Binder kernel basic method 2.Android Binder Meizu kernel team 3.Android system anonymous shared memory Ashmem driver source code analysis 4


Previous articles recommended: 1.Jetpack Compose UI creation process + principles — Full explanation of the concept 2. How to use and principle analysis of Jetpack App Startup 3. Source code analysis | ThreadedRenderer null pointer, the Choreographer to meet 5, by the way. Source code analysis | events is how to the Activity? 7. Do not fall into the loop of the need to keep alive