AndroidAPP間的消息通信

首先要明白消息通信一般包括兩種,一種是簡單的數(shù)據(jù)訪問,如ContentProvider,使用文件或云端方式共享,一種是消息的傳遞(傳遞的任然是數(shù)據(jù),但不再是單純的數(shù)據(jù)訪問,而是組件之間的相互通信),如AIDL,BroadcastReceiver,Messenger。

一使用ContentProvider

ContentProvider(內(nèi)容提供者)是Android中的四大組件之一,內(nèi)容提供者將一些特定的應(yīng)用程序數(shù)據(jù)供給其它應(yīng)用程序使用,它主要作用是用來在多個APP之間共享數(shù)據(jù),如騰訊QQ中的QQ電話功能需要獲取用戶手機(jī)上的聯(lián)系人的手機(jī)號碼,還算不上標(biāo)準(zhǔn)的多個APP之間的消息通信(因?yàn)楣蚕頂?shù)據(jù)只是消息通信中很小的一部分)。共享的數(shù)據(jù)可以存儲于文件系統(tǒng)、SQLite數(shù)據(jù)庫或其它方式。內(nèi)容提供者繼承于ContentProvider 基類,為其它應(yīng)用程序取用和存儲它管理的數(shù)據(jù)實(shí)現(xiàn)了一套標(biāo)準(zhǔn)方法。然而,應(yīng)用程序并不直接調(diào)用這些方法,而是使用一個 ContentResolver 對象,調(diào)用它的方法作為替代。ContentResolver可以與任意內(nèi)容提供者進(jìn)行會話,與其合作來對所有相關(guān)交互通訊進(jìn)行管理。

無論數(shù)據(jù)的來源是什么,ContentProvider都會認(rèn)為是一種表,然后把數(shù)據(jù)組織成表格形式,因此該類提供給我們的方法類似于sqlite數(shù)據(jù)庫中的增刪改查的操作。

要使用ContentProvider我們需要先來了解一個概念URI。

1 URI(統(tǒng)一資源標(biāo)識符(Uniform Resource

Identifier))用來唯一的標(biāo)識一個資源。在上述我說過ContentProvider是用來在多個APP之間共享數(shù)據(jù)的,那么首先我們得找到這個數(shù)據(jù),這個資源(數(shù)據(jù)屬于資源的一種)是采用URI來標(biāo)識的。它包括三個部分:scheme

authority and

path,其中在安卓中共ContentProvider訪問的scheme固定值為:content://(就像Http協(xié)議固定值為http://),authority包括host和port。

scheme:標(biāo)準(zhǔn)前綴,用來說明一個Content Provider控制這些數(shù)據(jù),無法改變的;"content://"

authority:URI

的標(biāo)識,用于唯一標(biāo)識這個ContentProvider,外部調(diào)用者可以根據(jù)這個標(biāo)識來找到它。它定義了是哪個Content

Provider提供這些數(shù)據(jù)。對于第三方應(yīng)用程序,為了保證URI標(biāo)識的唯一性,它必須是一個完整的、小寫的類名。這個標(biāo)識在 元素的

authorities屬性中說明:一般是定義該ContentProvider的包.類的名稱

path:路徑(path),通俗的講就是你要操作的數(shù)據(jù)庫中表的名字,或者你也可以自己定義,記得在使用的時候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"

如果URI中包含表示需要獲取的記錄的ID(數(shù)據(jù)以表的形式表示),則就返回該id對應(yīng)的數(shù)據(jù),如果沒有ID,就表示返回全部

要操作的數(shù)據(jù)不一定來自數(shù)據(jù)庫,也可以是文件、xml或網(wǎng)絡(luò)等其他存儲方式,如要操作xml文件中person節(jié)點(diǎn)下的name節(jié)點(diǎn),可以構(gòu)建這樣的路徑:/person/name

如果要把一個字符串轉(zhuǎn)換成Uri,可以使用Uri類中的parse()方法,如下:Uri uri = Uri.parse("content://com.demo.provider.personprovider/person");

2ContentProvider共享數(shù)據(jù)

我們先來看一下ContentProvider(ContentProvider為一個抽象類)的重要方法:

publicabstractbooleanonCreate();

publicabstractCursorquery(Uri uri, String[] projection,

? ? ? ? ? ? String selection, String[] selectionArgs, String sortOrder);

publicCursorquery(Uri uri, String[] projection,

? ? ? ? ? ? String selection, String[] selectionArgs, String sortOrder,

? ? ? ? ? ? CancellationSignal cancellationSignal){

returnquery(uri, projection, selection, selectionArgs, sortOrder);

? ? }

publicabstractStringgetType(Uri uri);

publicabstractUriinsert(Uri uri, ContentValues values);

publicabstractintdelete(Uri uri, String selection, String[] selectionArgs);

publicabstractintupdate(Uri uri, ContentValues values, String selection,

? ? ? ? ? ? String[] selectionArgs);

可以看到ContentProvider中的增刪改查這些重要的方法都是抽象的,因此當(dāng)我們繼承自該類時需要重寫其抽象方法。

3ContentResolver來操作數(shù)據(jù)

當(dāng)外部應(yīng)用需要對ContentProvider中的數(shù)據(jù)進(jìn)行添加、刪除、修改和查詢操作時,需要使用ContentResolver類來完成,要獲取ContentResolver對象,可以使用Context提供的getContentResolver()方法(即該方法位于Context類中)。

ContentResolver cr = getContentResolver();

ContentProvider負(fù)責(zé)組織應(yīng)用程序的數(shù)據(jù),向其他應(yīng)用程序提供數(shù)據(jù),ContentResolver則負(fù)責(zé)獲取ContentProvider提供的數(shù)據(jù)。因此可以知道ContentResolver中也因該存在增刪改查的接口。

publicfinalCursorquery(Uri uri, String[] projection,

? ? ? ? ? ? String selection, String[] selectionArgs, String sortOrder){

returnquery(uri, projection, selection, selectionArgs, sortOrder,null);

? ? }

publicfinalUriinsert(Uri url, ContentValues values)

publicfinalintdelete(Uri url, String where, String[] selectionArgs)

publicfinalintupdate(Uri uri, ContentValues values, String where,

? ? ? ? ? ? String[] selectionArgs)

publicfinalStringgetType(Uri url)

可以看到在ContentResolver中存在于ContentProvider相對應(yīng)的增刪改查的方法接口,而且這些方法都是final修飾的,另外這些方法的第一個參數(shù)為Uri,代表要操作的ContentProvider(通常用包名+類名表示)和對其中的什么數(shù)據(jù)(通常是以表形式存儲的)進(jìn)行操作。代碼如下:

ContentResolver resolver =? getContentResolver();//首先獲取ContentResolver對象

Uri uri = Uri.parse("content://com.demo.provider.personprovider/person");//定義要訪問的ContentProvider的RUI

//添加一條記錄

ContentValues values =newContentValues();

values.put("name","htq");

values.put("age",20);

resolver.insert(uri, values);?

//獲取person表中所有記錄

Cursor cursor = resolver.query(uri,null,null,null,"personid desc");

while(cursor.moveToNext()){

Log.i("ContentTest","personid="+ cursor.getInt(0)+",name="+ cursor.getString(1));

}

//把id為1的記錄的name字段值更改新為zhangsan

ContentValues updateValues =newContentValues();

updateValues.put("name","zhangsan");

Uri updateIdUri = ContentUris.withAppendedId(uri,2);

resolver.update(updateIdUri, updateValues,null,null);

//刪除id為2的記錄

Uri deleteIdUri = ContentUris.withAppendedId(uri,2);

resolver.delete(deleteIdUri,null,null);

二使用文件或云端方式共享

使用文件就是把數(shù)據(jù)以文件的形式保存,然后提供一定的訪問權(quán)限供其它APP訪問,云端共享與此類似只不過存儲位置位于云端而已,云端共享最典型的莫過于搜索引擎與云盤共享。

三使用BroadcastReceiver

上述介紹的幾種方式算不上完完全全的APP之間的消息通信,因?yàn)樯鲜鰞H僅是多個APP之間共享數(shù)據(jù)而已,而在安卓中廣播機(jī)制就是用來在多個APP之間通信的較好的方式,如多個APP可以響應(yīng)系統(tǒng)網(wǎng)絡(luò)監(jiān)聽的廣播。一般廣播的工作流程如下:

1.廣播接收者BroadcastReceiver通過Binder機(jī)制向AMS(Activity Manager Service)進(jìn)行注冊;

2.廣播發(fā)送者通過binder機(jī)制向AMS發(fā)送廣播;

3.AMS查找符合相應(yīng)條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發(fā)送到BroadcastReceiver(一般情況下是Activity)相應(yīng)的消息循環(huán)隊(duì)列中;

4.消息循環(huán)執(zhí)行拿到此廣播,回調(diào)BroadcastReceiver中的onReceive()方法。

一般廣播的使用流程如下:

1定義一個子類繼承自抽象的BroadcastReceiver類,重寫其抽象的onReceive(Context context, Intent intent)方法,當(dāng)廣播接收者收到廣播會自動回調(diào)該方法。

2注冊/注銷該廣播:通過intentFilter.addAction(Constants.ACTION_MSG);來指定注冊的廣播對哪種廣播消息進(jìn)行響應(yīng)

3在另一個組件中使用intent.setAction(Constants.ACTION_MSG);?sendBroadcast(intent);來指定發(fā)送的廣播類型,如果要傳遞數(shù)據(jù)可以使用intent.putExtra(Constants.MSG,

msg);

如在BaseActivity中響應(yīng)ACTION_MSG,在getMsgService中發(fā)送ACTION_MSG的廣播,代碼如下:

//接受廣播的BaseActivity類

publicabstractclassBaseActivityextendsActivity{

protectedvoidonCreate(Bundle savedInstanceState){

// TODO Auto-generated method stub

super.onCreate(savedInstanceState);

}

protectedvoidonStart(){

// TODO Auto-generated method stub

super.onStart();

IntentFilter intentFilter=newIntentFilter();

intentFilter.addAction(Constants.ACTION_MSG);//指定響應(yīng)Constants.ACTION_MSG)的廣播

registerReceiver(MsgReceiver, intentFilter);//注冊廣播

}

@Override

protectedvoidonStop(){

// TODO Auto-generated method stub

super.onStop();

unregisterReceiver(MsgReceiver);

}

BroadcastReceiver MsgReceiver=newBroadcastReceiver()//定義一個類繼承自抽象的BroadcastReceiver類,此處采用的是匿名類的方式

{

@Override

publicvoidonReceive(Context context, Intent intent){//重寫抽象的onReceive(Context context, Intent intent)方法

// TODO Auto-generated method stub

TransportObject msg=(TransportObject)intent.getSerializableExtra(Constants.MSG);

getMessage(msg);

}};

protectedabstractvoidgetMessage(TransportObject msg);

}

//發(fā)送廣播的getMsgService類

publicclassGetMsgServiceextendsService{

...

@Override

publicvoidonStart(Intent intent,intstartId){

newThread(){

publicvoidrun()

? {

...

if(isStart)

{

cit=client.getClientInputThread();

if(cit!=null)

{

cit.setMessageListener(newMessageListener() {

publicvoidgetMessage(TransportObject msg){

if(msg!=null&&msginstanceofTransportObject)

{

//通過廣播向Activity傳遞消息

Intent intent=newIntent();

intent.setAction(Constants.ACTION_MSG);

intent.putExtra(Constants.MSG, msg);//通過廣播傳遞數(shù)據(jù)

? ? ? ? sendBroadcast(intent);

}

}

});

}

else{

Log.i("GetMsgService","服務(wù)器端連接暫時出錯");

// Toast.makeText(getApplicationContext(), "服務(wù)器端連接暫時出錯,請稍后重試!",0).show();

}

}


? }

? }.start();

}

...

}

雖然上述的代碼是在同一個APP中響應(yīng)的該廣播,但是如果多個APP中的廣播注冊時使用Constants.ACTION_MSG字符串指定的廣播則getMsgService類中sendBroadcast時多個APP的廣播可以響應(yīng)。

四使用AIDL讓多個APP與同一個Service通信

AIDL(Android Interface definition

language),在Android中,每個應(yīng)用運(yùn)行在屬于自己的進(jìn)程中,無法直接調(diào)用到其他應(yīng)用的資源,那么當(dāng)多個APP之間相互通信的話,那么自然就轉(zhuǎn)化為IPC機(jī)制了,而AIDL就是安卓系統(tǒng)中用來實(shí)現(xiàn)IPC機(jī)制的。那么哪些場合需要使用AIDL呢,安卓官方文檔上說的很清楚:

Using AIDL is necessary onlyifyou allow clients from different applications to access your serviceforIPC and want to handle multithreading in your service. If youdonot need to perform concurrent IPC across different applications, you should create yourinterfacebyimplementingaBinderor,ifyouwanttoperformIPC,butdonotneedtohandlemultithreading,implementyourinterfaceusingaMessenger.Regardless,besurethatyouunderstandBoundServicesbeforeimplementinganAIDL.

通過上述文檔敘述可以看到,當(dāng)需要在不同APP之間訪問同一個服務(wù)且處理多線程的時候需要用到AIDL,如果不是多個APP之間的IPC只需使用Binder機(jī)制,如果需要處理不同APP之間但是只是單線程的話只需使用Messager機(jī)制,即下面將介紹的一類情況。所以可以知道AIDL最佳使用情況是在不同APP之間訪問同一個服務(wù)且處理多線程,因此可以知道AIDL可以用來處理不同APP之間的通信。

AIDL的使用:

一服務(wù)端:

1定義AIDL文件(該文件是一個接口,文件中的方法全部為抽象方法,如果格式正確,IDE會自動在gen目錄下生成對應(yīng)的java文件)

interfaceMyAIDL{

intplus(inta,intb);

}

2定義服務(wù)類(AIDL就是用來在多個APP之間訪問同一個service的),在該服務(wù)類中定義對應(yīng)的stub對象,在該stub對象中實(shí)現(xiàn)上述AIDL文件中定義的抽象方法,在服務(wù)的onBind(Intent

intent)中返回該stub對象。AndroidManifest.xml配置相關(guān)屬性。

publicclassMyServiceextendsService{

......

@Override

publicIBinderonBind(Intent intent){

returnmBinder;//在onBind中返回該stub對象

}

DemoAIDL.Stub mBinder =newStub() {//在服務(wù)類中定義對應(yīng)的stub對象,實(shí)現(xiàn)aidl中定義的抽象方法

@Override

publicintplus(inta,intb)throwsRemoteException{

returna + b;

}

};

}

AndroidManifest.xml配置相關(guān)屬性如下:


package="com.example.servicetest"

android:versionCode="1"

android:versionName="1.0" >

......

android:name="com.example.servicetest.MyService"

android:process=":remote" >

//注意action的android:name屬性,該屬性在客戶端bindService中將會用到

上述定義服務(wù)的APP相當(dāng)于服務(wù)端。

二客戶端:

1我們只需要把服務(wù)端aidl文件拷到相應(yīng)的目錄中即可,IDE會自動生成相對應(yīng)的java文件,這一部分和服務(wù)端相同,這樣服務(wù)端和客戶端就在通信協(xié)議上達(dá)到了統(tǒng)一。

2在客戶端的Activity中與Service通信,在客戶端的Activity中定義ServiceConnection類,在該類的onServiceConnected(ComponentName

name, IBinder

service)方法中通過xxx.Stub.asInterface(service);獲取定義的AIDL文件生成的java類,(xxx為aidl文件自動生成的對應(yīng)的java文件類名),使用bindService(intent,

conn,Context.BIND_AUTO_CREATE);來綁定遠(yuǎn)程服務(wù),注意此時的intent需要指定為我們在服務(wù)端創(chuàng)建的service的name屬性。

publicclassMainActivityextendsActivityimplementsOnClickListener{

...

privateMyAIDL myAIDL;

privateServiceConnection connection =newServiceConnection() {

@Override

publicvoidonServiceDisconnected(ComponentName name){

}

@Override

publicvoidonServiceConnected(ComponentName name, IBinder service){

myAIDL = MyAIDL.Stub.asInterface(service);//在onServiceConnected中將IBinder轉(zhuǎn)換為aidl對應(yīng)的java類

try{

intresult = myAIDL.plus(3,5);

Log.d("TAG","result is "+ result);

}catch(RemoteException e) {

e.printStackTrace();

}

}

};

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button bindService = (Button) findViewById(R.id.bind_service);

bindService.setOnClickListener(newOnClickListener() {

@Override

publicvoidonClick(View v){

Intent intent =newIntent("com.example.servicetest.MyAIDLService");//intent指定為我們在服務(wù)端創(chuàng)建的service的intent-filter中action的android:name屬性。

? bindService(intent, connection, BIND_AUTO_CREATE);

}

? ? });

}

}

五使用Messager

Messager實(shí)現(xiàn)IPC通信,底層也是使用了AIDL方式。和AIDL方式不同的是,Messager方式是利用Handler形式處理,因此,它是線程安全的,這也表示它不支持多線程處理;而AIDL方式是非線程安全的,支持多線程處理,因此,我們使用AIDL方式時需要保證代碼的線程安全。

首先我們來看一下其構(gòu)造函數(shù):

publicMessenger(Handler?target){

????????mTarget?=?target.getIMessenger();??

????}

可以看到Messenger的構(gòu)造函數(shù)中的參數(shù)為Handler對象,Messager本質(zhì)上就是跨進(jìn)程使用Handler。

Messenger使用步驟:

客戶端綁定服務(wù)端,在ServiceConnection類的onServiceConnection方法中將遠(yuǎn)程服務(wù)端傳過來的binder對象轉(zhuǎn)換為Messenger對象,調(diào)用Messenger的send函數(shù),就可以把Message發(fā)送至服務(wù)端的Handler。同時,如果需要服務(wù)端回調(diào)客戶端(往客戶端的Handler發(fā)消息),則可以在send的Message中設(shè)置replyTo,服務(wù)端就可以往客戶端發(fā)送消息了。

客戶端代碼:

publicclassMainActivityextendsActivity{


protectedstaticfinalString?TAG?="MainActivity";

????Messenger?messenger;

????Messenger?reply;

@Override

protectedvoidonCreate(Bundle?savedInstanceState){

super.onCreate(savedInstanceState);

????????setContentView(R.layout.activity_main);

reply?=newMessenger(handler);

Intent?intent?=newIntent("test.messenger.MessengerTestService");

//?綁定服務(wù)

bindService(intent,newServiceConnection()?{

@Override

publicvoidonServiceDisconnected(ComponentName?name){

????????????}

@Override

publicvoidonServiceConnected(ComponentName?name,?IBinder?service){

Toast.makeText(MainActivity.this,"bind?success",0).show();

messenger?=newMessenger(service);//將遠(yuǎn)程服務(wù)端中返回的IBinder對象轉(zhuǎn)換為Messenger對象

????????????}

????????},?Context.BIND_AUTO_CREATE);??????

????}

publicvoidsendMessage(View?v){

Message?msg?=?Message.obtain(null,1);

//?設(shè)置回調(diào)用的Messenger

msg.replyTo?=?reply;//如果需要服務(wù)端回調(diào)客戶端,則可以在send的Message中設(shè)置replyTo,將客戶端的Messenger傳遞給服務(wù)端

try{

????????????messenger.send(msg);

}catch(RemoteException?e)?{

????????????e.printStackTrace();

????????}

????}

privateHandler?handler?=newHandler()?{//回調(diào)Messenger處理的Handler

@Override

publicvoidhandleMessage(Message?msg){

Log.d(TAG,"回調(diào)成功");

????????}

????};

}

服務(wù)端通過Message的replyTo取出客戶端傳遞過來的Messenger,這樣就可以通過該Messenger與客戶端通信。

服務(wù)端通過Messenger的getBinder方法將IBinder對象返給客戶端,用于共享服務(wù)端的Messenger。

服務(wù)端代碼:

publicclassMessengerTestServiceextendsService{

protectedstaticfinalString?TAG?="MessengerTestService";

privateHandler?mHandler?=newHandler()?{

@Override

publicvoidhandleMessage(Message?msg){

switch(msg.what)?{

case1:

Log.d(TAG,"收到消息");

//獲取客戶端message中的Messenger,用于回調(diào)

finalMessenger?callback?=?msg.replyTo;

try{

//?回調(diào)

callback.send(Message.obtain(null,0));

}catch(RemoteException?e)?{

//?TODO?Auto-generated?catch?block

????????????????????e.printStackTrace();

????????????????}

break;

????????????}

????????}

????};

@Override

publicIBinderonBind(Intent?intent){

returnnewMessenger(mHandler).getBinder();//在onBind(Intent intent)方法中返回Messenger對應(yīng)的binder對象。

????}

}

可以看到該方式與用AIDL方式整體大的框架基本相同,都是在遠(yuǎn)程服務(wù)端的Service中的onBind(Intent intent)中返回Ibinder對象,在客戶端的ServiceConnection類的onServiceConnectioned(ComponentName name, IBinder service)中獎Ibinder轉(zhuǎn)換為對應(yīng)的對象,在AIDL中通過xxx.Stub.asInterface(service);轉(zhuǎn)換為對應(yīng)的aidl的java類,在Messenger中通過messenger = new Messenger(service);轉(zhuǎn)換為Messenger對象,然后利用這個對象就可相互通信。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容