什么是IPC?
IPC是Inter-Process Communication的缩写,含义为进程间通信或跨进程通信。
一般一个应用程序只有一个进程,至少有一个线程,即主线程,在Android中,主线程也叫UI线程,用于更新UI,如果进行耗时的操作,导致界面无法响应,就会出现ANR(Application not responding),所以要把操作放在子线程。
Android中的多进程模式
开启多进程模式:
在AndroidManifest.xml中,为activity设置android:process=”:remote”(remote可以随便起),这种方式进程名为:”程序包名:remote”
设置android:process=”com.ice.android.remote”,这种方式启动的进程名就是填入的字符串,不会附加包名。
第一种的为应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
第二种属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中
序列化
Serializable接口
此接口是Java自带的。
该接口实现起来非常简单,只需要在类中加入一个属性即可:private static final long serialVersionUID = 1L;
,序列化过程:
User user = new User(20,"李斌");
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)+"user");
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(user);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
反序列化过程类似,从文件读入Object即可。
但是两个对象不是同一个对象,只是属性的值一样而已。
serialVersionUID
是用来辅助序列化与反序列过程的,只有序列化数据中的serialVersionUID与当前类的serialVersionUID相同才能正常地被反序列化。
如果不指定该值,那么会自动根据当前类的结构自动生成hash值当做serialVersionUID,反序列化的时候也会生成hash值进行比较,所以我们改动了类的结构就无法成功反序列化,因为hash值会变化。因为如此,所以我们应该手动指定serialVersionUID,这样就算改动了类的结构,还是能最大限度的反序列化。
Parcelable接口
此接口是Android特有的。
package com.ice.androiddevart;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = 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(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
writeToParcel
方法进行,protected Book(Parcel in)
进行反序列化。
区别
Parcelable和Serializable都能实现序列化并且都可以用于Intent间传递数据。但是Serializable开销较大,需要大量的I/O操作。而Parcelable是Android中特有的序列化方式,效率较高。
Parcelable主要用在内存序列化上。而Serializable主要用于将对象序列化到存储设备或用于网络传输。
Binder
暂时跳过
Android中的IPC方式
使用Bundle
Intent intent = new Intent("");
Bundle bundle = new Bundle();
bundle.putSerializable("key", serializable_object);
bundle.putParcelable("key",parcelable_object);
intent.putExtras(bundle);
四大组件中的三大组件(没有广播)都支持在Intent中传递Bundle数据。
使用文件共享
实现Serializable接口,将对象序列化写入文件,然后在其他进程读取即可。
但是在并发读写的时候是有问题的,所以适合在对数据同步要求不高的进程之间通信。
使用Messenger
使用步骤:
服务端:
创建一个MessengerHandler,用于接受客户端发送过来的消息。
private static class MessengerHandler extends Handler{ @Override public void handleMessage(Message msg) { switch (msg.what){ case FROM_CLIENT: Log.d(TAG, "handleMessage: get message from client" + msg.getData()); break; } } }
通过上面的Handler创建一个Messenger对象:
private final Messenger mMessenger = new Messenger(new MessengerHandler());
在Service的onBind中返回Messenger的底层Binder。
客户端:
绑定Service,绑定成功后用服务器端返回的IBinder对象创建一个Messenger,通过它向服务器发送数据。
private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mServiceMessenger = new Messenger(service); Message message = Message.obtain(null, MessengerService.FROM_CLIENT); Bundle data = new Bundle(); data.putString("msg","Hello this is client"); message.setData(data); try { mServiceMessenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } };
如果服务器要向客户端通信,那么在客户端要创建一个Handler对象,然后创建一个客户端的Messenger对象,将此对象通过Message对象的replyTo参数传递给服务端即可。
Messenger的工作方式是串行的,只能一个消息一个消息的处理,如果大量的并发请求,就不合适了。
而且Messenger无法调用服务端的方法。要实现方法的调用,要使用AIDL。
Messenger本质上也是AIDL,只不过系统做了很好的封装。
使用AIDL
AIDL:Android Interface Definition Language
服务端:
将服务端要暴露的接口写在AIDL文件中:
interface IbookManager{ List<Book> getBookList(); void addBook(in Book book); }
在服务器端实现方法:
private CopyOnWriteArrayList<Book> mBookList = 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); } };
并通过onBind方法返回Binder对象
在客户端通过服务端返回的Binder对象转化成AIDL接口所属的类型,接着调用方法即可。
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IbookManager bookManager = IbookManager.Stub.asInterface(service);
try{
List<Book> list = bookManager.getBookList();
Log.d(TAG, "onServiceConnected: book list class:" + list.getClass().getCanonicalName());
Log.d(TAG, "onServiceConnected: book list :" + list.toString());
}catch (RemoteException e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
注意:
在AIDL文件中支持的数据类型:
- 基本的数据类型
- String和CharSequence
- List:只支持ArrayList,里面的每个元素都必须被AIDL支持
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- Parcelable:所有实现了Parcelable接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
自定义的Parcelable对象和AIDL对象必须要显示的import,就算位于同一个包下面也要导入。
如果AIDL文件中用到了自定义的Parcelable对象,那么要新建一个与之同名的AIDL文件,并声明为Parcelable类型:
package com.ice.androiddevart;
parcelable Book;
普通方法能够工作的很好,但是如果采用了观察者模式,进行监听回调,那么再注册与解除的时候会出现问题,即使在Activity中,listener是同一个对象,但是在Service中,listener由于经过了序列化与反序列化,所以对象以及不再是同一个了,无法通过判断是否是同一个对象而解除监听。要使用RemoteCallbackList来进行注册与解除,原理是通过listener底层的Binder对象来判断是否是同一个listener,因为底层的Binder对象是不会变的。
在远程访问服务端的方法时,方法是在Binder线程池中运行的,所以服务端的方法不需要再套一个子线程,应该在客户端调用的时候套子线程,避免ANR。
ContentProvider
底层依然采用Binder,但是由于封装的很好了,所以使用起来特别方便。
自定义一个ContentProvider,重写方法crud onCreate,getType方法即可:
其中onCreate运行在主线程,其他运行在Binder线程池。
在xml文件中声明provider:
<provider android:name=".provider.BookProvider" android:authorities="com.ice.androiddevart.provider" #通过这个访问 名字要唯一 android:process=":provider" />
在Activity中访问:
Uri uri = Uri.parse("content://com.ice.androiddevart.provider"); getContentResolver().query(uri,null,null,null,null);
contentProvider用来对其他应用进行增删改查是十分简单的,但是如果自己定义contentProvider的话,要做好线程同步的工作!
Socket通信
暂时跳过