Summary of Binder
Binder is ubiquitous in Android, using Bindder to call media services, sensors, startActivity, startService, etc. The entire Android system can be seen as a C/S architecture based on Binder, Binder is the glue that binds systems together. Binder is so important that as Android developers we need to understand it.
Begin your Binder learning journey.
Binder is used to communicate between processes. Android is based on Linux. Linux already has some solutions for communication between processes, such as pipes, shared memory, sockets, etc. Then we need to know their strengths and weaknesses
The pipe
For example, if there is A pipe between A and B, A copies data into the pipe, and B reads data from the pipe, the process requires the pipe to be set up and the data to be copied twice
And the pipe is half duplex meaning that the data can only flow in one direction, so if you want to communicate with each other, you have to build two pipes
So pipes cost performance
The Shared memory
Multiple processes share an area of memory, which is efficient without copying, but is difficult to manage and secure because it is visible to all processes
Socket
Socket is a universal interface, mainly used for communication between the network, although it can achieve inter-process communication, is to kill chicken with a knife, low transmission efficiency, high overhead, also need two copies.
Binder
Only one copy of data is required, second only to shared memory in performance. In terms of stability, Binder is based on C/S architecture mode, which leaves whatever the client needs to be done to the server, with a clear structure and clear responsibilities.
In terms of security, traditional inter-process communication does not do this. There are so many apps in an Android system, and each APP runs in a separate process. We don’t want other processes to be able to access our APP information.
Android assigns its own UID, PID, to each newly installed application, which is an important proof of identity during communication.
Binder has four important roles:
- Server
- Client
- ServiceManager
- Binder drive
As the picture above shows
- The server uses Binder drivers to register our services with the ServiceManager
- The client uses the Bindr driver to query services registered in the ServiceManager
- The ServiceManager returns proxy objects to the server using Binder drivers
- The client takes the proxy object from the server and communicates with the process.
Client and Server are implemented by developers, Binder drivers and ServiceManager are provided by the system.
Core principles of Binder
- The Client sends data to the kernel cache, also known as copy
- This kernel cache maps to the data cache in Binder drivers
- The server and Bindr data cache have a direct memory mapping relationship.
- This is equivalent to copying the data directly to the server
Binder source code (9.0)
The code below is all system code myself, and I’m not sure about it myself, but we don’t need to go deep into it, just to strengthen the understanding of how it works
1. Open the Binder equipment
Source location: / frameworks/native/CMDS servicemanager/service_manager. C
The main method in this file has a sentence driver = “/dev/binder”; Here the Binder driver is turned on
2. Create buffer for transferring data between processes, open up memory mapping (128K)
Bs = binder_open(driver, 128*1024); This is to open a 128K memory map
Memory mapping command is mmap (), it’s in/frameworks/native/CMDS servicemanager/binder. The c file, Mmap (NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
It’s called when the system starts up, In a 9.0 System source/device/Google/cuttlefish_kernel / 4.4 – x86_64 / System. The 25306 lines in the map file can see the following instruction ffffffff815dbf50 t binder_mmap to open mapping
3. The ServiceManager starts
Source location in the system/system/core/rootdir/init. Rc file, start servicemanager instructions can be found
4. Package to Parcel and write data to binder device copy_from_user
In the system source: / frameworks/native/libs/binder/IServiceManager CPP can see the parcel in the packaging process
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated, int dumpsysPriority) {
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
data.writeInt32(dumpsysPriority);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
Copy the code
In the system files/frameworks/native/libs/binder/IPCThreadState CPP, Err = writeTransactionData(BC_TRANSACTION, FLAGS, Handle, code, data, NULL);
Encapsulate the information in the parcel into a structure and write it to mOut, waiting for it to return
5. Register the service and add it to svclist
The Server registers with the ServiceManager
Code in the system: / frameworks/native/CMDS/servicemanager/service_manager c file
if(! svc_can_register(s, len, spid, uid)) { ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
str8(s, len), handle, uid);
return - 1;
}
// Go to svclist to see if the service is registeredsi = find_svc(s, len); .Copy the code
Define the thread pool in the main thread
In the system source code/frameworks/native/libs/binder/IPCThreadState joinThreadPool methods can be found in the CPP files.
So here’s the thread pool that defines the main thread, which reads and writes all the time
Loop to fetch read/write requests from MLN and mOut min.setdatacapacity (256); mOut.setDataCapacity(256); They default to 256 bytes in size.
In the talkWithDriver method, it determines whether it can be read or written and ultimately sent to a binder device.
All of this code is really hard to read, and you just need to use it to deepen the implementation of Binder.
Hand rolled AIDL
Binder is a bit of a hassle to work with directly, and AIDL is used to simplify using Binder in Andorid.
AIDL four important objects
- IBinder: An interface that represents the ability to communicate across processes
- IInterance: What capabilities does the server process have and what methods can it provide
- Binder: Binder’s native objects inherit from IBinder
- Stub: Inheriting from Binder implements IInterance, a natively implemented server-side capability
Example: Using AIDL to implement A third-party login, now there is an application A and an application B, application A calls application B to implement the login.
The final effect is shown below:
package com.chs.binderb;
interface ILoginInterface {
void login(a);
void loginCallBack(boolean isSuccess,String user);
}
Copy the code
Create two methods: a login method and a login callback.
Then copy the full package name and files of the AIDL to the same location in project A. It must be copied exactly and directly.
In project B, create A LoginService to listen for messages sent by project A. FLAG_ACTIVITY_NEW_TASK specifies the Intent.FLAG_ACTIVITY_NEW_TASK
public class LoginService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new ILoginInterface.Stub() {
@Override
public void login(a) throws RemoteException {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@Override
public void loginCallBack(boolean isSuccess, String user) throws RemoteException {}}; }}Copy the code
Then register the service in the androidMainfest.xml file
<service android:name=".service.LoginService"
android:enabled="true"
android:exported="true"
android:process=":remote_server"
>
<intent-filter>
<action android:name="BinderB_Action"></action>
</intent-filter>
</service>
Copy the code
- Android: Enabled =”true” indicates that it can be instantiated
- Android: Exported =”true” indicates that it can be called implicitly by other applications
- Android :process=”:remote_server” : start a new process named remote_server
- The name in the action is used when A applies the implicit call
Let’s go to project A and write the method called
public class MainActivity extends AppCompatActivity {
/** * Whether to bind the remote service */
private boolean isStartRemote;
private ILoginInterface mILoginInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initBinderService();
}
/** * Bind the service */ of application B via implicit schematics
private void initBinderService(a) {
Intent intent = new Intent();
/ / set the action
intent.setAction("BinderB_Action");
// Set the package name of application B
intent.setPackage("com.chs.binderb");
// Bind the service
bindService(intent,cnn,BIND_AUTO_CREATE);
isStartRemote = true;
}
ServiceConnection cnn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Bind successfully, you can use the server-side method
mILoginInterface = ILoginInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}};public void startWeiXinLogin(View view) {
if(mILoginInterface! =null) {try {
mILoginInterface.login();
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(this."Please install wechat first",Toast.LENGTH_SHORT).show(); }}}@Override
protected void onDestroy(a) {
super.onDestroy();
if(isStartRemote){ unbindService(cnn); }}}Copy the code
The layout style is the one shown in the GIF above, and the wechat icon is clicked by the startWeiXinLogin method. It calls the login method of ILoginInterface
So let’s talk about ILoginInterface
Once we’ve created the AIDL file and rebuilt the project, the system will generate an ILoginInterface file for us, Location in the app/build/generated \ aidl_source_output_dir \ debug \ compileDebugAidl \ out \ com \ CHS \ binderb \ ILoginInterface Java
/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\android\\A1\\BinderA\\app\\src\\main\\aidl\\com\\chs\\binderb\\ILoginInterface.aidl */
package com.chs.binderb;
public interface ILoginInterface extends android.os.IInterface {
/** * Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.chs.binderb.ILoginInterface {
private static final java.lang.String DESCRIPTOR = "com.chs.binderb.ILoginInterface";
/** * Construct the stub at attach it to the interface. */
public Stub(a) {
this.attachInterface(this, DESCRIPTOR);
}
/** * Cast an IBinder object into an com.chs.binderb.ILoginInterface interface, * generating a proxy if needed. */
public static com.chs.binderb.ILoginInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin ! =null) && (iin instanceof com.chs.binderb.ILoginInterface))) {
return ((com.chs.binderb.ILoginInterface) iin);
}
return new com.chs.binderb.ILoginInterface.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder(a) {
return this;
}
@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) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_login: {
data.enforceInterface(descriptor);
this.login();
reply.writeNoException();
return true;
}
case TRANSACTION_loginCallBack: {
data.enforceInterface(descriptor);
boolean _arg0;
_arg0 = (0! = data.readInt()); java.lang.String _arg1; _arg1 = data.readString();this.loginCallBack(_arg0, _arg1);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags); }}}private static class Proxy implements com.chs.binderb.ILoginInterface {
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 login(a) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
_reply.readException();
} finally{ _reply.recycle(); _data.recycle(); }}@Override
public void loginCallBack(boolean isSuccess, java.lang.String user) 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.writeInt(((isSuccess) ? (1) : (0)));
_data.writeString(user);
mRemote.transact(Stub.TRANSACTION_loginCallBack, _data, _reply, 0);
_reply.readException();
} finally{ _reply.recycle(); _data.recycle(); }}}static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_loginCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void login(a) throws android.os.RemoteException;
public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException;
}
Copy the code
It inherits the IInterface interface, which is required for all interfaces that can be transmitted in Binder. It is also an interface itself.
It declares two methods login() and loginCallBack which we wrote in our AIDL file. Two integer ids, TRANSACTION_login and TRANSACTION_loginCallBack, are declared to identify the two methods. These two ids are used in the onTransact method to identify which method the client is requesting
It has an internal class Stub, a Binder, that communicates with its internal Proxy, which has several important methods
asInterface
Binder objects on the server side are converted into AIDL interface type objects that can be used by clients. This transformation is process-specific, returning the Stub itself if the client and server are in the same process, or the stub.proxy (obj) Proxy object if they are in different processes
asBinder
Returns the current Binder object
onTransact
This method is the onTransact method in the Binder class overridden. It runs in a pool of Binder threads on the server side, and when a remote client initiates a request, the request is wrapped by the system and handed to the method for processing. It determines which method to call with a different code. The method is then executed and the return value is written
Proxy#login
So this method is running on the client side, so in the previous MainActivity we called the asInterface method and basically we got this Proxy object, so we can call its login method, The incoming Parcel object _data and the outgoing Parcel object _reply are created when the client calls the method, and the transact method is called to initiate the remote call request. The current thread is suspended, after which the onTransact method on the server is called until it completes and returns the result
Proxy#loginCallBack
Same as the login method above.
OK now goes back to MainActivity and binds the service in the B application with an implicit call in the onCreate method.
When the button is clicked, the onBind method of the LoginService in application B will be called and the login Activity will start.
In fact, the cross-process communication from A to B has been completed, but if we click on the user name and password in application B, the success or failure should be reported to application A, how to feedback?
The method is the same as when A communicates with B. Write A Service in A and ask B to bind the Service in A. After login, call A’s remote method.
The following code
public class LoginService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new ILoginInterface.Stub() {
@Override
public void login(a) throws RemoteException {}@Override
public void loginCallBack(boolean isSuccess, String user) throws RemoteException {
Log.i("Login Status"."State."+isSuccess+">>>>>user:"+user); }}; }}Copy the code
Create A LoginService, print the callback status and user name in the callback method, and register it in androidMasfet.xml
Methods in B that simulate logging in and invoking the service in A
public class MainActivity extends AppCompatActivity {
private static final String NAME = "chs";
private static final String PWD = "123";
private EditText etName;
private EditText etPwd;
/** * Whether to bind the remote service */
private boolean isStartRemote;
private ILoginInterface mILoginInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etName = findViewById(R.id.et_name);
etPwd = findViewById(R.id.et_pwd);
initBinderService();
}
/** * Bind the service */ of application A via implicit schematics
private void initBinderService(a) {
Intent intent = new Intent();
/ / set the action
intent.setAction("BinderA_Action");
// Set the package name of application B
intent.setPackage("com.chs.bindera");
// Bind the service
bindService(intent,cnn,BIND_AUTO_CREATE);
isStartRemote = true;
}
ServiceConnection cnn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Bind successfully, you can use the server-side method
mILoginInterface = ILoginInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {}};public void qqLogin(View view) {
final String name = etName.getText().toString();
final String pwd = etPwd.getText().toString();
ProgressDialog dialog = new ProgressDialog(this);
dialog.setTitle("Logging in");
new Thread(){
@Override
public void run(a) {
super.run();
SystemClock.sleep(1000);
runOnUiThread(new Runnable() {
@Override
public void run(a) {
boolean isSuccess = false;
if(name.equals(NAME)&&pwd.equals(PWD)){
isSuccess = true;
showToast("Login successful");
finish();
}else {
showToast("Login failed");
}
try {
mILoginInterface.loginCallBack(isSuccess,name);
} catch(RemoteException e) { e.printStackTrace(); }}}); } }.start(); }private void showToast(String text) {
Toast.makeText(this,text,Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy(a) {
super.onDestroy();
if(isStartRemote){ unbindService(cnn); }}}Copy the code
The OK code is done, and when it runs it looks like the GIF above. The following information is displayed about the callback of LoginService in A.
The 2019-07-10 21:51:27. 225, 10173-10191 / com. CHS. Bindera: remote_a I/login: status:false> > > > > the user: the 2019-07-10 21:51:35. 343, 10173-10191 / com. CHS. Bindera: remote_a I/login: status:true>>>>>user:chs
Copy the code