本文就不是抄的别人的博客,而是抄书了
主要讲的是 监听手段实现自动回复和注册注解监听
前言
在上篇文章中,我们是调用接口方法getbooklist()来被动查询服务端的数据,那么我们怎么能做到服务端数据只要改变就能主动通知客户端呢?
好,我们等下在服务中加个线程,每5秒添加一本书,然后通知客户端
那么我们经常用的就是设置监听这个方法。无非就是老套路:
1. 声明监听。当然这里稍微有点不同就是用aidl文件来创建,毕竟这个接口要跨进程。
// autoReplyListener.aidl
package com.kt.ktservice;
import com.kt.ktservice.Book;
// Declare any non-default types here with import statements
interface autoReplyListener {
void onAutoReply(in Book book);
}
2. 给服务加上注册接口,作用是只有注册的客服端才能调用服务,也加上注销接口。
// BookController.aidl
package com.kt.ktservice;
import com.kt.ktservice.Book;
import com.kt.ktservice.autoReplyListener;
// Declare any non-default types here with import statements
interface BookController {
List<Book> getBookList();
void addBookInOut(in Book newbook);
void registListener(autoReplyListener l);
void unRegistListener(autoReplyListener l);
}
3. 完成BookeController里新加的两个接口
这里将服务端代码全部列出
public class AIDLService extends Service {
private final String TAG = "yjm";
private List<Book> bookList;
//重点!!!!!!!!!!!!!
private RemoteCallbackList<autoReplyListener> autoReplyListenerRemoteCallbackList
=new RemoteCallbackList<>();
//这个暂且不懂
private AtomicBoolean b=new AtomicBoolean(false);
public AIDLService() {
}
@Override
public void onCreate() {
super.onCreate();
bookList=new ArrayList<>();
initDate();
}
private void initDate() {
Book book1 = new Book("1");
Book book2 = new Book("2");
bookList.add(book1);
bookList.add(book2);
new Thread(new Runnable() {
@Override
public void run() {
/ /用while(true)不行,不懂为什么,就先抄着
while (!b.get()) {
try {
Thread.sleep(5000);
int i = bookList.size() + 1;
Book book = new Book("new book#" + i);
bookList.add(book);
sendAllListener(book);
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
private final BookController.Stub stub = new BookController.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return bookList;
}
@Override
public void addBookInOut(Book book) throws RemoteException {
if (book != null) {
book.setName("服务器收到了提交的新书");
bookList.add(book);
} else {
Log.e(TAG, "接收到了一个空对象 InOut");
}
}
@Override
public void registListener(autoReplyListener l) throws RemoteException {
autoReplyListenerRemoteCallbackList.register(l);
Log.e("yjm","success");
}
@Override
public void unRegistListener(autoReplyListener l) throws RemoteException {
autoReplyListenerRemoteCallbackList.unregister(l);
Log.e("yjm","defeat");
}
};
//遍历通知所有客户端
private void sendAllListener(Book newbook){
if(newbook!=null){
//遍历和查询的方式稍微不同
int N = autoReplyListenerRemoteCallbackList.beginBroadcast();
for (int i=0;i<N;i++) {
autoReplyListener l
= autoReplyListenerRemoteCallbackList.getBroadcastItem(i);
if(l!=null) {
try {
l.onAutoReply(newbook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
//和beginBroadcast配对使用
autoReplyListenerRemoteCallbackList.finishBroadcast();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
好,那么这里有个重点也是难点,请看这句话
private RemoteCallbackList<autoReplyListener> autoReplyListenerRemoteCallbackList=new RemoteCallbackList<>();
我直接说吧,这个RemotoCallbackList有特殊作用。我来看一下如果用普通的List会怎么样。
如果是以往单进程的话,注册时传入的监听器是哪一个,注销的我们也会传入哪一个,保证注销的就是之前注册的那个监听器。但是在我们这个例子中,注册传入的监听器和注销传入的监听器经过不同的接口(registListener(autoReplyListener l),unRegistListener(autoReplyListener l))跨进程通信,会在服务端会产生两个新的对象!!,虽然他们的内容一样,但不是同一个对象。那么能在容器中找到之前注册的那个监听器注销吗?不能
所以我们用到RemotoCallbackList,它是系统专门提供的用于删除跨进程listener的接口,支持管理任意的AIDL接口。
public class RemoteCallbackList<E extends IInterface>
它的原理是,虽然说从客户端跨进程传过来的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,就是他们底层的Binder对象是同一个!。所以,我们注销的时候遍历监听器,找出和那个当初注册的监听器的Binder是一样的就可以了,然后把它从容器中移除。而这件事正是RemoteCallbackList帮我做的。除此之外,其内部还自动实现了线程同步,所以我们注册注销的时候就不用做额外的线程同步工作。调用它的方法就好了。
服务端还要提一个最后的点就是:RemoteCallbackList的两个方法beginBroadCast和finishBroadCast必须要成对使用,哪怕只是想获得其中元素的个数。然后,嗯,不要叫它容器。。。
4.客户端注册注销
客户端就简单了,做好注册注销监听
客户端代码
public class MainActivity extends AppCompatActivity {
public BookController bookController;
public boolean connect;
public List<Book> bookList;
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==3){
Log.e("yjm","new bookname="+msg.obj);
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setPackage("com.kt.ktservice");
intent.setAction("com.kt.abc");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void getshu(View view) {
if(connect) {
try {
bookList = bookController.getBookList();
Log.e("////////////", "");
log();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public void change(View view) {
if(connect) {
try {
bookController.addBookInOut(new Book("这是新书"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookController = BookController.Stub.asInterface(service);
try {
bookController.registListener(l);
} catch (RemoteException e) {
e.printStackTrace();
}
connect=true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
try {
bookController.unRegistListener(l);
} catch (RemoteException e) {
e.printStackTrace();
}
connect=false;
}
};
private autoReplyListener l=new autoReplyListener.Stub(){
@Override
public void onAutoReply(Book book) throws RemoteException {
mHandler.obtainMessage(3,book).sendToTarget();
}
};
private void log(){
for (Book b: bookList) {
Log.e("YJM",b.toString());
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected void onStop() {
unbindService(serviceConnection);
super.onStop();
}
}
网友评论