大家都知道Android程序是运行于Dalvik实例的一个进程中,为了让各个Android程序可以相互独立更加安全的运行,彼此之间是内存不共享的,即线程之间是不能够直接通信的。
但有时候为了性能需求,我们的项目在运行的时候可能会开多个进程。如果有一个 Service 进程专门来做某一项工作,如果我们在其他进程中也需要调用其功能,我们该怎么来做呢?这个时候就涉及到了我们今天要说的 IPC (进程间通信)。
AIDL 是 Android 接口定义语言,用来支持 Service 和应用程序组件之间的进程间通信。
提到进程间通信,我们可以回忆下 Android 中可以实现进程间通信的技术:
- 广播:在我们刚接触 Android 的时候肯定都会学到广播是可以跨程序间通信的,而且广播还是连接 Android 组件的纽带。但广播在通信商事属于单向的,例如:我们可以发送广播启动一个Service服务,但不能指望该服务可以直接给我们反馈信息。
- Content Provider:可共享的数据存储器,严格来说这并不算一种进程间的通信。但在某些场景中确实是很不错的工具。
- Messenger:确实是Android为进程间通信提出的解决方案,并且其内部实际也是封装了 IBinder 实现进程间通信。与AIDL不同的时其内部维护了一个Message队列,所有的任务只能一个个的完成。的想要了解的同学可以学习鸿祥前辈的一篇博客,讲解的十分详细。http://blog.csdn.net/lmj623565791/article/details/47017485
注意啦注意啦,Boss提需求了“在我们的Activity可以向位于另一个线程的Service中加入一本书(Book 包括 书籍名和作者两个属性)也可以从其中读取Book列表“。今天就通过这个简单的例子来认识一下AIDL.
①创建我们的 Book 类
AIDL支持的数据类型:
- Java 基本数据类型(int, boolean, float, char等)
- String 和 CharSequence值
- List(包含泛型),其中每一个元素都是支持的类型。接收类将总是接收实例化为ArrayList的List对象
- Map(不包含泛型),其中每一个键和元素都是支持的类型。接收类将总是接收实例化为HashMap 的 Map 对象
- 实现Parcelable接口的类,这些类型总是需要 import 导入
实现序列化的工程在此不再累赘,值得注意的一点是,要穿件一个与我们序列化的类名相同的aidl文件并在里边声明序列化。
同名 aidl 文件比较简单
package com.ygl.aidl;
//注意修饰词 parcelable 的 p 为小写字母
parcelable Book;
<script type="math/tex" id="MathJax-Element-26"> </script> 代 码 结 构 图
②创建aidl接口
在Android Studio 中直接新建AIDL文件,会自动生产一个aidl的目录,值得注意的是:接口所在的包的名需要和所在App的 packageName 相同。
AIDL接口跟我们平时所写的接口类语法相似,有所不同的是在AIDL接口类中用到的所有数据类型除了原始数据类型其他的都是需要导入的,即便是在同一个包内。
package com.ygl.aidl;
//Book 类与我们在同一个包内,但我们任需导入
import com.ygl.aidl.Book;
interface IBookInterface {
void addBook(in Book book);
List<Book> getBooks();
}
编写完毕后 点击 Make Project (ctrl+F9),便会自动生成 IBookInterface 接口,我们在第三部中将会用到。我们先学习他的用法,随后在对该接口进行详细讲解。
③创建我们的 Service
继承系统的Service组件实现我们的自定义Service
public class MyService extends Service{
private List<Book> books = new ArrayList<>();
/*创建一个 自动生成的 IBookInterface 的内部抽象类 Stub 的实例对象,并
* 重写其中的抽象方法。
* Stub 继承自 Binder ,是一个 Binder 对象,我们在 Activity 中将利用其实例
* 对象来对方法进行调用。
*/
private IBinder onBookManager = new IBookInterface.Stub(){
//其实重写的两个方法就是我们在 aidl 中定义的方法
@Override
public void addBook(Book book) throws RemoteException {
Log.v("ygl","add book");
books.add(book);
}
@Override
public List<Book> getBooks() throws RemoteException {
Log.v("ygl","return all books");
return books;
}
};
@Override
public IBinder onBind(Intent intent) {
//返回我们的 Stub 实例对象
return onBookManager;
}
@Override
public void onCreate() {
super.onCreate();
Log.v("ygl","Service onCreate");
}
}
因为我们要进行跨进程间通信,所以在注册我们的 Service 时,通过 process 属性,让其运行在单独的进程中。
<service android:name="com.ygl.service.MyService"
android:process=":MyService">
<intent-filter>
<action android:name="com.ygl.service.interface"/>
</intent-filter>
</service>
④创建我们的 Activity
public class MainActivity extends AppCompatActivity {
//创建接口对象
private IBookInterface bookInterface;
private EditText bookName,bookAuthor;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v("ygl","service connected");
//可以通 IBinder 获得我们想要的接口实例对象,是我们 Activity 部分的重点
bookInterface = IBookInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v("ygl","service disconnected");
bookInterface = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bookName = (EditText) findViewById(R.id.tv_book_name);
bookAuthor = (EditText) findViewById(R.id.tv_book_author);
Intent intent = new Intent();
intent.setAction("com.ygl.service.interface");
//Android 5.0 之后隐式启动时禁止的,需要加上这句话
intent.setPackage(getPackageName());
bindService(intent, conn, BIND_AUTO_CREATE);
}
public void onClick(View view){
switch (view.getId()){
case R.id.btn_add:
try {
String name = bookName.getText().toString();
String author = bookAuthor.getText().toString();
Book book = new Book( name, author);
//直接通过我们获得接口实例,调用接口中的方法
/*需要注意的是:通过 AIDL 实现的通信全部都是同步的,因为我们这做的操作比较简单不耗时。
* 如果是一些可能耗时的操作一定要在子线程中做,要不很可能导致 ANR
*/
bookInterface.addBook(book);
Log.i("ygl","加入 Book "+book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_get:
try {
List<Book> books = bookInterface.getBooks();
for(Book book : books){
Log.i("ygl",book.toString()+"\n");
}
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
在 Activity 中并没有什么很特别的操作,到此为止我们的小 AIDL Dome 便完成了。
一起来看下效果吧!!运行起来妥妥的。
可能许多同学在看完以后虽然也能够使用,但是仍不明白其中的道理,在后续我会尽快的带大家从代码的角度来详细的理解AIDL。
最后附上项目git地址:https://git.coding.net/ygl12337/Android_AIDL.git