There is a lot of cross-process communication with new companies that use a lot of standalone process services in their projects. I haven’t actually done AIDL for a long time before. I looked up some materials and articles and watched some demos to introduce the mental process of reviewing.

To simulate a KTV broadcast control system (client) to control the playback of songs on the large screen, pause action

  • KtvAIDLClient

  • KtvAIDLService

  • Client: The client initiates the binding

  • Server: The bound service is the server

Service creation

1 Create the service first


public class KtvService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null; }}Copy the code

manifest.xml

<service
    android:name=".KtvService"
    android:exported="true" />
Copy the code

Here are a few key label definitions:

Tag name meaning
android:enabled Can the service be instantiated? Default is true
android:exported Can components of other applications interact with this service
android:process Example Set an independent process, such as remote. The process name is remote

After knowing these keywords, we need to pay attention to the following points: If we need to communicate between two processes, we have two projects: 1 KtvAIDLClient and 2 KtvAIDLService. In fact, the two projects are already running as separate processes, so there is no need to declare process. If we only have one project such as client and server under the same APP, we need to name the service process separately, so that it is a separate process under the same APP besides the main process.

2 Let’s start by defining AIDL files in the same directory as Java

Create the AIDL file, following the rules we start with I

It’s as simple as a pause and play

KtvAIDLService/app/src/main/aidl/com/thunder/ktvaidlservice/IKtvController.aidl

interface IKtvController {

    void setPause(String pause);

    void setPlay(String play);

}
Copy the code

The AIDL file is defined. Let’s go to the build option in the toolbar and select Make Project or Rebuild. After the build is finished, a new product will be generated in the build directory.

We notice that the production has been generated and is an interface file at the end of a.java file. Let’s try to reference it in our code. Take a look at the whole file

public class KtvService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("zxm"."onBind");
        return new KtvBinder();
    }
    
    private static class KtvBinder extends IKtvController.Stub {

        @Override
        public void setPause(String pause) throws RemoteException {
            Log.e("zxm", pause);
        }

        @Override
        public void setPlay(String play) throws RemoteException {
            Log.e("zxm", play); }}@Override
    public boolean onUnbind(Intent intent) {
        Log.e("zxm"."onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy(a) {
        Log.e("zxm"."onDestroy");
        super.onDestroy();
    }

    @Override
    public void onCreate(a) {
        Log.e("zxm"."onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("zxm"."onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onRebind(Intent intent) {
        Log.e("zxm"."onRebind");
        super.onRebind(intent); }}Copy the code

Client Creation

The code of the service is finished, and the client AIDL is written. Create the AIDL folder in the Java equivalent directory as before and copy the AIDL file you just served to the client, noting the package name change.

Next we bind the service:

private void bindKtvService(a) {
    Intent intent = new Intent();
    intent.setClassName("com.thunder.ktvaidlservice"."com.thunder.ktvaidlservice.KtvService");
    getApplicationContext().bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("zxm"."onServiceConnected: ");
            
            
            
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e("zxm"."onServiceDisconnected: ");

        }
    }, Context.BIND_AUTO_CREATE);
}
Copy the code

Then we get an instance of the stub, remember that new instances are not needed here

.Stub.asInterface(service);
Copy the code

Methods. Then we added two buttons on the interface as control, the complete code of client is as follows:

public class MainActivity extends AppCompatActivity { private IKtvController ktvController; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { ktvController.setPause("sorry ~ pause"); } catch (RemoteException e) { e.printStackTrace(); }}}); findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try {  ktvController.setPause("hi ~ play"); } catch (RemoteException e) { e.printStackTrace(); }}}); bindKtvService(); } private void bindKtvService() { Intent intent = new Intent(); intent.setClassName("com.thunder.ktvaidlservice", "com.thunder.ktvaidlservice.KtvService"); getApplicationContext().bindService(intent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("zxm", "onServiceConnected: "); try { ktvController = IKtvController.Stub.asInterface(service); } catch (Exception e) { } } @Override public void onServiceDisconnected(ComponentName name) { Log.e("zxm", "onServiceDisconnected: "); } }, Context.BIND_AUTO_CREATE); }}Copy the code

At this point, both ends of the code is finished, the service and client code to run again, you can see pid 3919/3842 two processes

Startup log: The onCreate and onBind methods are called on the server side when the service is bound. The client successfully obtains the BIND instance of the KTV controller. We are trying to handle the data transfer of the next boast process with a controller instance, one for pause and one for play:

After some minor setbacks, the client data was successfully exported to the server via aiDL’s cross-process transfer, as shown in the log above.

Two-way communication

Above, we completed cross-process communication between two independent processes, but I don’t know if you noticed that there is only one one-way communication between the client and the server. However, there are many application scenarios of bidirectional communication in cross-process communication. Let’s continue with the example above, where the server returns a success or failure status to the client after the client clicks pause and play.

1 modify the AIDL file, we added a state of the AIDL file let me see the changes in the AIDL section

interface IConrtollerStatusListener {
    void onPauseSuccess();
    void onPauseFailed(int errorCode);
    void onPlaySuccess();
    void onPlayFailed(int errorCode);
}
Copy the code
import com.thunder.ktvaidlservice.IConrtollerStatusListener;

interface IKtvController {

    void setOnControllerStatusListener(in IConrtollerStatusListener i);

    void setPause(String pause);

    void setPlay(String play);
}
Copy the code

Re-add them to the server and client projects, and then make their respective projects.

2 the work of the client is very simple, set the listening on the code as follows, note that after the operation of the UI needs to cut thread:

private void bindKtvService() {
    Intent intent = new Intent();
    intent.setClassName("com.thunder.ktvaidlservice", "com.thunder.ktvaidlservice.KtvService");
    getApplicationContext().bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("zxm", "onServiceConnected: ");
            try {
                ktvController = IKtvController.Stub.asInterface(service);
                ktvController.setOnControllerStatusListener(new IConrtollerStatusListener.Stub() {
                    @Override
                    public void onPauseSuccess() {
                        new Handler(Looper.getMainLooper()).post(() -> {
                            Toast.makeText(MainActivity.this, "onPauseSuccess", Toast.LENGTH_SHORT).show();
                        });

                    }

                    @Override
                    public void onPauseFailed(int errorCode) {
                        new Handler(Looper.getMainLooper()).post(() -> {
                            Toast.makeText(MainActivity.this, "onPauseFailed" + errorCode, Toast.LENGTH_SHORT).show();
                        });
                    }

                    @Override
                    public void onPlaySuccess() {
                        Toast.makeText(MainActivity.this, "onPlaySuccess", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onPlayFailed(int errorCode) throws RemoteException {
                        Toast.makeText(MainActivity.this, "onPlayFailed" + errorCode, Toast.LENGTH_SHORT).show();
                    }
                });
            } catch (Exception e) {

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e("zxm", "onServiceDisconnected: ");
        }
    }, Context.BIND_AUTO_CREATE);
}
Copy the code

3 server code, we back and forth to pause and play. Each simulated a one-second time-consuming operation with the following code:

private static class KtvBinder extends IKtvController.Stub { private IConrtollerStatusListener listener; @Override public void setOnControllerStatusListener(IConrtollerStatusListener i) throws RemoteException { listener = i; } @Override public void setPause(String pause) throws RemoteException { Log.e("zxm", pause); If (listener! = null) { new Thread(() -> { try { Thread.sleep(1000); if (System.currentTimeMillis() % 2 == 0) { listener.onPauseSuccess(); } else { listener.onPauseFailed(1002); } } catch (InterruptedException e) { e.printStackTrace(); } catch (RemoteException remoteException) { remoteException.printStackTrace(); } }).start(); } } @Override public void setPlay(String play) throws RemoteException { Log.e("zxm", play); If (listener! = null) { new Thread(() -> { try { Thread.sleep(1000); if (System.currentTimeMillis() % 2 == 0) { listener.onPlaySuccess(); } else { listener.onPlayFailed(1001); } } catch (InterruptedException e) { e.printStackTrace(); } catch (RemoteException remoteException) { remoteException.printStackTrace(); } }).start(); }}}Copy the code

Now that we have a two-way communication between two processes, let’s look at the UI effect:

Both failures and successes are returned from another process, including an error code output.

in out inout

Notice that the AIDL file code we modified above has in, which is different from what we usually see in Java code.

Why don’t we change AIDL’s in to out? See if you can still communicate from the server back to the client.

Out indicates an error

Inout literally supports dual streams, but compilations also report errors

Let’s take a look at the explanation of this article

Maimai. Cn/article/det…

Matters needing attention

1 Click Setup error

The 2021-10-16 14:33:07. 061, 3919-3919 / com. Thunder. Ktvaidlclient E/AndroidRuntime: FATAL EXCEPTION: the main Process: com.thunder.ktvaidlclient, PID: 3919 java.lang.IllegalStateException: Could not execute method for android:onClick at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:414) at android.view.View.performClick(View.java:6256) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View$PerformClick.run(View.java:24697) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6541) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Method.invoke(Native Method) at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409) at android.view.View.performClick(View.java:6256) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View$PerformClick.run(View.java:24697) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6541) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) Caused by: java.lang.SecurityException: Binder invocation to an incorrect interface at android.os.Parcel.readException(Parcel.java:1942) at android.os.Parcel.readException(Parcel.java:1888) at com.thunder.ktvaidlclient.IKtvController$Stub$Proxy.setPause(IKtvController.java:110)Copy the code

2 Ensure that the client and server aiDL package names are the same; otherwise, the following error is reported

Process: com.thunder.ktvaidlclient, PID: 4087 java.lang.SecurityException: Binder invocation to an incorrect interface at android.os.Parcel.readException(Parcel.java:1942) at android.os.Parcel.readException(Parcel.java:1888) at com.thunder.ktvaidlclient.IKtvController$Stub$Proxy.setPause(IKtvController.java:110) at com.thunder.ktvaidlclient.MainActivity$2.onClick(MainActivity.java:37) at android.view.View.performClick(View.java:6256)  at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View$PerformClick.run(View.java:24697) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6541) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)Copy the code

3 in aiDL methods, if you want to manipulate the UI, you need to handle thread switching yourself. Otherwise, an error is reported as follows:

    java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:390)
        at android.widget.Toast.<init>(Toast.java:114)
        at android.widget.Toast.makeText(Toast.java:277)
        at android.widget.Toast.makeText(Toast.java:267)
        at com.thunder.ktvaidlclient.MainActivity$3$1.onPauseFailed(MainActivity.java:67)
        at com.thunder.ktvaidlservice.IConrtollerStatusListener$Stub.onTransact(IConrtollerStatusListener.java:77)
        at android.os.Binder.execTransact(Binder.java:674)
Copy the code

Other

There are many different types of services, such as the cross-process BIND service, as well as the normal service and the foreground service, all of which have different uses and scenarios. See the Google Docs Service Overview

The resources

Developer. The android. Google. Cn/guide/topic… www.jianshu.com/p/d1fac6cce… Github.com/FelixLee052… www.bilibili.com/video/BV1oD… www.bilibili.com/video/BV185…