AndroidAPP间的消息通信

首先要明白消息通信一般包括两种,一种是简单的数据访问,如ContentProvider,使用文件或云端方式共享,一种是消息的传递(传递的任然是数据,但不再是单纯的数据访问,而是组件之间的相互通信),如AIDL,BroadcastReceiver,Messenger。

一使用ContentProvider

ContentProvider(内容提供者)是Android中的四大组件之一,内容提供者将一些特定的应用程序数据供给其它应用程序使用,它主要作用是用来在多个APP之间共享数据,如腾讯QQ中的QQ电话功能需要获取用户手机上的联系人的手机号码,还算不上标准的多个APP之间的消息通信(因为共享数据只是消息通信中很小的一部分)。共享的数据可以存储于文件系统、SQLite数据库或其它方式。内容提供者继承于ContentProvider 基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。

无论数据的来源是什么,ContentProvider都会认为是一种表,然后把数据组织成表格形式,因此该类提供给我们的方法类似于sqlite数据库中的增删改查的操作。

要使用ContentProvider我们需要先来了解一个概念URI。

1 URI(统一资源标识符(Uniform Resource

Identifier))用来唯一的标识一个资源。在上述我说过ContentProvider是用来在多个APP之间共享数据的,那么首先我们得找到这个数据,这个资源(数据属于资源的一种)是采用URI来标识的。它包括三个部分:scheme

authority and

path,其中在安卓中共ContentProvider访问的scheme固定值为:content://(就像Http协议固定值为http://),authority包括host和port。

scheme:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://"

authority:URI

的标识,用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。它定义了是哪个Content

Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。这个标识在 元素的

authorities属性中说明:一般是定义该ContentProvider的包.类的名称

path:路径(path),通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"

如果URI中包含表示需要获取的记录的ID(数据以表的形式表示),则就返回该id对应的数据,如果没有ID,就表示返回全部

要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name

如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri = Uri.parse("content://com.demo.provider.personprovider/person");

2ContentProvider共享数据

我们先来看一下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中的增删改查这些重要的方法都是抽象的,因此当我们继承自该类时需要重写其抽象方法。

3ContentResolver来操作数据

当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,需要使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Context提供的getContentResolver()方法(即该方法位于Context类中)。

ContentResolver cr = getContentResolver();

ContentProvider负责组织应用程序的数据,向其他应用程序提供数据,ContentResolver则负责获取ContentProvider提供的数据。因此可以知道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相对应的增删改查的方法接口,而且这些方法都是final修饰的,另外这些方法的第一个参数为Uri,代表要操作的ContentProvider(通常用包名+类名表示)和对其中的什么数据(通常是以表形式存储的)进行操作。代码如下:

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);

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

使用文件就是把数据以文件的形式保存,然后提供一定的访问权限供其它APP访问,云端共享与此类似只不过存储位置位于云端而已,云端共享最典型的莫过于搜索引擎与云盘共享。

三使用BroadcastReceiver

上述介绍的几种方式算不上完完全全的APP之间的消息通信,因为上述仅仅是多个APP之间共享数据而已,而在安卓中广播机制就是用来在多个APP之间通信的较好的方式,如多个APP可以响应系统网络监听的广播。一般广播的工作流程如下:

1.广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;

2.广播发送者通过binder机制向AMS发送广播;

3.AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;

4.消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

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

1定义一个子类继承自抽象的BroadcastReceiver类,重写其抽象的onReceive(Context context, Intent intent)方法,当广播接收者收到广播会自动回调该方法。

2注册/注销该广播:通过intentFilter.addAction(Constants.ACTION_MSG);来指定注册的广播对哪种广播消息进行响应

3在另一个组件中使用intent.setAction(Constants.ACTION_MSG); sendBroadcast(intent);来指定发送的广播类型,如果要传递数据可以使用intent.putExtra(Constants.MSG,

msg);

如在BaseActivity中响应ACTION_MSG,在getMsgService中发送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);//指定响应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);

}

//发送广播的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);//通过广播传递数据

        sendBroadcast(intent);

}

}

});

}

else{

Log.i("GetMsgService","服务器端连接暂时出错");

// Toast.makeText(getApplicationContext(), "服务器端连接暂时出错,请稍后重试!",0).show();

}

}


  }

  }.start();

}

...

}

虽然上述的代码是在同一个APP中响应的该广播,但是如果多个APP中的广播注册时使用Constants.ACTION_MSG字符串指定的广播则getMsgService类中sendBroadcast时多个APP的广播可以响应。

四使用AIDL让多个APP与同一个Service通信

AIDL(Android Interface definition

language),在Android中,每个应用运行在属于自己的进程中,无法直接调用到其他应用的资源,那么当多个APP之间相互通信的话,那么自然就转化为IPC机制了,而AIDL就是安卓系统中用来实现IPC机制的。那么哪些场合需要使用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.

通过上述文档叙述可以看到,当需要在不同APP之间访问同一个服务且处理多线程的时候需要用到AIDL,如果不是多个APP之间的IPC只需使用Binder机制,如果需要处理不同APP之间但是只是单线程的话只需使用Messager机制,即下面将介绍的一类情况。所以可以知道AIDL最佳使用情况是在不同APP之间访问同一个服务且处理多线程,因此可以知道AIDL可以用来处理不同APP之间的通信。

AIDL的使用:

一服务端:

1定义AIDL文件(该文件是一个接口,文件中的方法全部为抽象方法,如果格式正确,IDE会自动在gen目录下生成对应的java文件)

interfaceMyAIDL{

intplus(inta,intb);

}

2定义服务类(AIDL就是用来在多个APP之间访问同一个service的),在该服务类中定义对应的stub对象,在该stub对象中实现上述AIDL文件中定义的抽象方法,在服务的onBind(Intent

intent)中返回该stub对象。AndroidManifest.xml配置相关属性。

publicclassMyServiceextendsService{

......

@Override

publicIBinderonBind(Intent intent){

returnmBinder;//在onBind中返回该stub对象

}

DemoAIDL.Stub mBinder =newStub() {//在服务类中定义对应的stub对象,实现aidl中定义的抽象方法

@Override

publicintplus(inta,intb)throwsRemoteException{

returna + b;

}

};

}

AndroidManifest.xml配置相关属性如下:


package="com.example.servicetest"

android:versionCode="1"

android:versionName="1.0" >

......

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

android:process=":remote" >

//注意action的android:name属性,该属性在客户端bindService中将会用到

上述定义服务的APP相当于服务端。

二客户端:

1我们只需要把服务端aidl文件拷到相应的目录中即可,IDE会自动生成相对应的java文件,这一部分和服务端相同,这样服务端和客户端就在通信协议上达到了统一。

2在客户端的Activity中与Service通信,在客户端的Activity中定义ServiceConnection类,在该类的onServiceConnected(ComponentName

name, IBinder

service)方法中通过xxx.Stub.asInterface(service);获取定义的AIDL文件生成的java类,(xxx为aidl文件自动生成的对应的java文件类名),使用bindService(intent,

conn,Context.BIND_AUTO_CREATE);来绑定远程服务,注意此时的intent需要指定为我们在服务端创建的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转换为aidl对应的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指定为我们在服务端创建的service的intent-filter中action的android:name属性。

  bindService(intent, connection, BIND_AUTO_CREATE);

}

    });

}

}

五使用Messager

Messager实现IPC通信,底层也是使用了AIDL方式。和AIDL方式不同的是,Messager方式是利用Handler形式处理,因此,它是线程安全的,这也表示它不支持多线程处理;而AIDL方式是非线程安全的,支持多线程处理,因此,我们使用AIDL方式时需要保证代码的线程安全。

首先我们来看一下其构造函数:

publicMessenger(Handler target){

        mTarget = target.getIMessenger();  

    }

可以看到Messenger的构造函数中的参数为Handler对象,Messager本质上就是跨进程使用Handler。

Messenger使用步骤:

客户端绑定服务端,在ServiceConnection类的onServiceConnection方法中将远程服务端传过来的binder对象转换为Messenger对象,调用Messenger的send函数,就可以把Message发送至服务端的Handler。同时,如果需要服务端回调客户端(往客户端的Handler发消息),则可以在send的Message中设置replyTo,服务端就可以往客户端发送消息了。

客户端代码:

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");

// 绑定服务

bindService(intent,newServiceConnection() {

@Override

publicvoidonServiceDisconnected(ComponentName name){

            }

@Override

publicvoidonServiceConnected(ComponentName name, IBinder service){

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

messenger =newMessenger(service);//将远程服务端中返回的IBinder对象转换为Messenger对象

            }

        }, Context.BIND_AUTO_CREATE);      

    }

publicvoidsendMessage(View v){

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

// 设置回调用的Messenger

msg.replyTo = reply;//如果需要服务端回调客户端,则可以在send的Message中设置replyTo,将客户端的Messenger传递给服务端

try{

            messenger.send(msg);

}catch(RemoteException e) {

            e.printStackTrace();

        }

    }

privateHandler handler =newHandler() {//回调Messenger处理的Handler

@Override

publicvoidhandleMessage(Message msg){

Log.d(TAG,"回调成功");

        }

    };

}

服务端通过Message的replyTo取出客户端传递过来的Messenger,这样就可以通过该Messenger与客户端通信。

服务端通过Messenger的getBinder方法将IBinder对象返给客户端,用于共享服务端的Messenger。

服务端代码:

publicclassMessengerTestServiceextendsService{

protectedstaticfinalString TAG ="MessengerTestService";

privateHandler mHandler =newHandler() {

@Override

publicvoidhandleMessage(Message msg){

switch(msg.what) {

case1:

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

//获取客户端message中的Messenger,用于回调

finalMessenger callback = msg.replyTo;

try{

// 回调

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对应的binder对象。

    }

}

可以看到该方式与用AIDL方式整体大的框架基本相同,都是在远程服务端的Service中的onBind(Intent intent)中返回Ibinder对象,在客户端的ServiceConnection类的onServiceConnectioned(ComponentName name, IBinder service)中奖Ibinder转换为对应的对象,在AIDL中通过xxx.Stub.asInterface(service);转换为对应的aidl的java类,在Messenger中通过messenger = new Messenger(service);转换为Messenger对象,然后利用这个对象就可相互通信。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容