Android之IPC(三常用的进程间通信方式的介绍)
使用Bundle
之前序列化介绍过Parcelable接口,是支持不同进程传输的,一般我们都时把它放在Intent中传递,当然还有其他的用法,下面的Messenger就会用到。在Bundle中我们放入我们需要的信息即可。(后面就会用的到,这里不做过多介绍,常用的Intent,相信大家很熟悉了)
使用文件共享(FileShare)
顾名思义,Android基于Linux,并发读和写是没有限制的,多个线程来写也是可以的(可能会出问题)
要点: 我们可以使用之前说的序列化和反序列化,通过指定文件保存的地方,来进行数据的传输(有点类似我们操作系统的管道)
注意:SharedPreference是一个轻量级的方案,我们平时也就是用来存储一些配置文件,这里不建议使用在进程间通信。 原因:SharedPreference有缓存方案,内存会存一份,多进程下,就会在读/写中变得不可靠。
使用Messenger
在进程之间传递Message对象,通过handler来接收来完成,当然,这个底层也是用的AIDL
步骤解读
从Client到Service,首先在服务端创建一个Service来处理连接的请求,客户端传过来的消息要通过服务端的handler来处理,于是就创建一个Handler,通过这个handler进而创建一个Messenger对象,用来返回底层的binder对象(是不是和AIDL很相像,如果要想给客户端发送信息,使用msg.replyTo调用send方法,向Client发送消息)
public class MessengerService extends Service {
private static final int MSG_FROM_CLIENT = 1;
// 用来接受信息
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@SuppressLint("HandlerLeak")
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_CLIENT:
// 接受local发送来的消息
Toast.makeText(MessengerService.this, msg.getData().getString("msg"), Toast.LENGTH_SHORT).show();
// 向Client发送消息
Messenger reply = msg.replyTo;
Message replyMessage = Message.obtain(null, 2);
Bundle data = new Bundle();
data.putString("reply", "good");
replyMessage.setData(data);
try {
reply.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
当然,客户端,首先要绑定服务端的Service,通过返回的IBinder创建Messenger,通过这Messenger来向服务端发送消息(注意这里同样用的时Binder,当然,如果你想处理来自客户端的消息,同样在Client创建一个handler,进而创建一个Messenger,message.replyTo = Messenger,让handler来处理消息
private Messenger mService;
private static final int MSG_FROM_SERVER = 2;
// 接受回复信息并处理的handler
private Messenger mGetMessage = new Messenger(new MessengerHandler());
// 用于接受回复的消息
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_SERVER:
Toast.makeText(MainActivity.this, msg.getData().getString("reply"), Toast.LENGTH_LONG).show();
break;
default:
super.handleMessage(msg);
break;
}
}
}
// 用来发送信息
private ServiceConnection messengerConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 发送数据
mService = new Messenger(service);
// 携带发送数据
Message message = Message.obtain(null, 1);
Bundle data = new Bundle();
data.putString("msg", "messenger");
message.setData(data);
// 接受信息媒介
message.replyTo = mGetMessage;
// 发送数据
try {
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
小结:通过以上我们发现,Messenger发送消息是通过把消息填充到Bundle中,让Message携带Bundle来处理的,Bundle时实现Parcelable接口的。
AIDL
客户端代码
private IBookManager mRemoteBookManager;
private static final int MSG_NEW_BOOK_ARRIVED = 1;
// 这里设置死亡代理,当Binder死亡的时候,会回调其中的方法
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
// 运行在binder线程池中
@Override
public void binderDied() {
if (mRemoteBookManager == null) return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// 重新绑定服务
Intent intent = new Intent("com.ali.aidl");
bindService(intent, aidlConn, BIND_AUTO_CREATE);
}
};
@SuppressLint("HandlerLeak")
private Handler messengerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_NEW_BOOK_ARRIVED:
System.out.println("receive new book : " + msg.obj);
break;
default:
break;
}
}
};
// 从这里可以看出一个binder对应一个listener,跨进程传输的时候,binder是同一个
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book book) {
// 此时mOnNewBookArrivedListener是观察者的具体实现(换言之治理就是具体的实现逻辑,
//相当于服务端,remote必须想要通知观察者,就必须调用这里的方法,remote相当于客户端
messengerHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, book).sendToTarget();
}
};
// ServiceConnection方法运行在主线程,所以调用服务端的方法最好在非UI线程(远程调用本地也是一样)
private ServiceConnection aidlConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获取接口对象
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
// 设置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
List<Book> bookList = bookManager.getBookList();
System.out.println("bookList size : " + bookList.size());
// 向服务端提交信息
bookList.add(new Book(3, "JAVA"));
List<Book> newList = bookManager.getBookList();
bookManager.registerListener(mOnNewBookArrivedListener);
System.out.println("bookList size : " + newList.size());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 档然在这里也是可以重新绑定绑定服务的(相当于死亡代理的,具体区别后面有介绍)
System.out.println("binder died");
}
};
服务端代码
public class BookManagerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
//private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() {
return mBookList;
}
@Override
public void addBook(Book book) {
mBookList.add(book);
}
// 如果没有特殊的处理,跨进程之后传过来就会变你成一个不确定对象(对象的地址控件会发生变化,导致对象不一样
// 所以要使用RemoteCallbackList(实现了自动同步,如果客户端停止,自动移除相关的listener)下面的unRegister一样如此
@Override
public void registerListener(IOnNewBookArrivedListener listener) {
//if (!mListenerList.contains(listener)) mListenerList.add(listener);
//else System.out.println("not found");
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) {
// if (mListenerList.contains(listener)) mListenerList.remove(listener);
//else System.out.println("not found");
mListenerList.unregister(listener);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "IOS"));
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
// 在这里可以进行权限的验证,不是所有的都可以调用来自服务端的方法
return mBinder;
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
//for (IOnNewBookArrivedListener listener : mListenerList) {
// listener.onNewBookArrived(book);
//}
IOnNewBookArrivedListener listener;
int count = mListenerList.beginBroadcast();
for (int i = 0; i < count; i++) {
listener = mListenerList.getBroadcastItem(i);
// 这里是为了演示,其实这个应该也放在非UI线程
if (listener != null) listener.onNewBookArrived(book);
}
mListenerList.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
Book newBook;
// 这里处理一些后太进程
while (!mIsServiceDestroyed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
newBook = new Book(bookId, "newBook" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
AIDL文件
1.
// Book.aidl
package com.example.remote;
parcelable Book;
2.
// IBokManager.aidl
package com.example.remote;
import com.example.remote.Book;
import com.example.remote.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
3.
// IOnNewBookArrivedListener.aidl
package com.example.remote;
import com.example.remote.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book book);
}
小结:
1.这里从Client通过Binder传递到Server的时候,会产生两个对象,但是Binder确是一样的,因此,这里我们用的观察者模式,在unRegistere的时候就会出意外,完全是两个对象,因此,要利用Binder一样,使用RemoteCallbackList(专门用来提供删除跨进程的接口,是一个泛型,支持管理任意的AIDL接口。而且内部自动实现了线程同步的功能,我们就不需要额外的线程同步的功能是不是很NB)
2.同时RemoteCallbackList并不是一个List,在遍历的时候beginBroadCase和finishBroadcase要配对使用,哪怕是获取其中元素的个数。
3.之前说过,客户端请求当前线程会挂起,不同进程的方法会运行在服务端的Binder线程池中,当前线程阻塞,如果在UI线程可能导致ANR。也要注意,onServiceConnected和onServiceDisconnected也是运行在UI线程,不能有耗时方法。
4.我们为了方法对远程连接不意外中断,都有重连机制。其一,我们可以使用设置死亡代理,通过监听来判断。其二,在onServiceDisconnect中重新连接。死亡代理,我们用的到服务端传过来的Ibinder来设置监听,运行在客户端的Binder线程池中,不能访问UI。但是后者是运行在UI线程当中的。
5.aidl文件中用到了自定义序列化对象不要忘记导入(哪怕是在同一个包)
使用ContentProvider
这是Android的四大组件之一,通过表格的形式组织数据,和数据库很类似,一般也是和数据库联系起来用的。
客户端
private void contentProvider() {
Uri bookUri = Uri.parse("content://com.ali.contentProvider/book");
ContentValues bookValues = new ContentValues();
bookValues.put("_id", 6);
bookValues.put("name", "Python");
getContentResolver().insert(bookUri, bookValues);
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
Book book;
while (bookCursor.moveToNext()) {
book = new Book(bookCursor.getInt(0), bookCursor.getString(1));
System.out.println(book.toString());
}
bookCursor.close();
Uri userUri = Uri.parse("content://com.ali.contentProvider/user");
Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name", "sex"}, null, null, null);
User user;
while (userCursor.moveToNext()) {
user = new User(userCursor.getInt(0), userCursor.getString(1), userCursor.getInt(2) == 1);
System.out.println(user.toString());
}
userCursor.close();
}
服务端
ContentProvider
public class BookProvider extends ContentProvider {
// 可以理解为URI匹配的必要前缀(自己定义的)
private static final String AUTHORITY = "com.ali.contentProvider";
// content:// 默认前缀
private static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
private static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
// 表的选择索引
private static final int BOOK_URI_CODE = 0;
private 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 Context mContext;
private SQLiteDatabase mDataBase;
@Override
public boolean onCreate() {
mContext = getContext();
// 初始化数据库(这是为了演示,实际开发中不应该在主线程中进行)
initProviderData();
return true;
}
// 初始化创建数据库并插入部分数据
private void initProviderData() {
mDataBase = new DBOpenHelper(mContext).getReadableDatabase();
mDataBase.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);
mDataBase.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);
mDataBase.execSQL("insert into book values(3, 'Android');");
mDataBase.execSQL("insert into book values(4, 'IOS');");
mDataBase.execSQL("insert into book values(5, 'HTML5');");
mDataBase.execSQL("insert into user values(1, 'jake', 1);");
mDataBase.execSQL("insert into user values(2, 'jasmine', 0);");
}
// 以下是具体的增删改查等基本操作
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String tableName = getTableName(uri);
if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri);
return mDataBase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
String tableName = getTableName(uri);
if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri);
int count = mDataBase.delete(tableName, selection, selectionArgs);
if (count > 0) mContext.getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
String tableName = getTableName(uri);
if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri);
mDataBase.insert(tableName, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String tableName = getTableName(uri);
if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri);
int count = mDataBase.update(tableName, values, selection, selectionArgs);
if (count > 0) mContext.getContentResolver().notifyChange(uri, null);
return count;
}
// 选择表
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;
}
}
SQLiteOpenHelper
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(Context context) {
super(context, DB_NAME, null, DB_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) {
}
}
小结:
1.这个一般就是一些固定的格式,书写没有什么过多讲解的。ContentProvider中处理onCreate方法运行在UI线程,其他的五个方法都是运行在Binder线程池中的,query,update,insert,delete,getType。在我们对数据有更改的时候,一般都是调用mContext.getContentResolver().notifyChange(uri, null)来发出通知。我们通过ContentResolyer来注册观察者检测即可。
2.同时query,inset,update,delete,存在多线程并发访问,内部要做好同步。当时SQLiteDatabase内部对数据库是由同步处理的,当时多个SQLiteDatabase就无法保证了,因为多个SQLiteDatabase对象之间没有办法做到同步,需要使用者自己处理。
Socket
也是可以使用进程间通信的,我们注意不要在主线程中访问网络。
客户端(UI的更新通过Handler在UI更新)下面是主要方法
private void connectTCPServer() {
Socket socket = null;
try {
// 建立连接
socket = new Socket("localhost", 8688);
mClientSocket = socket;
// 向服务端提供客户端给的消息
writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
socketHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connected success");
} catch (IOException e) {
e.printStackTrace();
System.out.println("connected failed");
}
try {
// 接受服务端的信息
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!isFinishing()) {
String content = reader.readLine();
if (content != null) {
String time = "server " + formatDateTime(System.currentTimeMillis()) + ":" + content + "\n";
socketHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, time).sendToTarget();
System.out.println("对话");
}
}
System.out.println("quit...");
reader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
// 发送信息
@SuppressLint("SetTextI18n")
private void sendMessage() {
String content = etContent.getText().toString().trim();
if (!TextUtils.isEmpty(content) && writer != null) {
writer.println(content);
etContent.setText("");
String showMsg = "self" + formatDateTime(System.currentTimeMillis()) + ":" + content + "\n";
tvShow.setText(tvShow.getText() + showMsg);
}
}
服务端
public class TCPService extends Service {
private boolean mIsServiceDestroyed = false;
private String[] mDefinedMessages = new String[]{
"你好啊,很高兴认识你!",
"欢迎来到我见做客。",
"今天天气不错啊,很适合出去走走,约不?",
"你知道吗?据说爱笑的女孩子,运气不会太差。",
"做我女朋友吧。"
};
@Override
public void onCreate() {
super.onCreate();
new Thread(new TCPServer()).start();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestroyed = true;
}
private class TCPServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket;
try {
// 监听8688端口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
e.printStackTrace();
System.out.println("establish tcp server failed, port:8688");
return;
}
while (!mIsServiceDestroyed) {
try {
// 接受客户端的请求
Socket client = serverSocket.accept();
System.out.println("accept");
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用于接受客户端消息
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用于向客户端发送消息
PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream()),true);
writer.println("欢迎来到聊天室");
String clientContent, serverContent;
while (!mIsServiceDestroyed) {
clientContent = reader.readLine();
if (clientContent == null) break; // 断开连接
serverContent = mDefinedMessages[(int) (Math.random() * (mDefinedMessages.length))];
writer.println(serverContent);
}
System.out.println("client quit");
// 关闭输入输出流
reader.close();
writer.close();
client.close();
}
}
小结: 形成一个循环,客户端和服务端两者之间相互传递消息即可,就是输入输出流的使用。
总结
以上的通信方式,都不要放及解绑和停止服务即可。