前言
AIDL其实是属于IPC方面的东西,而Android的IPC是一个比较大的内容,主要涉及到Android的跨进程通信的内容,此篇文章会先介绍一下Android的IPC。
Android IPC简介
IPC是Inter-Process Communication的缩写,意思就是进程间通信或者跨进程的通信,就是两个进程之间交换数据的过程。说到进程,就不得不说到进程和线程这两个东西,这两个是完全不一样的概念,简单描述就是,一个进程可以包含多个线程,也就是一对多的关系。进程一般指一个执行单元,线程是CPU调度的最小单元。在Android里面,进程里可以只有一个线程,即主线程,在Android里面也可以叫作UI线程,一般在这个线程里面都是进行UI更新的操作,而耗时的操作都是交给子线程去进行,这样可以防止Android的ANR状态。
Android的多进程模式
再讲IPC之前我们先来讲讲Android的多进程模式,在Android中多进程是值一个应用中存在多个进程的情况,所以这里暂时不讨论两个应用之间的情况。在Android使用多进程的只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性。参数为你要自定义的包名,如果没有指定进程的话,那么就会运行在默认的进程中。
<activity android:name=".MainActivity"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
但是,看起来使用多进程模式很简单,但其实是会造成一些问题的,比如:
- 静态成员和单例模式完全失效。
- 线程同步机制完全失效。
- SharedPreferences的可靠性下降。
- Application会多次创建。
虽然多进程会带来很多问题,但是不能因为就这样而不去正视它们,为了解决这个问题,系统提供了很多跨进程通信的方法,虽说不能直接的共享内存,但是通过跨进程通信我们还是可以实现数据的交互,实现跨进程的通信方式有很多种方法,我们后续会进行讲解。
AIDL的使用
刚刚也说过了,跨进程的通信方式有很多,比如通过BInder来进行跨进程通信,还有ContentProvider,它天生就是支持跨进程访问的,此外也可以使用Bundle,Messager等等方式,还有一种方式就是AIDL,AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码。
Android的多进程方式如图,侵删
而刚刚说的像Messager之类的底层其实就是AIDL,只是Messager做了一些封装。还有一个就是Binder,它也是Android里跨进程通信里的一种机制,而AIDL是对Binder的封装。因为要实现IBinder来支持远程调用,应从Binder类派生一个类。而使用AIDL,它会自动生成Binder的派生类,可以直接用,那么怎么用AIDL进行跨进程通信呢
我们这里举一个例子,创建一个App,我们称之为服务端,然后先创建一个一个AIDL文件,然后自定义一个方法,创建后记住要build一下,build之后AIDL就会快速生成对应的java文件了。
interface IAppServiceRemoteBinder {
/**
* 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);
//定义一个set方法
void setData(String data);
}
AIDL文件创建如图
这里说明一下,AIDL支持的数据类型有以下这些:
- 基本数据类型(int、long、char、boolean、double)
- String和CharSequence
- List和Map集合
- 集合内元素必须是AIDL支持的数据类型
- 服务端具体使用的集合必须是ArrayList和HashMap
- Parcelable:实现了Parcelable接口的对象
- AIDL本身接口也可以在AIDL文件使用。
接着我们创建一个服务,因为其实AIDL就是基于Binder,所以我们就直接在onBind()
里面进行返回。
public class AppService extends Service {
private String data = "默认数据";
private boolean running = false;
public AppService() {
}
@Override
public IBinder onBind(Intent intent) {
//我们这里直接返回这个接口
return new IAppServiceRemoteBinder.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void setData(String data) throws RemoteException {
//客户端传过来的值赋值给data
AppService.this.data = data;
}
};
}
@Override
public void onCreate() {
super.onCreate();
Log.d("TAG", "service started");
//创建一个测试线程,把客户端传过来的数据进行循环输出。
new Thread() {
@Override
public void run() {
super.run();
running = true;
while (running) {
try {
Thread.sleep(1000);
Log.d("TAG", data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("TAG", "service onDestroy");
running = false;
}
}
接着我们创建一个客户端,来远程启动这个服务,首先我们要把之前创建的那个AIDL文件给复制过来,这里需要注意的是,包名的路径是要相同的,然后记住要build,然后设置一下布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_start_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" 启动外部服务" />
<Button
android:id="@+id/btn_stop_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" 停止外部服务" />
<Button
android:id="@+id/btn_bind_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" 绑定外部服务" />
<Button
android:id="@+id/btn_unbind_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" 解除外部服务" />
<Button
android:id="@+id/btn_edit_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" 发送值给服务端" />
</LinearLayout>
然后在MainActivity里输入以下代码。
//要实现ServiceConnection方法
public class MainActivity extends AppCompatActivity implements View.OnClickListener, ServiceConnection {
private Button btnStart;
private Button btnStop;
private Button btnBind;
private Button btnUnBind;
private Button btnEdit;
private Intent serviceIntent;
private IAppServiceRemoteBinder binder = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent();
//这里设置intent的包名,第一个参数为要启动app的包名,第二个为服务包名
serviceIntent.setComponent(new ComponentName("com.ju.startservicefromanotherapp", "com.ju.startservicefromanotherapp.AppService"));
btnStart = findViewById(R.id.btn_start_service);
btnStart.setOnClickListener(this);
btnStop = findViewById(R.id.btn_stop_service);
btnStop.setOnClickListener(this);
btnBind = findViewById(R.id.btn_bind_service);
btnBind.setOnClickListener(this);
btnUnBind = findViewById(R.id.btn_unbind_service);
btnUnBind.setOnClickListener(this);
btnEdit = findViewById(R.id.btn_edit_service);
btnEdit.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start_service:
//远程启动服务
startService(serviceIntent);
break;
case R.id.btn_stop_service:
//远程开始服务
stopService(serviceIntent);
break;
case R.id.btn_bind_service:
//远程绑定服务
bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);
break;
case R.id.btn_unbind_service:
//远程解绑服务
unbindService(this);
binder = null;
break;
case R.id.btn_edit_service:
//这里给服务端传值设置
if (binder != null) {
try {
binder.setData("hello");
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
default:
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("TAG", "Bind Service");
Log.d("TAG", "IBinder:" + service);
//这里进行实例化接口,不能强转。
binder = IAppServiceRemoteBinder.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
这里我们通过IAppServiceRemoteBinder.Stub.asInterface()
方法来获取到Service里的Binder,然后就可以直接调用Binder里的setData()
方法了。最后把这启动这个App,点击绑定外部服务按钮,服务就会运行起来了,然后点击发送给值客户端的按钮,就会远程发送一个值给服务端进行接收了。
发送服务端给值效果如图
如果说传输的是一个Object对象的话,就需要对Object进行序列化操作,Java的序列化是继承Serialzable接口,而Android也有一个自己的序列化,就是继承Parcelable接口。
参考
Android中AIDL的理解和使用
[任玉刚] Android开发艺术探索
网友评论