Binder communication
Due to my current understanding of Binder communication is not in place, I did not go deep into the source code, so I will mainly read others: A more detailed post, I think it is very detailed to the code, all involved. Blog.csdn.net/qq_38998213…
To summarize, Android has a Binder approach for cross-process communication (IPC). The architecture is C/S architecture, that is, one side Client, one side Server. Server registers its methods or data with Binder drivers, and clients register with Binder drivers to use services provided by Server.
Aidl
What is Aidl?
Android Interface Defination Language (Aidl) is an Interface design language provided by Android for users, which is intended to facilitate users to design cross-process communication. Instead of writing a bunch of code from scratch each time, just create a new AIDL file and specify the data and methods in it. The IDE automatically helps generate a basic framework for communication between the Binder drivers, servers and clients. What the user needs to achieve is:
- Implement the specific functions of the Server
- Associate yourself with the service you want to use in the client
Specific implementation
Create an AIDL file in a new Android project and Android Studio will automatically generate an initial AIDL file:
// IMyAidlInterface.aidl
package com.ronghua.deviceselfcheck;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
Copy the code
Aidl tells you here some basic types you can use if you need to transfer data content beyond these, as Binder communication can only transfer serialized data, you need to define it yourself by implementing the Parcelable interface. Here, the method of data types can be change, back to my own a piece of code in doing project examples, this method is derived from a blog darvincitech.wordpress.com/2019/11/04/ MagiskDetect…
I’m going to list a file that you don’t want to look at and you can just cross it out
interface IIsolatedProcess {
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
boolean detectMagiskHide(a);
}
Copy the code
Build the project. The IDE generates the following code that we need to use:
/* * This file is auto-generated. DO NOT MODIFY. */
package com.ronghua.deviceselfcheck;
// Declare any non-default types here with import statements
public interface IIsolatedProcess extends android.os.IInterface {
/** * Default implementation for IIsolatedProcess. */
public static class Default implements com.ronghua.deviceselfcheck.IIsolatedProcess {
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
@Override
public boolean detectMagiskHide(a) throws android.os.RemoteException {
return false;
}
@Override
public android.os.IBinder asBinder(a) {
return null; }}/** * Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.ronghua.deviceselfcheck.IIsolatedProcess {
private static final java.lang.String DESCRIPTOR = "com.ronghua.deviceselfcheck.IIsolatedProcess";
/** * Construct the stub at attach it to the interface. */
public Stub(a) {
this.attachInterface(this, DESCRIPTOR);
}
/** * Cast an IBinder object into an com.ronghua.deviceselfcheck.IIsolatedProcess interface, * generating a proxy if needed. */
public static com.ronghua.deviceselfcheck.IIsolatedProcess asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin ! =null) && (iin instanceof com.ronghua.deviceselfcheck.IIsolatedProcess))) {
return ((com.ronghua.deviceselfcheck.IIsolatedProcess) iin);
}
return new com.ronghua.deviceselfcheck.IIsolatedProcess.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_detectMagiskHide: {
data.enforceInterface(descriptor);
boolean _result = this.detectMagiskHide();
reply.writeNoException();
reply.writeInt(((_result) ? (1) : (0)));
return true;
}
default: {
return super.onTransact(code, data, reply, flags); }}}private static class Proxy implements com.ronghua.deviceselfcheck.IIsolatedProcess {
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;
}
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
@Override
public boolean detectMagiskHide(a) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_detectMagiskHide, _data, _reply, 0);
if(! _status && getDefaultImpl() ! =null) {
return getDefaultImpl().detectMagiskHide();
}
_reply.readException();
_result = (0! = _reply.readInt()); }finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.ronghua.deviceselfcheck.IIsolatedProcess sDefaultImpl;
}
static final int TRANSACTION_detectMagiskHide = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.ronghua.deviceselfcheck.IIsolatedProcess impl) {
if (Stub.Proxy.sDefaultImpl == null&& impl ! =null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.ronghua.deviceselfcheck.IIsolatedProcess getDefaultImpl(a) {
returnStub.Proxy.sDefaultImpl; }}/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */
public boolean detectMagiskHide(a) throws android.os.RemoteException;
}
Copy the code
The file is quite long, but we can skip some of the code and focus on two areas
/* * This file is auto-generated. DO NOT MODIFY. */
package com.ronghua.deviceselfcheck;
// Declare any non-default types here with import statements
public interface IIsolatedProcess extends android.os.IInterface {
// A bunch of... Don't need to look
public static abstract class Stub extends android.os.Binder implements com.ronghua.deviceselfcheck.IIsolatedProcess {
// A bunch of... Don't need to look
public static com.ronghua.deviceselfcheck.IIsolatedProcess asInterface(android.os.IBinder obj) {
// Return a Proxy that the client can use
}
// A bunch of... Don't need to look
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
/ / blablabla
boolean _result = this.detectMagiskHide();
// Then return the result}}private static class Proxy implements com.ronghua.deviceselfcheck.IIsolatedProcess {
private android.os.IBinder mRemote;
@Override
public boolean detectMagiskHide(a) throws android.os.RemoteException {
/ / blablabla
boolean _status = mRemote.transact(Stub.TRANSACTION_detectMagiskHide, _data, _reply, 0);
/ / blablabla}}// A Stub implementation of default, but we don't care
public boolean detectMagiskHide(a) throws android.os.RemoteException;
}
Copy the code
After this simplification, we have much less to focus on:
- A Stub class
- The Proxy class
- IIsolatedProcess itself
The first is the Proxy class. The purpose of the Proxy class is to enable clients to use the functions of the Server. If I give you a Proxy, you can use it. So how does a Client get a Proxy?
We can now see that a static method in the Stub, asInterface, does just that. That is to say, the Client only needs to call the asInterface method at the appropriate time to obtain the Proxy I need. Through the Proxy, binder driver can be invoked to call the Server.
The Stub class is the binder driver described above and associates the communication between the two ends. However, there is no logical code in the Stub to help you implement the detectMagiskHide request when it is received. That is, if we can implement stubs on the server side, we are done.
So far our thinking goes like this:
- Set up a Service that implements the detectMagiskHide real logic in the Stub and uses it as its own binder driver.
- A Client binds to a Service in order to use the Service and expects a binder driver
- After binding, the Service will tell the Client the Stub interface (Binder driver) that has just been implemented. The Client can use the Stub interface directly, but it is very troublesome because inter-process communication uses different data types. Therefore, we need to convert the asInterface into a Proxy that is convenient for us to use
Implementing the above logic into code looks like this:
- Service.class
package com.ronghua.deviceselfcheck;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class IsolatedService extends Service {
private static String[] blackListMountPaths = {"/sbin/.magisk/"."/sbin/.core/mirror"."/sbin/.core/img"."/sbin/.core/db-0/magisk.db"};
private static String TAG = "DetectMagisk";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private IBinder mBinder = new IIsolatedProcess.Stub() {
@Override
public boolean detectMagiskHide(a) throws RemoteException {
try{
Native.isLibLoaded();
}catch(UnsatisfiedLinkError e){
Log.i(TAG, "Native lib is not loaded successfully");
return true;
}
boolean isMagiskDetect = false;
int count = 0;
try {
FileReader fr = new FileReader("/proc/self/mounts");
BufferedReader br = new BufferedReader(fr);
String line = "";
while((line = br.readLine()) ! =null) {for(String path:blackListMountPaths){
Log.i(TAG, "Checking mount path: " + path);
if(line.contains(path)){
count++;
Log.i(TAG, "Magisk path detected: " + path);
break; }}}if(count > 0)
isMagiskDetect = true;
else{ isMagiskDetect = Native.detectMagiskNative(); }}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
returnisMagiskDetect; }}; }Copy the code
The IsolatedService implements the Service and then returns the Binder drivers we wish to return using the onBind method. This will tell MainActivity what the driver is when the Service is bound.
- MainActivity.class
package com.ronghua.deviceselfcheck;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
public class MainActivity extends AppCompatActivity {
private boolean isBound = false;
private static String TAG = "DetectMagisk";
private IIsolatedProcess mServiceBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart(a) {
super.onStart();
Intent intent = new Intent(this, IsolatedService.class);
getApplicationContext().bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mServiceBinder = IIsolatedProcess.Stub.asInterface(service);
mRemote = service;
isBound = true;
Log.i(TAG, "service is bound");
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false; }}; }Copy the code
Bind to your Service (MainActivity onStart()), and save the Binder driver you have when you bind onServiceConnected(). As mentioned earlier, Binder is not convenient to use due to data format problems, so we obtained Proxy through asInterface. Whenever we want the Service to do a task, we can use the Proxy to do it.
Binder drivers handle onBind for different processes. The asInterface process directly obtains the Stub(Service and MainActivity process), so you can directly call the Stub method, which is similar to the Binder implementation, to obtain the Service instance. If it is a different process, we cannot get obj directly, so we will be given a BinderProxy to call Service with Binder driver.)