Service and Android system design (2)-parcel

Source: Internet
Author: User
 

Special statement: this series of articles are copyrighted by lianlab.org. Please indicate the source for reprinting. The author is Mr. Wu He, Senior Android technical consultant at lianlab.org. Exchange and discussion in this series of articles: @ song Baohua Barry

A total of 18 times of serialization, describes the implementation principle behind the android service, and analyzes the RPC related to the binder.
Parcel and parcelable when we call a remote method, we need to pass parameters and return results between processes. This kind of similar processing method requires that the data and process relevance be removed into an intermediate form, and then read and write operations are performed according to the unified interface. This mechanism is generally called serialization in advanced programming languages. In the Android world, parcel is used to process data serialization. parcelable is used to process data sequences. Therefore, when we need to transmit an object between processes, the class that implements this object must implement the corresponding attributes or methods defined in the parcelable interface. When using this object, you can use a parcel reference to process basic operations during transmission. The aidl programming described above is only for the basic data types of Java such as string and Int. If we need to process some complex data types or define some custom data types, in this case, we need another utility in aidl to import the parcelable interface.

It is relatively easier to use the parcelable interface in the aidl environment. We have also stressed that in the implementation of aidl, in order to achieve a concise and efficient design goal, aidl only supports basic data types, an interface class only defines methods in the aidl environment and does not involve attribute definitions. Therefore, this restriction applies to parcelable. parcelable is only a simple line in aidl, when other aidl uses these parcelable interfaces, you can directly reference the aidl file defining this parcelable.

Expand a parcelable example based on our previous example. If we want the process to pass Descriptions between processes, we will organize such data to a class for transmission, we can call this class taskinfo, so in a single process environment, we generally have this class:

class TaskInfo {    public long mPss;    public long mTotalMemory;    public long mElapsedTime;    public int mPid;    public int mUid;    public String mPackageName;    TaskInfo() {       mPss = 0;       mTotalMemory = 0;       mElapsedTime = 0;       mPid = -1;       mUid = -1;       mPackageName = null;    }}

When we create an object through this class, the valid scope of use of this object is only in a process, because the process space is independent of each other, in the process space, in fact, this pair is saved in heap. If we want to pass such an object directly through the IPC Mechanism, it is unrealistic, because in the Java environment, the object is accessed through reference, and other objects in an object, for example, to access the packagename of the taskinfo class, it is accessed through a reference similar to a pointer.

Therefore, when using IPC to copy objects with complex structures, you must use a mechanism to split objects into an intermediate format, the intermediate format is transmitted in IPC to obtain the effect of mutual transmission of objects between processes. No matter what IPC Mechanism is used, this transmission process will write data and read the data from the other side. According to our previous analysis, we can use an object inherited from the binder to complete this function. However, the Code at both ends of the read/write process will be repeated and may cause potential errors. The Android system will use the object-oriented technique to simplify this operation. Different from calling methods between processes, passing objects between processes does not care about interaction, but focuses more on the flexibility in use. When an object is transmitted, the common feature is the transmission mechanism, and the difference is the composition of the transmitted object. Therefore, we create a parcel class on top of the binder class, which is used to process data transmission that is a common part, for the composition of the difference object, a parcelable interface class can be used to notify the difference in the composition of the object to parcel.

With the parcel class and parcelable interface class, it is easier to pass objects like taskinfo. We can enable our taskinfo class to implement the parcelable function, so that all the generated objects will be transmitted at the underlying layer through the parcel object. The taskinfo class we defined earlier will be changed to the following:

package org.lianlab.services;import android.os.Parcel;import android.os.Parcelable; public class TaskInfo implements Parcelable {    public long mPss;    public long mTotalMemory;    public long mElapsedTime;    public int mPid;    public int mUid;    public String mPackageName;    TaskInfo() {       mPss = 0;       mTotalMemory = 0;       mElapsedTime = 0;       mPid = -1;       mUid = -1;       mPackageName = null;}     public int describeContents() {                1       return 0;    }     public void writeToParcel(Parcel out, int flags) {    2       out.writeLong(mPss);       out.writeLong(mTotalMemory);       out.writeLong(mElapsedTime);       out.writeInt(mPid);       out.writeInt(mUid);       out.writeString(mPackageName);    }     public static final Parcelable.Creator<TaskInfo>CREATOR = new Parcelable.Creator<TaskInfo>() {                 3       public TaskInfo createFromParcel(Parcel in) {            4           return new TaskInfo(in);       }        public TaskInfo[] newArray(int size) {             5           return new TaskInfo[size];       }    };     privateTaskInfo(Parcel in) {                 6       mPss = in.readLong();       mTotalMemory = in.readLong();       mElapsedTime = in.readLong();       mPid = in.readInt();       mUid = in.readInt();       mPackageName = in.readString();    }}

  1. Describecontents (), one of the interface methods required by parcelabl, must be implemented. The function of this method is very simple, that is, to describe the role of this parcel through the returned integer. Through this integer, each bit describes its type, and generally returns 0.
  2. Writetoparcel (), the second interface method required by parcelabl, must be implemented. The writetoparcel () method is used to send data. It writes the attributes to be transmitted by the class to parcel, which is used to provide the sending function and is passed as the first parameter, therefore, writeint () and writelong () are used in this method to write data to parcel. The second parameter of this method is a flag value, which can be used to specify whether such sending is unidirectional or bidirectional. It can match with the three delimiters of aidl: In, out, And inout.
  3. The Creator object, the third item required by the parcelable interface, must be implemented, but this is an interface object. As we can see, this creator object is obtained by applying the template class parcelable. creator to taskinfo. parcelable. creator <taskinfo>. This creator object is largely a factory class used to create remote objects on the acceptor. In a sense, writetoparcel () corresponds to the Creator one by one. The sender process uses writetoparcel () to save the intermediate results using a parcel object, the acceptor process uses the Creator object to restore the intermediate object of the parcel object. The class initialization method is used to create a new object based on the parcel object. The subsequent 4-6 is to complete the implementation of this creator object.
  4. Createfromparcel () is the interface method required by the parcelablecreator <t> template class. It provides the ability to escape new objects from parcel. When the receiving end receives the transmitted parcel object, it obtains the object using this interface method. Here we call the initialization method of the parcel-based class directly, and then return the created object.
  5. Newarray () is another interface method that must be implemented by the parcelablecreator <t> template class. However, this method is used to create multiple classes that implement the parcelable interface. Using this method, the Creator object can return multiple created empty objects, but cannot be created based on a certain parcel object, the default class creation method is used.
  6. It is not necessary to implement the specific initialization method with parcel as reference. We can also assign values to the object based on the parcel value in createfromparcel, however, this implementation is clearer. This method is basically paired with writetoparcel (). In what order will the object attribute be written to parcel, then in createfromparcel () the object attributes will be read from parcel in the same order and completed using parcel's readint (), readlong (), and other methods.

With the definition of this class, we get a class that can be transferred across processes. The objects created by this class can be transmitted seamlessly in the process. To use this defined parcelable class, we only need to create a new aidl file to describe the existence of this parcelable, and then notify the aidl compilation tool. If it is based on the parcelable class taskinfo defined in the previous example, we need to use a file named taskinfo. aidl with the same name:

Package org. lianlab. Services;

Parcelabletaskinfo;

This file is very simple. It is actually a line used to define the package name, and a line definition has a taskinfo class that implements the parcelable interface. Then, we need to use this part before importing this aidl file. We can use the previous itaskservice. aidl expands and defines a new gettaskstatus () interface method to return a taskinfo object. It is very easy to use the parcelable class in the aidl file that defines these interface methods. You just need to reference the aidl file that defines this parcelable class:

package org.lianlab.services;importorg.lianlab.services.ITaskServiceCallback;import org.lianlab.services.TaskInfo; interface ITaskService {    intgetPid (ITaskServiceCallback callback);   TaskInfo getTaskStatus ();}

Of course, our itaskservice interface is changed, so we need to rewrite our stub class implementation, and we need to go to taskservice. in Java implementation, the implementation of the new gettaskstatus () method is enhanced:

    private final ITaskService.StubmTaskServiceBinder =new ITaskService.Stub() {       public int getPid(ITaskServiceCallback callback) {           mCount++;           try {                callback.valueCounted(mCount);           } catch (RemoteException e) {                e.printStackTrace();           }           return Process.myPid();       }        public TaskInfo getTaskStatus() {            TaskInfo mTaskInfo = new TaskInfo();           mTaskInfo.mUid = Process.myUid();           Debug.MemoryInfo memInfo = new Debug.MemoryInfo();           Debug.getMemoryInfo(memInfo);           mTaskInfo.mPss = memInfo.nativePss;            Runtime runtime = Runtime.getRuntime();           mTaskInfo.mTotalMemory = runtime.totalMemory();            mTaskInfo.mPid = Debug.getBinderReceivedTransactions();           mTaskInfo.mElapsedTime = Process.getElapsedCpuTime();           mTaskInfo.mPackageName = getPackageName();           return mTaskInfo;       }     };

In the newly added gettaskstatus () method, we will create a new taskinfo object and assign a value to this taskinfo object according to the context of the current process, then return this object. Because taskinfo has now been upgraded to a parcelable object, this return actually returns this object on the remote service caller in a cross-process environment.

From the code example above, we can roughly analyze the principle of passing objects through parcel. This is similar to sending an item consisting of parts. In our daily life, we send items made up of parts, which are generally divided into components, so they are packed and easy to transport. We can place parts in a box as flexibly as possible and then send them out. The party who received the package will split the parts from the box, and then assemble the parts together according to the same structure during disassembly, so we get the original styling of all kinds of items. Out of this category, the class we are responsible for carrying is called parcel, which is the meaning of the package. It is used for the Disassembly and assembly process, or, accurately, the ability to be detachable and then assembled, it is called parcelable.

For parcel transmission, Mr. Huang jingqun (jserv) of 0xlab makes an image metaphor, hoping to send a paper box to the remote end through a fax machine. The fax machine is not a time-and-space conveyor belt and does not really realize the cross-space transfer of an item, but we can make some modifications to achieve it:

We can split a paper box and spread it out. Then we can get a completely flat graph with six grids. It is possible that we can fax the figure with six grids to the remote end. Then, the remote fax machine will receive fax images with six grids, print to a piece of paper. Then we cut the paper and paste it together, so we can also get a paper box with the same appearance as our original paper box.

Therefore, during sending, We can generally think of it as splitting and packing the object and then inserting it into the parcel object. Parcel is equivalent to the container function at this time, so its content must have a buffer for storing this intermediate result. Therefore, this process splits the attributes of the object through the writetoparcel () method and fills in the buffer of the parcel:

This process transfers the non-linear stored objects to a linear memory space for storage in terms of computing processing, therefore, this operation is called flatter ". We can also see from the previous code that the so-called flat operation is written into a parcel object through writetoparcel. We can take a closer look at this process by using different operation methods such as the writeint (), writelogn (), and writestring () of the parcel object, write the attributes of the object to the built-in buffer of parcel.

At the read end, we will read the parcel object by some means and get the object saved in parcel through the initialization method defined in Creator:

Corresponding to the flatten at the time of sending, the operation we perform when receiving is unflatten, which restores the object from the data stored in the built-in buffer of parcel in A linearity to a specific object. This step is done by initializing the object createfromparcel () through parcel in the Creator.

At this point, we can at least get the ability to transfer objects through an intermediate area. Objects cannot be directly copied and transmitted, but linear memory is acceptable. We can send the memory from 0 to the result location through some IPC Mechanism and receive it at the other end, you can get the memory content. Finally, if we transmit the content of the parcel object through the binder communication, then we get the ability to transfer objects between processes:

During the cross-process object transfer process, all objects are transmitted through packaging such as parcel. So as long as our parcel is powerful and flexible enough, we can deliver everything between processes. In our example, only basic types such as int, long, and string are used. In fact, the power of parcel is far more powerful than that. Parcel supports all basic data types, because parcelable. the Creator <t> template supports both the newarray () and createfromparcel () interfaces. Therefore, you can combine the two interfaces during transmission and create objects through newarray, at the same time, it is initialized through createfromparcel (), so parcel automatically supports array data. The support for the [] Operator makes it easy to implement the list, map, and Other types in the parcel object. In order to save storage and copy overhead during transmission, we provide support for loose arrays such as sparearray in array. Parcel can also contain the parcel subtype, which further improves its ability to encapsulate and process objects. Finally, the support of ibinder, bundle, and serializable types almost improves the ability of parcel to transmit objects. All data types supported by parcel include:

  • Null
  • String
  • Byte
  • Short
  • Integer
  • Long
  • Float
  • Double
  • Boolean
  • String []
  • Boolean []
  • Byte []
  • Int []
  • Long []
  • Object []
  • Bundle
  • Map
  • Parcelable
  • Parcelable []
  • Charsequence
  • List
  • Sparsearray
  • Ibinder
  • Serializable

However, parcel does not provide complete serialization support. It is only an efficient method for cross-process transmission. Our objects are not automatically supported when packaged as parcel, but all rely on writetoparcel () to split and write an object, we must ensure that the reading and writing order is consistent. The operations in createfromparcel () and writetoparcel () must be one-to-one. In addition, after the parcel object is stored, it is also our custom storage. If there are any changes in the read/write, the intermediate results that have been saved will become invalid. If serialization is required, the bundle type must be passed through parcel. But from the implementation point of view, this simplifies the design, and we get more efficient object transmission capabilities. In the internal operations of parcel, most of them are more efficient versions implemented by JNI. Therefore, this transmission mechanism is not called a serialization operation, but a parcelable transmission protocol.

Therefore, in the Cross-process communication process, we not only use the binder to construct the ability to communicate in RPC mode, but also get the ability to pass complex objects through parcel. In this way, our cross-process communication capabilities can accomplish almost any operation. In our applications, the aidl design can make the architecture more flexible, and the aidl can be used to implement code that requires long-term backend existence and will be shared by multiple parts, at this time, although it will bring a certain amount of process scheduling overhead, our architecture will become more flexible. In this way, application scenarios include downloading, logon, third-party API, music playing, and many other use cases.

Aidl is more important to the Android system because the entire android environment is built on a multi-process Sandbox Model. To support this highly flexible multi-process design, android must use aidl to split system functions in large quantities. Aidl is only a programming environment effective for Java, so for the Android system architecture, aidl is the basic environment built by Java in the implementation of the Android system, every kind of system function that needs to be exposed to the Supply Application must be a cross-process call based on aidl. Of course, the implementation of the Android system is not all built by Java. For the Code with higher performance requirements at the underlying layer, it may also be compiled by C/C ++, this is the nativeservice we will introduce later, but we need to know that the so-called nativeservice is only connected to the aidl environment through executable code, the native code is used to simulate the Service Code of aidl. The upper-Layer Code uses the same calling method as aidl. The underlying implementation details are transparent to the upper layer.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.