1、服务端
在main目录右键New>Folder>AIDL Folder,操作完后main目录下会自动新建一个aidl目录。
在aidl目录下新建包名:com.test.binder_aidl,在包内创建一个ICalcAIDL.aidl文件:
package com.test.binder_aidl;
interface ICalcAIDL
{
int add(int x , int y);
int min(int x , int y );
}
选择Make Project编译一下,在build/generated/aidl_source_output_dir…目录下会生成ICalcAIDL.java文件,然后我们在项目中新建一个Service,代码如下:
package com.test.binderserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.test.binder_aidl.ICalcAIDL;
public class CalcService extends Service {
private static final String TAG = "server";
public void onCreate()
{
Log.e(TAG, "onCreate");
}
public IBinder onBind(Intent t)
{
Log.e(TAG, "onBind");
return mBinder;
}
public void onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();
}
public boolean onUnbind(Intent intent)
{
Log.e(TAG, "onUnbind");
return super.onUnbind(intent);
}
public void onRebind(Intent intent)
{
Log.e(TAG, "onRebind");
super.onRebind(intent);
}
private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub() {
@Override
public int add(int x, int y) throws RemoteException {
return x + y;
}
@Override
public int min(int x, int y) throws RemoteException {
return x - y;
}
};
}
记得在AndroidManifest中注册Service:
<service android:name=".CalcService"
android:exported="true">
<intent-filter>
<action android:name="com.test.binderserver.aidl.calc" />
</intent-filter>
</service>
这个不需要Activity,所以我也就没写Activity,安装完成也看不到安装图标,悄悄在后台运行着。
到此,服务端编写完毕。下面开始编写客户端。
2、客户端
客户端也必须要有对应的aidl文件,并且aidl的包名要与服务端保持一致,我们可以把服务端的aidl复制到客户端。
客户端的代码比较简单,创建一个布局,里面包含4个按钮,分别为绑定服务,解除绑定,调用加法,调用减法。布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bindService"
android:text="BindService" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unbindService"
android:text="UnbindService" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="addInvoked"
android:text="12+12" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="minInvoked"
android:text="50-12" />
</LinearLayout>
MainActivity代码如下:
package com.test.binderclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.test.binder_aidl.ICalcAIDL;
public class MainActivity extends AppCompatActivity {
private ICalcAIDL mCalcAidl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bindService(View view) {
Intent intent = new Intent();
intent.setAction("com.test.binderserver.aidl.calc");
intent.setPackage("com.test.binderserver");
boolean result = bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
Toast.makeText(this, "result:" + result, Toast.LENGTH_SHORT).show();
}
public void unbindService(View view) {
unbindService(mServiceConn);
}
public void addInvoked(View view) throws RemoteException {
if(mCalcAidl != null){
int addRes = mCalcAidl.add(12, 12);
Toast.makeText(this, addRes + "", Toast.LENGTH_SHORT).show();
} else
{
Toast.makeText(this, "服务器被异常杀死,请重新绑定服务端", Toast.LENGTH_SHORT)
.show();
}
}
public void minInvoked(View view) throws RemoteException {
if (mCalcAidl != null)
{
int addRes = mCalcAidl.min(50, 12);
Toast.makeText(this, addRes + "", Toast.LENGTH_SHORT).show();
} else
{
Toast.makeText(this, "服务端未绑定或被异常杀死,请重新绑定服务端", Toast.LENGTH_SHORT)
.show();
}
}
ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("client", "onServiceConnected");
mCalcAidl = ICalcAIDL.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("client", "onServiceDisconnected");
mCalcAidl = null;
}
};
}
注意android 5.0以后需要调用setPackage()或setComponent(),android在这里添加了校验,具体位置在ContextImpl.binderService()>ContextImpl.bindServiceCommon()>ContextImpl.validateServiceIntent():
private void validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException(
"Service Intent must be explicit: " + service);
throw ex;
} else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
}
}
}
此时运行客户端,点击BindService按钮,结果弹出的Toast显示result:false,bindService调用不成功。
想要成功调用,第一种方式是把targetSdkVersion 版本修改到30以下;第二种方式是在客户端的manifest中指定服务端的包名(谷歌的文档说明 https://developer.android.google.cn/guide/topics/manifest/queries-element?hl=en),在客户端的manifst文件中添加如下配置:
<manifest>
<queries>
<!--如果想要与其他的应用进行AIDL通信的话,需要在这里注册包名的信息-->
<package android:name="com.test.binderserver" />
</queries>
</manifest>
重新运行,点击BindService按钮,调用成功,打印如下:
2022-08-02 10:42:00.731 25654-25654/com.test.binderserver E/server: onCreate
2022-08-02 10:42:00.732 25654-25654/com.test.binderserver E/server: onBind
2022-08-02 10:42:00.734 25709-25709/com.test.binderclient E/client: onServiceConnected
然后点击12+12,50-12可以成功的调用服务端的代码并返回正确的结果。
最后点击UnbindService,打印如下:
2022-08-02 10:42:02.299 25654-25654/com.test.binderserver E/server: onUnbind
2022-08-02 10:42:02.299 25654-25654/com.test.binderserver E/server: onDestroy
发现客户端的onServiceDisconnected并没有打印,我们继续点击12+12,50-12发现依然可以正常执行,也就是说即使onUnbind被调用,连接也是不会断开的,那么什么时候会断开呢?即当服务端被异常终止的时候,比如我们现在在手机设置>正在执行的程序中找到该服务,点击Force Stop,此时查看log:
2022-08-02 11:03:35.413 26801-26801/com.test.binderclient E/client: onServiceDisconnected
可以看到onServiceDisconnected打印了,此时连接被断开,现在点击12+12,50-12的按钮,则会弹出Toast服务端断开的提示。
注意到连续两次点击BindService按钮,bindService()都能调用成功;然后连续两次点击UnbindService按钮,此时客户端会崩溃,报错信息:Caused by: java.lang.IllegalArgumentException: Service not registered,所以在我们unbindService()前必须先bindService()。
参考:http://blog.csdn.net/lmj623565791/article/details/38461079