Android中的IPC方式
1.Bundle
其中Intent就是用Bundle进行传播
四大组件间的进程间通信
2.使用文件共享
2.1文件共享的缺点
无并发,适用于交换简单的数据实时性不高的情景
使用文件共享容易出:
- 内容不一致:如果多个线程同时写入文件,可能会导致文件内容不一致。这是因为多个线程同时写入文件时,写入的顺序和时间不确定,可能会出现覆盖、丢失或重复写入等问题,导致文件内容与预期不符。
- 文件损坏:如果多个线程同时读写文件,可能会导致文件损坏。这是因为多个线程同时读写文件时,可能会出现读写冲突、死锁等问题,导致文件被破坏或无法正常访问。
- 性能下降:如果多个线程同时读/写同一个文件,可能会导致性能下降。这是因为多个线程同时读/写文件时,会竞争文件系统的资源,导致频繁的上下文切换和锁竞争,降低了系统的并发性能和吞吐量。
Sharedpreferences对于其他文件共享来说是个特例,因为它只能被应用程序中的其他组件共享,并且访问权限是应用程序私有的,数据格式也非常简单。不仅如此,系统对它有一定的缓存策略,即在内存中年会有一份Sharedpreferences文件的缓存,在多线程模式下,系统对它的读/写不靠谱,面对高并发的读/写访问,Sharedpreferences有很大概率丢失数据。所以不建议在进程通信中使用Sharedpreferences
3.Messenger
先说服务端无法回应客户端的情况:
3.1服务端无法回应客户端
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"
android:process=":remote" />
先在AndroidManifest中加入:
android:process=":remote"
这样表示多进程
之后在MainActivity
public class MainActivity extends AppCompatActivity {
// 服务端Messenger
private Messenger mServerMessenger;
// 服务端连接状态
private boolean mIsBound = false;
// 绑定服务端
private Button message_0;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mServerMessenger = new Messenger(service);
Message message = new Message();
message.what = 1;
Bundle bundle = new Bundle();
bundle.putString("data","你好啊");
message.setData(bundle);
try {
mServerMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
message_0 = findViewById(R.id.message_0);
// 绑定服务端
if(!mIsBound){
message_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsBound = true;
Intent intent = new Intent(MainActivity.this,MyService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
});
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解绑服务端
unbindService(mConnection);
}
}
在MyService
public class MyService extends Service {
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
Log.d("TAG","revice"+msg.getData().getString("data"));
break;
default:
break;
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
3.2服务端回应客户端
首先是MyService
public class MyService extends Service {
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
Log.d("TAG",msg.getData().getString("data"));
Messenger client = msg.replyTo;
Message replyMessage = new Message();
replyMessage.what = 2;
Bundle bundle = new Bundle();
bundle.putString("TAG","reply"+"我大后台收到你的来信了");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
在
Log.d("TAG",msg.getData().getString("data"));
下面加上
Messenger client = msg.replyTo;
Message replyMessage = new Message();
replyMessage.what = 2;
Bundle bundle = new Bundle();
bundle.putString("TAG","reply"+"我大后台收到你的来信了");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
首先:
Messenger client = msg.replyTo;
msg是一个消息对象。该消息对象有一个replyTo属性
replyMessage.what = 2;
Bundle bundle = new Bundle();
bundle.putString("TAG","reply"+"我大后台收到你的来信了");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
这块代码和之前MainActivity里面差不多
MainActivity部分
public class MainActivity extends AppCompatActivity {
// 服务端Messenger
private Messenger mServerMessenger;
// 服务端连接状态
private boolean mIsBound = false;
// 绑定服务端
private Button message_0;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mServerMessenger = new Messenger(service);
Message message = new Message();
message.what = 1;
Bundle bundle = new Bundle();
bundle.putString("data","你好啊");
message.setData(bundle);
message.replyTo = mGetReplyMessenger;
try {
mServerMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
message_0 = findViewById(R.id.message_0);
// 绑定服务端
if(!mIsBound) {
message_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsBound = true;
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
});
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解绑服务端
unbindService(mConnection);
}
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
private static class MessengerHandle extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 2:
Log.d("TAG",msg.getData().getString("TAG").toString());
}
}
}
}
要加的地方是两个
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
private static class MessengerHandle extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 2:
Log.d("TAG",msg.getData().getString("TAG").toString());
}
}
}
这块和Service那端一样,是用来获取消息的
然后还有这块
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mServerMessenger = new Messenger(service);
Message message = new Message();
message.what = 1;
Bundle bundle = new Bundle();
bundle.putString("data","你好啊");
message.setData(bundle);
message.replyTo = mGetReplyMessenger;
try {
mServerMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
多的只有:
message.replyTo = mGetReplyMessenger;
感觉这块就是为了调用mGetReplyMessenger,否则就没办法显示出来service端发送的消息
以下是我对Messenger的一些理解
3.3Messenger的缺点
Messenger是以串行的方式处理客户端发来的消息。如果大量消息同时发送到服务端,服务端仍然只能一个一个处理。
如果有大量的并发请求,那么用 Messenger 就不太合适了。同时,Messenger经完成的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,
4.AIDL
我们可以使用AIDL来实现跨进程的方法调用。AIDL也this is是Messenger的底层实现,因此 Messenger 本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。
4.1AIDL接口创建
我们先在创建一个Book类,让它实现Parcelable接口
public class Book implements Parcelable {
public int getBookNum() {
return BookNum;
}
private int BookNum;
public String getBookName() {
return BookName;
}
private String BookName;
public Book(String BookName,int BookNum) {
this.BookName = BookName;
this.BookNum = BookNum;
}
protected Book(Parcel in) {
BookNum = in.readInt();
BookName = 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(@NonNull Parcel dest, int flags) {
dest.writeInt(BookNum);
dest.writeString(BookName);
}
}
然后
通过这个创建AIDL接口
我们创建了两个,一个是Book.aidl
一个是IBookManager.aidl
其中Book.aidl的代码如下
package com.example.ipc;
// Declare any non-default types here with import statements
parcelable Book;
表示在 AIDL 接口中定义了一个 Book
类型,该类型实现了 Parcelable
接口,可以被序列化和反序列化。
然后是IBookManager.aidl的代码
import com.example.ipc.Book;
interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book>getBookList();
void addBook(in Book book);
}
注意:虽然Book与IBookManager在同一个包下,但是也得导入
然后我们定义了两个方法,List类型的getBookList()与void类型的addBook()
那么为什么addBook()里面的是in Book book呢?
因为AIDL中除了基本数据类型,其他的参数必须标注上方向
in | 输入类型参数 |
---|---|
out | 输出类型参数 |
inout | 输入输出行参数 |
而AIDL有哪些数据类型呢?
基本数据类型 | 懒得写 |
---|---|
String和CharSequence | |
List | 只支持ArrayList,里面每个元素都必须被AIDL支持 |
Map | 只支持HashMap,里面的key和value都必须被AIDL支持 |
Parcelable | 所有实现了Parcelable接口的对象 |
AIDL | 所有的AIDL接口本身也可以在AIDL文件中使用 |
然后《Android艺术开发探索》中说到这样做完之后得把这个包和包里面的所有文件复制到客户端中
所以
如果你直接在服务端调用IBookManager会发现没有这个类,解决的办法就是把代码跑一下,之后就可以导入成功了
4.2服务端
首先先在AndroidManifest中
<service
android:name=".BookManagerService"
android:enabled="true"
android:process=":remote"
android:exported="true"></service>
之后
public class BookManagerService extends Service {
public BookManagerService() {
}
private CopyOnWriteArrayList<Book>mBookList = new CopyOnWriteArrayList<Book>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book("Android",1));
mBookList.add(new Book("不想上班",2));
}
}
上面是一个服务端 Service 的典型实现,首先在 onCreate 中初始化添加了两本图书息,然后创建了一个 Binder 对象并在 onBind 中返回它,这个对象继承自IBookManager并实现了它内部的AIDL 方法,getBookList和addBook 这两个 AIDL方法的实现,实现过程也比较简单,这里采用了CopyOnWriteArrayList,这个 CopyOnWriteArrayList支持并发读/写。在前面们提到,AIDL 方法是在服务端的 Binder 线程池中执行的,因此当多个客户端同时连糖时候,会存在多个线程同时访问的情形,所以我们要在 AIDL 方法中处理线程同步,而们这里直接使用 CopyOnWriteArrayList 来进行自动的线程同步。
为什么之前说AIDL能使用的List是ArrayList这里却可以用CopyOnWriteArrayList,并且它并不是继承自ArrayList
在 Android 中,AIDL 支持使用的 List 类型默认是
ArrayList
,因为ArrayList
是 Java 中最常用的 List 实现类之一,也是序列化和反序列化比较方便的类。但是,实际上 AIDL 也可以支持其他类型的 List,只要这些 List 类型实现了List
接口并且是可序列化的。这里的List是抽象的,只是一个接口。
虽然Service端是CopyOnWriteArrayList,但是客户端接收到的是ArrayList等会儿就看到了
4.3客户端
4.3.1客户端获得数据
按下按钮
aidl_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsBound = true;
Intent intent = new Intent(MainActivity.this,BookManagerService.class);
bindService(intent,mConnection1,BIND_AUTO_CREATE);
}
});
其中mConnection1的代码如下:
private ServiceConnection mConnection1 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
List<Book>list = iBookManager.getBookList();
Log.d("TAG","list type"+list.getClass().getCanonicalName());
for (int i = 0;i<list.size();i++){
Log.d("TAG","listNum"+list.get(i).getBookNum()+" "+"listName"+list.get(i).getBookName());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
运行的结果如下:
这里类型就变成了ArrayList
但是:
如果
<service
android:name=".BookManagerService"
android:enabled="true"
android:process=":remote"
android:exported="true"></service>
中没有:
android:process=":remote"
那么最后客户端的类型也是copyonWriteArrayList
4.3.2客户端调用addBook()
就放这块的代码
private ServiceConnection mConnection1 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
List<Book>list = iBookManager.getBookList();
Log.d("TAG","list type"+list.getClass().getCanonicalName());
for (int i = 0;i<list.size();i++){
Log.d("TAG","listNum"+list.get(i).getBookNum()+" "+"listName"+list.get(i).getBookName());
}
Book book = new Book("我爱纯爱",3);
iBookManager.addBook(book);
Log.d("TAG","添加后:");
List<Book>list1 = iBookManager.getBookList();
for (int i = 0;i<list1.size();i++){
Log.d("TAG","listNum"+list1.get(i).getBookNum()+" "+"listName"+list1.get(i).getBookName());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
记住两点:
是
iBookManager.addBook(book);
而不是
list.add(book);
如果用下面的这个就没办法将新加的书传递给service端。
得重新创建list,用原先的list读出来还是原先的数据,没有新加的数据。
4.4进阶1(如何每隔一段时间服务端给客户端发送消息)
我们想每隔一段时间,我们就能知晓现在有哪些书
这是一种典型的观察者模式
4.4.1AIDL
在aidl文件夹下面创建IOnNewBookArrivedListener.aidl文件夹
代码如下,
import com.example.ipc.Book;
interface IOnNewBookArrivedListener {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void onNewBookArrived(in Book book);
}
我们的希望是:当服务端有新书到来的时候,会通知每个已经申请提醒功能的用户,
IBookManager.aidl中:
import com.example.ipc.Book;
import com.example.ipc.IOnNewBookArrivedListener;
interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book>getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
4.4.2服务端
public class BookManagerService extends Service {
public BookManagerService() {
}
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book>mBookList = new CopyOnWriteArrayList<Book>();
private CopyOnWriteArrayList<IOnNewBookArrivedListener>mListenerList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(!mListenerList.contains(listener)){
mListenerList.add(listener);
}
else{
Log.d("TAG","already exist");
}
Log.d("TAG","registerListener,size:"+mListenerList.size());
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(mListenerList.contains(listener)){
mListenerList.remove(listener);
Log.d("TAG","unregister listener succeed");
}
else {
Log.d("TAG","not found,can not unregister");
}
Log.d("TAG","unregisterListener,size:"+mListenerList.size());
}
};
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book("Android",1));
mBookList.add(new Book("不想上班",2));
new Thread(new ServiceWorker()).start();
}
private class ServiceWorker implements Runnable{
@Override
public void run() {
while(!mIsServiceDestroyed.get()){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size()+1;
Book newBook = new Book("new book#"+bookId,bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
for(int i =0;i<mListenerList.size();i++){
IOnNewBookArrivedListener listener = mListenerList.get(i);
Log.d("TAG","onNewBookArrived,notify listener:"+listener);
listener.onNewBookArrived(book);
}
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestroyed.set(true);
}
}
先看这块:
4.4.2.1register与unregister
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(!mListenerList.contains(listener)){
mListenerList.add(listener);
}
else{
Log.d("TAG","already exist");
}
Log.d("TAG","registerListener,size:"+mListenerList.size());
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if(mListenerList.contains(listener)){
mListenerList.remove(listener);
Log.d("TAG","unregister listener succeed");
}
else {
Log.d("TAG","not found,can not unregister");
}
Log.d("TAG","unregisterListener,size:"+mListenerList.size());
}
};
我们重写了IBookManager中的registerListener与unregisterListener
mListenerList是一个支持并发读写的IOnNewBookArrivedListener集合
在registerListener中
IOnNewBookArrivedListener的作用前面说了,当这个集合没有listener的时候,把它加入,。
在unregisterListener中
当集合有listener的时候,把它移除
4.4.2.2 oncreate()
在**oncreate()**中,和之前不一样的就是:多了:
new Thread(new ServiceWorker()).start();
就是对开了一个线程,我们想实现每隔一段时间收到一条消息,这么耗费时间的工作就得另开一条线程
4.4.2.3多线程中ServiceWorker
在ServiceWorker中:
private class ServiceWorker implements Runnable{
@Override
public void run() {
while(!mIsServiceDestroyed.get()){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size()+1;
Book newBook = new Book("new book#"+bookId,bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
for(int i =0;i<mListenerList.size();i++){
IOnNewBookArrivedListener listener = mListenerList.get(i);
Log.d("TAG","onNewBookArrived,notify listener:"+listener);
listener.onNewBookArrived(book);
}
}
其中mIsServiceDestroyed是
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
这个操作表示mIsServiceDestroyed提供了原子性操作,初始为false
我们让它在为false的情况下进入while循环,true的时候退出
什么时候为true呢?
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestroyed.set(true);
}
当它退出的时候,我们让它为true
否则一直进入while循环。
看看while循环里面
首先让它每5s休息一次
然后执行
int bookId = mBookList.size()+1;
Book newBook = new Book("new book#"+bookId,bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
我们在这里不断的创建新的Book
然后交由onNewBookArrived(newBook);
4.4.2.4onNewBookArrived()
让它帮我们传递给客户端
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
for(int i =0;i<mListenerList.size();i++){
IOnNewBookArrivedListener listener = mListenerList.get(i);
Log.d("TAG","onNewBookArrived,notify listener:"+listener);
listener.onNewBookArrived(book);
}
}
第一行代码就是把新生成的书加入mBookList
然后
遍历所有已注册的客户端IOnNewBookArrivedListener,将新生成的图书信息发送给每个客户端。对于每个客户端,调用其onNewBookArrived()方法,以将新的图书对象book传递给客户端。
4.4.3客户端
public class MainActivity extends AppCompatActivity {
// 服务端Messenger
private Messenger mServerMessenger;
// 服务端连接状态
private boolean mIsBound = false;
// 绑定服务端
private Button message_0;
private Button aidl_0;
private IBookManager mRemoteBookManager;
private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(555,book).sendToTarget();
}
};
@SuppressLint("HandlerLeak")
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 555:
Log.d("TAG","receive new book:"+msg.obj);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
message_0 = findViewById(R.id.message_0);
aidl_0 = findViewById(R.id.aidl_0);
// 绑定服务端
aidl_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,BookManagerService.class);
bindService(intent,mConnection1,BIND_AUTO_CREATE);
}
});
}
private ServiceConnection mConnection1 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = iBookManager;
List<Book>list = iBookManager.getBookList();
Log.d("TAG","query book list,list type:"+list.getClass().getCanonicalName());
Book book = new Book("好好学习",3);
iBookManager.addBook(book);
Log.d("TAG","Now add book named"+" "+book.getBookName());
Log.d("TAG","添加后");
List<Book>newList = iBookManager.getBookList();
for(int i = 0;i<newList.size();i++){
Log.d("TAG",newList.get(i).getBookName()+" "+newList.get(i).getBookNum());
}
iBookManager.registerListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
// 解绑服务端
if(mRemoteBookManager!=null&&mRemoteBookManager.asBinder().isBinderAlive()){
Log.d("TAG","unregister listener:"+mIOnNewBookArrivedListener);
try {
mRemoteBookManager.unregisterListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
}
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle());
private static class MessengerHandle extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 2:
Log.d("TAG",msg.getData().getString("TAG").toString());
break;
}
}
}
}
客户端不同的地方从这块开始
4.4.3.1ServiceConnection()
private ServiceConnection mConnection1 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = iBookManager;
List<Book>list = iBookManager.getBookList();
Log.d("TAG","query book list,list type:"+list.getClass().getCanonicalName());
Book book = new Book("好好学习",3);
iBookManager.addBook(book);
Log.d("TAG","Now add book named"+" "+book.getBookName());
Log.d("TAG","添加后");
List<Book>newList = iBookManager.getBookList();
for(int i = 0;i<newList.size();i++){
Log.d("TAG",newList.get(i).getBookName()+" "+newList.get(i).getBookNum());
}
iBookManager.registerListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
我们定义了一个全局变量IBookManager属性的mRemoteBookManager让它等于 mRemoteBookManager以便后面**onDestroy()**用
然后就是
iBookManager.registerListener(mIOnNewBookArrivedListener);
了
看mIOnNewBookArrivedListener
4.4.3.2mIOnNewBookArrivedListener
private IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
mHandler.obtainMessage(555,book).sendToTarget();
}
};
当客户端调用远程服务端的 onNewBookArrived
方法时,服务端会将传入的 Book
对象作为消息的 obj
字段,将 what
字段设置为 555,然后通过 mHandler
发送到主线程的消息队列中,以便在主线程中处理该消息并通知相应的监听器。由于该监听器是在客户端注册的,所以通过该监听器发送的消息最终会通知到客户端。
然后消息在
4.4.3.3mHandler实现
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 555:
Log.d("TAG","receive new book:"+msg.obj);
break;
}
}
};
最后就是
4.4.3.4onDestroy()
@Override
protected void onDestroy() {
super.onDestroy();
// 解绑服务端
if(mRemoteBookManager!=null&&mRemoteBookManager.asBinder().isBinderAlive()){
Log.d("TAG","unregister listener:"+mIOnNewBookArrivedListener);
try {
mRemoteBookManager.unregisterListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
}
主要是:
mRemoteBookManager.asBinder().isBinderAlive()
这行代码的作用是检查mRemoteBookManager的底层Binder是否处于活动状态。Binder是Android系统中进程间通信的基本组件,每个Binder都有一个唯一的标识符,它可以用来检查Binder是否仍然可用。因此,通过调用mRemoteBookManager的asBinder()方法,可以获取到其底层Binder对象,然后通过调用isBinderAlive()方法,可以检查该Binder是否处于活动状态。如果Binder已经死亡,则不能再向其发送消息,否则会导致异常。因此,这行代码的作用是确保mRemoteBookManager仍然处于活动状态,以避免发生异常。
4.4.4注意:
IOnNewBookArrivedListener
是一个接口,用于在客户端注册一个回调函数,当有新的书籍信息到达时,远程服务端会通过该接口通知客户端。客户端需要实现该接口的 onNewBookArrived()
方法,用于处理远程服务端传递过来的书籍信息。
4.4.5流程图:
4.5进阶2(如何安全注销)
当我们退出来实现unregisterListener的时候,Log.d()会打印
用aidl解除注册的时候会爆出找不到之前注册的listener
因为Binder会把客户端传递过来的对象重新转化并生成新的对象,虽然注册和解除注册用的是同一个listener但是进入到服务端是另一个
产生两个全新的对象。那应该怎么实现解除注册的功能?
用RemoteCallbackList,它是系统专门提供用于删除跨进程的listener接口。并且在客户端进程终结的时候,能自动移除客户端的listener。并内部自动实现线程同步。
在service端
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
就是把原来的CopyOnWriteArrayList改成RemoteCallbackList
然后之前判断 mListenerList是否存在该IOnNewBookArrivedListener,不存在的话,register部分就把它加进去。
存在的话,unregister就把它remove。但是在这里面,不用进行判断存不存在
直接
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
然后在当有新书时,通知所有已经注册的listener那块:
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for(int i = 0;i<N;i++){
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if(l!=null){
l.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
通过beginBroadcast()
方法获取当前已经注册的监听器数量,并返回一个整型值N
。
通过
ListenerList.getBroadcastItem(i)
获取监听器l,并判断是否为空,不为空的话就把它发送给客户端。
改进之后
成功退出。
4.5.1注意:
为什么RemoteCallbackList可以保证客户端和服务端传递的是同一个listener?
chatpgt给出的解释是:
RemoteCallbackList 通过维护客户端传递进来的 IInterface 对象的列表,并根据这些对象生成相应的 ICallback 类型的对象实例,将它们返回给客户端,从而确保服务端和客户端得到的是同一个 listener 对象的实例。
具体来说,RemoteCallbackList 通过一个底层的 Map 数据结构,将传递进来的 IInterface 对象和 ICallback 类型的对象实例一一映射。当客户端需要注册一个 ICallback 时,RemoteCallbackList 首先根据传递进来的 IInterface 对象在 Map 中查找对应的 ICallback 实例,如果找到了就返回这个实例,否则就创建一个新的 ICallback 实例,并将 IInterface 对象和 ICallback 实例一一对应存储在 Map 中,然后返回新创建的 ICallback 实例给客户端。
Map的key是IBinder类型,value是Callback类型
4.6进阶3(处理耗时的情况)
如果我在service端中在getBookList中让它每5s才返回一次
@Override
public List<Book> getBookList() throws RemoteException {
SystemClock.sleep(5000);
return mBookList;
}
如果你这时候打开app并连续按按钮,会出现:
即ARN
为了解决这个问题,我们在客户端调用getBookList的地方加入多线程就可以了
4.7防止Binder意外死亡
4.7.1给Binder设置DeathRecpient监听
4.7.1.1 服务端
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// 当客户端与服务端的连接断开时,系统会自动调用该方法
// 在这里进行一些清理工作,例如注销监听器、关闭数据库等
}
};
4.7.1.2客户端
在客户端中,需要实现相应的Binder.DeathRecipient接口,然后在获取远程服务时,调用linkToDeath()
方法将该接口注册到远程Binder中。
private ServiceConnection mConnection = new ServiceConnection() {
...
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mRemoteService = IRemoteService.Stub.asInterface(service);
try {
// 注册DeathRecipient监听
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// 当与远程服务断开连接时,系统会自动调用该方法
// 在这里进行相应的重连操作
}
};
这样,在客户端与服务端之间的连接意外断开时,系统会自动调用客户端中注册的DeathRecipient接口的binderDied()
方法,我们可以在这个方法中进行一些处理,例如重连操作。
4.7.2onServiceDisconnected
在这里面重连就行了
而二者的区别就是前者不能访问UI
4.8AIDL如何进行权限验证
现在AndroidManifest申明权限
然后service的onBind()调用
5.ContentProvider
ContentProvider生来就是用来进程间通信的,它在底层实现的是Binder
《Android艺术开发探索》中主要用的是它来获取sqLite数据库里面的数据进行展示,为了展示多进程间的通信,ContentProvider也是被设置为
android:process=":provider
我们先用一个简单的样例讲述ContentProvider
5.1初级
public class BookProvider extends ContentProvider {
@Override
public boolean onCreate() {
Log.d("TAG","onCreate,current thread:"+Thread.currentThread().getName());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d("TAG","query,current thread:"+Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d("TAG","getType");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d("TAG","insert");
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d("TAG","delete");
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d("TAG","update");
return 0;
}
}
让一个类继承ContentProvider会重写这些方法
然后在AndroidManifest中
<provider
android:authorities="com.example.ipc.BookProvider"
android:name=".BookProvider"
android:permission="com.example.ipc.PROVIDER"
android:process=":provider">
</provider>
上面的两个我输入一会儿就有api显示,应该就是根据你的类名固定了,
但是permission感觉随便写就行,只要符合命名规范
别忘了下面的process
然后就是MainActivity
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
contentProvider_0.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = Uri.parse("content://com.example.ipc.BookProvider");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
}
});
}
});
thread.start();
Uri uri = Uri.parse("content://com.example.ipc.BookProvider")
其中括号里面的就写为AndroidManifest中的authorities就行了
getContentResolver()
是 Android 中的一个方法,用于获取应用程序的内容解析器(ContentResolver)对象。内容解析器提供了对应用程序数据提供者的访问和操作功能
点击按钮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ax4rPfLl-1684168988786)(…/…/assets/QQ图片20230515205612.png)]
没截图截全
其实还有一个是12582_1
执行了3个
getContentResolver().query(uri,null,null,null,null);
然后3个不在同一个线程执行
5.2进阶
5.2.1创建数据库
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME ="user";
private static final int DB_VERSION = 1;
//图书和用户信息表
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "+
BOOK_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "+
USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT,"+"sex INT)";
public DbOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
其中
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "+
BOOK_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT)";
只在该表不存在时才创建一个:第一列名为 _id
,数据类型为 INTEGER
,并且被指定为主键(PRIMARY KEY)。第二列名为 name
,数据类型为 TEXT
,用于存储文本数据的且**名为:**BOOK_TABLE_NAME
下面
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "+
USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY," + "name TEXT,"+"sex INT)";
然后在
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
创建CREATE_BOOK_TABLE与CREATE_USER_TABLE数据库
5.2.2ContentProvider
public class provider extends ContentProvider {
private Context mContext;
private SQLiteDatabase mDb;
public static final String AUTHORITY = "com.example.ipc.provider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/user");
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
}
private String getTableName(Uri uri){
String tableName = null;
switch (sUriMatcher.match(uri)){
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:break;
}
return tableName;
}
@Override
public boolean onCreate() {
Log.d("TAG","onCreate,current thread:"+Thread.currentThread().getName());
mContext = getContext();
initProviderData();
return true;
}
private void initProviderData(){
mDb = new DbOpenHelper(mContext,AUTHORITY,null,1).getWritableDatabase();
mDb.execSQL("delete from "+DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from "+DbOpenHelper.USER_TABLE_NAME);
mDb.execSQL("insert into book values (3,'Android');");
mDb.execSQL("insert into book values (4,'iOS');");
mDb.execSQL("insert into book values (5,'HarmonyOs');");
mDb.execSQL("insert into user values (1,'jack',1);");
mDb.execSQL("insert into user values (2,'hi',0);");
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d("TAG","query,current thread:"+Thread.currentThread().getName());
String table = getTableName(uri);
if(table==null){
throw new IllegalArgumentException("Unsupport URI:"+uri);
}
return mDb.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d("TAG","getType");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d("TAG","insert");
String table =getTableName(uri);
if(table==null){
throw new IllegalArgumentException("Unsupport URI:"+uri);
}
mDb.insert(table,null,values);
mContext.getContentResolver().notifyChange(uri,null);
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d("TAG","delete");
String table =getTableName(uri);
if(table==null){
throw new IllegalArgumentException("Unsupport URI:"+uri);
}
int count =mDb.delete(table,selection,selectionArgs);
if(count>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return count;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d("TAG","update");
String table = getTableName(uri);
if(table==null){
throw new IllegalArgumentException("Unsupport URI:"+uri);
}
int row = mDb.update(table,values,selection,selectionArgs);
if(row>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return row;
}
}
其中:
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
}
声明了一个静态的 UriMatcher
对象 sUriMatcher
,并使用 UriMatcher.NO_MATCH
作为构造函数的参数初始化它。UriMatcher.NO_MATCH
表示默认的不匹配值。
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
:将AUTHORITY
和 “book” 这个路径关联起来,并为它们设置一个对应的BOOK_URI_CODE
。这意味着当传入的Uri
与这个路径匹配时,可以通过BOOK_URI_CODE
来识别和处理。sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
:将AUTHORITY
和 “user” 这个路径关联起来,并为它们设置一个对应的USER_URI_CODE
。这意味着当传入的Uri
与这个路径匹配时,可以通过USER_URI_CODE
来识别和处理。
后面没啥说的
5.2.3MainActivity
contentprovier_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri bookUri = Uri.parse("content://com.example.ipc.provider/book");
ContentValues values = new ContentValues();
values.put("_id",6);
values.put("name","程序设计的艺术");
getContentResolver().insert(bookUri,values);
Cursor bookCursor = getContentResolver().query(bookUri,new String[]{"_id","name"},null,null,null);
while(bookCursor.moveToNext()){
Book book = new Book(bookCursor.getString(1),bookCursor.getInt(0));
Log.d("TAG","query book:"+book.toString());
}
bookCursor.close();
Uri userUri = Uri.parse("content://com.example.ipc.provider/user");
Cursor userCursor = getContentResolver().query(userUri,new String[]{"_id","name","sex"},null,null,null);
while(userCursor.moveToNext()){
User user = new User();
user.userName = userCursor.getString(1);
user.userId = userCursor.getInt(0);
user.isMale = userCursor.getInt(2) ==1;
Log.d("TAG","query user:"+user.toString());
}
userCursor.close();
}
});
6.Socket
我之前写聊天室就是这么用的
客户端
socket.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
Intent intent = new Intent(MainActivity.this, SocketService.class);
startService(intent);
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("10.0.2.2", 5223);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("你好啊!");
// 发送消息给服务器
Log.d("TAG", "Client端" + " 收到Service端发的消息:" + in.readLine());
// 接收服务器的回应消息
socket.close();
} catch (IOException e) {
e.printStackTrace();
Log.d("TAG", "客户端问题");
}
}
}
});
thread.start();
}
});
服务端
@Override
public void onCreate() {
super.onCreate();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(5223);
Log.d("TAG","Service端成功");
try {
Socket clientSocket = serverSocket.accept();
Log.d("TAG","Service端:"+" "+"Client端等待连接");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
out.println("服务端收到");
String message = in.readLine();
Log.d("TAG","Service端:"+" "+"收到Client端来信"+message);
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
Log.d("TAG","不知道");
}
finally {
if (serverSocket != null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
Log.d("TAG","1666不行");
e.printStackTrace();
}
Log.d("TAG","Service端:"+" "+"Service端开启");
}
});
thread.start();
}
}
但是我只能打印出
Log.d(“TAG”,“Service端成功”);
其他打印不出来