“Zefengengchen” wechat technology account is now open, in order to get the first-hand technical articles push, welcome to search attention!

This is the first in a series of instant messaging articles. Before we get into IM development technology, let’s talk about the role of the client in a complete chat system. To do this, we must first clarify the role of the client.

Today’s mainstream IM applications almost all use the server transfer mode to carry out message transmission, in order to better support offline, group and other services. In this mode, all clients need to connect to the server, and the server forwards or broadcasts messages sent by different clients according to the user id carried in the messages.

Therefore, as a terminal device for sending and receiving messages, one of the important responsibilities of the client is to maintain the connection with the server. The stability of the connection directly determines the real-time and reliability of sending and receiving messages. As we mentioned in the previous article, mobile devices are resource constrained, which poses great challenges to the stability of connection, which can be embodied in the following two aspects:

  • To keep the multitasking environment running properly, Android sets a hard upper limit on the heap size of each app. The exact heap size for each device depends on the total available RAM on the device. If an app reaches the heap limit and tries to allocate more content, it may trigger an OOM.
  • When a user switches to another application, the system keeps the process of the original application in the cache. If the user returns to the application later, the system reuses the process to speed up application switching. However, when system resources (such as memory) are insufficient, the system considers terminating the processes with the highest memory usage and lower priority to free up RAM.

Although ART and Dalvik virtual machines routinely perform garbage collection tasks, if the application has memory leaks and only one main process, the memory usage will increase gradually as the application is used for longer, which will increase the probability of OOM initiation and the risk of the cache process being terminated by the system.

Therefore, in order to ensure the stability of the connection, can consider to be responsible for the connection of information service in a separate process, even if the Lord after separation process exits, appear crash or memory consumption is too high, the service can still run normally, can even at the right time to arouse the main process by way of radio, etc.

However, dividing applications into processes often means writing additional process communication code, especially for highly interactive scenarios such as message services. Because each process is running in relatively independent memory space, it is unable to communicate directly. To this end, Android provides AIDL(Android Interface Definition Language) for interprocess communication, its essence is to realize the serialization, transmission, receive and deserialization of objects, You get an actionable object before you make a normal method call.

Next, let’s step by step implement communication across processes.

Step1 create the service

Since connection holding is an operation that needs to be performed in the background for a long time and usually does not provide an interface, the component that fits this feature is Service, so we chose Service as the interprocess communication (IPC) component with remote processes. When subclasses of Service are created, we must implement the onBind callback method, here we temporarily return an empty implementation.

class MessageAccessService : Service() { override fun onBind(intent: Intent?) : IBinder? { return null } }Copy the code

In addition, another advantage of using Service is that we can upgrade it to foreground Service at appropriate time. Foreground Service is a Service that users actively realize, and the process has a high priority. Therefore, the system will not consider to terminate it when the memory is insufficient.

The only downside to using a front desk service is that you have to provide an unremovable notification in the drawer, which is very unfriendly to the user experience, but we can coordinate this by customizing the notification style, as we’ll see in a future article.

Step2 specify the process

By default, all components of the same application run in the same process. To control the process to which a component belongs, set the Android :process property in the manifest file:

<manifest ... > <application ... > <service android:name=".service.MessageAccessService" android:exported="true" android:process=":remote" /> </application> </manifest>Copy the code

In addition, to enable components of other processes to call or interact with the service, you need to set the Android: Exported property to true.

Step3 create an. Aidl file

Let’s go back to the onBind callback method, which requires the return of the IBinder object that the client can use to communicate with the service using the interface defined by the object. IBinder is the basic interface for remote objects. This interface describes an abstract protocol for interacting with remote objects. It is not recommended to implement this interface directly. The usual practice is to use an. Aidl file to describe the desired interface and make it subclass with the appropriate Binder.

So, how should this crucial.aiDL file be created and what interfaces should be defined?

Creating an. Aidl File is simple. Android Studio itself provides a way to create an AIDL File: right-click your project -> New -> AIDL -> AIdl File

As mentioned earlier, the client is the terminal device that sends and receives messages, and the access service provides the gateway for the client to send and receive messages. The messages sent by the client are sent to the server via the access service, and the client entrusts the access service to receive the messages and notifies itself when the server has pushed the messages.

This makes it clear that there are three interfaces to define:

  • Send a message
  • Registering a message receiver
  • Unregister the message receiver

MessageCarrier.aidl

package com.xxx.imsdk.comp.remote;
import com.xxx.imsdk.comp.remote.bean.Envelope;
import com.xxx.imsdk.comp.remote.listener.MessageReceiver;

interface MessageCarrier {
    void sendMessage(in Envelope envelope);
    void registerReceiveListener(MessageReceiver messageReceiver);
    void unregisterReceiveListener(MessageReceiver messageReceiver);
}
Copy the code

Here are the meanings of the parameters carried in the above interface:

Envelope ->

To explain this parameter, we need to introduce the Envelope. Java class, which is an entity class used as data transfer in multi-process communication. In addition to the basic data types, String and CharSequence, AIDL supports objects that implement the Parcelable interface, as well as lists and maps with these elements.

Envelope.java

** * Envelope class for multi-process communication * <p> * For objects passed in AIDL, create a file with the same name but with the suffix. AIDL in the same path as the class file and declare the class using the parcelable keyword in the file; * But in real business, the classes of objects that need to be passed tend to be scattered in different modules. So build a wrapper class to contain the objects that really need to be passed (you must also implement the Parcelable interface) */ @parcelize Data class Envelope(Val messageVo: messageVo? = null, val noticeVo: NoticeVo? = null) : Parcelable { }Copy the code

In addition, for objects passed in AIDL, you need to create a file with the same name but with the suffix. AIDL in the same package path as the class files mentioned above, and declare the class using the parcelable keyword in the file.

Envelope.aidl

package com.xxx.imsdk.comp.remote.bean;

parcelable Envelope;
Copy the code

The paths of the two files are compared as follows:

So why the Envelope class and not the MessageVO class (message view object) directly? This is due to the fact that in real business, objects that need to be passed tend to belong to classes that are scattered across different modules (MessageVO belongs to another module and needs to be referenced by other modules), so by building a wrapper class that contains the object that really needs to be passed (which must also implement the Parcelable interface), This is what the class is named Envelope means.

MessageReceiver ->

Cross-process message receiving callback interface used to deliver server-side messages received by the message access service to the client. The callback interface used here is a little different. The callback interface passed in AIDL cannot be a normal interface, only an AIDL interface, so we need to create another. AIDL file:

MessageReceiver.aidl

package com.xxx.imsdk.comp.remote.listener;
import com.xxx.imsdk.comp.remote.bean.Envelope;

interface MessageReceiver {
    void onMessageReceived(in Envelope envelope);
}
Copy the code

The package directory structure is shown below:

Step4 return the IBinder interface

When building an application, the Android SDK generates an IBinder interface file based on an.aidl file and saves it to the project’s gen/ directory. The name of the generated file must be the same as that of the. Aidl file, except that it uses the. Java extension (for example, messagecarrier. aidl generates messagecarrier.java). This interface has an internal abstract class called Stub that extends the Binder class and implements the methods in the AIDL interface.

Private val MessageCarrier: IBinder = object: private val MessageCarrier: IBinder = object: MessageCarrier.Stub() { override fun sendMessage(envelope: Envelope?) { } override fun registerReceiveListener(messageReceiver: MessageReceiver?) { remoteCallbackList.register(messageReceiver) } override fun unregisterReceiveListener(messageReceiver: MessageReceiver?) { remoteCallbackList.unregister(messageReceiver) } } override fun onBind(intent: Intent?) : IBinder? { return messageCarrier }Copy the code
Step5 bind services

A component (such as an Activity) can be bound to a service by calling the bindService method, which must provide an implementation of the ServiceConnection to monitor the connection to the service. When the connection between the component and the service is established, the onServiceConnected() method on the ServiceConnection is called back, containing the IBinder object returned in the previous step, which can then be used to communicate with the bound service.

/** * ## bind bindService and startService, * @param Context */ Synchronized fun setupService(Context: context? = null) { if (! ::appContext.isInitialized) { appContext = context!! . ApplicationContext} val intent = intent (appContext MessageAccessService: : class. Java) / / documenting the results of binding service, and to avoid an error occurred when unbundling services if (! isBound) { isBound = appContext.bindService(intent, serviceConnection, Private val serviceConnection = object; private val serviceConnection = object; private val serviceConnection = object; ServiceConnection { override fun onServiceConnected(name: ComponentName? , service: IBinder?) {/ / MessageCarrier. Aidl corresponding operation interface MessageCarrier = MessageCarrier. Stub. AsInterface (service)... } override fun onServiceDisconnected(name: ComponentName?) {}}Copy the code

Multiple components can be bound to the same service at the same time, but when the last component is unbound from the service, the system destroys the service. To enable the service to run indefinitely, you can create a service with both started and bound states by calling startService() and bindService() simultaneously. This way, even if all components unbind the service, the system does not destroy the service until it is explicitly stopped by a call to stopSelf() or stopService().

@param Action */ Private fun startService(Intent: Intent = Intent(appContext, MessageAccessService::class.java), action: String? = null) {// Android8.0 does not allow background service to start directly by startService, will raise IllegalStateException if (build.version.sdk_int >= Build.VERSION_CODES.O && ! ProcessUtil.isForeground(appContext) ) { if (! TextUtils.isEmpty(action)) intent.action = action intent.putExtra(KeepForegroundService.EXTRA_ABANDON_FOREGROUND, False) appContext. StartForegroundService (intent)} else {appContext. StartService (intent)}} / stop message access * * * * / fun StopService () {// clear the cached WebSocket server address immediately to prevent login using the old WebSocket server address (with invalid session), To receive the user logoff notification GlobalScope. Launch {DataStoreUtil. WriteString (appContext, RemoteDataStoreKey WEB_SOCKET_SERVER_URL, "") } unbindService() appContext.stopService(Intent(appContext, MessageAccessService: : class. Java))} / unbundling message access * / * * * @ Synchronized fun unbindService () {if (! IsBound) return / / must determine whether the service is unbound, otherwise the Java. Lang. IllegalArgumentException: Service not registered // Unregister the message listening interface if (messageCarrier? .asBinder()? .isBinderAlive == true) { messageCarrier? .unregisterReceiveListener(messageReceiver) messageCarrier = null } appContext.unbindService(serviceConnection) isBound = false }Copy the code

conclusion

By doing this, we were able to split the application into master and remote processes. The main process is mainly responsible for user interaction and interface presentation, while the remote process is mainly responsible for message sending and receiving, connection holding, etc. Because the remote process only keeps the minimum business logic processing and the memory growth is relatively stable, the probability of the remote process being terminated when the system memory is tight is greatly reduced. Even if the main process exits due to unexpected circumstances, the remote process can still keep running, thus ensuring the stability of the connection.

“Zefengengchen” wechat technology account is now open, in order to get the first-hand technical articles push, welcome to search attention!

reference

WebSocket detailed explanation (a) : a preliminary understanding of WebSocket technology

www.52im.net/thread-331-…

Overview of memory management

Developer. The android. Google. Cn/topic/perfo…

Process and application lifecycle

Developer. The android. Google. Cn/guide/compo…

An overview of the service

Developer. The android. Google. Cn/guide/compo…

Overview of the binding service

Developer. The android. Google. Cn/guide/compo…

Android Interface Definition Language (AIDL)

Developer. The android. Google. Cn/guide/compo…