一、序言
????????该系列文章旨在让AIDL初学者入门,不一定全,但通过自己在Android Studio上的实际编写运行,尽可能会把相关的知识点和编译运行过程中遇到的问题列出来并给出解决方案。
????????本文主要介绍使用Android Studio进行AIDL项目代码的编写,并针对过程中遇到的问题,提供相关解决方法,学习本文之前,建议大家先学习下面这篇文章,将AIDL的原理了解一下,对于后续的AIDL的开发可以起到事半功倍的作用: https://blog.csdn.net/qq_41739313/article/details/123322808
二、服务器端代码
??????? 建议在学习本文前,先将代码成功运行起来,本文源码链接如下:AIDL开发-文章源码-Android文档类资源
2.1 MainActivity.java
????????单个Service组件无法运行,要么通过BroadcastReceiver来启动,要么依托于Activity来启动;MainActivity位于com.example.ipservcer包;
??????? MainActivity.java使用默认的代码就行,不过要在AndroidManifest.xml中配置一下android:exported属性:
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
2.2 activity_main.xml
??????? 使用默认的代码即可;
2.3 Book.java
??????? 位于com.example.ipcserver包,定义一个继承了Parcelable类的可序列化的Book类;
public class Book implements Parcelable {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
private String name;
private int price;
public Book(){}
public Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
public void readFromParcel(Parcel dest) {
name = dest.readString();
price = dest.readInt();
}
@Override
public String toString() {
return "name : " + name + " , price : " + price;
}
}
2.4 Book.aidl
??????? 位于com.example.ipcserver包,定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型。
??????? 直接在包名上单击鼠标右键,选择new -> AIDL,可能会出现不能创建同名文件的提示,因为前面我们创建了Book.java文件,这里随意命名,创建后在另行改名为Book.aidl;
package com.example.ipcserver;
parcelable Book;
2.5? BookManager.aidl
????????位于com.example.ipcserver包,用于声明服务器端相关方法的接口;
package com.example.ipcserver;
import com.example.ipcserver.Book;
interface BookManager {
List<Book> getBooks();
void addBook(inout Book book);
}
2.6 AIDLService.java
??????? (1)位于com.example.ipcserver包,用于处理和客户端的连接,以及继承并定义Stub中声明的方法;
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.util.ArrayList;
import java.util.List;
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//包含Book对象的list
private List<Book> mBooks = new ArrayList<>();
//由AIDL文件生成的BookManager
private final IBinder mBookManager = new BookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in In");
book = new Book();
}
//尝试修改book的参数,主要是为了观察其到客户端的反馈
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,观察客户端传过来的值
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
}
}
};
@Override
public void onCreate() {
Book book = new Book();
book.setName("Android开发艺术探索");
book.setPrice(28);
mBooks.add(book);
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
return mBookManager;
}
public void onDestroy() {
Log.e("onDestroy", "service on destroy");
super.onDestroy();
}
}
??????? (2)在AndroidManifest.xml中定义该service组件;
<service android:name=".AIDLService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.AIDLService"/>
</intent-filter>
</service>
2.7 AndroidManifest.xml
??????? 其中的内容在前面步骤中定义相关组件的时候已经列出了;
????????唯一要注意的就是,看一下manifest标签中的package属性值,是否service类的包名对应上了,这个属性值在客户端连接服务器的时候需要使用到。
2.8 BookManager.java
??????? 编写完前面的代码后,运行该项目,将项目结构切换为Project,在app -> build -> generated -> aidl_source_output_dir -> debug -> out -> com.example.ipcserver目录下,可以找到生成的BookManager.java接口文件。
package com.example.ipcserver;
public interface BookManager extends android.os.IInterface
{
public static abstract class Stub extends android.os.Binder implements com.example.ipcserver.BookManager
{
...
private static class Proxy implements com.example.ipcserver.BookManager
{
...
}
}
...
}
三、客户端代码
3.1 AIDLActivity.java
??????? (1)位于com.example.ipclient包下,服务器端的主要代码,用于建立与服务器端的连接,调用服务器端的方法;
public class AIDLActivity extends AppCompatActivity implements View.OnClickListener{
//由AIDL文件生成的Java类
private BookManager mBookManager = null;
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private boolean mBound = false;
//包含Book对象的list
private List<Book> mBooks;
private Button addBookBtn, ServiceBtn;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
initView();
}
private void initView() {
addBookBtn = findViewById(R.id.addBookIn);
addBookBtn.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.addBookIn:
addBook(view);
break;
default:
break;
}
}
public void addBook(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研发录In");
book.setPrice(30);
try {
mBookManager.addBook(book);
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 尝试与服务端建立连接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setPackage("com.example.ipcserver");
intent.setAction("android.intent.action.AIDLService");
boolean b = bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
}
??????? (2)在AndroidManifest.xml中声明activity组件;
<activity android:name="com.example.ipcclient.AIDLActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
3.2 AndroidManifest.xml
??????? 其中的相关内容已在定义相关activity的时候进行了说明;
??????? 注意将 "需要让其他进程访问的组件的android:exported属性" 设置为true;
3.3 buid.gradle(app)
??????? 因为Activity文件和aidl文件位于两个不同的包下,由于Android Studio的特殊编译机制,需要在build.gradle(Module:app)中配置java编译路径,才能正确编译到所有文件,否则,编译系统找不到aidl文件;
android {
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
}
四、注意事项
4.1 aidl编译配置
????????编写AIDL的目的是生成对应的java文件,而在Android Studio中,默认情况下只会编译java目录下的文件,aidl文件和java文件在两个不同的目录下,所以需要在build.gradle(app)进行如下配置:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
4.2 AndroidManifest配置
4.2.1 配置方法
????????服务器端的service需要配置andrdoid:export属性为true,才能被客户端访问,export="true"的作用就是:支持其它应用调用当前组件,具体配置如下:
<service android:name=".AIDLService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.AIDLService"/>
</intent-filter>
</service>
4.2.2 android:exported属性
??????? 参考:android:exported 属性详解 - 天之骄子19 - 博客园
4.3 package一致性
????????(1)client客户端是通过如下方式与服务器端连接的,所以要保证客户端的setPackage(" ")中的参数和服务器端的build.gradle(app)中android的defaultConfig的applicationId属性的值保持一致,最好也与AndroidManifest.xml中的manifest标签的package属性保持一致。
????????(2)客户端和服务器端都有相同的aidl通信接口文件,要保证两端aidl文件所在目录的包名一致;
4.4 软件可见性
????????Android 11之后,在默认情况下,系统会自动让部分应用对您的应用可见,但会隐藏其他应用,也影响到了AIDL通信;需要加如下权限:
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
??????? 参考:Android 11 适配 软件包可见性_Lucky_William的博客-CSDN博客
4.5 无法连接service?
??????? 报错内容:Unable to start service Intent { cmp=com.glad.android.action/com.glad.android.services.bg.DataProcessService }: not found?
????????如果你的service和启动的activity不在同一个包内,需要把service标签中的android:name配置成service类的完全名(全路径)(AIDL跨进程通信暂未出现该问题)
??????? 无法连接的可能原因:
??????? 参考:https://www.jb51.net/article/121679.htm
??????? 参考:Android bindService失败,解决方法。_VNanyesheshou的博客
??????? 参考:使用AIDL时bindService失败---packageName_胡飞洋的博客-CSDN博客
??????? 特定手机:进程间bindService失败(魅族手机) - 简书
??????? 参考:bindService失败的解决办法 - it610.com
参考文章:
AIDL | Android:学习AIDL,这一篇文章就够了(上)_lypeer的博客-CSDN博客_aidl
AIDL |?Android中AIDL的使用详解 - 简书
AIDL开发 |?Android进程间通信(一):AIDL使用详解 - 帅气陈吃苹果 - 博客园
软件可见性 |?Android 11 适配 软件包可见性_Lucky_William的博客-CSDN博客
AIDL在安卓系统中的应用 |?AIDL在android系统中的作用_园荐_博客园
|