Detailed explanation of Android aidl dual app process communication

1. Scene

        There are many cross-process communications between services that use many independent processes and other processes in the company's projects. I haven't actually done cross-process communication AIDL for a long time before, and I have consulted some materials and articles and read some demos to introduce the mental journey of the review.

tool

Android studio

Xiaomi phone Android11 ​​(api 30)

Emulator phone Android8 (api 26)

Why do you need to prepare two mobile phones, because the use of aidl in the higher version of Android is a bit tricky, and compatibility processing is required, and the reason will be mentioned later.

2. Create the aidl server AIDLService (separate process)

Create the aidl server first, because under normal circumstances an APP is a process

Create a new service. At present, this service is still an empty class, because we need to wait for the creation of aidl and the new class generated by aidl compilation before we can further improve this service.

package com.kang.aidlservice

import android.app.Service
import android.content.Intent
import android.os.IBinder

class KtvService:Service() {
    override fun onBind(p0: Intent?): IBinder? {//这里参数变量显示p0是因为没下载对应sdk源码
        return null
    }
}

Register in the manifest file

<service
    android:name=".KtvService"
    android:enabled="true"
    android:exported="true" >
            
    <intent-filter>
         <!--添加了一个唯一的action,供客户端隐式启动service-->
         <action android:name="com.kang.aidlservice.KtvService"/>
    </intent-filter>

</service>

3. Create an aidl file on the server side

1. Right click on main--->New--->Directory--->select aidl

2. Create a new aidl file:

Right-click the newly created aidl directory---"New--->AIDL--->AIDL File--->enter the name of the newly created aidl file 

Default aidl file content 

Our modified aidl file content

 After that, just make project or Rebuild project and wait for compilation.

 After the compilation is successful, an IKtvController.java file will be generated in the build under the app directory.

Let's look at the content generated in this file. We need to use the class that inherits Binder, because Binder is the principle of IPC, and the communication between processes is the Binder mechanism used.

 3. Use the content generated above to improve the KtvService.kt class just created

package com.kang.aidlservice

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class KtvService : Service() {

    companion object {
        private const val TAG = "binkang"
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate: ")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i(TAG, "onStartCommand: ")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(p0: Intent?): IBinder? {
        return KtvBinder()
    }


    //内部类KtvBinder,实现了aidl文件生成的Stub类
    inner class KtvBinder : IKtvController.Stub() {
        override fun setPause(pause: String?) {
            Log.i(TAG, "setPause: $pause")
        }

        override fun setPlay(play: String?) {
            Log.i(TAG, "setPlay: $play")
        }
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "onUnbind: ")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "onDestroy: ")
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        Log.i(TAG, "onRebind: ")
    }
}

 Fourth, create a client

1. I created a new project here as another APP client. Of course, you can also create a new APP module in the project on the server side, which is the same.

 2. Copy the entire directory of the aidl file under the main of the server just now, and put it in the main directory of the client

Note that the package name here is also consistent with that of the server.

 The same is true afterwards, make project or Rebuild project will also generate an IKtvController.java file in the app--->build directory.

 3. The next step is to write the code of the mainActivity of the client to bind the KtvService of the server.

package com.kang.aidlclient

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Button
import com.kang.aidlservice.IKtvController

class MainActivity : AppCompatActivity() {

    var iKtvController: IKtvController? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bindKtvService()

        findViewById<Button>(R.id.pause).setOnClickListener {
            iKtvController?.setPause("sorry~, pause")
        }

        findViewById<Button>(R.id.play).setOnClickListener {
            iKtvController?.setPlay("hi~, play")
        }
    }

    private fun bindKtvService() {
        //通过action隐式去绑定service
        val intent = Intent()
        intent.action = "com.kang.aidlservice.KtvService"//服务端的清单文件中的action
        intent.setPackage("com.kang.aidlservice")
        bindService(intent, object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName?, service: IBinder?){
                //这个回调就代表客户端和服务端建立了链接,之后便可以通信
                Log.i("binkang", "onServiceConnected: ")
                //获取Stub(Binder)实例,此处不能使用new的方式创建
                iKtvController = IKtvController.Stub.asInterface(service)
            }

            override fun onServiceDisconnected(name: ComponentName?) {
                //这个断开链接的回调,主动调用service的unBindService()不会回调到这个方法
                //service所在的宿主由于异常终止或者其他原因终止,导致service与访问者间断才会
                //回调这个方法
                Log.i("binkang", "onServiceDisconnected: ")
            }

        }, Context.BIND_AUTO_CREATE)
    }
}

4. Run, first run and start the server AIDLService, and then run and start the client AIDLClient :

Here you can see that there are two processes.

 5. Look at the results of the operation:

At this point, there is no log output on the Android 11 phone. The mobile phone running on Android8 (API26) has log output. Compatible with higher versions (see the seventh point of this Android 12 compatibility issue, only to find out that it is a version problem, and it took a long time to toss: share the pitfalls encountered when adapting to Android 12 )

 

 So we need to add the queries attribute to the client 's manifest file .

package is the server process package name.

 Now I can see the log on my Android11 ​​Xiaomi mobile phone: here I missed a log of the onBind() method

I added this log print later, it will be printed after onCreate: com.kang.aidlservice I/binkang: onBind: 

 Click the two buttons pause and play on the client.

 At this point, the client sends data to the server .

5. The server sends data communication to the client

1. Let's modify the contents of the aidl directory on the server side 

Add an aidl file IControllerStatusListener.aidl (function of callback monitoring), the content is as follows

 Modify the IKtvController.aidl just now, the content is as follows: One more method is provided to set the monitoring callback

 After the modification, the next step is to make project. At this time, KtvService will report an error: because we have added a new interface method

    inner class KtvBinder : IKtvController.Stub() {
        override fun setPause(pause: String?) {
            Log.i(TAG, "setPause: $pause")
        }

        override fun setPlay(play: String?) {
            Log.i(TAG, "setPlay: $play")
        }

        override fun setOnControllerStatusListener(i: IControllerStatusListener?) {
            Log.i(TAG, "setOnControllerStatusListener: ")

        }
    }

 After modifying the server, copy the modified aidl directory to overwrite the client's aidl directory (you can delete it first, then copy it in, so as not to overwrite errors), and make project is fine.

2. Modify the work of the client, set the monitor to the code as follows, note that you need to cut the thread by yourself to operate the UI after returning

Modified code:

package com.kang.aidlclient

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.kang.aidlservice.IControllerStatusListener
import com.kang.aidlservice.IKtvController


class MainActivity : AppCompatActivity() {

    var iKtvController: IKtvController? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bindKtvService()

        findViewById<Button>(R.id.pause).setOnClickListener {
            iKtvController?.setPause("sorry~, pause")
        }

        findViewById<Button>(R.id.play).setOnClickListener {
            iKtvController?.setPlay("hi~, play")
        }
    }

    private fun bindKtvService() {
        //通过action隐式去绑定service
        val intent = Intent()
        intent.action = "com.kang.aidlservice.KtvService"//服务端的清单文件中的action
        intent.setPackage("com.kang.aidlservice")
        bindService(intent, object : ServiceConnection {
            override fun onServiceConnected(name: ComponentName?, service: IBinder?){

                Log.i("binkang", "onServiceConnected: ")
                //获取Stub(Binder)实例,此处不能使用new的方式创建
                iKtvController = IKtvController.Stub.asInterface(service)

                iKtvController?.setOnControllerStatusListener(object :
                    IControllerStatusListener.Stub() {

                    override fun onPauseSucess() {
                        Log.i("binkang", "onPauseSucess: ")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPauseSuccess",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPauseFailed(errorCode: Int) {
                        Log.i("binkang", "onPauseFailed: $errorCode")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPauseFailed $errorCode",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPlaySuccess() {
                        Log.i("binkang", "onPlaySuccess: ")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPlaySuccess",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }

                    override fun onPlayFailed(errorCode: Int) {
                        Log.i("binkang", "onPlayFailed: $errorCode")
                        Handler(Looper.getMainLooper()).post {
                            Toast.makeText(
                                this@MainActivity,
                                "onPlayFailed $errorCode",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }
                })
            }

            override fun onServiceDisconnected(name: ComponentName?) {
                Log.i("binkang", "onServiceDisconnected: ")
            }

        }, Context.BIND_AUTO_CREATE)
    }
}

3. The server code, let's call back pause and play. Each simulates a time-consuming operation of 1 second, the code is as follows

package com.kang.aidlservice

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.RemoteException
import android.util.Log

class KtvService : Service() {

    var listener: IControllerStatusListener? = null

    companion object {
        private const val TAG = "binkang"
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate: ")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i(TAG, "onStartCommand: ")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(p0: Intent?): IBinder? {
        Log.i(TAG, "onBind: ")
        return KtvBinder()
    }

    inner class KtvBinder : IKtvController.Stub() {
        override fun setPause(pause: String?) {
            Log.i(TAG, "setPause: $pause")

            //模拟播放耗时 1000 毫秒
            if (listener != null) {
                Thread {
                    try {
                        Thread.sleep(1000)
                        if (System.currentTimeMillis() % 2 == 0L) {
                            listener!!.onPauseSucess()
                        } else {
                            listener!!.onPauseFailed(1002)
                        }
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    } catch (remoteException: RemoteException) {
                        remoteException.printStackTrace()
                    }
                }.start()
            }
        }

        override fun setPlay(play: String?) {
            Log.i(TAG, "setPlay: $play")

            //模拟播放耗时 1000 毫秒
            if (listener != null) {
                Thread {
                    try {
                        Thread.sleep(1000)
                        if (System.currentTimeMillis() % 2 == 0L) {
                            listener!!.onPlaySuccess()
                        } else {
                            listener!!.onPlayFailed(1002)
                        }
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    } catch (remoteException: RemoteException) {
                        remoteException.printStackTrace()
                    }
                }.start()
            }
        }

        override fun setOnControllerStatusListener(i: IControllerStatusListener?) {
            Log.i(TAG, "setOnControllerStatusListener: ")
            listener = i
        }
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "onUnbind: ")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "onDestroy: ")
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        Log.i(TAG, "onRebind: ")
    }
}

4. After modification, run to see the effect:

 5. Click the button pause and play to see the log:

 

 In addition, the client also has toast, so I won’t take a screenshot here. At this point, the two-way communication between processes is completed.

6. Summary

 Android uses aidl to realize two-way communication between processes, which is through the Binder mechanism. In the above implementation process, the only stuck place is the high version compatibility problem.

Another point to note: in the aidl method, if you want to operate the UI, you need to switch the thread to the main thread by yourself, otherwise an error will be reported:

Can't toast on a thread that has not called Looper.prepare(), I did run into that too.

Guess you like

Origin blog.csdn.net/sunbinkang/article/details/124605887