Android中IPC常见的方式包括:文件、Bundle、Messenger、ContentProvider、Socket和AIDL(这种可能用的最多吧)。
在使用IPC之前,必须要知道另外两个小东西-Serializable和Parcelable,这两个类是java中用来序列化对象的,他们都是接口,随便implement哪个都能实现序列化和反序列化。
1、Serializable
这个是java自带的序列化接口,一切想要被序列化的对象,只需要实现这个接口就具备序列化的功能了。
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
}
String file=getExternalCacheDir().getAbsolutePath()+ File.separator+"book.txt";
Book book=new Book("android develop","Rainbow556");
try {
ObjectOutputStream out =new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(book);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
ObjectInputStream in =new ObjectInputStream(new FileInputStream(file));
Book b= (Book) in.readObject();
in.close();
上面是使用ObjectOutputStream把可被序列化的Book对象写到SD卡上,然后利用ObjectInputStream把之前写入的Book对象再读出来。读出来的Book对象和原来写进去的Book属性值是一模一样的,不过是两个对象。这是因为它实现了Serializable接口,在读出来的时候根据原来保存的值重新创建了一个新的Book对象,能够成功反序列化的关键在于
serialVersionUID,如果这个值没有改变的话,是可以重新反序列化成功的(其他情况,类名改了什么,那当然是不行的)。
serialVersionUID是非常重要的,它标识着该Book类的版本,因为在把Book类写到SD卡上的时候,同时也会把 serialVersionUID也写入进去,而在反序列化的时候就会比较这个值是否相等,相等则可以反序列化,反之。
2、Parcelable
这个是Android特有的序列化类,Parcelable的在效率上比Serializable高,但实现起来比较麻烦,如下:
public class User implements Parcelable {
private String name;
private int age;
public User(String name,int age){
this.name=name;
this.age=age;
}
/**
* 用于反序列化
* @param in
*/
private User(Parcel in) {
name=in.readString();
age=in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
/**
* 用于序列化
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
以上是一个很简单的实现,但是一般的都差不多。
几种常见的IPC方式
1、文件
就是进程之间按照约定去访问一个共享文件来实现数据交换实现通信,例如SharedPreference、File,这种方法比较简单,按照约定存数据和取数据,不多说。
2、Bundle
Bundle是可以放到Intent里的,和Map差不多,通过key-value的形式映射,但是里面的value只能是基本数据类型、String和实现了Serializable或者Parcelable接口的对象。我们在做activity跳转、发送广播和启动服务的时候都可以传入一个Intent参数,如果要启动的组件是运行在另一个进程中的话,而且又要传递一个我们自定义的对象时,这个时候之前说的Serializable和Parcelable接口就发挥作用了,因为我们实现了这个接口,就能放到Bundle中通过Intent去传递了(如果该进程是其他app的话,后面再说,因为其他app是没有我们那个自定义对象.java文件的)。
3、Messenger
翻译过来就是“信使”的意思,这是一个轻量级的IPC方式,底层是通过AIDL来实现,只不过做了封装,方便使用而已。Messenger发送消息的载体是Message,也就是handler里常用的Message,Message里只能放基本数据类型和实现了Serializable或者Parcelable接口的数据。简单的使用如下:
创建一个Service,运行在另外一个进程中:
<service
android:name=".service.MessengerService"
android:enabled="true"
android:exported="true"
android:process=":messenger"></service>
服务端代码:
public class MessengerService extends Service {
/**
* 创建一个Messenger,并与一个handler关联
*/
private Messenger messenger=new Messenger(new ServerHandler());
public MessengerService() {
}
@Override
public IBinder onBind(Intent intent) {
//返回Messenger底层的Binder对象
return messenger.getBinder();
}
/**
* 用于接收和处理客户端发来的消息
*/
public static class ServerHandler extends Handler{
public final static int MSG_FROM_CLIENT=0;
public final static int MSG_FROM_SERVER=1;
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what==MSG_FROM_CLIENT){
Log.e("lx",msg.getData().getString("msg"));
//拿到客户端的Messenger,给客户端发消息
Messenger clientMessenger=msg.replyTo;
Message replyMsg=Message.obtain(null, MSG_FROM_SERVER);
Bundle bundle=new Bundle();
bundle.putString("msg","yes , i got it");
replyMsg.setData(bundle);
try {
clientMessenger.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e("lx","service destroyed");
}
}
客户端代码:
public class MainActivity extends Activity {
/**给服务端发消息的Messenger*/
private Messenger messenger;
/**接收服务端消息的Messenger*/
private Messenger mGetReplyMsger =new Messenger(new ClientHandler());
private ServiceConnection mServiceConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("lx","service connected");
//拿到服务端传来的IBinder对象实例化Messenger
messenger=new Messenger(service);
Message msg=Message.obtain(null, MessengerService.ServerHandler.MSG_FROM_CLIENT);
Bundle bundle=new Bundle();
bundle.putString("msg","this is a msg from client");
msg.setData(bundle);
//把接收服务端消息的Messenger传递给服务端,这样就可以对服务端的消息做出回应
msg.replyTo= mGetReplyMsger;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("lx","service disconnected");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
useMessengerForIPC();
}
/**
* 使用Messenger实现IPC
*/
private void useMessengerForIPC() {
Intent intent=new Intent(this,MessengerService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
/**接收和处理服务端消息的handler*/
public static class ClientHandler extends android.os.Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what== MessengerService.ServerHandler.MSG_FROM_SERVER){
String msgFromServer=msg.getData().getString("msg");
Log.e("lx",msgFromServer);
}
}
}
首先创建一个Service,给客户端来连接,在onBind方法中,返回了Messenger底层的Binder对象,另外给服务端的Messenger关联了一个Handler,用于接收和处理客户端发来的消息。在activity创建的时候,绑定了一个MessengerService服务,在onServiceConnected的时候给服务端发了一个消息“this is a msg from client”,并且把客户端的另一个mGetReplyMsger通过replyTo属性传递给服务端,然后在服务端的Handler的handleMessage方法中接收消息和客户端传过来的
mGetReplyMsger对象,利用mGetReplyMsger给客户端发了一条“yes , i got it”消息,然后客户端在mGetReplyMsger关联的Handler中响应消息,这就是一个完整的使用Messenger进行IPC的简单示例。
需要注意的是:在同一个进程当中,我们一般使用Message.obj传递对象,不过当在不同进程中时, 只支持系统提供的某些实现了Parcelable接口的对象。如果要传输自定义的对象,我们可以使用Bundle来传输。
4、ContentProvider
看到ContentProvider我们应该不陌生吧,相信在平时的开发中我们多多少少使用过这个小东西,比如获取手机联系人、短信等数据,都是通过ContentProvider实现的,仔细想想联系人和短信都是系统app的数据,而我们在自己的app中获取其他app的数据,这也就是进程间通信了。ContentProvider只是提供操作真正数据源的接口,对于具体的数据源是来自文件或是数据库这只是ContentProvider自己需要实现的,外界要获取这些数据时,需要通过ContentResolver类。我们创建一个数据库DBHelper类,这里我们把数据库作为数据源,代码如下:
public class DBHelper extends SQLiteOpenHelper{
public static final String TABLE_NAME="book";
private static final String SQL="create table if not exists book(_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, author VARCHAR) ";
public DBHelper(Context context) {
super(context, "book.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists book");
onCreate(db);
}
}
再实现自定义的ContentProvider:
public class BookProvider extends ContentProvider {
private static final String AUTHORITY = "com.bookprovider.www";
private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/books");
private static final int BOOK_ALL = 0;
private static final int BOOK_ONE = 1;
/**这里的数据源来自database数据库*/
private DBHelper dbHelper;
/**用于匹配调用者传来的Uri*/
private final static UriMatcher matcher;
//添加Uri
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY, "books", BOOK_ALL);
matcher.addURI(AUTHORITY, "books/#", BOOK_ONE);
}
public BookProvider() {
}
@Override
public boolean onCreate() {
dbHelper = new DBHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
int type = matcher.match(uri);
switch (type) {
case BOOK_ONE:
return "";
case BOOK_ALL:
return "";
default:
throw new UnsupportedOperationException("Not yet implemented");
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//先用UriMatcher匹配传入的Uri,得到type
int type = matcher.match(uri);
switch (type) {
case BOOK_ONE:
//获取uri中要操作的id,也就是book表中的_id主键
long id = ContentUris.parseId(uri);
selection = "_id=?";
selectionArgs = new String[]{String.valueOf(id)};
break;
case BOOK_ALL:
break;
default:
throw new UnsupportedOperationException("unsupport uri");
}
int rows = dbHelper.getWritableDatabase().delete(DBHelper.TABLE_NAME, selection, selectionArgs);
//如果数据库中有数据变动,则通知其他ContentObserver
if (rows > 0) {
notifyDataChanged();
}
return rows;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int type = matcher.match(uri);
if (type != BOOK_ALL)
throw new UnsupportedOperationException("unsupport uri");
long id = dbHelper.getWritableDatabase().insert("book", null, values);
if(id!=-1)
notifyDataChanged();
return ContentUris.withAppendedId(NOTIFY_URI, id);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int type = matcher.match(uri);
switch (type) {
case BOOK_ONE:
long id = ContentUris.parseId(uri);
selection = "_id=?";
selectionArgs = new String[]{String.valueOf(id)};
break;
case BOOK_ALL:
break;
default:
throw new UnsupportedOperationException("unsupport uri");
}
return dbHelper.getReadableDatabase().query("book", projection, selection, selectionArgs, null, null, sortOrder);
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int type = matcher.match(uri);
switch (type) {
case BOOK_ONE:
long id = ContentUris.parseId(uri);
selection = "_id=?";
selectionArgs = new String[]{String.valueOf(id)};
break;
case BOOK_ALL:
break;
default:
throw new UnsupportedOperationException("unsupport uri");
}
int rows = dbHelper.getWritableDatabase().update("book", values, selection, selectionArgs);
if (rows > 0) {
notifyDataChanged();
}
return rows;
}
private void notifyDataChanged() {
getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
}
}
<provider
android:name=".provider.BookProvider"
android:authorities="com.bookprovider.www"
android:enabled="true"
android:exported="true"
android:process=":provider"></provider>
继承ContentProvider必须要实现几个方法,onCreate、getType、insert、delete、update、query, 以上代码就是实现一个简单的ContentProvider,切记还要在清单文件中注册,在provider标签中有两个属性需要注意一下,name和authorities,name对应的是ContentProvider的具体实现类,authorities对应的是这个provider的唯一标识,这里我特地让这个ContentProvider运行在另一个进程中。
接下来看如何在activity中调用,由于activity的布局比较简单,包含四个button,对应着insert、delete、update、query四个操作,直接贴上onClick方法:
@Override
public void onClick(View v) {
Uri uri=null;
ContentValues values=null;
switch (v.getId()) {
case R.id.btn_insert:
values=new ContentValues();
values.put("name","android2");
values.put("author","rainbow556");
getContentResolver().insert(URI_ALL_BOOK,values);
break;
case R.id.btn_query:
// uri= ContentUris.withAppendedId(URI_ALL_BOOK,1);
Cursor cursor=getContentResolver().query(URI_ALL_BOOK, null, null, null, null);
if(cursor!=null && cursor.getCount()>0){
while (cursor.moveToNext()){
Log.e("lx",cursor.getString(0)+" , "+cursor.getString(1)+" , "+cursor.getString(2));
}
}
if(cursor!=null){
cursor.close();
}
break;
case R.id.btn_update:
uri= ContentUris.withAppendedId(URI_ALL_BOOK,1);
values=new ContentValues();
values.put("name","c++");
getContentResolver().update(uri,values,null,null);
break;
case R.id.btn_delete:
uri= ContentUris.withAppendedId(URI_ALL_BOOK,1);
getContentResolver().delete(URI_ALL_BOOK,null,null);
break;
}
}
一个简单的自定义ContentProvider和调用就完成了。
5、Socket
由于使用Socket进行进程间通信和一般的Socket操作基本类似,所以就不细说了。步骤如下:在服务端启动一个Service,在Service创建的时候启动一个线程,在这个线程中建立一个ServerSocket监听本地一个自定义的本地端口,等待客户端的连接;而客户端只需要创建一个Socket,去连接服务端,地址为“localhost”,而端口则是之前定义的,一旦建立了连接,就可以通过Socket的流进行数据传输了。
6、AIDL
将在下一篇博客中介绍。