This paper is mainly about collating and quoting notes of others.

Reference links:

Blog.csdn.net/fanleiym/ar…

Github.com/274942954/A…

www.kaelli.com/4.html

Carsonho.blog.csdn.net/article/det…

1 Android multi-process

By default, an app runs in only one process, which is the package name of the app.

1.1 Advantages of multiple processes

1. Spread the memory usage

The Android system has a limit on the memory usage of each application process. A process that occupies more memory is more likely to be killed by the system. Using multiple processes can reduce the memory occupied by the main process, avoid OOM problems, and reduce the probability of being killed by the system.

2. Realize multiple modules

A mature application must be multi-modular. Project decoupling, modularity, means opening up new processes, having separate JVMS, and bringing about data decoupling. Modules do not interfere with each other, the team develops in parallel, and the division of responsibilities is clear.

3. Reduce application crash rate

The crash of the child process does not affect the running of the main process and can reduce the crash rate of the program.

4. Implement special features

For example, push process can be implemented, so that after the master process exits, the message push service can be completed offline. You can also implement daemons to wake up the main process for survival purposes. Monitoring processes can also be used to report bugs, improving the user experience.

1.2 the android: process attribute

  • All four components can be set in the manifest fileandroid:processProperty to run in the specified process in order to implement multiple processes.
  • Set up the Application ofandroid:processProperty to change the default process name for your application (the default is the package name).

1.3 Public and Private Processes

Android: Process property values that start with a colon are private processes; otherwise, public processes. Of course, naming also needs to conform to the specification, can not start with a number and so on.

  • Private process: The process is owned by the application that created it. Components of other applications cannot run in this process.
  • Public process: also called global process. It is shared by all applications. Other applications can run in the same process with it by setting the same ShareUserId.

ShareUserId: used to share data between applications. In Android, each app has a unique Linux user ID, so the application’s files are visible only to itself and not to other applications. The Android :sharedUserId attribute in the <manifest> tag sets the application’s ShareUserId. Two APKs with the same userID can share access to each other’s files. For apKs with the same ID, it is possible (but not always) for the system to save resources by sharing a virtual machine in a Linux process.

1.4 Process life cycle

1. Foreground processes

  • Hosting the Activity that the user is interacting with (that has called the ActivityonResume()Methods)
  • Hosts a Service that is bound to the Activity the user is interacting with
  • Hosting the Service that is running in the foreground (called in the onCreate method of the Service)startForeground())
  • Hosting a Service that is performing a lifecycle callback (onCreate(),onStart()onDestroy())
  • The trustee is executing itonReceive()Methods the BroadcastReceiver

2. See the process

  • Host an Activity that is not in the foreground but is still visible to the user (invoked)onPause()Methods).
  • Hosts a Service bound to a visible (or foreground) Activity

3. Service process

  • A process that is running a service started with the startService() method and does not belong to either of the higher categories of processes described above.

4. Background processes

  • The process that contains the Activity that is currently invisible to the user (that has called the Activity)onStop()Methods). There are usually many background processes running, so they are saved in the LRU (Least Recently used) list to ensure that the process containing the Activity the user recently viewed is the last to terminate.

5. Empty process

  • A process that does not contain any active application components. The sole purpose of keeping such processes is to be used as a cache to reduce the startup time needed to run components in it the next time. To balance overall system resources between the process cache and the underlying kernel cache, systems often kill these processes.

Android rates the process as the highest it can be. A process that serves another process is never lower in rank than the process it serves.

1.5 Problems arising from multiple processes

  • Repeated creation of Application

When a new process is created, a new Application object is created, and we usually just do some global initialization in the onCreate method of the Application and don’t need to do it multiple times.

Solution: Obtain the name of the current process and determine whether the process is the active one. The initialization operation is performed only when the process is the active one

There are two ways to get the current process name:

  1. Obtain the process name based on the PID of the current process
fun getProcessName(context: Context, pid: Int): String? {
    val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val runningApps = am.runningAppProcesses ?: return null
    for (procInfo in runningApps) {
        if (procInfo.pid == pid) {
            return procInfo.processName
        }
    }
    return null
}
Copy the code
  1. Get the process name based on information in the cmdline file for the current process
fun getProcessName(a): String? {
    return try {
        val file = File("/proc/" + Process.myPid() + "/" + "cmdline")
        val mBufferedReader = BufferedReader(FileReader(file))
        val processName: String = mBufferedReader.readLine().trim()
        mBufferedReader.close()
        processName
    } catch (e: Exception) {
        e.printStackTrace()
        null}}The cmdline file contains the command line arguments for the process, including the start path of the process (argv[0]). ?
Copy the code

Application check if it is the main process (method 1 example) :

val processName = getProcessName(this, Process.myPid())// Obtain the process name based on the process ID
if(! TextUtils.isEmpty(processName) && processName.equals(this.packageName)) {
    // Initialize the logic. }Copy the code
  • Static members and singletons are completely invalidated

    Memory Spaces between processes are isolated from each other, and changes in the value of one memory space do not affect the value of the other.

  • The thread synchronization mechanism fails

  • The reliability of SharedPreferences decreases

    The problem of multi-process concurrency is difficult to solve. You should try to avoid multi-process concurrent operations, for example, you can only allow the main process to perform operations on the database.

2 the Serializable and Parcelable

Serializable and Parcelable are two ways to serialize data. In Android, only serialized objects can be delivered through IntEnts and binders.

Serialization is the transformation of an object into a storeable or transportable state.

Usually, after the serialized object is transferred, a new object is obtained through deserialization, not the original object.

2.1 the Serializable interface

Serializable is a Java interface and is located in the path of java.io. Serializable works by serializing Java objects to binary files and passing them. Serializable is very simple to use, just implement the interface directly.

2.2 the Parcelable interface

Parcelable is an interface specifically designed by Google for Android to address Serializable inefficiency. The principle of Parcelable is to completely decompose an object into transmissible data types (such as primitive data types) and pass them on.

There are two ways to use Parcelable
  1. Implement the Parcelable interface

    class Person() : Parcelable {
        var name = ""
        var age = 0
    
        constructor(parcel: Parcel) : this() { name = parcel.readString() ? :"" / / read the name
            age = parcel.readInt() / / read the age
        }
    
        override fun writeToParcel(parcel: Parcel, flags: Int) {
            parcel.writeString(name) / / write your name
            parcel.writeInt(age) / / write the age
        }
    
        / * * *@returnReturns the content description of the current object. Returns 1 if there is a file descriptor, 0 otherwise, and 0 in almost all cases. * /
        override fun describeContents(a): Int {
            return 0
        }
    
        companion object CREATOR : Parcelable.Creator<Person> {
            // Deserialize an object through a parcel object.
            // Call the constructor directly, or write the deserialization logic to this method instead.
            override fun createFromParcel(parcel: Parcel): Person {
                return Person(parcel)
            }
    
            // Create a Parcelable array of the specified size, each item initialized to NULL
            // The default is to call arrayOfNulls directly.
            override fun newArray(size: Int): Array<Person? > {return arrayOfNulls(size)
            }
        }
    
    }
    Copy the code

    Parcel internally wraps serializable data that can be transferred freely in Binder. Serialization is done by the writeToParcel method and ultimately through a series of write methods in the Parcel. The deserialization is done by CREATOR, through a series of Read methods on Parcel.

    Note: The order of read and write must be the same, otherwise an error will occur.

  2. Use @parcelize annotations

    Using the @parcelize annotation requires configuring two places in the Build. gradle file of the Module

    plugins {
        ...
        id 'kotlin-android-extensions' // The order must be after id' kotlin-android'
    }
    android {
        ...
        androidExtensions {
            experimental = true}}Copy the code

    It is very convenient to put the fields in the main constructor and serialize them directly using the @parcelize annotation.

    @Parcelize
    class Person(var name: String, var age: Int) : Parcelable
    Copy the code

2.3 Comparison of Serializable and Parcelable

  • Serializable uses I/O read and write storage on the hard disk, while Parcelable is directly read and write in memory

  • Serializable uses reflection and creates many temporary objects during serialization, which can easily trigger garbage collection. Serialization and deserialization processes require a lot of I/O operations and are inefficient overall. The marshalled &unmarshalled operation does not require reflection, and the data is stored in Native memory, which is much faster.

Serializable is usually used for data that needs to be saved to local disk, and Parcelable is used for other cases, which is more efficient.

3 IPC

IPC stands for inter-process Communication. Android is based on Linux, which, for security reasons, prevents different processes from manipulating each other’s data, a practice known as process isolation.

** In Linux, process isolation is implemented through the virtual memory mechanism. ** The virtual memory mechanism is to logically allocate linear contiguous memory space for each process, and each process only manages its own virtual memory space, thus logically implementing isolation between processes.

PS: The operating system maps the virtual memory space to the physical memory space. In this operation, the CPU uses the memory management unit (MMU) to replace the virtual address with a physical address.

Each process’s virtual memory space (process space) is divided into user space and kernel space. Processes can only access their own user space, and only the operating system can access the kernel space.

Operating system Knowledge supplement:

  • Of all the CPU instructions, some are dangerous and can cause a system crash if misused. For security reasons, the CPU hands off these instructions to trusted programs (operating system programs, or kernel programs in Linux) to execute. How do you do that? Cpus have different privilege levels, and the higher the privilege level, the more instructions they execute. The Intel x86 CPU provides four privilege levels of Ring0 to Ring3. The Ring0 privilege level is the highest. In Linux, only the Ring0 and Ring3 privilege levels are used. The **Ring0 privilege level corresponds to the kernel mode, and the Ring3 privilege level corresponds to the user mode. ** Kernel mode and user mode are the operating state of the CPU.
  • When running a user program, the CPU is in user mode, so the user program can perform very limited operations, but the kernel (operating system) provides interfaces that encapsulate certain functions, and the user program can use these interfaces (this process is called system calls) to instruct the operating system to do some operations for it. The essence of a system call is that the user program interrupts, jumps to the kernel program (the CPU is in kernel state), and returns to the user program (the CPU is in user state) after completing the specified operation.
  • Typically, the 32-bit Linux kernel (2^32, or 4G) divides the virtual address space of a process into 03G for user space, 34G is the kernel space. When the CPU is in user mode, only user space can be accessed. Only kernel space can be accessed when the CPU is in kernel mode. The kernel space holds the kernel programs, the data used, and so on.The kernel space of each process is mapped to the same physical address, so the kernel space is shared by all processes.

Since a process can only access its own user space, in traditional IPC, the sending process needs to copy data from its own user space to the kernel space through copy_FROm_user (system call), and then the receiving process needs to copy data from the kernel space to its own user space through copy_to_user, twice in total. It’s very inefficient. Android uses Binder as its IPC mechanism, which is copied once.

4 Binder

Binder, which translates as adhesive, is the bond between processes.

4.1 Why Binder was chosen as the IPC mechanism?

  • With binders, two data copies are required. With binders, only one data copy is required, second only to shared memory.
  • With few drawbacks, Binder is based on a C/S architecture that is very clear and simple to implement compared to memory sharing without having to deal with synchronization issues.
  • Good security. Android assigns its own UID to each installed application. Traditional IPC does not recognize each other’s IDS (Uids/Gids). With Binder IPC, these ids are automatically transmitted along the invocation process. The Server can easily know the identity of the Client, facilitating security checks.

4.2 Principle of Binder Primary copy

The basic principle of the Binder IPC communication is to map the user space of the receiving process to the kernel space through memory mapping (MMAP). With this mapping relationship, the receiving process can obtain the data of the kernel space through the user space address, so that the sending process only needs to copy the data to the kernel space.

One complete Binder IPC communication:

  1. Create a data receive cache in kernel space.
  2. Create a kernel cache in kernel space.
  3. Establish a mapping between the receiving process and the data receiving cache.
  4. Establish a mapping between the kernel cache and the data accept cache.
  5. The sending process calls copy_from_user() to copy the data into the kernel cache.

4.3 Binder mechanism

From the perspective of IPC, Binder is a cross-process communication mechanism (a model). Binder is based on THE C/S architecture. There are four main roles involved in this communication mechanism: Client, Server, ServiceManager and Binder driver.

role analogy location implementation role
Client Client (browser) The user space Developer implementation Obtain services provided by the Server from the ServiceManager
Server Server (Website) The user space Developer implementation Register the service with the ServiceManager
ServiceManager The DNS server The user space System implementation Manage registration and queries for services
Binder drive The router The kernel space System implementation Bridge between Client, Server, and ServiceManager.

Client, Server, and ServiceManager are user-space processes that use system calls (open, Mmap, and IOCtl) to access the device file /dev/binder and interact with binder drivers. Binder drivers provide interprocess communication (for low-level operations, such as creating a data accept cache) and are the bridge between Client, Server, and ServiceManager.

Client and Server are processes that need to communicate with each other. The communication flow is as follows:

  1. Registration Services: The Server process creates a Binder entity (with various interface methods provided by the Server) and gives the entity a character name. Through system calls, the Binder driver creates this entity in the kernel and establishes ServiceManager applications to this entity. The ServiceManager fills the lookup table with character names and entity references.
  2. Obtain services: Client processes request services based on character names. ServiceManager finds references to Binder entities based on characters. Binder drivers create proxy objects for this entity and return them to Client processes (or return them if they are looking for registered services).
  3. Using services: The Client process blocks after sending data to the Server process through the Binder driver. The Binder driver wakes up the Server process and runs the corresponding interface methods. The Binder driver wakes up the blocked Client process and returns the execution results to the Client process. (Sending data and returning execution results correspond to a complete underlying Binder IPC communication respectively)

As you may have noticed, registering the service and getting the service itself is cross-process communication with the ServiceManager. The process of communicating with The ServiceManager is to obtain Binder objects (already created in Binder drivers, with interface methods such as registration and query services) to use. All processes that need to communicate with the ServiceManager need to be referenced by 0. You can get the Binder object.

4.4 Code Analysis

The internal principles of AIDL are based on Binder to analyze the use of Binder.

AIDL is an interface definition language that allows you to define a complex Java interface with built-in functionality in a few sentences.

Take a look at the icallback.aidl file, which defines an interface that represents the functionality provided by the server.

// ICallBack.aidl
package com.zhy.aidltest.Server;

// Declare any non-default types here with import statements

interface ICallBack {
    void onSuccess(String result);

    void onFailed(String errorMsg);
}
Copy the code

The defined Java interface inherits from the IInterface interface and provides a Stub abstract class to the server (which encapsulates it and implements the functionality).

// Defines the functionality provided by Binder objects
public interface IInterface
{
    public IBinder asBinder(a);
}
Copy the code
package com.zhy.aidltest.Server;
// Declare any non-default types here with import statements

public interface ICallBack extends android.os.IInterface
{...// An abstract class that extends with Binder and implements the ICallBack interface
  //Binder is a Java class that inherits the IBinder interface, which defines cross-process communication capabilities. Binder is its implementation.
  public static abstract class Stub extends android.os.Binder implements com.zhy.aidltest.Server.ICallBack
  {
    private static final java.lang.String DESCRIPTOR = "com.zhy.aidltest.Server.ICallBack";// The character associated with the ICallBack interface
    public Stub(a)
    {
      this.attachInterface(this, DESCRIPTOR);// Bind an interface object to the IBinder object and associate the character (used to replace the IBinder object with an interface object at that time)
    }
    public static com.zhy.aidltest.Server.ICallBack asInterface(android.os.IBinder obj)
    {IBinder [[Binder or BinderProxy]]; // IBinder [[Binder or BinderProxy]]; // IBinder [[Binder or BinderProxy]];
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);// BinderProxy returns NULL, Binder returns interface
      if(((iin! =null)&&(iin instanceof com.zhy.aidltest.Server.ICallBack))) {
        return ((com.zhy.aidltest.Server.ICallBack)iin);// is a Binder object
      }
      return new com.zhy.aidltest.Server.ICallBack.Stub.Proxy(obj);// Return a proxy interface object if no interface object is available.
    }
    @Override public android.os.IBinder asBinder(a)
    {
      return this;
    }
    //Binder class onTransact is the method that will eventually be called, overriding this method to handle it
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)// Call its own interface method according to the code
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_onSuccess:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          this.onSuccess(_arg0);// Call the onSuccess method (which needs to be implemented after the server inherits the Stub)
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_onFailed:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          this.onFailed(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags); }}}private static class Proxy implements com.zhy.aidltest.Server.ICallBack
    {// Client-side encapsulation eliminates the tedious process of packaging data. Makes the client invocation look exactly the same as the server.
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder(a)
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor(a)
      {
        return DESCRIPTOR;
      }
      @Override public void onSuccess(java.lang.String result) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(result);
          boolean _status = mRemote.transact(Stub.TRANSACTION_onSuccess, _data, _reply, 0);//BinderProxy transact calls the local method transactNative(code, data, reply, flags)
          if(! _status && getDefaultImpl() ! =null) {
            getDefaultImpl().onSuccess(result);
            return;
          }
          _reply.readException();
        }
        finally{ _reply.recycle(); _data.recycle(); }}@Override public void onFailed(java.lang.String errorMsg) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(errorMsg);
          boolean _status = mRemote.transact(Stub.TRANSACTION_onFailed, _data, _reply, 0);
          if(! _status && getDefaultImpl() ! =null) {
            getDefaultImpl().onFailed(errorMsg);
            return;
          }
          _reply.readException();
        }
        finally{ _reply.recycle(); _data.recycle(); }}public static com.zhy.aidltest.Server.ICallBack sDefaultImpl;
    }
    static final int TRANSACTION_onSuccess = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_onFailed = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.zhy.aidltest.Server.ICallBack impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if(Stub.Proxy.sDefaultImpl ! =null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if(impl ! =null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.zhy.aidltest.Server.ICallBack getDefaultImpl(a) {
      returnStub.Proxy.sDefaultImpl; }}public void onSuccess(java.lang.String result) throws android.os.RemoteException;
  public void onFailed(java.lang.String errorMsg) throws android.os.RemoteException;
}
Copy the code

5 use AIDL

Reference: www.cnblogs.com/sqchen/p/10…

(Here is the final implementation with the added callback, see the resources link for a step-by-step guide.)

Create aiDL files for the classes you want to use.

// Student.aidl
package com.zhy.aidltest.Server;

parcelable Student;
Copy the code

The system automatically generates an AIDL folder in the main file and creates a corresponding directory in this folder.

Create the Student class in the same Java path. Do not use the @Parcelize annotation or you will get an error

package com.zhy.aidltest.Server

import android.os.Parcel
import android.os.Parcelable

class Student(var id:Int=0.var name:String=""):Parcelable {
    constructor(parcel: Parcel) : this( parcel.readInt(), parcel.readString()? :""
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(id)
        parcel.writeString(name)
    }

    override fun describeContents(a): Int {
        return 0
    }

    fun readFromParcel(parcel: Parcel): Student{// This is required when Student can be used as out
        returnStudent(parcel.readInt(),parcel.readString()? :"")}companion object CREATOR : Parcelable.Creator<Student> {
        override fun createFromParcel(parcel: Parcel): Student {
            return Student(parcel)
        }

        override fun newArray(size: Int): Array<Student? > {return arrayOfNulls(size)
        }
    }
}
Copy the code

Create istudentService. aidl and define an interface that defines the functions provided by the server. Rebuild the project after creation ** (every time you create and modify the definition interface file is rebuilt) **

// IStudentService.aidl
package com.zhy.aidltest.Server;

// Declare any non-default types here with import statements
import com.zhy.aidltest.Server.Student;
import com.zhy.aidltest.Server.ICallBack;

interface IStudentService {
    List<Student> getStudentList();

    void addStudent(inout Student student);

    void register(ICallBack callback);

    void unregister(ICallBack callback);
}
Copy the code
Directional tag meaning
in Data can only flow from the client to the server. The server will receive the complete data of the client object. The client object will not change because the server modifies the parameter.
out Data can only flow from the server to the client. The server will receive the client object, which is not empty but has empty fields in it, but the client’s parameter object will change synchronously after the server makes any changes to the object.
inout The server will receive complete information about the object from the client, and the client will synchronize any changes the server makes to the object.

Create StudentService on the server

package com.zhy.aidltest.Server

class StudentService : Service() { / / the server
    private val mStuList = CopyOnWriteArrayList<Student>()
    private val sCallbackList = RemoteCallbackList<ICallBack>()

    private var mBinder = object: IStudentService.Stub() { // The server provides a concrete implementation of the interface
        override fun register(callback: ICallBack?). {
            callback.apply { sCallbackList.register(this)}}override fun addStudent(student: Student?).{ student? .apply { mStuList.add(this)
                dispatchResult(true."add succeeded")
                return
            }
            dispatchResult(false."add failed")}override fun unregister(callback: ICallBack?). {
            callback.apply { sCallbackList.unregister(callback) }
        }

        override fun getStudentList(a): MutableList<Student> {
            return mStuList
        }
    }

    override fun onBind(intent: Intent): IBinder {
       return mBinder
    }

    /** * Distribution result *@param result
     * @param msg
     */
    private fun dispatchResult(result: Boolean, msg: String) { // Execute all registered callbacks
        val length = sCallbackList.beginBroadcast()
        for (i in 0 until length) {
            val callback: ICallBack = sCallbackList.getBroadcastItem(i)
            try {
                if (result) {
                    callback.onSuccess(msg)
                } else {
                    callback.onFailed(msg)
                }
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
        sCallbackList.finishBroadcast() //beginBroadcast() must be called}}Copy the code

If you see a callback, the client also provides an interface for the server to call back and forth (two-way communication, in which case the client becomes the server), icallback.aidl

// ICallBack.aidl
package com.zhy.aidltest.Server;

// Declare any non-default types here with import statements

interface ICallBack {
    void onSuccess(String result);

    void onFailed(String errorMsg);
}
Copy the code

The client is returned with Binder drivers and calls the implementation methods in StudentService

class MainActivity : AppCompatActivity(), View.OnClickListener {/ / the client
    companion object{
        private const val TAG = "MainActivity"
    }

    private lateinit var mViewBinding: ActivityMainBinding
    private lateinit var mStudentService:IStudentService
    private val mDeathRecipient = object :DeathRecipient {//Binder death callback
        override fun binderDied(a) {
            // Remove the death agent
            mStudentService.asBinder().unlinkToDeath(this.0);
            // Rebind the service
            //bindStudentService()}}private val mServiceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?).{}override fun onServiceConnected(name: ComponentName? , service:IBinder?). {
            mStudentService = IStudentService.Stub.asInterface(service) // The Binder object returns the IStudentService object
            try {
                mStudentService.apply {
                    asBinder().linkToDeath(mDeathRecipient, 0) // Set the death proxy
                    register(mCallback) // Call the register method provided by the Server}}catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
    }
    private val mCallback = object:ICallBack.Stub(){ // Provide a concrete implementation of the Server callback
        override fun onFailed(errorMsg: String?). {
            Log.d(TAG, "onFailed: $errorMsg")}override fun onSuccess(result: String?). {
            Log.d(TAG, "onSuccess: $result")}}override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        mViewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mViewBinding.root)
        mViewBinding.apply {
            btnBind.setOnClickListener(this@MainActivity)
            btnAddStudent.setOnClickListener(this@MainActivity)
            btnGetStudentList.setOnClickListener(this@MainActivity)
            btnUnBind.setOnClickListener(this@MainActivity)
        }
        startService(Intent(this,StudentService::class.java))
    }

    override fun onClick(v: View?). {
        mViewBinding.apply {
            when(v? .id){ btnBind.id->{ bindStudentService() } btnAddStudent.id->{ mStudentService.addStudent(Student(1."zhy"))
                }
                btnGetStudentList.id->{
                    mStudentService.studentList
                }
                btnUnBind.id->{
                    unbindService(mServiceConnection)
                }
            }
        }
    }

    override fun onDestroy(a) {
        unbindService(mServiceConnection)
        stopService(Intent(this,StudentService::class.java))
        super.onDestroy()
    }

    private fun bindStudentService(a){
    	bindService(Intent(this@MainActivity,StudentService::class.java),mServiceConnection,
            Context.BIND_AUTO_CREATE)
    }
}
Copy the code

      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_bind"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind"/>

    <Button
        android:id="@+id/btn_addStudent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add Student"/>

    <Button
        android:id="@+id/btn_getStudentList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Get Student List"/>

    <Button
        android:id="@+id/btn_unBind"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Unbind"/>

</LinearLayout>
Copy the code

Note for AIDL use:

  • The interface name is the same as the AIDL file name

  • Interfaces and methods are not preceded by access modifiers, nor are they final or static.

  • Aidl supports Java primitive types (int, Long, Boolean, etc.) and (String, List, Map, CharSequence, etc.) by default. Import statements are not required for these types. Element types in lists and maps must be supported by Aidl. If you use custom types as arguments or return values, you must implement the Parcelable interface.

  • Custom types and other interface types generated by AIDL should be explicitly imported in the AIDL description file, even if the class is in the same package as the defined package.

  • All non-Java primitive type parameters must be marked in, out, and inout in aiDL files to indicate whether they are input parameters, output parameters, or input/output parameters.

  • The default tag for Java primitive types is IN, not any other tag.

6 Messenger

Messenger can pass Message objects between different processes. By putting the data we need to pass in the Message, we can easily pass data between processes. Messenger is a lightweight IPC solution that encapsulates AIDL, with the underlying implementation being AIDL.

Use see: blog.csdn.net/qq951127336…