最近在看一些framework方面的东西,但是最后都绕不过binder,于是我再转向去了解binder的知识,但是看得我一头雾水。唉,仔细想想,我好想都没有正儿八经的写过跨进程通信的代码,那么,我就来手写一个跨进程的demo玩玩,同时也正好记录所遇到的问题。
AIDL
AndroidStudio对AIDL开发支持真的很好啊,在新建文件的时候可以直接选择AIDL文件编写,AS会自动帮我们生成对应的java接口文件。我是从《Android开发艺术探索》上看到的AIDL编程,所以案例也用了同一个。
首先定义Book类和Book.aidl
package com.example.myapplication.aidl;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by 洪彬 on 2020-05-16.
*/
public class Book implements Parcelable {
private String bookName;
private String author;
public Book() {
}
protected Book(Parcel in) {
bookName = in.readString();
author = in.readString();
}
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(bookName);
dest.writeString(author);
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package com.example.myapplication.aidl;
parcelable Book;
需要注意的有两点:Book因为是要自定义的类,所以要实现Parcelable接口,并补全相关代码;Book.java和Book.aidl包路径必须相同。
接着就写“服务端”需要实现的功能了
package com.example.myapplication.aidl;
import com.example.myapplication.aidl.Book;
interface IBookManager {
List<Book> getAllBooks();
void addBook(in Book book);
}
这里需要注意的是在接口类中如果要引用自定义类的时候要import进来。(虽然很像废话,因为不import语法上不会报错,所以我在写的时候忘记引入了)
可以注意到,addBook方法的入参前面有个in,这个是aidl中的tag,指定了数据流的方向,分为in、out、inout,代表客户端流向服务端、服务端流向客户端、双向流动。
好啦,这样写完之后编译一下代码,就能在build目录下找到IBookManager.java,这个java文件就是我们需要的。文件有点长,AndroidStudio帮我们补足了代码。这里也有个注意点,如果gradle找不到这个java文件,我们需要到build.gradle的android{}中加上这么一段,来指定java文件的查找路径:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl']
}
}
========= 一条手动分割线 ==========
写完AIDL之后,下面要用两个app来验证进程间通信,我分为ServiceApp和ClientApp,通过ClientApp向ServiceApp发送请求以及获取结果来验证。
ServiceApp
服务端的核心就是Service,Service中的Stub中实现了getAllBooks()和addBook()两个方法。
public class BookService extends Service {
private List<Book> books = new ArrayList<>();
private IBookManager.Stub manager = new IBookManager.Stub(){
@Override
public List<Book> getAllBooks() throws RemoteException {
return books;
}
@Override
public void addBook(Book book) throws RemoteException {
books.add(book);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return manager;
}
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setBookName("《彬彬仔的Binder之旅》");
book.setAuthor("彬彬仔");
books.add(book);
}
}
ClientApp
public class MainActivity extends AppCompatActivity implements ServiceConnection {
private IBookManager bookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//点击绑定
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName("com.example.myapplication",
"com.example.myapplication.aidl.BookService"));
bindService(serviceIntent,MainActivity.this, Context.BIND_AUTO_CREATE);
}
});
//点击向服务端加一本书
findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
if(bookManager != null){
Book book = new Book();
book.setBookName("《彬彬仔的Binder之旅Ⅱ》");
book.setAuthor("彬彬仔");
bookManager.addBook(book);
}
} catch (Exception e){
e.printStackTrace();
}
}
});
//点击获取服务端所有的书籍,并打印
findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
if(bookManager != null){
List<Book> list = bookManager.getAllBooks();
if(list != null){
for(Book book:list){
Logger.getLogger("ClientApp", book.getBookName() + "-" +book.getAuthor());
}
}
}
} catch (Exception e){
e.printStackTrace();
}
}
});
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookManager = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
bookManager = null;
}
}
从代码上看,ClientApp中的Service在启动的时候回自动添加一本书,在ClientApp中执行获取方法看看实际打印的:
2020-05-16 17:14:31.329 17990-17990/com.nuonuo.myapplication D/ClientApp: 《彬彬仔的Binder之旅》-彬彬仔
2020-05-16 17:14:31.329 17990-17990/com.nuonuo.myapplication D/ClientApp: over
哦豁~
试一下添加再查看:
2020-05-16 17:15:31.011 17990-17990/com.nuonuo.myapplication D/ClientApp: 《彬彬仔的Binder之旅》-彬彬仔
2020-05-16 17:15:31.011 17990-17990/com.nuonuo.myapplication D/ClientApp: 《彬彬仔的Binder之旅Ⅱ》-彬彬仔
2020-05-16 17:15:31.011 17990-17990/com.nuonuo.myapplication D/ClientApp: over
哦豁哦豁哦豁哦豁哦豁哦豁哦豁~
看来是成功了。开心得像个800斤的小孩。
这是进程间通信的一小步,也是我学习binder的一大步。