要跟上刚哥的小弟们的步伐了, 少打游戏多读书
本文参考: 《Android开发艺术探索》
以前写过AIDL代码层的文章,暂时不对代码层面的AIDL以及生成的代码来解读了。
通过文件
可以通过多个进程同时读写同一个文件。但这种情况下的并发是不安全的。所以建议在对于同步要求不高的情况下使用文件读写。同时设计一系列策略来避免并发问题,如文件标识,时间戳等。
但是不建议使用SharePreference来进行文件通信,因为系统对于Sp的低些有一定的缓存策略,也就是说在内存中会有Sp的一份缓存。这样一来,在并发的情况下有很大几率丢失数据。
通过Messenger
Messenger可以在不同进程中传递Message对象, 可以视为信使. 它的本质是对AIDL进行了封装 . 同时 , 由于它一次只处理一个请求 , 所以不用考虑同步的问题 . 可以视为简单的一次性消息.
来看构造方法:
public final class Messenger implements Parcelable {
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
恩 ,asInterface方法 ,说明有AIDL的影子.
现在我们模拟情景:
我们在Activity中启动服务 , 启动完成后 , aty想Service发送消息 , Service在接受到消息后 , 进行回应. 同时要保证Service在子进程中跑.
这里的消息都只是一次发送 , 所以我们可以简单用来练手Messenger的使用.
- 创建服务的框架
在服务端创建Service, 同时使用Handler来创建Messenger对象, 最后在onBind方法中返回Messenger对应的binder
public class MessengerService extends Service {
private String TAG = getClass().getSimpleName();
private Messenger messenger = new Messenger(new MyHandler(this));
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
static class MyHandler extends Handler {
WeakReference<Context> weakReference;
MyHandler(Context context) {
if (weakReference == null)
weakReference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MessengerService context = (MessengerService) weakReference.get();
Log.e(context.TAG, "handleMessage: "+msg.getData().getString("msg"));
super.handleMessage(msg);
}
}
}
由于服务是需要运行在子进程中的 , 我们记得要在清单中配置进程:
<service
android:name="ziye.service.MessengerService"
android:process=":messenger"/>
- 客户端发送消息
绑定服务不必说, 在完成绑定后, 根据返回的binder对象创建Messenger对象 , 然后使用该对象发送消息.
private ServiceConnection createConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger=new Messenger(service);
Message msg=Message.obtain();
Bundle data=new Bundle();
data.putString("msg","from main process");
msg.setData(data);
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
输出结果如图所示
在messenger进程中成功输出目标字符串. 接下来我们进行完善 .
既然服务端是通过Handler来接收数据 , 我们也使用类似的方法来在主进程中接收
修改服务端的Handler的代码:
public void handleMessage(Message msg) {
MessengerService context = (MessengerService) weakReference.get();
Log.e(context.TAG, "handleMessage: "+msg.getData().getString("msg"));
// 添加如下的代码用于回应消息
Messenger replyTo = msg.replyTo;
Message msgs=Message.obtain();
Bundle bundle=new Bundle();
bundle.putString("reply","I got`t");
msgs.setData(bundle);
try {
replyTo.send(msgs);
} catch (RemoteException e) {
e.printStackTrace();
}
super.handleMessage(msg);
}
服务端很简单, 拿到message后取出Messenger ,然后对messenger重新设置消息并发送出去 , 接下来修改主进程中
private Messenger mGetReplyMessenger=new Messenger(new MyHandler());
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
Log.e( "handleMessage: ", msg.getData().getString("reply"));
super.handleMessage(msg);
}
}
private ServiceConnection createConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger=new Messenger(service);
Message msg=Message.obtain();
Bundle data=new Bundle();
data.putString("msg","from main process");
msg.setData(data);
// 指定replyTo的messenger
msg.replyTo=mGetReplyMessenger;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
运行结果如图所示:
至此 , 使用Messenger来进行进程间通讯的需求完成 .
使用Messenger来进行进程间通讯是很方便的事情 , 我把图画出来
话说回来,我们的信息载体是Message , 还记得Message支持那些数据的传输么? what arg1 arg2 Bundle obj replyTo. 这里不要一看obj就想用了 , 在Android 2.2以前 ,obj是不被支持传输的 , 2.2之后 , 也只有系统提供的实现了Parcelable接口的对象才可以参与传输 , 自定义的Parcelable字段是无法进行传输的 . 这种情况下 , 还是使用Bundle吧.
通过AIDL
Messenger是不是美滋滋? 简单是简单,但同时有一些事情是无法完成的. 比如: Messenger是一次性的消息 , 无法处理大量消息的并发; 或者我们需要调用跨进程的服务端的方法 , Messenger也是无法做到的. 所以我们也有必要来看看最底层的AIDL
模拟情景:
在远程服务端初始化Book的集合 , 然后提供getBookList方法 , 最后在主进程中调用该方法获取集合.
- AIDL接口创建: 声明一个.aidl文件, 在文中提供addBook与getBookList的方法. AIDL文件最终可以对应的接口 , 并支持多进程调用. 但是我们发现 , 在AIDL中使用了Book这个类 , 所以除了要创建对应的Book.aidl之外 , 还要再接口aidl中显示的import.
首先编写Book类与Book.aidl
Book.java
public class Book implements Parcelable {
private String name;
private int bookId;
protected Book(Parcel in) {
name = in.readString();
bookId = in.readInt();
}
public Book(int bookId, String name) {
this.name = name;
this.bookId = bookId;
}
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(bookId);
}
@Override
public String toString() {
return "bookId="+bookId+" bookName="+name;
}
}
Book.aidl文件中 , 只需要声明一个parcelable类型的Book
package ziye.skintest;
parcelable Book;
接下来编写接口文件IBookAidl:
package ziye.skintest;
import ziye.skintest.Book;
// Declare any non-default types here with import statements
interface IBookAidl {
List<Book> getBookList();
void addBook(in Book book);
}
注意这些文件的位置 , 必须在aidl文件下
这时候编译一次即可生成对应的接口文件了.
在AIDL中并不是支持所有的数据类型
- 基本数据类型
- String CharSequence;
- List, 只支持ArrayList , 且元素被AIDL支持
- Map , 只支持HashMap, 且元素被AIDL支持
- Parcelable
- AIDL , 接口也可以作为参数传递
同时 , 为了保证aidl中class文件的编译正确性 , 我们需要在app/build.gradle中添加:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
- 服务端
服务端创建一个服务 , 并编写相应的方法 . 然后声明创建好的AIDL文件 , 并实现改接口
/**
* 充当server端
*/
public class BookManagerService extends Service {
private final String TAG = getClass().getSimpleName();
private CopyOnWriteArrayList<Book> lists;
private Binder binder;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
lists = new CopyOnWriteArrayList<>();
binder = new IBookAidl.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return lists;
}
@Override
public void addBook(Book book) throws RemoteException {
lists.add(book);
}
};
lists.add(new Book(1, "安卓艺术探索 1"));
lists.add(new Book(2, "安卓艺术探索 2"));
}
/**
* @time 2018/11/28 0028 10:24
*/
private void addBook(Book book) throws RemoteException {
lists.add(book);
}
}
看到这有人会问了 , 不是说List只支持ArrayList么 , 这里怎么用了CopyOnWriteArrayList啊 . 上文所说的是支持List , List是一个接口类型 ,声明了几个公共的方法 , 这里说的支持 , 是支持传输 , 用啥您随意 , 只要传输符合ArrayList就可以了 . 同时在注册时指定进程名
<service android:name="ziye.aidls.BookManagerService"
android:process=":book"/>
- 客户端
客户端首先要绑定一个服务 , 必然要使用ServiceConnection . 在绑定完成之后 , 我们拿到AIDL对应的接口来调用接口的方法
public class FourActivity extends Activity {
private Button btnCreateRemote;
private ServiceConnection connection;
IBookAidl iBookAidl;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_four);
initBase();
initListener();
}
private void initBase() {
btnCreateRemote = findViewById(R.id.btn_service_remote);
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookAidl = IBookAidl.Stub.asInterface(service);
try {
List<Book> bookList = iBookAidl.getBookList();
Log.e("fourAty", "onServiceConnected: " + bookList.toString());
//绑定完成后在服务端进行添加一本书
Book newBook = new Book(3, "艺术探索3");
iBookAidl.addBook(newBook);
//添加之后再请求一次列表
List<Book> bookList1 = iBookAidl.getBookList();
Log.e("fourAty", "onServiceConnected: added " + bookList1.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
private void initListener() {
btnCreateRemote.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FourActivity.this, BookManagerService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
}
@Override
protected void onDestroy() {
if (iBookAidl != null && iBookAidl.asBinder().isBinderAlive()) {
//binder存活时
try {
unbindService(connection);
} catch (RemoteException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
}
主进程运行结果
好了我们再加需求: 当有新书加入的时候 , 远程进程通知主进程更新
如果是同一个进程 , 我们就可以直接用观察者模式 , 一个回调就搞定了 . 那么这边能不能这么干呢? 诶 , AIDL不止可以支持AIDL作为参数嘛 , 我们就直接用接口回调试试看.
- 创建一个新的接口用于回调IBookArrivedListener.aidl
package ziye.skintest;
import ziye.skintest.Book;
// Declare any non-default types here with import statements
interface IOnNewBookArrivedListener {
// void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
// double aDouble, String aString);
void onNewBookArrived(in Book book);
}
- 补充IBookAidl , 新增注册于注销的方法 , 注意要手动import
package ziye.skintest;
import ziye.skintest.Book;
import ziye.skintest.IOnNewBookArrivedListener;
interface IBookAidl {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
- 服务端: 定义一个接口容器 , 存放参与注册的接口 , 然后每5秒添加一本书 , 在添加书的方法中 , 进行接口回调.
public class BookManagerService extends Service {
private final String TAG = getClass().getSimpleName();
private CopyOnWriteArrayList<Book> lists;
private CopyOnWriteArrayList<IOnNewBookArrivedListener> listeners;
private Binder binder;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
lists = new CopyOnWriteArrayList<>();
listeners = new CopyOnWriteArrayList<>();
binder = new IBookAidl.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return lists;
}
@Override
public void addBook(Book book) throws RemoteException {
lists.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
//普通list时
if (listeners.contains(listener)) {
Log.e(TAG, "already register: ");
} else {
listeners.add(listener);
Log.e(TAG, "registerListener: " + listeners.size());
}
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
//普通list时
listeners.remove(listener);
}
};
lists.add(new Book(1, "安卓艺术探索 1"));
lists.add(new Book(2, "安卓艺术探索 2"));
new Thread(new ServiceWoker()).start();
}
/**
* @time 2018/11/28 0028 10:24
* @desc 加入一本书, 并通知注册监听
*/
private void addBook(Book book) throws RemoteException {
lists.add(book);
//普通list时
for (int i = 0; i < listeners.size(); i++) {
IOnNewBookArrivedListener listener = listeners.get(i);
Log.e(TAG, "addBook: notify listener" + book.toString());
listener.onNewBookArrived(book);
}
}
/**
* 开启线程 , 模拟5秒加入一本书
*/
private class ServiceWoker implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000);
int bookId = lists.size() + 1;
Book newBook = new Book(bookId, "Crazy" + bookId);
//添加一本书
Log.e(TAG, "服务端生成一本书: " + newBook.toString());
addBook(newBook);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
- 客户端 . 接下来 , 要在绑定服务后注册接口.
public class FourActivity extends Activity {
private Button btnCreateRemote;
private ServiceConnection connection;
IBookAidl iBookAidl;
private MyHandler handler;
private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
Message msg = Message.obtain();
msg.obj = book;
handler.sendMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_four);
initBase();
initListener();
}
private void initBase() {
btnCreateRemote = findViewById(R.id.btn_service_remote);
handler = new MyHandler();
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookAidl = IBookAidl.Stub.asInterface(service);
try {
List<Book> bookList = iBookAidl.getBookList();
Log.e("fourAty", "onServiceConnected: " + bookList.toString());
//绑定完成后在服务端进行添加一本书
Book newBook = new Book(3, "艺术探索3");
iBookAidl.addBook(newBook);
List<Book> bookList1 = iBookAidl.getBookList();
Log.e("fourAty", "onServiceConnected: added " + bookList1.toString());
//注册监听
iBookAidl.registerListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
private void initListener() {
btnCreateRemote.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FourActivity.this, BookManagerService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
//监听完成后 , 在Handler中输出信息
Log.e("handleMessage: ", msg.obj.toString());
}
}
@Override
protected void onDestroy() {
if (iBookAidl != null && iBookAidl.asBinder().isBinderAlive()) {
//binder存活时
try {
iBookAidl.unregisterListener(listener);
unbindService(connection);
} catch (RemoteException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
}
有新书时通知主进程
需求完成. 我们按返回键退出这个activity.....突然就崩溃掉了....
啥玩意儿 , 说我们服务未注册? 这说明我们主进程销毁的时候 , 子进程还在往主进程发送消息 . 但是我们明明在主进程onDestroy的时候注销了 , 怎么没有起作用?
这是因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象. 对象本身是不能跨进程的 , 必须要通过序列化与反序列化 .
接下来我们就要使用RemoteCallbackList来代替CopyOnWriteList来改造一下监听容器了.
private RemoteCallbackList<IOnNewBookArrivedListener> listeners;
listeners = new RemoteCallbackList<>();
//注册时
listeners.register(listener);
//注销时
listeners.unregister(listener);
//通知监听时
int N = listeners.beginBroadcast();
for (int i=0;i<N;i++){
IOnNewBookArrivedListener listenersBroadcastItem = listeners.getBroadcastItem(i);
if (listenersBroadcastItem!=null){
listenersBroadcastItem.onNewBookArrived(book);
}
}
listeners.finishBroadcast();
注意在遍历RemoteCallbackList的时候, 先beginBroadcast , 遍历完成要finishBroadcast . 毕竟RemoteCallbackList本质上并不是list . 这两盒函数必须配对使用.
客户端在调用远程方法的时候,被调用的方法运行在服务端的Binder线程池中,同时客户端被挂起。如果服务端执行时间较长,客户端会被长时间阻塞,如果是UI线程发起的请求,会直接导致ANR异常。所以我们要避免直接在UI线程中发起耗时的跨进程请求。而服务端本身就可以执行耗时操作,无需再新建线程。同理,不能再服务端调用客户端的耗时请求。
在真机环境下,也有可能会因为各种原因让进程死亡 , 这时候我们就需要重连服务. 有两种方法:
- 通过DeathRecipient监听 , 在客户端的Binder回调 , 是不能访问客户端的UI的 .
- 在onServiceDisconnected中重连 , 在客户端的UI线程回调
网友评论