Android IPC簡介
IPC爲進程間通訊,兩個進程之間進行數據交換的過程。
IPC不是Android所獨有的,任何一個操作系統都有對應的IPC機制。Windows上通過剪切板、管道、油槽等進行進程間通訊。Linux上通過命名空間、共享內容、信號量等進行進程間通訊。Android中沒有完全繼承於Linux,有特色的進程間通訊方式是Binder,還支持Socket。
Android中的多進程模式
- 同一個應用,通過給四大組件在AndroidMenifest指定android:process屬性,就可以開啓多進程模式。
- 非常規的方式,通過JNI在Native層fork一個新的進程。
- 查看進程信息:DDMS;adb shell ps;adb shell ps | grep [包名]。
- 進程名以":“開頭的屬於當前應用的私有進程,其他應用的組件不可以和他跑在同一個進程裏面。而進程名不以”:"開頭的進程屬於全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。
- 兩個應用可以通過ShareUID跑在同一個進程並且簽名相同,他們可以共享data目錄、組件信息、共享內存數據。
- 多進程通訊的問題:
-
靜態成員和單例模式完全失效。
-
線程同步機制完全失效。
-
SharedPreferences的可靠性下降
-
Application會多次創建
問題1、2原因是因爲進程不同,已經不是同一塊內存了;
問題3是因爲SharedPreferences不支持兩個進程同事進行讀寫操作,有一定機率導致數據丟失;
問題4是當一個組件跑在一個新的進程中,系統會爲他創建新的進程同時分配獨立的虛擬機,其實就是啓動一個應用的過程,因此相當於系統又把這個應用重新啓動了一遍,Application也重新創建。
多進程中,不同的進程組件擁有獨立的虛擬機,Application,內存。
實現跨進程通訊有很多方式,比如:通過Intent,共享文件、SharedPreferences、基於Binder的Messenger和AIDL、Socket等。
IPC基礎概念介紹
Serializable接口
Serializable是Java所提供的一個序列化接口,空接口。
serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的數據中的serialVersionUID要和當前類的serialVersionUID相同才能正常的序列化。
-
靜態成員變量屬於類不屬於對象,所以不會參加序列化過程;
-
其次用transient關鍵字標明的成員變量也不參加序列化過程。
重寫如下兩個方法可以重寫系統默認的序列化和反序列化過程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
Parcelable接口
Android中特有的序列化方式,效率相對Serializable更高,佔用內存相對也更少,但使用起來稍微麻煩點。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User() {
}
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;//返回當前對象的內容描述,含有文件描述符返回1,否則0
}
public void writeToParcel(Parcel out, int flags) {//將當前對象寫入序列號結構中
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
@Override
public String toString() {
return String.format(
"User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
userId, userName, isMale, book);
}
}
序列化功能由writeToParcel方法來完成,最終通過Parcel中的一系列write方法完成的。反序列化功能由CREATOR來完成,其內部標明瞭如何創建序列號對象和數組,並通過Parcel的一系列read方法來完成反序列化過程。內容描述功能由describeContents方法來完成,幾乎所有情況都返回0,只有當前對象存在文件描述符時,才返回1。
Serializable是Java中的序列化接口,簡單但開銷大,序列化和反序列化需要大量的IO操作。
Parceable是Android中的序列化方式,使用起來麻煩,但是效率高。
Binder
Binder簡介
直觀來說,Binder是Android中一個類,實現了IBinder接口;
從IPC的角度來說,Binder是Android的一種跨進程的通訊方式;Binder還可以理解爲一種虛擬的物理設備,設備驅動是/dev/binder;
從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager、等等)和相應ManagerService的橋樑;
從Android應用層來說,Binder是客戶端與服務端通訊的媒介。在Android開發中,Binder主要用於Service中,包括AIDL和Messenger,其中普通的Service的Binder不涉及進程間通訊;而Messenger的底層其實就是AIDL。
AIDL:
Book.aidl:
package com.zza.stardust.app.ui.androidart;
parcelable Book;
IBookManager.aidl
package com.zza.stardust.app.ui.androidart;
import com.zza.stardust.app.ui.androidart.Book;
// /build/generated/aidl_source_output_dir目錄下的com.zza.stardust.app.ui.androidart包中
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
生成的Java類:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /app/src/main/aidl/com/zza/stardust/app/ui/androidart/IBookManager.aidl
*/
package com.zza.stardust.app.ui.androidart;
//gen目錄下的com.zza.stardust.app.ui.androidart.aidl包中
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.zza.stardust.app.ui.androidart.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.zza.stardust.app.ui.androidart.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.zza.stardust.app.ui.androidart.IBookManager interface,
* generating a proxy if needed.
*/
public static com.zza.stardust.app.ui.androidart.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.zza.stardust.app.ui.androidart.IBookManager))) {
return ((com.zza.stardust.app.ui.androidart.IBookManager) iin);
}
return new com.zza.stardust.app.ui.androidart.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 {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<com.zza.stardust.app.ui.androidart.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.zza.stardust.app.ui.androidart.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.zza.stardust.app.ui.androidart.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.zza.stardust.app.ui.androidart.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.zza.stardust.app.ui.androidart.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.zza.stardust.app.ui.androidart.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.zza.stardust.app.ui.androidart.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.zza.stardust.app.ui.androidart.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.zza.stardust.app.ui.androidart.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.zza.stardust.app.ui.androidart.Book book) throws android.os.RemoteException;
}
系統會根據AIDL文件生成同名的.java類;首先聲明與AIDL文件相對應的幾個接口方法,還申明每個接口方法相對應的int id來做爲標識,這幾個id在transact過程中標識客戶端請求的到底是什麼方法。接着會聲明一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端處於同一個進程的時候,方法調用不會走transact過程,處於不同進程時,方法調用會走transact過程,這個邏輯由Stub的內部代理類Proxy來完成。所以核心實現在於它的內部類Stub和Stub的內部代理類Proxy,下面分析其中的方法:
DESCRIPTOR:Binder的唯一標識,一般用當前Binder的類名錶示。
asInterface(android.os.IBinder obj):將服務端的Binder對象轉換成客戶端所需要的AIDL接口類型的對象;如果客戶端和服務端位於相同進程,那麼此方法返回的就是服務端Stub對象本身,否則返回系統封裝後的Stub.proxy對象。
asBinder:用於返回當前的Binder對象
onTransact:運行在服務端的Binder線程池中,當客戶端發起跨進程通訊時,遠程請求會通過系統底層封裝交由此方法處理。
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
- 服務端通過code確定客戶端請求的目標方法是什麼
- 接着從data取出目標方法所需要的參數,然後執行目標方法
- 執行後向reply寫入返回值
- 如果返回false,服務端請求會失敗,可以做權限驗證
Proxy#getBookList和Proxy#addBook:
- 運行在客戶端,首先該方法所需要的輸入型對象Parcel _data對象,輸出對象Parcel _reply對象和返回值對象List
- 然後把該方法的參數信息寫入Parcel _data對象。
- 接着調研transact方法發起RPC,同時當前線程掛起
- 然後服務端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply取出RPC的返回結果,最後返回 _reply中的數據
Binder的兩個重要方法linkToDeath和unlinkToDeath。通過linkToDeath可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,然後就可以重新發起連接請求。聲明一個DeathRecipient對象,DeathRecipient是一個接口,其內部只有一個方法binderDied,實現這個方法後就可以在Binder死亡的時候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:接下來重新綁定遠程Service
}
}
在客戶端綁定遠程服務成功後,給Binder設置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
Android中的IPC方式
使用Bundle
由於Binder實現了Parcelable接口,所以可以方便的在不同進程中傳輸;Activity、Service和Receiver都支持在Intent中傳遞Bundle數據。
使用文件共享
兩個進程通過讀/寫一個文件來交換數據
適合對數據同步要求性不高的場景,並要避免併發寫這種場景或者處理好線程同步問題。
SharedPreferences是個特例,雖然也是文件的一種,但系統在內存中有一份SharedPreferences文件的緩存,因此在多線程模式下,系統的讀/寫就變得不可靠,高併發讀寫SharedPreferences有一定機率會丟失數據,因此不建議在多進程通信中使用SharedPreferences。
使用Messenger
可以在不同的進程之間傳遞Message對象。Messenger是輕量級的IPC方案,底層實現是AIDL,對AIDL進行了封裝。Messenger 服務端是以串行的方式來處理客戶端的請求的,不存在併發執行的情形。
服務端:
- 創建一個Service來處理客戶端的連接請求
- 創建一個Handler並通過它來創建一個Messager對象
- 在Service的onBind中返回這個Messager對象底層的Binder即可
public class MessengerService extends Service {
public MessengerService() {
}
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
ToastUtil.show("receive msg from Client:" + msg.getData().getString("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null, 1);
Bundle bundle = new Bundle();
bundle.putString("reply", "嗯,你的消息我已經收到,稍後會回覆你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
}
客戶端
- 綁定這個服務端的Server
- 用服務端返回的IBinder對象創建一個Messager對象
- 若需要回應客戶端,需要創建一個Handler並創建一個新的Messager,並通過Messa的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以迴應客戶端
public class MessengerActivity extends Activity {
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
LogUtil.i("receive msg from Service:" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
LogUtil.d("bind service");
Message msg = Message.obtain(null, 1);
Bundle data = new Bundle();
data.putString("msg", "hello, this is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
findViewById(R.id.bt_start_service).setOnClickListener(v -> {
Intent intent = new Intent();
intent.setAction("com.zza.MessengerService.launch");
intent.setPackage(this.getPackageName());//需要添加包名
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
});
}
}
使用AIDL
Messenger不適合處理大併發請求。Messenger主要作用傳遞消息,跨進程調用服務端方法,無法做到。
服務端首先創建一個Service用來監聽客戶端的連接請求,然後創建一個AIDL文件,將暴露給客戶端的接口在AIDL文件中聲明,最後在Service中實現這個AIDL接口即可。
客戶端首先綁定服務端的Service,綁定成功後,將服務端返回的Binder對象轉化成AIDL接口所屬的類型,調用相對應的AIDL中的方法。
AIDL支持的數據類型:
-
基本數據類型;
-
String、CharSequence;
-
List: 只支持ArrayList,裏面的元素必須都能被AIDL所支持;
-
Map: 只支持HashMap,裏面的元素(key和value)必須都能被AIDL所支持;
-
Parcelable: 所有實現了Parcelable接口的對象;
-
AIDL: 所有AIDL接口本身也可以在AIDL文件中使用。
自定義的Parcelable對象和AIDL對象必須顯示的import進來(即使在同一個包)。
如果ALDL文件中用到了自定義的Parcelable類型,必須新建一個和它同名ALDL文件,並在其中聲明它爲Parcelable類型。
AIDL中除了基本數據類型,其他類型參數必須標上方向:in、out或inout。
AIDL接口中只支持方法,不支持聲明靜態常量。
爲了方便AIDL開發,建議把所有和AIDL相關的類和文件都放在同一個包中,好處在於,當客戶端是另一個應用的時候,我們可以直接把整個包複製到客戶端工程中去。
AIDL的包結構在服務端和客戶端要保持一致,否則會運行出錯。
客戶端的listener和服務端的listener不是同一個對象,RemoteCallbackList是系統專門提供用於刪除跨進程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因爲所有AIDL接口都繼承自android.os.IInterface接口。
需注意AIDL客戶端發起RPC過程的時候,客戶端的線程會掛起,如果是UI線程發起的RPC過程,如果服務端處理事件過長,就會導致ANR。
AIDL:
package com.zza.stardust.app.ui.androidart.aidl;
parcelable Book;
package com.zza.stardust.app.ui.androidart.aidl;
import com.zza.stardust.app.ui.androidart.aidl.Book;
import com.zza.stardust.app.ui.androidart.aidl.IOnNewBookArrivedListener;
// /build/generated/aidl_source_output_dir目錄下的com.zza.stardust.app.ui.androidart包中
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
package com.zza.stardust.app.ui.androidart.aidl;
import com.zza.stardust.app.ui.androidart.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
服務端:
public class BookManagerService extends Service {
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
// private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList =
// new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
//測試
//SystemClock.sleep(5000);
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.zza.permission.ACCESS_BOOK_SERVICE");
LogUtil.d("check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
LogUtil.d("onTransact: " + packageName);
if (!packageName.startsWith("com.zza")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.register(listener);
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
LogUtil.d("registerListener, current size:" + N);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
boolean success = mListenerList.unregister(listener);
if (success) {
LogUtil.d("unregister success.");
} else {
LogUtil.d("not found, can not unregister.");
}
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
LogUtil.d("unregisterListener, current size:" + N);
}
;
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.zza.permission.ACCESS_BOOK_SERVICE");
LogUtil.d("onbind check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
// do background processing here.....
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} 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();
}
}
}
}
}
客戶端:
public class BookManagerActivity extends MActivity {
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
LogUtil.d("receive new book :" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
LogUtil.d("binder died. tname:" + Thread.currentThread().getName());
if (mRemoteBookManager == null)
return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:這裏重新綁定遠程Service
}
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
List<Book> list = bookManager.getBookList();
LogUtil.d("query book list, list type:"
+ list.getClass().getCanonicalName());
LogUtil.d("query book list:" + list.toString());
Book newBook = new Book(3, "Android進階");
bookManager.addBook(newBook);
LogUtil.d("add book:" + newBook);
List<Book> newList = bookManager.getBookList();
LogUtil.d("query book list:" + newList.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
mRemoteBookManager = null;
LogUtil.d("onServiceDisconnected. tname:" + Thread.currentThread().getName());
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)
.sendToTarget();
}
};
@Override
protected void onInit(Bundle savedInstanceState) {
super.onInit(savedInstanceState);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
public void onButton1Click(View view) {
Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
if (mRemoteBookManager != null) {
try {
List<Book> newList = mRemoteBookManager.getBookList();
runOnUiThread(() -> ToastUtil.show("size:" + newList.size()));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()) {
try {
LogUtil.d("unregister listener:" + mOnNewBookArrivedListener);
mRemoteBookManager
.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
@Override
protected BasePresenter createPresenter() {
return null;
}
@Override
protected int provideContentViewId() {
return R.layout.activity_book_manager;
}
}
使用ContentProvider
ContentProvider是Android專門用於不同應用之間進行數據共享的方式,適合跨進程通信,底層採用Binder實現。
ContentProvider主要以表格的形式來組織數據,可以包含多個表;ContentProvider支持普通文件,甚至可以採用內存中得一個對象來進行數據存儲。
通過ContentProvider的notifyChange方法來通知外界當前ContentProvider中的數據已經發生改變。
query、update、insert、delete四大方法是存在多線程併發訪問的,內部需要做好線程同步。
使用Socket
Socket也被稱爲“套接字”,是網絡通訊中得概念,分爲流式套接字和用戶數據報套接字兩種,分別對應網絡的傳輸控制層中得TCP和UDP協議。
Service:
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefinedMessages = 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 {
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
try {
// 接受客戶端請求
final Socket client = serverSocket.accept();
System.out.println("accept");
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
// 用於接收客戶端消息
BufferedReader in = new BufferedReader(new InputStreamReader(
client.getInputStream()));
// 用於向客戶端發送消息
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())), true);
out.println("歡迎來到聊天室!");
while (!mIsServiceDestoryed) {
String str = in.readLine();
System.out.println("msg from client:" + str);
if (str == null) {
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
System.out.println("send :" + msg);
}
System.out.println("client quit.");
// 關閉流
out.close();
in.close();
client.close();
}
}
Client:
public class TCPClientActivity extends MActivity implements OnClickListener {
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG: {
mMessageTextView.setText(mMessageTextView.getText()
+ (String) msg.obj);
break;
}
case MESSAGE_SOCKET_CONNECTED: {
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
@Override
protected void onInit(Bundle savedInstanceState) {
super.onInit(savedInstanceState);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
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
protected BasePresenter createPresenter() {
return null;
}
@Override
protected int provideContentViewId() {
return R.layout.activity_tcpclient;
}
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
//不能主線程 android.os.NetworkOnMainThreadException
//不應該這要做,這裏爲方便不做修改
new Thread(() -> mPrintWriter.println(msg)).start();
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
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);
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 接收服務器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
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...");
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Binder連接池
當項目越來越龐大後,需要使用到的AIDL接口文件也越來越多,但我們不能有多少個AIDL就添加多少個Service,Service是四大組件之一,是一種系統資源,太多的Service會讓我們的App看起來很重量級;我們應該把所有AIDL放在一個Service中去管理。
這時候不同業務模塊之間是不能有耦合的,所有實現細節需要單獨來開,然後向服務端提供一個queryBInder接口,這個接口根據業務模塊的特徵來返回Binder對象給它們,不同的業務模塊拿到所需的Binder對象給它們,不同的業務模塊拿到所需的Binder對象後就可以進行遠程方法調用了;由此可見Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service中去執行。
當新業務模塊加入新的AIDL,那麼在它實現自己的AIDL接口後,只需要修改BinderPoolImpl中的queryBinder方法,給自己添加一個新的binderCode並返回相對應的Binder對象即可,不需要添加新的Service。建議在AIDL開發過程中引入BinderPool機制。
選用合適的IPC方式
名稱 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸 Bundle 支持的數據類型 | 四大組件的進程間通信 |
文件共享 | 簡單易用 | 不適合高併發場景,並且無法做到進程間的即時通信 | 無開發訪問情形,交換簡單的數據實時性不高的場景。 |
AIDL | 功能強大,支持一對多併發通信,支持實時通信。 | 使用複雜,需要處理好線程同步 | 一對多通信,且有 RPC 需求。 |
Message | 功能一般,支持一對多串行通信,支持實時通信。 | 不能很好地處理高併發情形,不支持 RPC,數據通過 Message 進行傳輸,因此只能傳輸 Bundle 支持的數據類型 | 低併發的一對多即時通信,無RPC 需求,或者無需要返回結果的 RPC 請求。 |
ContentProvider | 在數據源訪問方面數據強大,支持一對多併發數據共享,可通過 Call 方法擴展其他操作。 | 可以理解爲受約束的 AIDL,主要提供數據的 CRUD 操作。 | 一對多的進程間數據共享。 |
Socket | 功能強大,可以通過網絡傳輸字節流,支持一對多併發實時通信。 | 實現細節稍微有點繁瑣,不支持直接的 RPC. | 網絡數據傳輸。 |