一、 什么是IPC?
IPC,全称Inter-Process Communication,字面意思就是进程间通信或者跨进程通信。那什么是进程呢?它和线程有什么暧昧的关系?
进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础;早期表现为一个程序,现在可以看作线程的容器。线程是CPU调度的最小单位。�一个进程可以包含一个或者多个线程,进程向系统申请资源,线程使用进程拥有的资源。
IPC不是Android所独有的,是现代操作系统都存在的机制,对于Android来说。它是一种基于Linux内核的移动OS,他的进程通信方式不仅仅包含信号量、套接字、管道等等,还包括了Android独有的、特色的Binder机制。
二、 为什么需要多进程?应用场景...
谈到IPC的使用场景就必须提到多进程,只有面对多进程这种场景下,才需要考虑多进程通信,这是很好理解的。至于一个应用使用多进程的原因,这个可能很多,辟如有些模块由于特殊原因需要运行在独立的进程;又或者为了加大一个应用可使用的内存,通过多进程的方式申请多份内存空间。还有一个情况需要使用IPC,那就是多个应用之间进行数据共享,使用ContentProvider去查询数据时也是一种进程通讯,只是对我们来说透明化了,无法感知到。
三、 Android如何开启多进程模式?
我们可以通过给四大组件在AndroidMenifest.xml指定指定 android:process属性,亦或是在JNI层fork一个新进程,别无他法,过程看似轻松简单,却暗藏杀机,如何抵消多进程带来的负面影响?
这里有个示例:
<activity
android:name="com.ryg.chapter_2.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="standard" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity
<activity
android:name="com.ryg.chapter_2.SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process=":remote" />
<activity
android:name="com.ryg.chapter_2.ThirdActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process="com.ryg.chapter_2.remote" />
眼尖的人都发现了两个android:process属性的值不一样,这意味在他们会在3个不同进程内运行。通过DDMS或者命令adb shell ps 观察。进程名看起来没有什么区别,其实它们是有区别的,进程名以“:”开头的进程会属于当前应用的私有进程,其他应用组件不能通过shareUID的方式让其在同一进程中运行,相反,就是全局进程,可以被共享。
ShareUID的方式需要两个应用拥有相同的UID以及相同的签名才可以,在这种情况下,不管他们是否跑在统一进程,他们可以互相访问对方的私有数据,譬如data目录、组件信息等等,如果是的话,那么它们还能共享内存数据,看起来就是一个应用的不同部分。
四、 奇怪现象(哎呀,奇怪了。这个值怎么变了~)
有的Android开发者会选择在Application或者静态类中储存可变的属性,静态亦或是非静态的,当你开启多进程模式,你会发现你的世界不再简单如旧。
一般来说,使用多进程会造成如下几方面的问题:
**(1) 静态成员和单例模式完全失效。 **
**(2) 线程同步机制完全失效。 **
**(3) SharedPreferences 的可靠性下降。 **
(4) Application会多次创建。
我们知道Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。既然不是同一块内存了,那就算是使用了进程锁也会失效,因为进程锁也是不同内存对象。SharedPreferences虽然最终写入XML文件,但也有利用内存机制进行加速,在不同进程下,并发写入,也会出现问题。在开启多进程模式下,一般认为Application是程序的入口而不应该变化,但可以通过打印进程名称,得知Application会启动多次,因此用于存储公共信息并不可靠。
五、 IPC基础概念
Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好地理解跨进程通信的各种方式。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。
1.如何使用Serializable来完成对象的序列化。
想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现。
/**
* @ClassName ObjectUtil
* @Description 对可序列对象BASE64字符串化
* @author K.W.
* @date 2013年9月19日 上午2:10:53
*/
public class ObjectUtil {
public static String getBASE64String(Object obj) {
if(obj==null){
return null;
}
ByteArrayOutputStream toByte = new ByteArrayOutputStream();
ObjectOutputStream oos;
try {
oos = new ObjectOutputStream(toByte);
oos.writeObject(obj);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 对byte[]进行Base64编码
return Base64.encode(toByte.toByteArray());
}
public static Object getObject(String base64String) {
byte[] base64Bytes;
try {
base64Bytes = Base64.decodeToByteArray(base64String);
ByteArrayInputStream bais = new ByteArrayInputStream(base64Bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2.Android中如何使用Parcelable进行序列化操作
Implements Parcelable的时候需要实现内部的方法:
1).writeToParcel 将对象数据序列化成一个Parcel对象(序列化之后成为Parcel对象.以便Parcel容器取出数据)
2).重写describeContents方法,默认值为0
3).Public static final Parcelable.Creator<T>CREATOR (将Parcel容器中的数据转换成对象数据) 同时需要实现两个方法:
3.1 CreateFromParcel(从Parcel容器中取出数据并进行转换.)
3.2 newArray(int size)返回对象数据的大小
public class Book implements Parcelable{
private String bookName;
private String author;
private int publishDate;
public Book(){
}
public String getBookName(){
return bookName;
}
public void setBookName(String bookName){
this.bookName = bookName;
}
public String getAuthor(){
return author;
}
public void setAuthor(String author){
this.author = author;
}
public int getPublishDate(){
return publishDate;
}
public void setPublishDate(int publishDate){
this.publishDate = publishDate;
}
@Override
public int describeContents(){
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags){
out.writeString(bookName);
out.writeString(author);
out.writeInt(publishDate);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){
@Override
public Book[] newArray(int size){
return new Book[size];
}
@Override
public Book createFromParcel(Parcel in){
return new Book(in);
}
};
public Book(Parcel in){
//如果元素数据是list类型的时候需要: lits = new ArrayList<?> in.readList(list);
//否则会出现空指针异常.并且读出和写入的数据类型必须相同.如果不想对部分关键字进行序列化,可以使用transient关键字来修饰以及static修饰.
bookName = in.readString();
author = in.readString();
publishDate = in.readInt();
}
}
3.Parcelable与Serializable的性能比较
首先Parcelable的性能要强于Serializable的原因我需要简单的阐述一下
1). 在内存的使用中,前者在性能方面要强于后者
2). 后者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用,因此在性能上会稍微逊色
3). Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择.
4). 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上.
但是:虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)
六、 Binder (AIDL)
Binder 是一个可以很深入的话题,这里不打算深入探究Binder的内部。Android 开发中,Binder主要使用在Service中,包括AIDL和Messager。
我们来新建一个AIDL,体验下这个过程,还是以上面的BOOK为例子,首先建两个aidl文件:Book.aidl、IBookManager.aidl。
// Book.aidl
package com.ajb.kotlintest;
parcelable Book ;
// IBookManager.aidl
package com.ajb.kotlintest;
// Declare any non-default types here with import statements
import com.ajb.kotlintest.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
build一下就有了java类文件。
package com.ajb.kotlintest;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.ajb.kotlintest.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.ajb.kotlintest.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.ajb.kotlintest.IBookManager interface,
* generating a proxy if needed.
*/
public static com.ajb.kotlintest.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.ajb.kotlintest.IBookManager))) {
return ((com.ajb.kotlintest.IBookManager) iin);
}
return new com.ajb.kotlintest.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.ajb.kotlintest.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.ajb.kotlintest.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.ajb.kotlintest.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.ajb.kotlintest.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.ajb.kotlintest.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.ajb.kotlintest.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.ajb.kotlintest.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.ajb.kotlintest.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.ajb.kotlintest.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.ajb.kotlintest.Book book) throws android.os.RemoteException;
}
上述代码是系统生成的,它继承了IInterface这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要继承IInterface接口。首先,它声明了两个方法getBookList和addBook,显然这就是我们在IBookManager.aidl中所声明的方法,同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这么来看,IBookManager这个接口的确很简单,但是我们也应该认识到,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍针对这两个类的每个方法的含义。
asInterface(android.os.IBinderobj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。
asBinder
此方法用于返回当前Binder对象。
onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为 public Booleanon Transact(intcode,android.os.Parcel
data,android.os.Parcel reply,int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
Proxy#getBookList
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。
Proxy#addBook
这个方法运行在客户端,它的执行过程和getBookList是一样的,addBook没有返回值,所以它不需要从_reply中取出返回值。
在服务端需要创建一个 BookManagerImpl 的对象并在 Service 的 onBind 方法中返回即可。以下就是一个服务端的典型实现。
//服务器端
public class CustomService extends Service {
private List<Book> mBookList;
public CustomService() {
}
@Override
public IBinder onBind(Intent intent) {
return new BookManagerImpl();
}
private class BookManagerImpl extends IBookManager.Stub {
@Override
public List<Book> getBookList() throws RemoteException {
synchronized (mBookList) {
return mBookList;
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (mBookList) {
if (!mBookList.contains(book)) {
mBookList.add(book);
}
}
}
@Override
public void linkToDeath(DeathRecipient recipient, int flags) {
super.linkToDeath(recipient, flags);
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return super.unlinkToDeath(recipient, flags);
}
}
}
Binder有两个很重要的方法linkToDeath和unlinkToDeath。
Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。
为了解决这个问题,Binder中提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。那么到底如何给Binder设置死亡代理呢?也很简单。
首先,声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务。
//客户端(Activity)内
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// TODO Auto-generated method stub
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// TODO:重新绑定远程服务
bindService(new Intent("com.ajb.kotlintest.CustomService").
setPackage("com.ajb.kotlintest"), conn, BIND_AUTO_CREATE);
}
};
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = IBookManager.Stub.asInterface(service);
try {
service.linkToDeath(mDeathRecipient, 0);
Toast.makeText(getApplicationContext(), mBookManager.getName(),
Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
//然后在onCreate先bindService一次,由上面处理重绑。
这样我们就能简单的传递信息了,但是这其中还有一些坑我们没遇到过,通过IBinder在服务端与客户端之间传递自定义对象,自定义对象需要实现Parcelable接口,就是说他们是在序列化和反序列化之间运作,服务端与客户端之间传递的对象并不是同一个对象。
5.Listener 在Binder中使用
虽然说多次,跨进程传输客户端的对象会在服务端生成不同的对象,但是生成的对象有一个共同点,就是它们底层的Binder对象是同一个,RemoteCallbackList利用这一特性,在解除注册Listener时,找到和注册时一样Binder对象的listener进行删除。RemoteCallbackList 当客户端死亡,它自动解除客户端持有的 listener,而且由于其线程同步的特性,不需要额外的线程同步工作。
添加注册和解注册的办法。
// IBookManager.aidl
package com.ajb.kotlintest;
// Declare any non-default types here with import statements
import com.ajb.kotlintest.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener( IOnNewBookArrivedListener listener);
void unregisterListener( IOnNewBookArrivedListener listener);
}
在Service中,添加相关实现代码
private RemoteCallbackList< IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList< IOnNewBookArrivedListener>();
@Override
public void registerListener( IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList. register( listener);
}
@Override
public void unregisterListener( IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList. unregister( listener);
};
利用这些就可以在新书到来之时,通知需要知道的监听者,不需要时就解除注册即可。
七、 ContentProvider
创建一个自定义的ContentProvider很简单,只需要继承ContentProvider类并实现六个抽象方法即可:onCreate、query、update、insert、delete和getType。
onCreate
代表ContentProvider的创建,一般来说我们需要做一些初始化工作;
getType
用来返回一个Uri请求所对应的MIME类型(媒体类型),比如图片、视频等,这个媒体类型还是有点复杂的,如果我们的应用不关注这个选项,可以直接在这个方法中返回null或者** “ */* ” **;Android系统所提供的MediaStore功能就是文件类型的ContentProvider,详细实现可以参考MediaStore。
query、update、insert、delete
剩下的四个方法对应于CRUD操作,即实现对数据表的增删改查功能。
根据Binder的工作原理,我们知道这六个方法均运行在ContentProvider的进程中,除了onCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中。
// DbOpenHelper. java
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_TALBE_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_TALBE_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) { // TODO ignored
}
}
// ContentProvider. java
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.ajb.kotlintest.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 {
//使用 UriMatcher 的 addURI 方法将 Uri 和 Uri_Code 关联到一起
sUriMatcher.addURI(AUTHORITY, " book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, " user", USER_URI_CODE);
}
private Context mContext;
private SQLiteDatabase mDb;
@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).getWritableDatabase();
mDb.execSQL(" delete from " + DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL(" delete from " + DbOpenHelper.USER_TALBE_NAME);
//ContentProvider创建时,初始化数据库。注意:这里仅仅是为了演示,实际使用中不推荐在主线程中进行耗时的数据库操作
mDb.execSQL(" insert into book values( 3,' Android');");
mDb.execSQL(" insert into book values( 4,' Ios');");
mDb.execSQL(" insert into book values( 5,' Html5');");
mDb.execSQL(" insert into user values( 1,' jake', 1);");
mDb.execSQL(" insert into user values( 2,' jasmine', 0);");
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.d(TAG, " query, current thread:" + Thread.currentThread().getName());
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException(" Unsupported URI: " + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public String getType(Uri uri) {
Log.d(TAG, " getType");
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.d(TAG, " insert");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException(" Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, " delete");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException(" Unsupported URI: " + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[]
selectionArgs) {
Log.d(TAG, " update");
String table = getTableName(uri);
if (table == null) {
throw new IllegalArgumentException(" Unsupported URI: " + uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().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_TALBE_NAME;
break;
default:
break;
}
return tableName;
}
}
ContentProvider 通过 Uri 来区分外界要访问的的数据集合,为了知道外界要访问的是哪个表,我们需要为它们定义单独的 Uri 和 Uri_Code ,并将 Uri 和对应的 Uri_Code 相关联,我们可以使用 UriMatcher 的 addURI 方法将 Uri 和 Uri_Code 关联到一起。这样,当外界请求访问 BookProvider 时,我们就可以根据请求的 Uri 来得到 Uri_Code ,有了 Uri_Code 我们就可以知道外界想要访问哪个表,然后就可以进行相应的数据操作了,
接着我们需要注册这个 BookProvider,否则它无法向外界提供有效的数据。 如下所示。
<provider
android:name = ".provider.BookProvider"
android:authorities = "com.ajb.kotlintest.provider"
android:permission = "com.ajb.kotlintest.PROVIDER"
android:process = ":provider" />
注册了ContentProvider以后,我们就可以在外部应用中访问它了。
注意点:android:authorities 必须是唯一的,这里建议加上包名前缀。android:process让BookProvider运行在独立的进程中并给它添加了权限,这样外界应用如果想访问BookProvider,就必须声明“com.ajb.kotlin.PROVIDER”这个权限。ContentProvider的权限还可以细分为读权限和写权限,分别对应android:readPermission和android:writePermission属性,如果分别声明了读权限和写权限,那么外界应用也必须依次声明相应的权限才可以进行读/写操作,否则外界应用会异常终止。
八、Messenger
Messenger 的使用方法很简单,它对 AIDL 做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
private static final int MSG_FROM_CLIENT = 1000;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_CLIENT:
Log.i(TAG, " receive msg from Client:" + msg.getData().getString(" msg"));
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
关于 Messenger的介绍就这么多,更详细的资料请查看相关网络资料。
九、 Socket
我们也可以通过 Socket 来实现进程间的通信。 Socket 也称为“套接字”,它分为流式套接字( TCP 协议)和用户数据报套接字( UDP 协议)两种。
TCP 协议是面向连接的协议,提供稳定的双向通信功能, TCP 连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;
而 UDP 是无连接的,提供不稳定的单向通信功能,当然 UDP 也可以实现双向通信功能。在性能上, UDP 具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。关于 TCP 和 UDP 的介绍就这么多,更详细的资料请查看相关网络资料。
至此,Android IPC机制的介绍暂告一段落了。
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很处理高并发清醒,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间数据共享 |
Socket | 功能请打,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点烦琐,不支持直接的RPC | 网络数据交换 |