This article mainly sorts out the relevant content of remote services in Service, focusing on learning the related usage of adil. If the foundation of Servcie is not solid, it is recommended to read the comprehensive analysis of Servcie-Comprehensive analysis of local services
AIDL in brief
The definition of AIDL is the Android Interface Definition Language, which is the Android interface definition language. Yes, AIDL is a language, then it contains a series of syntax definitions and various usages of it.
Why did Android design this language?
The following is an introduction to AIDL in the official documentation
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger.
Note: AIDL is only required if you allow clients from different applications to access your IPC service and want to handle multithreading in the service. If you don't need to perform concurrent IPC between different applications, you should create your interface by implementing a Binder, or, if you want to perform IPC, but don't need to deal with multithreading, you can use a Messenger to implement your interface.
It can be seen that AIDL is suitable for inter-process communication in Android, and the scenario when concurrent IPC needs to be performed. While Messenger communicates between processes in Android, it is synchronous and cannot handle concurrency.
AIDL syntax
The types supported in AIDL are as follows:
- All basic data types in Java, char, byte, short, int, long, float, double, boolean
- String type
- CharSequence
- List, the data elements in the List must be able to be supported by AIDL
- Map: only supports HashMap, each element in it must be supported by AIDL, including key and value
- Parcelable: An object that implements the Parcelable interface
- AIDL Interfaces: All AIDL interfaces themselves are also available in AIDL files.
- Directional tag: where in means that data can only flow from the client to the server, out means that data can only flow from the server to the client, and inout means that the data can flow in both directions between the server and the client.
AIDL writing process
The implementation of AIDL is divided into the following steps:
- Create an entity class to implement the Parcelable interface for serialization and deserialization
- Create AIDL file
- Create a service on the server side, create a Binder object and return
- Implement the ServiceConnection object on the client side, and obtain the AIDL class entity class provided by the server side for interaction
AIDL specific implementation
- Create NBAStar class to implement Parcelable interface for serialization and deserialization
public class NBAStar implements Parcelable { private String name; private int age; private String team; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getTeam() { return team; } public void setTeam(String team) { this.team = team; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeInt(age); dest.writeString(team); } public static final Creator<NBAStar> CREATOR = new Creator<NBAStar>() { @Override public NBAStar createFromParcel(Parcel source) { NBAStar NBAStar = new NBAStar(); NBAStar.setName(source.readString()); NBAStar.setAge(source.readInt()); NBAStar.setTeam(source.readString()); return NBAStar; } @Override public NBAStar[] newArray(int size) { return new NBAStar[size]; } }; @Override public String toString() { return name + "," + age + "," + team + ";"; } }
- Create AIDL file
The created directory is shown in the figure above, in which the IAidlInterfase.aidl file is the AIDL interface file, which is used for client calls
// IAidlInterface.aidl package review.heal.com.review; // Declare any non-default types here with import statements import review.heal.com.review.NBAStar; interface IAidlInterface { List<NBAStar> getAllStars(); void addStar(in NBAStar NBAStar); }
The NBAStar class that implements the Parcelable interface needs an entity mapping .aidl file NBAStar.aidl, and the entity class and type need to be declared in NBAStar.aidl
// IAidlInterface.aidl package review.heal.com.review; // Declare any non-default types here with import statements parcelable NBAStar;
Note: The package name of NBAStar.aidl should be consistent with the package name of the entity class NBAStar
After creating the above files, select Build->Make Project in Android Studio, an error will appear at this time
Error:Execution failed for task ':app:compileDebugJavaWithJavac'. > Unable to find source java class: '/Users/Shared/app/AndroidApp/remoteService/app/src/main/aidl/review/heal/com/review/NBAStar.java'
出现这个错误的原因,是因为在Android Studio中使用Gradle来构建Android项目,而Gradle会默认使用sourceSet来配置不同文件的访问路径,Gradle默认将Java文件访问路径设置在Java包下,若Java文件被放置在aidl包下,那么Android Studio将无法找到此java文件。问题的解决方法实在build.gradle中添加下面语句
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
意思是将Java代码的访问路径设置成Java包和aidl包,这样便能在aidl包中找到java文件了。
此时再在Android Studio中选择Build->Make Project,Android Studio就会帮我们生成IAidlInterface文件。
以下代码由系统自动生成,这些代码用来设定基于IAidlInterface接口的Binder IPC连接,以便客户端与远程服务通信。
public interface IAidlInterface extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements review.heal.com.review.IAidlInterface { private static final java.lang.String DESCRIPTOR = "review.heal.com.review.IAidlInterface"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an review.heal.com.review.IAidlInterface interface, * generating a proxy if needed. */ public static review.heal.com.review.IAidlInterface asInterface(android.os.IBinder obj) { ...... return new review.heal.com.review.IAidlInterface.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { ...... return super.onTransact(code, data, reply, flags); } private static class Proxy implements review.heal.com.review.IAidlInterface { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } //所有的返回值前都不需要加任何东西,不管是什么数据类型 @Override public java.util.List<review.heal.com.review.NBAStar> getAllStars() throws android.os.RemoteException { ...... return _result; } @Override public void addStar(review.heal.com.review.NBAStar NBAStar) throws android.os.RemoteException { ...... } } static final int TRANSACTION_getAllStars = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addStar = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } //所有的返回值前都不需要加任何东西,不管是什么数据类型 public java.util.List<review.heal.com.review.NBAStar> getAllStars() throws android.os.RemoteException; public void addStar(review.heal.com.review.NBAStar NBAStar) throws android.os.RemoteException; }
- 在服务端创建service,创建Binder对象并返回
public class NBAStarAidlService extends Service { private List<NBAStar> nbaStars; private IBinder iBinder = new IAidlInterface.Stub() { //返回所有NBAStar @Override public List<NBAStar> getAllStars() throws RemoteException { return nbaStars; } //增加NBAStar @Override public void addStar(NBAStar NBAStar) throws RemoteException { nbaStars.add(NBAStar); } }; @Nullable @Override public IBinder onBind(Intent intent) { nbaStars = new ArrayList<>(); NBAStar nbaStar = new NBAStar(); nbaStar.setName("科比"); nbaStar.setAge(30); nbaStar.setTeam("洛杉矶湖人"); nbaStars.add(nbaStar); return iBinder; } }
同时要在AndroidMainfest文件中声明服务
<service android:name=".NBAStarAidlService" android:process=":remote"> <intent-filter> <action android:name="review.heal.com.review.IAidlInterface"/> </intent-filter> </service>The main thing the server does is to implement the Stub interface of IAidlInterface, and return IBinder in the onBind() method, and the client obtains the IBinder object for interaction.
- Implement the ServiceConnection object on the client side, and obtain the AIDL class entity class provided by the server side for interaction
First, you need to transplant the IAidlInterfase.aidl, NBAStar.aidl and NBAStar entity classes of the server to the client. To ensure that the package name is the same as that of the server , select Build->Make Project in Android Studio to generate the IAidlInterface file.
Then instantiate the ServiceConnection and get the server-side IBinder object
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { iAidlInterface = IAidlInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } };
通过IAidlInterface的Stub抽象类调用asInterface()方法,可以将IBinder对象转换成IAidlInterface的实例,调用bindService()方法绑定远程服务
bindService(new Intent(IAidlInterface.class.getName()), serviceConnection, Context.BIND_AUTO_CREATE);
在Android5.0之后,上面的绑定调用会提示 Service Intent must be explicit 错误,也就是说,在Android5.0之后,服务必须显式的开启。以下方法是一个隐式调用转换为显式调用
public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) { PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); if (resolveInfo == null || resolveInfo.size() != 1) { return null; } ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); Intent explicitIntent = new Intent(implicitIntent); explicitIntent.setComponent(component); return explicitIntent; }
bindService(new Intent(createExplicitFromImplicitIntent(MainActivity.this, new Intent(IAidlInterface.class.getName()))), serviceConnection, Context.BIND_AUTO_CREATE)
这样客户端便完成了远程绑定服务端,调用IAidlInterface的getAllStars()方法
try { if (iAidlInterface != null) { Log.e("aidl", "接收aidl数据:" + iAidlInterface.getAllStars()); } } catch (RemoteException e) { Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); }
调用结果:
review.heal.com.remoteservice E/aidl: Receive aidl data: [Kobe, 30, Los Angeles Lakers;]
Call the addStar() method of IAidlInterface and then call the getAllStars() method
try { if (iAidlInterface != null) { NBAStar nbaStar = new NBAStar(); nbaStar.setName("McGrady"); nbaStar.setAge(31); nbaStar.setTeam("Houston Rockets"); iAidlInterface.addStar(nbaStar); } } catch (RemoteException e) { Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); }
Call result:
review.heal.com.remoteservice E/aidl: Receive aidl data: [Kobe, 30, Los Angeles Lakers;, McGrady, 31, Houston Rockets;]The above is the whole process of AIDL realizing cross-process communication