第一種方式,使用Bundle
Android中的4大元件都是支援在Intent中傳遞Bundle資料的,由于Bundle實作了Parcelable接口,是以它可以友善的在不同的程序間傳輸
第二種方式,使用檔案共享:
讀寫在同一個地方的檔案(例如讀取外存儲卡上的data.txt檔案,前提是要有讀寫檔案的權限)
第三種方式.使用Messenger(信使)
通過信使Messenger可以在不同的程序中傳遞Message(消息)對象,在消息對象中放入我們要傳遞的資料,就可以輕松實作程序中傳遞資料了,信使是一種輕量級的IPC方案,它的底層是AIDL,信使是一次處理一個請求,是以服務端不用考慮線程同步的問題,因為服務端不存線上程并發的情況
信使的2種構造函數如下:
//一般用在服務端
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
//一般用在用戶端
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
我們先看一下它的大體邏輯:
服務端程序:
首先,我們要在服務端建立一個Service來處理用戶端的請求,同時建立一個Handler并通過它來建立一個Mesenger對象,然後在Service的onBind中傳回這個Messenger對象底層的Binder即可
用戶端程序:
首先要綁定服務端的Service,綁定成功後用服務端傳回的IBinder對象建立一個Messenger,通過這個Messenger就可以向服務端發送消息,發送的消息類型是:Message對象.
都是在Handler裡面從msg上去擷取資料的
.
服務端具體實作代碼如下:
public class MessengerService extends Service{
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case Constants.MSG_FROM_CLIENT:
//從用戶端msg裡面取到的消息内容
String msgContent= msg.getData().getString("client");
LogUtils.d("服務端msgContent"+msgContent);
//從用戶端msg裡面取到的信使
Messenger msger= msg.replyTo;
//建立一個新的消息對象,一會兒給用戶端傳遞消息
Message tempMsg=Message.obtain(null, Constants.MSG_FROM_SERVICE);
//給消息裡面放資料
Bundle bundle=new Bundle();
bundle.putString("serviceReply","你好,用戶端,你發送的消息我已經收到了");
tempMsg.setData(bundle);
try {
//信使攜帶消息去用戶端
msger.send(tempMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
Messenger messenger=new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}
用戶端的具體實作完整代碼如下:
public class MessengerClient extends AppCompatActivity {
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case Constants.MSG_FROM_SERVICE:
//從服務端msg裡面取到的消息内容
String msgContent= msg.getData().getString("serviceReply");
LogUtils.d("msgContent"+msgContent);
break;
default:
super.handleMessage(msg);
break;
}
}
}
Messenger replyMsger = new Messenger(new MessengerHandler());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent a=new Intent(this,MessengerService.class);
bindService(a,mServiceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection=new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//執行在主線程
//用服務端傳回的service,建立Messenger信使,一會兒給服務端發送資訊
Messenger msger=new Messenger(service);
//建立一個新的消息對象,作為資料的載體
Message clientMsg=Message.obtain(null, Constants.MSG_FROM_CLIENT);
//給消息裡面放資料
Bundle bundle=new Bundle();
bundle.putString("client","你好,服務端,這是我用戶端發送的消息");
clientMsg.setData(bundle);
//注意這步:把信使放到msg裡面,友善服務端從msg裡面取信使
clientMsg.replyTo=replyMsger;
try {
//信使攜帶消息去服務端
msger.send(clientMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
//執行在主線程
}
@Override
public void onBindingDied(ComponentName name) {
//執行在主線程 :重新連接配接
}
};
@Override
protected void onDestroy() {
unbindService(mServiceConnection);
super.onDestroy();
}
}
第四種方式,使用AIDL
1,AIDL通信的流程:
第一步:服務端:
服務端首先要建立一個service來監聽用戶端的連接配接請求,然後建立一個AIDL檔案,把暴露給用戶端調用的接口在這個AIDL檔案中聲明,最後在service中實作這個接口
第2步:用戶端,
首先綁定服務端的service,綁定成功後,把服務端傳回的Binder對象自己轉換成AIDL接口所屬的類型,接口就可以調用AIDL中的方法了,注意連結斷開的時候增加Binder死亡監聽
第3步:AIDL接口的建立:建立檔案名為IBookManager2 .aidl的檔案
// IBookManager2.aidl
package com.hlj.demo.aidl;
// Declare any non-default types here with import statements
//Book1 是自定義的parcelable對象,必須要顯示的import進來
import com.hlj.demo.aidl.Book1;
import android.os.Parcel;
import android.os.Parcelable;
import com.hlj.demo.aidl.IOnNewBookArrivedListener;
interface IBookManager2 {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
List<Book1> getBookList();
//in:表示輸入類型
//Book1 是自定義的parcelable對象,必須要顯示的import進來
void addBook(in Book1 book1);
void registerListerner(IOnNewBookArrivedListener lister);
void unregisterListerner(IOnNewBookArrivedListener lister);
}
說明;
1,AIDL支援的資料類型為:基本資料類型,Arraylist,HashMap,Parcelable,AIDl
2,自定義的parcelable對象和AIDl的對象,必須要顯示的import進來,不管它們是否和目前的AIDL檔案處于同一個包内
3,除了基本資料類型之外,其他類型的參數必須要标上方向,in,out,或者inout
4,AIDL的包結構在服務端和用戶端要一緻,因為用戶端要反序列化服務端中和AIDl相關的所有類,如果類的完整路徑不一樣的話,否則會運作報錯,是以建議把所有和AIDL相關的類和檔案全部放入同一個包中
第4步和第5步就是服務端和用戶端的具體實作,後面會寫一個Demo具體看demo
代碼較多,就不具體貼出來啦 在這裡我提幾個重要的注意點:
1.集合用CopyOnWriteArrayList,理由是:它支援并發讀寫,因為AIDl方法是在Binder線程池中執行的,會存在多個線程同僚通路的情況,是以我們要在AIDl方法中處理線程同步,而CopyOnWriteArrayList可自動進行線程同步
CopyOnWriteArrayList<Book1> mBookList=new CopyOnWriteArrayList<Book1>();
2,Binder線程池中執行的程式不能更新UI,是子線程
3,有類似觀察者模式或者回調情況的集合要用RemoteCallbackList,RemoteCallbackList是系統專門提供的用于删除跨程序listener的接口,它内部也實作了線程同步
RemoteCallbackList<IOnNewBookArrivedListener> mListenerList=
new RemoteCallbackList<IOnNewBookArrivedListener>();
用法:
public void onNewBookArrived(Book1 book1) throws RemoteException {
mBookList.add(book1);
//注意beginBroadcast和finishBroadcast要配對使用
final int N= mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener lister= mListenerList.getBroadcastItem(i);
if (lister!=null) {
lister.onNewBookArrived(book1);
}
}
mListenerList.finishBroadcast();
};
注意beginBroadcast和finishBroadcast要配對使用
4遠端服務不是任何用戶端相連就可以連上的,是以我們要做一些驗證才可以:這裡做的是權限加包名驗證
<!-- 表示服務端自定義了一個權限 -->
<permission
android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
<!-- 表示所有連接配接這個服務的用戶端都要有這個權限,否則不會連接配接成功 -->
<service
android:name=".service.BookManagerService"
android:permission="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"
android:process=":BookManagerService" />
<!-- 表示用戶端可以使用這個權限,已經有了這個權限 -->
<uses-permission android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE" />
@Override
public IBinder onBind(Intent intent) {
//先驗證權限在驗證包名,隻要有一個沒有通過就傳回null
String pkgName="";
int permission= ActivityCompat.checkSelfPermission(this,"com.hlj.demo.permiss_ACCESS_BOOK_SERVICE");
if (permission == PackageManager.PERMISSION_DENIED) return null;
String[] pkgNames=getPackageManager().getPackagesForUid(Binder.getCallingUid());
if (pkgNames==null||pkgNames.length<=0) return null;
pkgName=pkgNames[0];
if (!pkgName.startsWith("com.hlj."))return null;
return mBinder;
}
5,Binder是有可能意外死亡的,那當服務斷開了的時候,我們要重新連接配接服務
第一種方式:
給Binder設定DeathRecipient監聽,當Binder死亡的時候,我們會受到binderDied方法的回調,可在此方法中重新連接配接(在非UI線程中執行)
第二種方式:
在onServiceDisconnected()方法中重新連接配接(在UI線程中執行)
第五種方式,使用ContentProvider
第六種方式,使用Socket(此2中方式在項目中使用較少,略過)
一點優化:
當要提供的AIDl服務過多時,可以使用Binder連接配接池
結尾:
終于寫完了,老衲甚是欣慰啊^- ^