1运提、IPC簡(jiǎn)介
IPC是Inter-Process Communication的縮寫俯在,進(jìn)程間通信或者跨進(jìn)程通信粒督,是指兩進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程杆烁。在Android中牙丽,UI是主線程,其可以操作界面元素兔魂,但耗時(shí)操作放在UI線程處理會(huì)導(dǎo)致ANR錯(cuò)誤烤芦。
2、多進(jìn)程模式
2.1入热、開啟多進(jìn)程
通過四大組件制定android:process屬性拍棕,可以開啟多進(jìn)程模式,例如:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity"
android:process=":test"/>
<activity android:name=".ThirdActivity"
android:process="com.fomin.ipc.test"/>
</application>
上面分別為SecondActivity和ThirdActivity增加了process屬性勺良,可以看出绰播,他們的命名是不一樣的,首先尚困,“:”的含義是指要在當(dāng)前的進(jìn)程名前面附加上當(dāng)前的包名蠢箩,簡(jiǎn)寫的寫法;其次事甜,“:”開頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有名稱谬泌,其它應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中,而進(jìn)程名不以":"開頭的進(jìn)程屬于全局進(jìn)程逻谦,可以通過ShareUID和它跑在同一個(gè)進(jìn)程中(要求簽名相同既可以)掌实。
2.2、運(yùn)行機(jī)制
Android為每個(gè)應(yīng)用都分配了獨(dú)立的虛擬機(jī)邦马,也可以說為每個(gè)進(jìn)程分配獨(dú)立虛擬機(jī)贱鼻,不同的虛擬機(jī)在內(nèi)存中分配上有不同的地址空間,導(dǎo)致不同的虛擬機(jī)中訪問一個(gè)類的對(duì)象會(huì)產(chǎn)生多個(gè)副本滋将。
一般來說邻悬,使用多進(jìn)程是產(chǎn)生下面的問題:
- 靜態(tài)成員和單列模式完全失效
- 線程同步機(jī)制完全失效
- SharedPreferences的可靠性下降
- Application會(huì)多次創(chuàng)建
3、Binder
Binder是Android中的一個(gè)類随闽,實(shí)現(xiàn)了IBinder接口父丰,從IPC角度來說,它是Android的一種跨進(jìn)程通信方式掘宪。主要在Service中蛾扇,包括AIDL和Messenger攘烛,其中普通Service中的Binder不涉及進(jìn)程間通信的。
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.fomin.ipc.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.fomin.ipc.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.fomin.ipc.IBookManager interface,
* generating a proxy if needed.
*/
public static com.fomin.ipc.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.fomin.ipc.IBookManager))) {
return ((com.fomin.ipc.IBookManager) iin);
}
return new com.fomin.ipc.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.fomin.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.fomin.ipc.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.fomin.ipc.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.fomin.ipc.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.fomin.ipc.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.fomin.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.fomin.ipc.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.fomin.ipc.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.fomin.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.fomin.ipc.Book book) throws android.os.RemoteException;
}
上面的類是通過AIDL自動(dòng)生成的屁桑,介紹下相關(guān)的方法含義医寿。
- DESCRIPTOR:Binder的唯一標(biāo)識(shí),一般用在當(dāng)前Binder的類名表示蘑斧。
- asInterface(android.os.IBinder obj):用于將服務(wù)端的Binder對(duì)象轉(zhuǎn)換成客戶端的AIDL接口類型對(duì)象靖秩,轉(zhuǎn)換過程是區(qū)分進(jìn)程的,如果客戶端和服務(wù)端位于同一進(jìn)程竖瘾,那么此方法返回的就是服務(wù)端的Stub對(duì)象本身沟突,否則返回的系統(tǒng)封裝后的Stub.proxy對(duì)象。
- asBinder:返回當(dāng)前的Binder對(duì)象
- onTransact:方法運(yùn)行在服務(wù)端中的Binder線程池中捕传,當(dāng)客戶端發(fā)起跨進(jìn)程請(qǐng)求時(shí)惠拭,遠(yuǎn)程請(qǐng)求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理。
- Proxy#getBookList:自定義的API
- Proxy#addBook:自定義的API
4庸论、Android中的IPC方式
4.1职辅、使用文件共享
共享文件是一種不錯(cuò)的進(jìn)程間通信方式,兩個(gè)進(jìn)程通過讀/寫一個(gè)文件來交換數(shù)據(jù)聂示。在Android中域携,SharedPreferences是輕量級(jí)的存儲(chǔ)方案,由于系統(tǒng)對(duì)它的讀/寫有一定的緩存策略鱼喉,即在內(nèi)存中會(huì)有一份SharedPreferences的文件緩存秀鞭,因此在多線程下,系統(tǒng)對(duì)讀/寫變得不可靠扛禽,面對(duì)高并發(fā)的讀/寫數(shù)據(jù)锋边,有很大的幾率會(huì)丟失數(shù)據(jù),不建議進(jìn)程間通信使用SharedPreferences编曼。
進(jìn)程一寫數(shù)據(jù):
fun saveDataToFile() {
Thread(Runnable {
var book: Book = Book(1, "Android")
val dir = File(path)
if (!dir.exists()) {
dir.mkdirs()
}
val cachedFile = File(cachePath)
var stream: ObjectOutputStream? = null
try {
stream = ObjectOutputStream(FileOutputStream(cachedFile))
stream.writeObject(book)
} catch (e: IOException) {
e.printStackTrace();
} finally {
stream?.close();
}
}).start()
}
進(jìn)程二讀取數(shù)據(jù):
fun readFromFile() {
Thread(Runnable {
var book: Book? = null
val cachedFile = File(cachePath)
if (cachedFile.exists()) {
var stream: ObjectInputStream? = null
try {
stream = ObjectInputStream(FileInputStream(cachedFile))
book = stream.readObject() as Book
} catch (e: IOException) {
e.printStackTrace();
} finally {
stream?.close();
}
}
}).start()
}
4.2豆巨、使用Messager
Messager中進(jìn)行的數(shù)據(jù)傳遞必須將數(shù)據(jù)傳入Message中,而Messager和Message都實(shí)現(xiàn)了Parcelable接口掐场。在Message中有what往扔、arg1、arg2刻肄、Bundle和replyTo瓤球,而另外一個(gè)object字段只能使用系統(tǒng)提供的Parcelable對(duì)象才能傳輸融欧,自定義的Parcelable對(duì)象是無法通過傳輸?shù)拿羝O旅嫱ㄟ^例子來看看它是怎樣使用的。
class MessageService : Service() {
private val TAG = "MessageService"
private val mMessenger = Messenger(MessageHandler())
private inner class MessageHandler : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
1001 -> {
Log.d(TAG, msg.data.getString("key"))
val client = msg.replyTo
val reply = Message.obtain(null, 1002)
val bundle = Bundle()
bundle.putString("key", "消息收到")
reply.data = bundle
try {
client.send(reply)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
else -> super.handleMessage(msg)
}
}
}
override fun onBind(intent: Intent): IBinder? {
return mMessenger.binder
}
}
注冊(cè)service
<service android:name=".MessageService"
android:process=":process1"/>
客戶端使用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent(this, MessageService::class.java)
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
private val TAG = "MainActivity"
private var mService: Messenger? = null
private val mMessenger = Messenger(MessengerHandler())
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
mService = Messenger(service)
val msg = Message.obtain(null, 1001)
val data = Bundle()
data.putString("msg", "客戶端發(fā)送消息")
msg.data = data
msg.replyTo = mMessenger
try {
mService?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName) {
}
}
private inner class MessengerHandler : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
1002 -> Log.i(TAG, msg.data.getString("key"))
else -> super.handleMessage(msg)
}
}
}
4.3噪馏、使用AIDL
AIDL首先需要?jiǎng)?chuàng)建一個(gè)Service監(jiān)聽客戶端的連接請(qǐng)求麦到;再次需要?jiǎng)?chuàng)建客戶端綁定Service绿饵。下面來看看AIDL接口創(chuàng)建(IBookManager.aidl和Book.aidl)
//IBookManager.aidl
import com.fomin.ipc.Book;
// Declare any non-default types here with import statements
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
// IBook.aidl
packag// IBook.aidl
package com.fomin.ipc;
// Declare any non-default types here with import statements
parcelable Book;
支持以下數(shù)據(jù)類型:
- 基本數(shù)據(jù)類型(int、long瓶颠、char拟赊、boolean、double等)
- String和CharSequence
- List:只支持ArraryList
- Map:只支持HashMap
- Parcelable:所有實(shí)現(xiàn)Parcelable的對(duì)象
- AIDL:所有AIDL本身也可以使用
創(chuàng)建Service:
class BookManagerService : Service() {
private val TAG = "BookManagerService"
private val mBookList = CopyOnWriteArrayList<Book>()
private val mBinder = object : IBookManager.Stub() {
@Throws(RemoteException::class)
override fun getBookList(): List<Book>? {
return mBookList
}
@Throws(RemoteException::class)
override fun addBook(book: Book) {
mBookList.add(book)
}
}
override fun onBind(intent: Intent?): IBinder {
return mBinder
}
}
注冊(cè)service
<service android:name=".BookManagerService"
android:process=":process2"/>
創(chuàng)建客戶端
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent(this, BookManagerService::class.java)
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
unbindService(mConnection)
super.onDestroy()
}
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val bookManager = IBookManager.Stub.asInterface(service)
try {
val bookList = bookManager.bookList
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName) {
}
}
在這有個(gè)問題粹淋,UI線程執(zhí)行耗時(shí)的操作會(huì)導(dǎo)致ANR吸祟,所以客戶端在getBookList的時(shí)候需要在非UI線程執(zhí)行
4.4、使用ContentProvider
ContentProvider底層也是實(shí)現(xiàn)Binder桃移,主要以表格形式來組織數(shù)據(jù)屋匕,可以包含多個(gè)表,支持文件數(shù)據(jù)(圖片借杰、視頻等)过吻。需要注意的是query、update蔗衡、insert纤虽、delete四大方法是存在多線程并發(fā)訪問的,因此內(nèi)部需要做好線程同步绞惦。
//數(shù)據(jù)庫(kù)操作工具
class DbHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("")//創(chuàng)建表
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
}
companion object {
private val DB_NAME = "book.db"
private val DB_VERSION = 1
}
}
//數(shù)據(jù)操作
class BookProvider : ContentProvider() {
private var mDb: SQLiteDatabase? = null
override fun onCreate(): Boolean {
mDb = DbHelper(getContext()).writableDatabase
return true
}
override fun insert(uri: Uri?, values: ContentValues?): Uri {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun query(uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String>?): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getType(uri: Uri?): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
注冊(cè)ContentProvider逼纸,android:authorities必須是唯一的,外界想使用BookProvider必須聲明com.fomin.PROVIDER這權(quán)限
<provider
android:authorities="com.fomin.ipc"
android:name=".BookProvider"
android:permission="com.fomin.PROVIDER"
android:process=":provider"/>
客戶端使用:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val bookUri = Uri.parse("content://com.fomin.ipc/book")
val contentValues = ContentValues()
contentValues.put("id", 1)
contentValues.put("name", "Android")
contentResolver.insert(bookUri, contentValues)
}
4.5翩隧、使用socket
使用socket通信樊展,首先需要聲明權(quán)限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
其次不能再主線程中訪問網(wǎng)絡(luò),網(wǎng)絡(luò)操作可能是耗時(shí)的堆生,影響程序的效率专缠,而且在4.0系統(tǒng)會(huì)報(bào)錯(cuò)android.os.NetworkOnMainThreadException.
5、選用合適的IPC方式
名稱 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場(chǎng)景 |
---|---|---|---|
Bundle | 簡(jiǎn)單 | 只能傳輸Bundle支持的數(shù)據(jù) | 四大組件進(jìn)程間的通信 |
文件共享 | 簡(jiǎn)單 | 不適合高并發(fā)場(chǎng)景淑仆,并無法做到進(jìn)程間的即時(shí)通信 | 無并發(fā)訪問情形 |
AIDL | 功能強(qiáng)大涝婉,支持一對(duì)多并發(fā)通信,支持實(shí)時(shí)通信 | 使用復(fù)雜蔗怠,需要處理好線程間的同步 | 一對(duì)多通信且有RPC需求 |
Messenger | 功能一般墩弯,支持一對(duì)多串行通信,支持實(shí)時(shí)通信 | 不能很好的處理高并發(fā)的情形寞射,不支持RPC渔工,數(shù)據(jù)通過Message進(jìn)行傳輸,因此只能傳輸Bundle支持的數(shù)據(jù) | 低并發(fā)的一對(duì)多即時(shí)通信桥温,無RPC需求引矩,或者不需要返回結(jié)果的RPC需求 |
ContentProvider | 數(shù)據(jù)源訪問功能強(qiáng)大,支持一對(duì)多并發(fā)數(shù)據(jù)共享 | 主要提供數(shù)據(jù)源的CRUD操作 | 一對(duì)多進(jìn)程間的數(shù)據(jù)共享 |
Socket | 功能強(qiáng)大,通過網(wǎng)絡(luò)傳輸字節(jié)流旺韭,支持一對(duì)多并發(fā)實(shí)時(shí)通信 | 實(shí)現(xiàn)繁瑣氛谜,不支持直接的RPC | 網(wǎng)絡(luò)數(shù)據(jù)交換 |