Android Service Basic Usage, AIDL, Binder Connection Pool Details

This article describes the communication between Service and Activity. It contains the following:

  • 1. Basic Service Usage
  • 2. Cross-process communication between Service and Activity via AIDL
  • 3. Binder Connection Pool
  • 4. Use Messenger for cross-process communication
  • 5. Sample source address for this article

The article is a bit long, mainly divided into the above five parts, because there is no way to set up links in the short book, so if you want to skip the basic and look at the back part directly, roll it over!However, the article as a whole from simple to complex, the previous basis of understanding the later knowledge will help, so it is recommended to read in order.

 

1. Basic Service Usage

The basic usage is two-way communication between Activity and Service under the same process, describing the overall implementation process first, then directly up the code:

  1. Create a new class MyService that inherits from Service and register it in AndroidManifest.xml
  2. Activity uses bindService to start MyService, that is, to bind MyService
    (Once the binding is implemented here, continue with the next steps if Activity communicates with Service)
  3. Create a new class MyBinder that inherits from Binder
  4. Instantiate a MyBinder object mBinder in MyService and return the mBinder object in the onBind callback method
  5. The second step of the bindService method requires a parameter of type ServiceConnection, in which an IBinder object is retrieved, which is the mBinder object returned by the fourth step onBinder (that is, the mBinder object inside the Service is retrieved in the Activity).
  6. Once you get the mBinder in the Activity, you can call the methods in the binder (that is, you can send messages to the Service), and you can just define the implementation in the MyBinder class.Register a custom callback through this binder if you need a service to send a message to the activity.

The code is as follows, and key sections provide comments for the above steps:

Activity

public class MainActivity extends Activity {

    private static final String TAG = "zjy";
    public MyBinder mBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //Get the binder object from the Service in Activity as mentioned in Step 5
            mBinder = (MyBinder)iBinder;
            //Step 6 Register Custom Callbacks
            mBinder.setOnTestListener(new MyBinder.OnTestListener() {
                @Override
                public void onTest(String str) {
                    Log.d(TAG, "receive msg from service: "+str);
                }
            });
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent,mConnection,BIND_AUTO_CREATE);

        findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Click the button to invoke the method inside mBinder and send a message to Service
                mBinder.testMethod("hi, service.");
            }
        });
    }
}

Service

public class MyService extends Service {
    private static final String TAG = "zjy";
    // Step 4, Instantiate a MyBinder object
    private MyBinder mBinder = new MyBinder(this);

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;//Step 4, return this mBinder object
    }

    public void serviceMethod(String str){
        Log.d(TAG, "receive msg from activity: " + str);
    }
}

Binder

public class MyBinder extends Binder {
    private static final String TAG = "zjy";
    private MyService mService;
    private OnTestListener mListener;

    public MyBinder(MyService service) {
        this.mService = service;
    }

    public void testMethod(String str) {
        // Activity uses Binder to call Service's method to pass messages to Service
        mService.serviceMethod(str);
        // And callback mListener.onTest to tell Activity that a message has been received
        mListener.onTest("hi, activity.");
    }

    // MyBinder provides a way to register callbacks
    public void setOnTestListener(OnTestListener listener) {
        this.mListener = listener;
    }

    //Customize a callback interface
    public interface OnTestListener {
        void onTest(String str);
    }
}

The code is simple. First Activity binds to the Service to get a MyBinder instance and register OnTestListener callback listener inside MyBinder. Then when you click the button, call the TesMethod (String) method inside MyBinder to send out the message. MyBinder holds an instance of MyService. Calling the method inside MyService in the TesMethod (String) will transfer the message of Activity.Service is given, and the mListener.onTest(String) callback inside the testMethod(String) sends the Service message to Activity.

MyBinder is also commonly written as an internal class in MyService, where it is written as a common class for the convenience of the later explanation.

This enables two-way communication between Activity and Service in the same process. Run the code and log after clicking the button as follows:

( 2360): receive msg from activity: hi, service.
( 2360): receive msg from service: hi, activity.

As you can see from the code, the communication between Activity and Service is through a binder object.

 

2. Cross-process communication between Service and Activity via AIDL

Activity and Service communicate under the same process. The conclusion is that the communication between Activity and Service is through a binder object. In fact, this statement is also valid in multiple processes. Then, verify it in multiple processes.Now you may have thought that AIDL actually uses Binder for cross-process communication.Let's first look at how the official documentation describes AIDL:

On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL.

The general meaning is that Android processes cannot communicate directly with each other, and the object needs to be translated into a native language that the computer can recognize, then arranged to cross the process boundary.But doing these things is cumbersome, so Android provides AIDL to do it.(In other words, a lot of complex code needs to be written to cross-process, so Android provides AIDL, which is generated by the compiler according to AIDL rules by writing simple AIDL files)

Overall, using AIDL to communicate across processes, the overall process, like a single process, is communicated through a Binder, except that a single process Binder is manually implemented by inheriting the Binder class, while a cross-process Binder is automatically generated by AIDL, which is an awesome Binder.

Now that you have a preliminary understanding of AIDL, start to practice it. Here, you can use AndroidStudio to implement AIDL. Refer to the article: How to use AIDL in Android Studio

First modify the above code, specify the Service to another process using the android:process=":remote" property in AndroidManifest.xml, and then run the code directly will error because the custom MyBinder does not have cross-process capability and cannot get a Binder when binding the Service.Then use AIDL to generate a cross-process Binder and replace MyBinder with this cross-process Binder.

1. Create a new AIDL file

Similar to a new class file: right-click -> new -> AIDL -> AIDL File, enter the file name and click Finish to finish (the example code here is IMyAidlInterface)

Whichever directory you right-click above will generate an Aidl directory in the src/main directory, where the newly created IMyAidlInterface.aidl file is located. Note the difference from eclipse.
Opening this file discovery is an interface (which may generate a basicTypes method by default, which is an example method, regardless of which you can delete), and then defining your own method inside (see Plus for yourself if you need another method)

The code is as follows:

interface IMyAidlInterface {
    void testMethod(String str);
}

2. Compile Project

Build -> Make Project
When completed, a java file named IMyAidlInterface.java with the same name as the AIDL file will be generated in the app/build/generated/source/debug/directory

This class file is used to provide interprocess communication, where the required Binder class is located.
Simply put, AIDL is a tool for generating code with the ultimate goal of getting the class IMyAidlInterface.java.Much like the database framework GreenDao, this is a simple way to generate a lot of complex and useful code that you can use directly.Of course, complex code can also be written manually, such as trying to copy IMyAidlInterface.java or simply copy IMyAidlInterface.java to a Java directory and delete the Aidl file for interprocess communication.

3. Analyzing IMyAidlInterface.java

AndroidStudio switches to Project Project mode. Find the IMyAidlInterface.java file in the app/build/generated/source/debug/path and open it.The generated code is in a messy format, so for easy viewing, you can use the shortcut keys to format the code.

IMyAidlInterface.java contains an interface with an internal abstract class and a method.This is the method we defined in the aidl file.The internal abstract class is our Binder class, named Stub.It's not difficult to imagine what's going on here: (1) a new Stub instance in Service and return this Stub (or Binder) instance in onBinder.(2) Get this Binder when binding a Service inside the Activity (strong to Stub type).(3) Call the testMethod method inside this Binder to communicate between Activity and Service.This is the big idea, but there are many differences in details.

Other code in IMyAidlInterface.java (mainly some methods) is temporarily ignored and will be said when used.All you need to know here is that this java file has a Stub class and a custom method.

4. Modify Service Code

Now that the AIDL-related code is complete, the next step is to use the code that AIDL generated for us.Modifying MyService first simply replaces MyBinder with Stub, but Stub is an abstract class that we need to implement by ourselves. Create a new class that inherits from Stub with a random class name. Name AidlBinder here. Then implement the testMethod() method following the same MyBinder process:

public class AidlBinder extends IMyAidlInterface.Stub {

    private MyService mService;

    public AidlBinder(MyService service) {
        this.mService = service;
    }

    @Override
    public void testMethod(String str) throws RemoteException {
        mService.serviceMethod(str);
    }
}

There is no callback-related code in the above code, because callbacks across processes are different from those under the same process, as you will see later.In addition, for illustration purposes, the AidlBinder class is specifically defined as an implementation class for Stubs, and it is common to implement Stubs directly within Service using anonymous internal classes.The code inside AidlBinder resembles that under the same process, which is not explained.
Then MyService can use AidlBinder with the following code:

public class MyService extends Service {

    private static final String TAG = "zjy";

    private AidlBinder mBinder = new AidlBinder(this);

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public void serviceMethod(String str) {
        Log.d(TAG, "receive msg from activity: " + str);
    }
}

As with the same process, no explanation is given.
To summarize: So far, there are only two steps to the actual operation except theoretical analysis: (1) Create a new AIDL file to generate some code.(2) Implement the abstract class Stub, which is AidlBinder.(3) Service replaces MyBinder with the Stub implementation class AidlBinder.

5. Modify Activity Code

To start with, the idea of communicating with a process is to declare a Binder of type IMyAidlInterface.Stub, then initialize the Binder:mBinder = (IMyAidlInterface.Stub)service when binding a Service, and then use this Binder to communicate with the Service.
Actually this is not possible, if you do this, mBinder = (IMyAidlInterface.Stub)service binds the service; this line of code reports an exception, java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.zjy.servicedemo.IMyAidlInterface$Stub
This means that the passed Binder is a BinderProxy type and cannot be converted to a Stub type (because Stub is not a subclass of BinderProxy but a subclass of Binder).

I don't know about BinderProxy. I know from some data that it is related to C++ layer. There is no corresponding java class in the source code. After compiling the source code, the BinderProxy.class class class class is generated, which implements the IBinder interface like Binder.

Source location\frameworks\base\core\jni\android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact (JNIEnv* env, jobject obj, jint code, jobject Obj, jobject replyj, jint Obflags)
Come from: Android FrameWork - Binder mechanism in detail (1)

Java-tier activities communicate with remote services through BinderProxy.
From the Java layer, myActivity can establish a connection to myBinder through bindService().However, this link is achieved through the mechanism of the C++ layer.
Come from: Know Android's BinderProxy and Binder Categories (Should be written by Taiwanese, traditional characters are not random ^ ^!)

How does Activit y use the Binder passed in?The AIDL generated code provides a static method, asInterface(IBinder), that converts IBinder to an Aidl interface, so you can do this: IMyAidlInterface mService = IMyAidlInterface.Stub.asInterface(service);

The book Art Exploration describes the asInterface method, which converts a Binder object on the server side into an object of the type of AIDL interface required by the client side. This transformation distinguishes processes. If the client and the server are in the same process, the method returns the Stub object on the server side itself, otherwise it returns the Stub.proxy object encapsulated by the system.

So in the same process, Activity uses Binder from Service in three ways:
IMyAidlInterface mService = IMyAidlInterface.Stub.asInterface(service);
IMyAidlInterface.Stub mBinder = (IMyAidlInterface.Stub)service;
IMyAidlInterface.Stub mService = (IMyAidlInterface.Stub)IMyAidlInterface.Stub.asInterface(service);
Cross-process can only use the first method, and the final Activity code is as follows:

public class MainActivity extends Activity {

    private static final String TAG = "zjy";
    public IMyAidlInterface mService;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = IMyAidlInterface.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);

        findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    mService.testMethod("hi, service.");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

 

6. Implementation of Cross-Process Callback Interface

At this point, the cross-process Activity is implemented to send messages to the Service, and then the Service is implemented to respond to the Activity after it receives the message.Large directions are implemented using callbacks just like single processes, but details are different.
First, the callback interface needs to be defined as an Aidl interface rather than a normal interface, so create a new IMyCallbackListener.aidl file that defines an onRespond method as the callback function:

interface IMyCallbackListener {
    void onRespond(String str);
}

Extend IMyAidlInterface.aidl, which defines a method to register callback listening (equivalent to the setOnTestListener method in the basics)

import com.zjy.servicedemo.IMyCallbackListener;

interface IMyAidlInterface {
    void testMethod(String msg);
    void registerListener(IMyCallbackListener listener);
}

Note the syntax rules of aidl, non-system classes import even under the same package, such as IMyCallbackListener for the code above, while system class String s do not need import

The compilation will prompt AidlBinder to implement the parent's Abstract method, registerListener(), and modify the AidlBinder as follows, following the code associated with callbacks in MyBinder under the process:

public class AidlBinder extends IMyAidlInterface.Stub {

    private MyService mService;
    private IMyCallbackListener mListener;

    public AidlBinder(MyService service) {
        this.mService = service;
    }

    @Override
    public void testMethod(String str) throws RemoteException {
        mService.serviceMethod(str);
        mListener.onRespond("hi, activity");
    }

    @Override
    public void registerListener(IMyCallbackListener listener) throws RemoteException {
        mListener = listener;
    }
}

With a foundation for communicating with processes, it's easy to read this code.Then a callback is registered in the Activity where appropriate to receive messages from the server:

try{
    mService.registerListener(new IMyCallbackListener.Stub() {
        @Override
        public void onRespond(String str) throws RemoteException {
            Log.d(TAG, "receive message from service: "+str);
        }
    });
} catch (RemoteException e){
    e.printStackTrace();
}

This completes the two-way communication between Activity and Service across processes, running the code, and clicking the button log as follows:

(11597): receive msg from activity: hi, service.
(11579): receive message from service: hi, activity

As far as the code in this application is concerned, the execution process of the code is the same as that of a single process, but the details of some implementations are different.In addition, you can use the adb shell ps | grep "Package name for this application" command to view process information and see the following two processes:
com.zjy.servicetest
com.zjy.servicetest:remote
com.zjy.servicetest:remote is the process in which the Service resides.If multiple processes are used in different applications, there is no essential difference between using AIDL communication and applying multiple processes.

7. Unregister callbacks across processes

Service responds to Activity messages through the registration callback interface. Next, we introduce the deregistration. Unlike the deregistration of the same process, multiprocesses need to be done with the RemoteCallbackList, so the method of registering callbacks needs to be changed to use the RemoteCallbackList to register callbacks. The AidlBinder code is modified as follows:

public class AidlBinder extends IMyAidlInterface.Stub {

    private MyService mService;
    private RemoteCallbackList<IMyCallbackListener> mListenerList = new RemoteCallbackList<>();

    public AidlBinder(MyService service) {
        this.mService = service;
    }

    @Override
    public void testMethod(String str) throws RemoteException {
        mService.serviceMethod(str);

        // Call all registered listeners in the mListenerList
        int count = mListenerList.beginBroadcast();
        for (int i = 0; i < count; i++) {
            mListenerList.getBroadcastItem(i).onRespond("hi, activity");
        }
        mListenerList.finishBroadcast();
    }

    @Override
    public void registerListener(IMyCallbackListener listener) throws RemoteException {
        mListenerList.register(listener);
    }

    @Override
    public void unregisterListener(IMyCallbackListener listener) throws RemoteException {
        mListenerList.unregister(listener);
    }
}

The unregisterListener method in the code above is added like a registerListener and implements the function of unregisterListener in it.RemoteCallbackList is easy to use, just look at the code.

Finally, add some code to the Activity to test the decommissioning, such as a button to call the remote decommissioning method when clicked. Here is the final complete code in the Activity:

public class MainActivity extends Activity {

    private static final String TAG = "zjy";
    public IMyAidlInterface mService;

    private IMyCallbackListener.Stub mListener = new IMyCallbackListener.Stub() {
        @Override
        public void onRespond(String str) throws RemoteException {
            Log.d(TAG, "receive message from service: "+str);
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = IMyAidlInterface.Stub.asInterface(iBinder);
            try{
                //Registering Callbacks
                mService.registerListener(mListener);
            } catch (RemoteException e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);

        findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    mService.testMethod("hi, service.");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        findViewById(R.id.test2_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    //Unregister Callback
                    mService.unregisterListener(mListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

The final function of the whole code is to bind the Service and register a callback when the activity is started, click the send message button, and the activity sends the message "hi, service" to the Service. After the Service receives the message, the log prints "receive message from activity: hi, service" and restores the message "hi,Activity ", activity logprints" receive message from service: hi, activity ".Then click the unregisterListener button to resolve the registration callback listener, and then click send message to print only the log "receive message from activity: hi, service" to explain that the registration was successful.

Here's the basic usage of AIDL, and you can refer to the article for passing custom serialized objects and multiprocess communication between different applications How to use AIDL in Android Studio

Here's a summary: Customizing MyBinder under the same process makes it easy to communicate between Activity and Service, and you need to use AIDL to generate a Binder across processes.As for the code inside Activity and Service, the process routine is basically the same, but only a few simple details are different.

 

3. Binder Connection Pool

From the above description, it is not difficult to find that a Service corresponds to a Binder, the actual project can not write all the logic together, different business logic is to be categorized, there will inevitably be multiple Binders, there will always be no Binder corresponding to a Service, then you can use the Binder connection pool.

First, introduce what a Binder connection pool is in a simple way: a Binder connection pool is a code structure similar to a design pattern.It can be viewed directly as a design pattern.
This pattern then addresses the problem of managing multiple AIDLs (or Binder s) with one Service instead of one AIDL corresponding to one Service.

To reinterpret, enhance understanding: We know that design patterns are not necessary for writing code, implementing functionality, and so on, but they have many advantages.The same is true for Binder connection pools, which can also be used to implement a Service to manage multiple AIDL s.But it can make the code structure elegant and clear, make code maintenance extension easier, and so on.

Now that you have a brief understanding of connection pooling, let's start with an example.Before you start, take a look at the overall catalog structure of the final project and see the following figure:

 
 
Project Structure Diagram

Here's an example of an animal, as shown in the picture.The next step is to implement the code in the diagram.

1. Prepare the corresponding classes first: create a new Activity and a Service, and create new AIDL files.

(1) Activity and Service do nothing first, they are not related to the Binder connection pool to be implemented, they are only used to use the Binder connection pool.
Create new AIDL files with the following names:

  • IAnimal.aidl
  • IBird.aidl
  • IFish.aidl
  • IMonkey.aidl

Their code is as follows:

interface IAnimal {
    IBinder queryAnimal(int animalCode);
}
interface IBird {
    void fly();
}
interface IFish {
    void swim();
}
interface IMonkey {
    void climbTree();
}

The above code is not difficult to understand. Each animal has its own method, and the IAnimal interface manages the other three animals. The method inside receives a parameter that represents the animal species. The subsequent implementation returns a corresponding animal Binder based on the animal species.

(2) Compile the project and generate the corresponding Binder for the AIDL file. The Binder generated by AIDL is an abstract class. Next, define the implementation class for each abstract Binder, named AnimalBinder.java, BirdBinder.java, FishBinder.java, MonkeyBinder.java.The code is as follows:

public class AnimalBinder extends IAnimal.Stub{

    public static final int ANIMAL_CODE_BIRD = 1;
    public static final int ANIMAL_CODE_FISH = 2;
    public static final int ANIMAL_CODE_MONKEY = 3;

    @Override
    public IBinder queryAnimal(int animalCode) throws RemoteException {
        IBinder binder = null;
        switch (animalCode) {
            case ANIMAL_CODE_BIRD:
                binder = new BirdBinder();
                break;
            case ANIMAL_CODE_FISH:
                binder = new FishBinder();
                break;
            case ANIMAL_CODE_MONKEY:
                binder = new MonkeyBinder();
                break;
            default:
                break;
        }
        return binder;
    }
}
public class BirdBinder extends IBird.Stub{
    private static final String TAG = "zjy";
    @Override
    public void fly() throws RemoteException {
        Log.d(TAG, "I'm bird, I can fly.");
    }
}
public class FishBinder extends IFish.Stub{
    private static final String TAG = "zjy";
    @Override
    public void swim() throws RemoteException {
        Log.d(TAG, "I'm fish, I can swim.");
    }
}
public class MonkeyBinder extends IMonkey.Stub {
    private static final String TAG = "zjy";
    @Override
    public void climbTree() throws RemoteException {
        Log.d(TAG, "I'm monkey, I can climb the tree.");
    }
}

The code is simple and does not explain.It's important to note that AnimalBinder is managed and distinguished from the three animal Binders. A better way to write AnimalBinder is to use it as an internal class in the class BinderPool representing the connection pool (the BinderPool class will be discussed later), which is more structurally sound, and the final code of the example is written as an internal class.

2. Write connection pool code

A connection pool is a generic Java class with arbitrary class names: BinderPool.java
The code inside the class is divided into several simple parts:

  • Implement singleton mode for BinderPool.java
  • Bind a Service (the Context required to bind a Service is passed in using its Activity)
  • Provide a queryAnimal method that provides users with different binder s based on parameters
  • And, as mentioned earlier, the AnimalBinder as an internal class of BinderPool

The full code for BinderPool.java is as follows:

public class BinderPool {

    private static final String TAG = "zjy";

    public static final int NO_ANIMAL = 0;
    public static final int ANIMAL_CODE_BIRD = 1;
    public static final int ANIMAL_CODE_FISH = 2;
    public static final int ANIMAL_CODE_MONKEY = 3;

    private Context mContext;
    @SuppressWarnings("all")
    private static BinderPool sInstance;
    private CountDownLatch mCountDownLatch;
    private IAnimal mAnimalPool;

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

    public static BinderPool getInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(mContext, MyService.class);
        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mAnimalPool = IAnimal.Stub.asInterface(service);
            mCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: ");
        }
    };

    public IBinder queryAnimal(int animalCode) {
        IBinder binder = null;
        try {
            if (mAnimalPool != null) {
                binder = mAnimalPool.queryAnimal(animalCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    public static class AnimalBinder extends IAnimal.Stub {

        @Override
        public IBinder queryAnimal(int animalCode) throws RemoteException {
            IBinder binder = null;
            switch (animalCode) {
                case ANIMAL_CODE_BIRD:
                    binder = new BirdBinder();
                    break;
                case ANIMAL_CODE_FISH:
                    binder = new FishBinder();
                    break;
                case ANIMAL_CODE_MONKEY:
                    binder = new MonkeyBinder();
                    break;
                default:
                    break;
            }
            return binder;
        }
    }
}

It's easy to see the code in terms of the sections, but there are some details to note:

  • Regarding the memory leak risk of a singleton, the code converts the context member into the context of the Application
  • AIDL supports concurrent access. The code uses synchronized and CountDownLatch to synchronize threads when binding a Service, so getting a BinderPool singleton object cannot be in the main thread.

3. Use Binder Connection Pool

The code for the Binder connection pool is complete here, mainly a BinderPool class, which is then used in Service and Activation.

Code for Service:

public class MyService extends Service {
    private BinderPool.AnimalBinder mBinder = new BinderPool.AnimalBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

Note: Don't forget to use the android:process=":remote" attribute in AndroidManifest.xml to assign a Service to another process.

Activity code:

public class MainActivity extends Activity {
    private static final String TAG = "zjy";
    private BinderPool mBinderPool;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        mBinderPool = BinderPool.getInstance(MainActivity.this);
                        IBinder birdBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_BIRD);
                        IBinder fishBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_FISH);
                        IBinder monkeyBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_MONKEY);

                        IBird bird = IBird.Stub.asInterface(birdBinder);
                        IFish fish = IFish.Stub.asInterface(fishBinder);
                        IMonkey monkey = IMonkey.Stub.asInterface(monkeyBinder);

                        try {
                            bird.fly();
                            fish.swim();
                            monkey.climbTree();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }
}

As you can see from the Service and Active code, BinderPool is easy to use.From the user's point of view, a Binder connection pool encapsulates what should be done in an Activity into a BinderPool class, such as binding a service, methods that clients call remote servers through a Binder, and so on.

4. Testing

As you can see from the test code, a Service is bound when you click a button in the Activity by initializing a BinderPool singleton object (you can also initialize a BinderPool object elsewhere, arbitrarily, this is just a test code, but not in the main thread), so there is only one process in which the Activity is running when the program clicks the buttonThe Service process will not be started.

(1) Run the code and execute the command adb shell ps | grep "com.zjy.servicedemo" to see a process

u0_a97    2228  523   1012056 57324 00000000 f774c915 S com.zjy.servicedemo

(2) Click the button to see the print log

D/zjy  ( 2264): I'm bird, I can fly.
D/zjy  ( 2264): I'm fish, I can swim.
D/zjy  ( 2264): I'm monkey, I can climb the tree.

(3) Execute the command adb shell ps | grep "com.zjy.servicedemo" again, and you can see two processes that start the service after clicking the button and the service is running in another process.

u0_a97    2228  523   1012056 57324 00000000 f774c915 S com.zjy.servicedemo
u0_a97    2264  523   995804 42180 00000000 f774c915 S com.zjy.servicedemo:remote

This concludes the BinderPool, which is essentially a BinderPool.java class

 

4. Use Messenger for cross-process communication

Messenger is also used for interprocess communication, which differs from AIDL by looking at an official document:

When you need to perform IPC, using a Messenger for your interface is simpler than implementing it with AIDL, because Messenger queues all calls to the service, whereas, a pure AIDL interface sends simultaneous requests to the service, which must then handle multi-threading.
For most applications, the service doesn't need to perform multi-threading, so using a Messenger allows the service to handle one call at a time. If it's important that your service be multi-threaded, then you should use AIDL to define your interface.

This means that Messenger is easier to use than AIDL, but if multiple clients send messages to the service at the same time, Messenger can only process one message at a time, while AIDL can be processed in multiple threads.

Messenger is also essentially implemented using AIDL, so you can browse through Messenger's source code (just over 100 lines) and see something about AIDL.

Then a brief introduction to the use of Messenger, starting with a list of processes:

  1. Service implements a Handler for receiving messages
  2. Use this Handler to create a Messenger object
  3. Use this Messenger object to create a Binder object and return it in the onBind method
  4. Use the Binder passed in to create a Messenger object when binding a Service within an Activity
  5. Use this Messenger object inside Activity to send messages to Service
  6. Handler inside the Service receives a message and processes it
  7. Activity implements a Handler to receive a message from a Service reply
  8. Step 5 sends the message with a Messenger object created by step 7 Handler
  9. Step 6 When Service receives the message, remove the Essenger it carries
  10. Message Activity with Messenger taken out in step 9
  11. Handler in Step 7 of Activity handles messages from Service replies

The entire process and the process of single-process communication are very similar, all around Binder.After step 7 above, all Service reply messages are relevant.The complete code is given directly below, and the comments correspond to the above process.

Service Code

public class MyService extends Service {
    private static final String TAG = "zjy";

    //1.Service implements a Handler to receive messages
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //6. Handler inside Service receives messages and processes them
            if (msg.what==1) {
                Bundle bundle = msg.getData();
                Log.d(TAG, "receive message from activity: "+bundle.getString("string"));

                //9. Remove the Messenger object from the message
                Messenger replyMessenger = msg.replyTo;

                Message  replyMsg= new Message();
                replyMsg.what = 2;
                Bundle b = new Bundle();
                b.putString("string", "hi, activity");
                replyMsg.setData(b);
                try {
                    //10. Use Messenger to send messages to Activity
                    replyMessenger.send(replyMsg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    };

    // 2. Use this Handler to create a Messenger object
    private Messenger mMessenger = new Messenger(mHandler);

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //3. Use this Messenger object to create a Binder object and return it in the onBind method
        return mMessenger.getBinder();
    }
}

Activity Code

public class MainActivity extends Activity {

    private static final String TAG = "zjy";

    private Messenger mMessenger;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //4. Use the Binder passed in to create a Messenger object when binding a Service within the activity
            mMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent,mConnection,BIND_AUTO_CREATE);

        findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = new Message();
                msg.what = 1;

                Bundle bundle = new Bundle();
                bundle.putString("string", "hi, service");
                msg.setData(bundle);
                //8. Carry a Messenger object with you when sending a message
                msg.replyTo = new Messenger(mGetReplyMsg);

                try {
                    //5. Use this Messenger object in Activity to send messages to Service
                    mMessenger.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    //7.Activity implements a Handler that receives a message from a Service reply
    private Handler mGetReplyMsg = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //11. Processing messages from Service replies
            if (msg.what==2) {
                Bundle bundle = msg.getData();
                Log.d(TAG, "receive message from service: "+bundle.getString("string"));
            }
        }
    };
}

Issues needing attention

(1) The message sent by Messenger is a Message object. Instead of using the obj field of the Message to assemble the Message message, use Bundle to assemble the data.Here's a passage from "Exploring the Art of Android Development":

Messenger is used to transfer messages. Only what, arg1, arg2, Bundle, and replyTo can be used in messages.Another field in the Message, obj, is useful in the same process, but when communicating between processes, obj did not support cross-processes until Android 2.2. Even after 2.2, only objects that implement the Parcelable interface provided by the system can be transferred through it.This means that custom arcelable objects cannot be transferred through the obj field.

(2) In the code of the receiver, when canceling a Message, the Bundle is taken out of the Message first, and then the data is taken directly from the Bundle.If the data is a custom Parcelable object and cannot be fetched directly from a Bundle, you need to set a ClassLoader for the Bundle before fetching the data."Before retrieving data" means not just before retrieving custom Parcelable objects, but before all data, including basic data types and system-provided Parcelable objects.The sample code is as follows:

Bundle bundle = msg.getData();
bundle.setClassLoader(getClassLoader());//Set ClassLoader
bundle.getxxx(key);//Get data

The getData method of the Message class is commented as follows:

/** 
 * Obtains a Bundle of arbitrary data associated with this
 * event, lazily creating it if necessary. Set this value by calling
 * {@link #setData(Bundle)}.  Note that when transferring data across
 * processes via {@link Messenger}, you will need to set your ClassLoader
 * on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
 * Bundle.setClassLoader()} so that it can instantiate your objects when
 * you retrieve them.
 * @see #peekData()
 * @see #setData(Bundle)
 */
public Bundle getData() {
    if (data == null) {
        data = new Bundle();
    }
    
    return data;
}

Tags: Mobile Java Android xml shell

Posted on Fri, 08 May 2020 12:43:37 -0400 by rem