前面我们介绍了Serializable和Parcelable的基础知识,以及Binder的相关内容。本篇,开始分析各种跨进程通信的方式,Intent中附加extra信息,共享文件,Binder。另外ContentProvider天生就是支持跨进程访问的。此外,网络通信也是跟进程无关,所以Socket也可以实现IPC。
ipc的BookSevice和MainActivity
Bundle
Android中四大组件中的三个组件(Activity、Service、Receiver)都是支持在Intent中传递数据的,Intent和Bunlde都实现了Parcelable接口,都支持跨进程通信,因为Intent.putExtra内部就是用Bundle实现的。
public Intent putExtra(String name, String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value);
return this;
}
这个跟平时一个进程中的写法都一样,可以说无感觉切换O(∩_∩)O
例子可见Bundle跨进程
文件共享
文章共享可参考前面关于serializable和Parcelable时的内容。可以实现进程间通信,因为毕竟是文件,跟进程无关,可是在处理并发时问题很严重,比如,一个进程写了一半儿,另一个进程读取了;或者一个进程写,另一个也写,那你说,同一个文件出来的是个啥?啥也不是!
Android中的SharedPreferences是个特例,虽然最终是在xml文件中,可是内部还有内存级的缓存,所以多进程模式下,数据极不可靠。不建议多进程使用。
Messenger
Messenger的底层其实也是AIDL。一次处理一个请求,所以不用考虑同步。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
简单实用方式如下:
Service
public class MessengerService extends Service {
private final static String TAG = "MessengerService";
private Handler serverHanlder = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.d(TAG, "handleMessage: receive msg "+msg.getData().getString("msg"));
Messenger clientMessenger = msg.replyTo;
Message obtain = Message.obtain(null, MyConstants.MSG_FROM_SERVER);
Bundle bundle = new Bundle();
bundle.putString("msg","Hi,This is from server.");
obtain.setData(bundle);
try {
clientMessenger.send(obtain);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
};
private Messenger serverMessenger = new Messenger(serverHanlder);
@Nullable
@Override
public IBinder onBind(Intent intent) {
return serverMessenger.getBinder();
}
}
Activity
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private Handler clientHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MyConstants.MSG_FROM_SERVER:
Log.d(TAG, "handleMessage: receive msg "+msg.getData().getString("msg"));
break;
}
}
};
private Messenger clientMessenger = new Messenger(clientHandler);
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Messenger serviceMessenger = new Messenger(service);
Message message = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "Hello,This is client.");
message.setData(bundle);
message.replyTo = clientMessenger;
try {
serviceMessenger.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_messenger);
bindService(new Intent(this, MessengerService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mServiceConnection);
super.onDestroy();
}
}
Messenger使用message传输数据,Message的what、arg1、arg2、obj、Bundle以及replyTo都可以充当数据载体。有人就说了,你看你,有那么多简单的不用,非用一个Bundle,别急,咱们来仔细看一下。
- what 就不说了,主要是区分不同消息
- arg1、arg2 如果是int类型时的简写形式
- obj 只能是Framework中的Parcelable类型的参数,自定义的不行
- replyTo 针对Messenger
- Bundle 所以你看,还能怎么办,有用的就不错了,走起来吧
借用下开发艺术探索的原理图
AIDL
AIDL的简单使用前面已经说过(Binder章节中),代码也有,可以先大概熟悉下写法。
接着以前的内容说
AIDL支持的数据类型
- 基本数据类型
- String、CharSequence
- Parcelable:所有实现了Parcelable的接口的对象
- List:只支持ArrayList,里面的每个元素需要支持AIDL
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- AIDL:所有的AIDL接口本身也可以在AIDL中使用
- Parcelable对象和AIDL必须显式的import进来。
- 使用都自定义的Parcelable对象时,必须新建一个同名的AIDL文件,并且声明为Parcelable类型,如:
// Book.aidl
package com.breezehan.ipc;
parcelable Book;
- AIDL接口方法中,参数如果不是基础类型,都需要标上方向:in、out、或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。你真的理解AIDL中的in,out,inout么?
我们改造下之前的AIDL写法,BookSevice中有个List,改造如下
public class BookService extends Service{
CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private IBinder binder = 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 onCreate() {
super.onCreate();
mBookList.add(new Book(1, "三体"));
mBookList.add(new Book(2, "Android"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
可是前面不是说List只能是ArrayList吗
AIDL是运行在Binder线程池中的,可以多个客户端同时连接,会存在多个线程同时访问的情况,而CopyOnWriteArrayList支持并发的读写
AIDL中支持抽象的List,虽然服务端返回的是CopyOnWriteArrayList,但是Binder中会按照List的规范访问数据并最终形成一个ArrayList给客户端。类似的还有ConcurrentHashMap。
代码参考
改进型AIDL
有种情况,假如Service相当于图书馆,客户端不想轮询查询有什么书,图书馆加入新书,直接通知我一下就好了,也可以取消提醒,这就是常说的观察者模式。
首先,我们需要有个监听接口,客户端实现。这里只能是AIDL接口IOnNewBookArrivedListener.aidl
// IOnNewBookArrivedListener.aidl
package com.breezehan.ipc;
import com.breezehan.ipc.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book book);
}
IBookManager也需要改造
// IBookManager.aidl
package com.breezehan.ipc;
import com.breezehan.ipc.Book;
import com.breezehan.ipc.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
Service和Activity见github代码。
运行结果如下
com.breezehan.ipc进程中
com.breezehan.ipc:remote进程中
哈哈,你看,多进程的观察者模式成功了。
不过,等会儿再高兴,本来我们在Activity中的onDestroy中设置了
@Override
protected void onDestroy() {
if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
Log.d(TAG, "onDestroy: unregister listener "+mOnNewBookArrivedListener);
try {
iBookManager.unRegisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mServiceConnection);
super.onDestroy();
}
真正推出Activity时,打印会出现
嘛意思,not found,can not unregister.
为什么呢?为什么会解注册失败呢。
我们要知道,Binder传输的的对象,整个是一个序列化、反序列化的过程,Binder会把客户端传过来的对象反序列化成一个新对象,不是一个对象,怎么能remote掉呢!
进阶化改进型AIDL
这里我们要使用到RemoteCallbackList。使系统提供的专门用于删除跨进程listener的接口,看它的定义,接收extend IInterface的参数,即所有的AIDL都可以支持。
public class RemoteCallbackList<E extends IInterface>
它的内部有个ArrayMap用户保存AIDL的listener,Map中的key时IBInder类型,value是Callback类型
ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
看register(E callback)详细代码,我们知道,虽然每次客户端传输过去的listener,最终都被binder生成一个个不同的对象(内部相同),但是底层的Binder对象只是一个。
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);//使用binder作为key,同一个binder只有一个对象。
return true;
} catch (RemoteException e) {
return false;
}
}
}
使用RemoteCallbackList改进的Service如下:
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mArrivedListeners.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mArrivedListeners.unregister(listener);
}
private void onNewBookArrived(Book newBook) throws RemoteException {
mBookList.add(newBook);
int N = mArrivedListeners.beginBroadcast();
for(int i=0;i<N;i++) {
IOnNewBookArrivedListener broadcastItem = mArrivedListeners.getBroadcastItem(i);
if (broadcastItem != null) {
broadcastItem.onNewBookArrived(newBook);
}
}
mArrivedListeners.finishBroadcast();
}
AIDL服务端 有时不想任何人都可以连接,必须加入验证服务,验证内容再次就不做探索了(我可不说是因为没用过)。
使用ContentProvider
ContentProvider跟Messenger一样,底层都是Binder实现。但是ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,天生就适合进程间通信。封装的很好,比AIDL简单很多。
像通讯录、日历等,都实现了ContentProvider,我们只需要通过ContentResolver的query、update、insert、delete方法即可。
Provider,虽然没有具体内容,但是不耽误使用。对象代码provider示例代码
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
@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, "onCreate,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;
}
}
ProviderActivity
public class ProviderActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
Uri uri = Uri.parse("content://com.breezehan.ipc.book.provider");
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri, null, null, null, null);
}
清单文件配置,provider中android:authorities需要时唯一的,android:permission="com.breezehan.PROVIDER"表示权限,其他app想使用这个provider,必须声明权限;process就不说了,不想弄两个应用,开个进程。
<provider
android:name=".provider.BookProvider"
android:authorities="com.breezehan.ipc.book.provider"
android:permission="com.breezehan.PROVIDER"
android:process=":provider" />
<activity android:name=".provider.ProviderActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
运行结果如下
看到了吧,Binder线程池。onCreate是UI线程,三次query可能会在不同线程。
我们用Sqlite简单实现逻辑内容,其实ContenProvider内部想怎么实现都行。
DbOpenHelper
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "bookprovider.db";
private static final int DB_VERSION = 1;
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
public static final String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "( _id INTEGER PRIMARY KEY, name TEXT)";
public static final 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) {
}
}
BookProvider改造
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHRORITY = "com.breezehan.ipc.book.provider";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHRORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHRORITY + "/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(AUTHRORITY, "book", BOOK_URI_CODE);//将uri和uricode关联起来
sUriMatcher.addURI(AUTHRORITY,"user",USER_URI_CODE);
}
private Context mContext;
private SQLiteDatabase sqLiteDatabase;
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate,current Thread: "+Thread.currentThread().getName());
mContext =getContext();
//初始化数据库,真正使用不能这样写,MainThread
initProviderData();
return true;
}
private void initProviderData() {
sqLiteDatabase = new DbOpenHelper(mContext).getWritableDatabase();
sqLiteDatabase.execSQL("delete from "+DbOpenHelper.BOOK_TABLE_NAME);
sqLiteDatabase.execSQL("delete from "+DbOpenHelper.USER_TABLE_NAME);
sqLiteDatabase.execSQL("insert into book values(3,'Android');");
sqLiteDatabase.execSQL("insert into book values(4,'Ios');");
sqLiteDatabase.execSQL("insert into book values(5,'Html5');");
sqLiteDatabase.execSQL("insert into user values(1,'jake',1);");
sqLiteDatabase.execSQL("insert into user values(5,'Html5',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 tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
return sqLiteDatabase.query(tableName,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("Unsupported URI:" + uri);
}
sqLiteDatabase.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri,null);//通知外界,ContentProvider的数据已经改变
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("Unsupported URI:" + uri);
}
int count = sqLiteDatabase.delete(table, selection, selectionArgs);
if (count > 0) {
mContext.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("Unsupported URI:" + uri);
}
int row = sqLiteDatabase.update(table, values, selection, selectionArgs);
if (row > 0) {
mContext.getContentResolver().notifyChange(uri,null);
}
return row;
}
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;
}
return tableName;
}
}
ProviderActivity中
public class ProviderActivity extends AppCompatActivity {
private static final String TAG = "ProviderActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
Uri bookUri = Uri.parse("content://com.breezehan.ipc.book.provider/book");//BOOK_CONTENT_URI
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.getInt(0),bookCursor.getString(1));
Log.d(TAG, "query book:"+book.toString());
}
bookCursor.close();
}
}
运行结果如下
上面只是简单介绍ContentProvider的基本使用方法,深入使用还有很多知识。
使用Socket
内容略