IPC:Inter-Process-Communication,即進程間通訊或者跨進程通訊,是指兩個進程之間進行數據交換的過程。任何一個操做系統都有相應的IPC,Android是一種基於Linux內核的移動操做系統,它的進程間通訊方式並不能徹底繼承自Linux,相反,它有本身的進程間通訊方式,在Android中最有特點的進程間通訊方式就是Binder了。java
線程:CPU調度的最小單元,同時線程是一種有限的系統資源。android
進程:指一個執行單元,在PC和移動設備上指一個程序或者一個應用。數據庫
開啓多進程:windows
一、經過給四大組件(Activity、Service、Receiver、ContentProder)在AndroidMenifest中指定android:process
屬性,開啓多進程模式。數組
二、經過JNI在native層fork一個新的進程(很是規)。bash
<service
android:name="com.lwj.mytestpro.aidl.BookManagerService"
android:exported="true"
android:process=":remote">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="com.lwj.ipc.aidl.BookManagerService"/>
</intent-filter>
</service>
複製代碼
android:process=":remote"
系統爲此建立一個單獨的名爲包名:remote
的進程。服務器
通常來講,使用多進程會形成以下幾個方面的問題: (1)靜態成員和單例模式徹底失效。 (2)線程同步機制徹底失效。 (3)SharedPreferences的可靠性降低。 (4)Application會屢次建立。網絡
關於(1)(2)問題本質上類似:Android爲每一個應用分配一個獨立的虛擬機,或者說爲每一個進程都分配一個獨立的虛擬機,不一樣的虛擬機在內存分配上有不一樣的地址空間,這就致使不一樣的虛擬機中訪問同一個類的對象會產生多分副本。多線程
關於問題(3):由於SharedPreferences不支持兩個進程同時執行寫操做,不然會致使必定的概率的數據丟失。這是一位SharedPreferences底層是經過讀/寫XML文件來實現的,併發寫顯然是可能出問題的。併發
關於問題(4):當一個組件跑在一個新的進程中的時候,因爲系統在建立新的進程時分配獨立的虛擬機,因此這個過程其實就是一個啓動應用的過程,所以至關於系統又把這個應用從新啓動了一遍,既然重啓了,那麼天然會建立新的Application。能夠理解爲:運行在同一個進程的組件是屬於同一個虛擬機和同一個Application的,同理,運行在不一樣進程中的組件是屬於兩個不一樣的虛擬機和Application的。
Serializable是Java所提供的一個序列化接口,也是一個空接口,爲對象提供標準的序列化和反序列化操做。使用Serializable來實現序列化至關簡單,只須要在類的聲明指定一個相似下面的標識便可自動實現默認的序列化過程。
public class User implements Serializable {
private static final long serialVersionUID = 1230212L;
public int userId;
public String userName;
public boolean isMale;
}
複製代碼
經過Serializable方式實現對象的序列化,實現起來很簡單,幾乎全部的工做都被系統自動完成,如何進行對象的序列化和反序列化也很是簡單,只須要採用ObjectOutputStream和ObjectInputStream便可輕鬆完成。
上述代碼演示了採用Serializable方式序列化對象的典型過程。只須要把實現的Serializable接口的對象寫到文件中就能夠快速恢復了,恢復後的對象newUser和user的內容徹底同樣,可是二者並非同一個對象。
serialVersionUID意義:指定serialVersionUID的值,編譯器自動生成hash值,這樣序列化和反序列化的serialVersionUID值相同,能夠正常進行反序列化。若是不指定,反序列化時當前類有所改變(增減成員變量)那麼系統會重現計算當前類的hash值並把它複製給serialVersionUID,這時候序列化和反序列化的serialVersionUID不一致,因而反序列化失敗,程序出現crash。
Parcelable:接口,類實現此接口,其對象就能夠實現序列化並能夠經過Intent和Binder傳遞。
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public int getBookId() {
return bookId;
}
public String getBookName() {
return 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方法完成,最終經過Parcel的一系列write方法完成。
反序列化:由CREATOR 來完成,其內部代表瞭如何建立序列化對象和數組,並經過Parcel的一系列read方法完成反序列化過程。
內容描述功能:由describeContents方法來完成,幾乎全部狀況下這個方法都應該返回0,當且僅當對象中存在文件描述符時,此方法返回1.
Serializable、Parcelable比較:Serializable是Java中的序列化接口,開銷很大,序列化和反序列化須要大量的I/O操做,而Parcelable是Android中的序列化方式,這是Android推薦的序列化方式,使用稍微麻煩,可是效率高,所以首選Parcelable。Parcelable主要用在內存序列化,經過Parcelable將對象序列化到存儲設備中或者對象序列化後經過網絡傳輸都是能夠的,可是過程稍微複雜,所以二者對比下,建議使用Serializable。
直觀來講,Binder是Android中的一個類,實現了IBinder接口。
從IPC來講,Binder是Android中的一種跨進程通訊方式,Binder還能夠理解爲虛擬的物理設備,它的存儲驅動是/dev/binder,該通訊方式在Linux是沒有的。
從Android Framework來講,Binder是ServiceManager鏈接各類Manager(ActivityManager、WindowManager等等)和ManagerService的橋樑。
從Android應用層來講,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含服務端業務調用的Binder對象,經過這個Binder對象,客戶端就能夠獲取服務端的服務或數據,這裏的服務包括普通服務和基於AIDL的服務。
Android開發中,Binder 主要用在Service中,包括AIDL和Messenger,其中Service總的Binder不涉及進程間通訊,而Messenger的底層其實就是AIDL。
Android中實現IPC方式有: (1)AIDL (2)Messenger (3)文件共享 (4)Bundle (5)ContentProvider (6)Socket
一、服務端 服務端首先要建立一個Service來監聽客戶端的鏈接請求,而後建立AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,最後在Service中實現AIDL接口便可。
二、客戶端 綁定服務端的Service,綁定成功後,將服務端返回的Binder對象轉換成AIDL接口所屬的類型,接着能夠調用AIDL中的方法了。
AIDL支持的數據類型:
自定義的Parcelable對象和AIDL對象必須顯式的import進來,無論是否和當前的AIDL文件在同一個包下。
AndroidStudio新建一個AIDL文件
項目右鍵——>new——>AIDL
1)建立一個Book.java 文件實現Parcelable接口
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public int getBookId() {
return bookId;
}
public String getBookName() {
return 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);
}
}
複製代碼
2)Book.java
數據對象須要建立相對應的Book.aidl
文件
3)AIDL接口的建立
建立一個後綴爲aidl
的文件,以下是IOnNewBookArrivedListener.aidl
文件
// IOnNewBookArrivedListener.aidl
package com.lwj.ipc.aidl;
// Declare any non-default types here with import statements
import com.lwj.ipc.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
複製代碼
這裏須要用到Book,須要顯式的import進來import com.lwj.ipc.aidl.Book;
。
再建立一個AIDL接口、用於註冊和解註冊IOnNewBookArrivedListener
接口的方法,和addBook
方法添書籍。
IBookManager.aidl
// IBookManager.aidl
package com.lwj.ipc.aidl;
import com.lwj.ipc.aidl.Book;
import com.lwj.ipc.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book>getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
複製代碼
一樣Book
和IOnNewBookArrivedListener
須要顯式import進來。
以上的文件最好放到同一個包下面,也就是aidl
包的下面。
4)建立Service,實現AIDL接口
建立Service,而且再AndroidManifest.xml
中註冊
<service
android:name="com.lwj.mytestpro.aidl.BookManagerService"
android:exported="true"
android:process=":remote">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="com.lwj.ipc.aidl.BookManagerService"/>
</intent-filter>
</service>
複製代碼
註冊Service的同時,android:process=":remote"
開啓了一個進程,也就是讓服務端跟客戶端不一樣的進程,來演示跨進程通訊。
BookManagerService
文件實現AIDL接口
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
//存放書本的集合
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
//存放監聽的集合,必須使用RemoteCallbackList,不然沒法解註冊
private RemoteCallbackList<IOnNewBookArrivedListener> mListeners = new RemoteCallbackList<>();
//實現AIDL接口
private Binder 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 registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListeners.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListeners.unregister(listener);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
new Thread(new ServiceWorker()).start();
}
/**
* 把新加的書本添加到集合中
* 而且通知有新的書本增長
* @param book
* @throws RemoteException
*/
private void onNewBookArrived(Book book)throws RemoteException{
mBookList.add(book);
final int N = mListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = mListeners.getBroadcastItem(i);
if (listener != null){
//通知
listener.onNewBookArrived(book);
}
}
mListeners.finishBroadcast();
}
/**
* 演示,每一個一秒中增長一本書
*/
private class ServiceWorker implements Runnable{
@Override
public void run() {
while (!mIsServiceDestoryed.get()){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size()+1;
Book newBook = new Book(bookId,"new book#"+bookId);
try {
//加入集合
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//權限驗證
int check = checkCallingOrSelfPermission("com.lwj.aidl.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED){
return null;
}
return binder;
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
}
複製代碼
這裏添加了一個權限驗證
BookManagerActivity
和Service的跨進程通訊
BookManagerActivity
客戶端實現:
public class BookManagerActivity extends AppCompatActivity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoterBookManager;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MESSAGE_NEW_BOOK_ARRIVED:
Log.i("lwjtag","receive new book:"+msg.obj);
break;
default:
super.handleMessage(msg);
break;
}
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoterBookManager = bookManager;
Book mBook = new Book(3,"Android 藝術開發探索");
bookManager.addBook(mBook);
List<Book> list = bookManager.getBookList();
Log.i("lwjtag","query book list type:"+list.getClass().getCanonicalName());
if (list != null && list.size() > 0){
for (Book book : list) {
Log.i("lwjtag","query book list:"+book.getBookName());
}
}
bookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();
}
@Override
public IBinder asBinder() {
return mRemoterBookManager.asBinder();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this,BookManagerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
if (mRemoterBookManager != null && mRemoterBookManager.asBinder().isBinderAlive()){
try {
mRemoterBookManager.unregisterListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_book_manager, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
複製代碼
若是有十、100個不一樣的業務模塊都須要AIDL來進行進程間通訊,那該怎麼處理?這種狀況不可能建立10或者100個Service來處理,這就須要減小Service數量,將全部的AIDL 文件放到同一個Service中去管理。
整個工做機制:每一個業務模塊建立本身的AIDL接口並實現此接口,這個時候不一樣的業務模塊之間是不能有耦合的,全部的細節咱們單獨開來,而後向服務端提供本身的惟一標識和其對應的Binder對象;對於服務端來講,只須要一個Service就能夠了,服務端只是提供一個queryBinder接口,這個接口可以根據業務模塊的特徵來返回相應的Binder對象給他們,不一樣的業務模塊拿到所需的Binder對象後就能夠進行遠程方法調用了,因而可知,Binder鏈接池的主要做用就是將每一個業務模塊的Binder請求統一轉發到遠程的Service中執行,從而避免了重複建立Service的過程,工做原理以下圖所示:
實例:提供兩個AIDL接口(ISecurityCenter.aidl和Icompute.aidl)來模擬上面多業務模塊都使用AIDL的狀況。
1) Icompute.aidl
:
// Icompute.aidl
package com.lwj.ipc.aidl;
// Declare any non-default types here with import statements
interface Icompute {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
* binder線程池中的
*/
int add(int a,int b);
}
複製代碼
2) ISecurityCenter.aidl
:
// ISecurityCenter.aidl
package com.lwj.ipc.aidl;
// Declare any non-default types here with import statements
interface ISecurityCenter {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
* binder線程池中的
*/
//加密
String encrypt(String content);
//解密
String decrypt(String password);
}
複製代碼
3) IBinderPool.aidl
:提供查找Binder的接口
// IBinderPool.aidl
package com.lwj.ipc.aidl;
// Declare any non-default types here with import statements
interface IBinderPool {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
* binder線程池接口
*/
IBinder queryBinder(int binderCoder);
}
複製代碼
4)IcomputeImpl
:實現Icompute接口
import android.os.RemoteException;
import com.lwj.ipc.aidl.Icompute;
/**
* Created by Administrator on 2017/5/20 0020.
*/
public class IcomputeImpl extends Icompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a+b;
}
}
複製代碼
5)ISecurityCenterImpl
:實現ISecurityCenter
接口
import android.os.RemoteException;
import com.lwj.ipc.aidl.ISecurityCenter;
/**
* Created by Administrator on 2017/5/20 0020.
*/
public class ISecurityCenterImpl extends ISecurityCenter.Stub {
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[i] ^= SECRET_CODE;
}
return new String(chars);
}
@Override
public String decrypt(String password) throws RemoteException {
return encrypt(password);
}
}
複製代碼
6)BinderPool
實現查詢binderpool接口的方法和邏輯
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_SECURITY_CENTER = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
//獲取上下文
this.mContext = context.getApplicationContext();
connectBinderPoolService();
}
/**
* 鏈接BinderPool
* 啓動Service
*/
private void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinerPoolService.class);
mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 單例模式實現
* @param context
* @return
*/
public static BinderPool getInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
/**
* 根據code查詢binder而且返回Binder對象
* @param binderCode
* @return
*/
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
/**
* 鏈接Service,拿到binderPool對象
*/
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
//死亡監聽
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* 死亡重連
*/
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
/**
* 實現binderpool接口方法
*/
public static class BinderPoolImpl extends IBinderPool.Stub {
public BinderPoolImpl() {
super();
}
@Override
public IBinder queryBinder(int binderCoder) throws RemoteException {
IBinder binder = null;
switch (binderCoder) {
case BINDER_SECURITY_CENTER:
binder = new ISecurityCenterImpl();
break;
case BINDER_COMPUTE:
binder = new IcomputeImpl();
break;
default:
break;
}
return binder;
}
}
}
複製代碼
7)建立Service,另開進程。
public class BinerPoolService extends Service {
private static final String TAG = "BinerPoolService";
private Binder mBinderPool = new BinderPool.BinderPoolImpl();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinderPool;
}
}
複製代碼
8)BinderPoolActivity
實現進程間通訊
public class BinderPoolActivity extends AppCompatActivity {
private ISecurityCenter sercurityCenter = null;
private Icompute mCompute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder_pool);
new Thread(new Runnable() {
@Override
public void run() {
dowork();
}
}).start();
}
private void dowork(){
//獲取binderPool實例
BinderPool binderPool = BinderPool.getInstance(BinderPoolActivity.this);
//查詢isecurityBinder
IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER);
//獲得ISecurityCenter實例
sercurityCenter = (ISecurityCenter)ISecurityCenterImpl.asInterface(securityBinder);
Log.i("lwjtag", "visit ISecurityCenter");
String msg = "helloworld-安卓";
System.out.println("content:"+msg);
try {
String password = sercurityCenter.encrypt(msg);
System.out.println("encrypt:"+password);
System.out.println("decrypt:"+sercurityCenter.decrypt(password));
} catch (RemoteException e) {
e.printStackTrace();
}
Log.i("lwjtag","visit ICompute");
IBinder computeBinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE);
mCompute = (Icompute) IcomputeImpl.asInterface(computeBinder);
try {
System.out.println("3+5="+mCompute.add(3,5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_binder_pool, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
複製代碼
結果:
這裏須要額外說明一下,爲何要在線程中去執行呢?這是由於在Binder鏈接池的實現中,咱們經過CountDownLath將binderService這一異步操做轉換成了同步操做,這就意味着,它有多是耗時的,而後就是Binder方法的調用過程也多是耗時的,所以不建議放在主線程去執行。注意到BinderPool是一個單例實現,所以在同一個進程中只會初始化一次,因此若是咱們提早初始化BinderPool,那麼能夠優化程序體驗,好比咱們能夠放到Application中提早對BinderPool進行初始化,雖然不能保證咱們調用BinderPool時它必定是初始化好的,但在絕大多數狀況下,這種初始化(綁定遠程服務)的時間開銷(若是BinderPool沒有提早初始化完成的話)是能夠接受的。另外,BinderPool中有短線重連的機制,當遠程服務意外終止時,BinderPool會從新創建鏈接,這個時候若是業務模塊中的Binder調用出現了異常,也須要手動去從新獲取最新的Binder對象,這個是須要注意的。
BinderPool大大方便了平常的開發,極大的提升了AIDL的開發效率,而且避免大量的Service建立,建議在開發工做中引入BinderPool機制。
AIDL:解決5.0以上找不到類的方法,須要再app下的build.gradle加入下的android
加入如下語句便可。
sourceSets {
main {
// Manifest.srcFile ['src/main/AndroidManifest.xml']
java.srcDirs = ['src/main/java', 'src/main/aidl']
resources.srcDirs = ['src/main/java']
aidl.srcDirs = ['src/main/aidl']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
複製代碼
三組件(Activity、Service、Receiver)都支持在Intent中傳遞Bundle數據的,因爲Bundle實現了Parcelable接口,因此能夠方便地在不一樣的進程間通訊。
傳送的數據必須可以被序列化,如:基本類型、實現Parcelable和Serializable接口,以及Android支持的特殊對象,具體內容能夠看Bundle這個類,就能夠看到其支持的類型。
特殊使用場景:如A進程正在進行一個計算,計算完成後它須要啓動B進程的一個組件並把計算結果傳送給B進程,但是遺憾的是計算結果不支持放入Bundle中,所以沒法經過Intent傳輸,這個時候使用AIDL略顯複雜。能夠考慮如下方式:咱們能夠經過Intent啓動B進程的一個Service組件(如IntentService),讓Service在後臺進行計算,計算完畢後再啓動B進程中真正要啓動的目標組件,因爲Service也運行在B進程中,因此目標組件就能夠直接獲取計算結果,這樣一來就輕鬆解決了跨進程的問題。這種方式的核心思想是在於將原來須要在A進程的計算任務轉移到B進程的後臺Service中執行,這樣就成功地避免了進程間通訊問題,並且只用了很小的代價。
文件共享也是一種不錯的進程間通訊方式,兩個進程經過讀寫同一個文件來交換數據,好比A進程把數據寫入文件,B進程經過讀取這個文件來獲取數據。windows上,一個文件若是被加入了排斥鎖將會致使其餘線程沒法對其進行訪問,包括讀寫,而因爲Android系統基於Linux,使得其併發讀/寫文件能夠沒有限制的進行,甚至兩個線程同時對同一個文件進行讀寫操做都是容許的,儘管這可能出問題,經過文件交換數據很好使用,除了交換一些文本信息外,還能夠序列化一個對象到文件系統中的同時從另外一個進程中恢復這個對象。以下采用的即是這一方式。
public class User implements Serializable {
private static final long serialVersionUID = 519067123721295773L;//通常須要手動指定此值,避免反序列化失敗
public int userId;
public String userName;
public boolean isMale;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public User() {
}
public int getUserId() {
return userId;
}
public String getUserName() {
return userName;
}
public boolean isMale() {
return isMale;
}
public void setUserId(int userId) {
this.userId = userId;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setIsMale(boolean isMale) {
this.isMale = isMale;
}
}
複製代碼
//序列化
private void persistToFile(){
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1,"hello world",false);
File dir = new File(MyConstants.FILE_PAHT);
if (!dir.exists()){
dir.mkdirs();
}
File cachedFile = new File(MyConstants.FILE_PAHT);
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(cachedFile));
out.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}finally {
MyUtils.close(out);
}
}
}).start();
}
複製代碼
//反序列化
private void recoverFromFile(){
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cacheFile = new File(MyConstants.FILE_PAHT);
if (cacheFile.exists()){
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream(cacheFile));
user = (User)in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
MyUtils.close(in);
}
}
}
}).start();
}
複製代碼
關閉輸出和輸入流
public class MyUtils {
public static void close(Closeable stream){
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
經過文件共享這種方式共享數據對文件格式沒有具體要求,能夠是文本文件,也能夠是XML文件,只要讀/寫雙方約定數據格式便可。
Messenger翻譯爲信使,經過它能夠在不一樣的進程間傳遞Message對象,在Message中放入咱們要傳遞的數據,就能夠輕鬆的實現數據在進程間傳遞。Messenger是一種輕量級的IPC方案,底層使用AIDL實現。Messenger對AIDL作了封裝,使得咱們能夠更加簡便的進行進程間通訊。同時,因爲它一次處理一個請求,所以在服務端不用考慮線程同步的問題,這是由於服務端中不存在併發執行
的情形。
步驟: 1)服務端進程 建立Service處理客戶端請求,同時建立一個Handler並經過它建立一個Messenger對象,而後在Service的onBind中返回這個Messenger對象底層的Binder便可。
2)客戶端進程 綁定服務端的Service,綁定成功後用服務端返回的IBinder對象建立一個Messenger,經過Messenger就能夠向服務端發送消息了,發消息類型爲message對象。若是須要服務端可以迴應客戶端,就和服務端同樣,咱們還須要建立一個Handler並建立一個新的Messenger,並把這個Messenger對象經過Message的replyTo參數傳遞服務端,服務端經過replyTo參數能夠迴應客戶端。
MessengerService
:
/**
* 使用Messenger實現進程間通信
*/
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回messenger底層binder
return mMessenger.getBinder();
}
//建立Handler
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MyConstants.MSG_FROM_CLIENT:
Log.i("lwjtag","receive msg from Client:"+msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message replyMessgae = Message.obtain(null,MyConstants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply","嗯,你的消息我已經收到,稍後會回覆你。");
replyMessgae.setData(bundle);
try {
client.send(replyMessgae);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
};
//建立一個Messenger對象
private final Messenger mMessenger = new Messenger(new MessengerHandler());
}
複製代碼
MessengerActivity
:
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerActivity";
private Messenger mService;
//建立Messenger對象
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
//建立handler對象
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MyConstants.MSG_FROM_SERVICE:
Log.i("lwjtag","receive msg from Service:"+msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);//發送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);
Intent intent = new Intent(this,MessengerService.class);
//綁定Service
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
//解綁
unbindService(mConnection);
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_messenger, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
複製代碼
在Messenger中進行數據傳遞就必須將數據放入Message中,而Messenger和Message都實現了Parcelable接口,所以能夠跨進程傳輸。簡單來講,Message所支持的數據類型就是Messenger所支持的傳輸類型,實際上,經過Messenger來傳輸Message,Message中能使用的載體只有what、arg一、arg二、Bundle和replyTo。Message中的另外一個字段不支持跨進程傳輸,即使是2.2之後,也僅僅是系統提供的實現了Parcelable接口的對象才能經過它來傳輸,這就覺得這咱們自定義的Parcelable對象是沒法經過object字段來傳輸的。
Contentprovider是Android中提供的專門用於不一樣應用間的數據共享的方式,從這一點來看,其天生就適合進程間通訊,其底層實現是Binder。
系統預置了許多ContentProvider,好比通信錄信息、日程表信息等,要跨進程訪問這些信息,只須要經過ContentResolver的query、insert和delete方法便可。
自定義ContentProvider,演示如何在其餘的應用中獲取Contentprovider中的數據從而實現進程間通訊的目的。建立BookProvider,繼承Contentprovider而且實現六個抽象方法便可:onCreate、query、update、insert、delete和getType。
BookProvider
:
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.lwj.ipc.provider";//contentProvider惟一
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_CONDE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private Context mContext;
private SQLiteDatabase mDb;
static {
sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CONDE);
}
/**
* 獲取表名
* @param uri
* @return
*/
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_CONDE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
@Override
public boolean onCreate() {
Log.i(TAG, "onCreate,current thread:" + Thread.currentThread().getName());
mContext = getContext();
//ContentProvider 建立時,初始化數據庫,注意:這裏僅僅是爲了演示,事件使用中不推薦在主線程中進行耗時的數據庫操做
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_TABLE_NAME);
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);");
}
/**
* 查詢
* @param uri
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.i(TAG, "query,current thread:" + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (tableName == null) {
try {
throw new IllegalAccessException("Unsupported uri:" + uri);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
/**
* 返回一個Uri請求所對應的MIME類型(媒體類型)
* MIME類型:圖片,視頻等。
* @param uri
* @return
*/
@Override
public String getType(Uri uri) {
Log.i(TAG, "getType");
return null;
}
/**
* 插入數據
* @param uri
* @param values
* @return
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "insert");
String table = getTableName(uri);
if (table == null) {
try {
throw new IllegalAccessException("Unsupported uri:" + uri);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
/**
* 刪除操做
* @param uri
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.i(TAG, "delete");
String table = getTableName(uri);
if (table == null) {
try {
throw new IllegalAccessException("Unsupported uri:" + uri);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
/**
* 更新
* @param uri
* @param values
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.i(TAG, "delete");
String table = getTableName(uri);
if (table == null) {
try {
throw new IllegalAccessException("Unsupported uri:" + uri);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return row;
}
}
複製代碼
AndroidManifest.xml註冊
<provider
android:name="com.lwj.ipc.contentprovider.BookProvider"
android:authorities="com.lwj.ipc.provider"
android:permission="com.wlj.PROVIDER"
android:process=":provider"/>
複製代碼
DbOpenHelper
:
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_TABLE_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_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) {
//TODO
}
}
複製代碼
使用contentprovider ProviderActivity
:
public class ProviderActivity extends AppCompatActivity {
private static final String TAG = "BookProvider";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
Uri uri = Uri.parse("content://com.lwj.ipc.provider");
// getContentResolver().query(uri,null,null,null,null);
// getContentResolver().query(uri,null,null,null,null);
// getContentResolver().query(uri,null,null,null,null);
Uri bookUri = Uri.parse("content://com.lwj.ipc.provider/book");
ContentValues values = new ContentValues();
values.put("_id", 6);
values.put("name", "程序設計的藝術");
getContentResolver().insert(bookUri, values);
Cursor cursor = getContentResolver().query(bookUri,new String[]{"_id","name"},null,null,null);
while (cursor.moveToNext()){
Book book = new Book();
book.bookId = cursor.getInt(0);
book.bookName = cursor.getString(1);
Log.i(TAG,"query book id:"+book.getBookId()+ "----bookName:"+book.getBookName());
}
cursor.close();
Uri userUri = Uri.parse("content://com.lwj.ipc.provider/user");
Cursor userCursor = getContentResolver().query(userUri,new String[]{"_id","name","sex"},null,null,null);
while (userCursor.moveToNext()){
User user = new User();
user.userId = userCursor.getInt(0);
user.userName = userCursor.getString(1);
user.isMale = userCursor.getInt(2) == 1;
Log.i(TAG,"query usre id:"+user.getUserId()+"----userName:"+user.getUserName());
}
userCursor.close();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
複製代碼
注意:query、update、insert、delete四大方法是存在多線程併發的訪問的,所以方法內部要作好線程同步。在本例中,因爲採用的是SQLite而且只有一個SQLitedatabase的鏈接,因此能夠正確應對多線程的狀況。具體緣由是SQLiteDatabase內部對數據庫的操做是有同步處理的。可是若是經過多個SQLitedatabase對象之間沒法進行線程同步。
Socket:套接字,是網絡通訊中的概念。分爲流式套接字和用戶數據包套接字,分別對應網絡的傳輸控制層中的TCP和UDP。
TCP:面向鏈接的協議,提供穩定的雙向通訊功能。TCP鏈接的創建須要通過"三次握手"才能完成,爲了提供穩定的數據傳輸功能,其自己提供了超時重傳機制,所以具備超高的穩定性。
UDP: 無鏈接的,提供不穩定的單向通訊功能,固然UDP也能夠實現雙向通訊功能。在性能上,UDP具備更好的效率,其肯定就是不能保證數據必定能正確傳輸,尤爲在網絡擁塞的狀況下。
TCPServerService
:
/**
* sockect實現進程間通信
*/
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefinderMessages = new String[]{
"你好啊,哈哈",
"請問你叫什麼名字呀?",
"今天北京天氣不錯呀,shy",
"你知道嗎,我但是能夠和多我的同時聊天的哦",
"給你講個笑話吧,聽說愛笑的人運氣不會太差,不知道真假"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
private class TcpServer implements Runnable{
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
} catch (IOException e) {
System.err.println("establish tcp server failed,port:868");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed){
//接收客戶端的請求
try {
final Socket client = serverSocket.accept();
System.out.println("accept");
new Thread(new Runnable() {
@Override
public void run() {
try {
respondseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void respondseClient(Socket client) throws IOException{
//用於接收客戶端消息
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
//用戶向客戶端發送消息
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));
out.println("歡迎來到聊天室");
while (!mIsServiceDestoryed){
String str = in.readLine();
System.out.println("msg form client:"+str);
if (str == null){
//客戶端斷開鏈接
return;
}
int i = new Random().nextInt(mDefinderMessages.length);
String msg = mDefinderMessages[i];
out.println(msg);
System.out.println("send :"+str);
}
System.out.println("client quit");
MyUtils.close(out);
MyUtils.close(in);
}
}
複製代碼
TCPClientActivity
:
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {
private TextView text;
private EditText edit;
private Button send;
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG:
text.setText(text.getText() + (String) (msg.obj));
break;
case MESSAGE_SOCKET_CONNECTED:
send.setEnabled(true);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
text = (TextView) this.findViewById(R.id.text);
edit = (EditText) this.findViewById(R.id.edit);
send = (Button) this.findViewById(R.id.send);
send.setOnClickListener(this);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
/**
* 關閉鏈接
*/
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@Override
public void onClick(View v) {
if (v == send) {
final String msg = edit.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
edit.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
text.setText(text.getText() + showedMsg);
}
}
}
/**
* 格式化時間
*
* @param l
* @return
*/
private String formatDateTime(long l) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(l));
}
/**
* 鏈接Server
*/
private void connectTCPServer() {
Socket socket = null;
while (socket == null) {//重連
try {
socket = new Socket("localhost",8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);//1000毫秒重連
e.printStackTrace();
}
try {
//接收服務器端的消息
// if (socket == null || socket.getInputStream() == null)return;
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// if (br == null)return;
while (!TCPClientActivity.this.isFinishing()){
String msg = br.readLine();
System.out.println("receive :"+msg);
if (msg != null){
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server "+time+":"+msg+"\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget();
}
}
System.out.println("quit.....");
MyUtils.close(mPrintWriter);
MyUtils.close(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
複製代碼
根據下圖選擇合適的IPC方式: