安卓中不同APP之间的消息通信
昨天在腾讯实习生招聘初试面试时面试官问道我关于两个APP之间相互通信的方式,当时自己回道到了contentProvider与BroadcastReceiver。但他接着问还有没有其它的方式,我跟他说可以使用AIDL,但是当时没说清楚,所以最后我说目前只知道这两种方式,然后他说可以使用文件的方式或云端存储的方式共享。面试回来后自己上网查了一下相关知识,根据自己的理解将安卓中不同APP之间消息通信总结如下:
首先要明白消息通信一般包括两种,一种是简单的数据访问,如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为一个抽象类)的重要方法:
public abstract boolean onCreate(); public abstract Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder); public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
CancellationSignal cancellationSignal) {
return query(uri, projection, selection, selectionArgs, sortOrder);
} public abstract String getType(Uri uri);
public abstract Uri insert(Uri uri, ContentValues values);
public abstract int delete(Uri uri, String selection, String[] selectionArgs);
public abstract int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs);
可以看到ContentProvider中的增删改查这些重要的方法都是抽象的,因此当我们继承自该类时需要重写其抽象方法。
3ContentResolver来操作数据
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,需要使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Context提供的getContentResolver()方法(即该方法位于Context类中)。
ContentResolver cr = getContentResolver();
ContentProvider负责组织应用程序的数据,向其他应用程序提供数据,ContentResolver则负责获取ContentProvider提供的数据。因此可以知道ContentResolver中也因该存在增删改查的接口。
public final Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}
public final Uri insert(Uri url, ContentValues values)
public final int delete(Uri url, String where, String[] selectionArgs) public final int update(Uri uri, ContentValues values, String where,
String[] selectionArgs) public final String getType(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 = new ContentValues();
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 = new ContentValues();
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类
public abstract class BaseActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(Constants.ACTION_MSG);//指定响应<span style="font-family: Arial, Helvetica, sans-serif;">Constants.ACTION_MSG)的广播</span> registerReceiver(MsgReceiver, intentFilter);//注册广播
} @Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop(); unregisterReceiver(MsgReceiver);
}
BroadcastReceiver MsgReceiver=new BroadcastReceiver()//定义一个类继承自抽象的BroadcastReceiver类,此处采用的是匿名类的方式
{ @Override
public void onReceive(Context context, Intent intent) {//重写抽象的onReceive(Context context, Intent intent)方法
// TODO Auto-generated method stub
TransportObject msg=(TransportObject)intent.getSerializableExtra(Constants.MSG);
getMessage(msg); }};
protected abstract void getMessage(TransportObject msg); } //发送广播的getMsgService类
public class GetMsgService extends Service {
... @Override
public void onStart(Intent intent, int startId) {
new Thread(){
public void run()
{
...
if(isStart)
{
cit=client.getClientInputThread();
if(cit!=null)
{
cit.setMessageListener(new MessageListener() { public void getMessage(TransportObject msg) { if(msg!=null&&msg instanceof TransportObject)
{
//通过广播向Activity传递消息
Intent intent=new Intent();
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 only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
通过上述文档叙述可以看到,当需要在不同APP之间访问同一个服务且处理多线程的时候需要用到AIDL,如果不是多个APP之间的IPC只需使用Binder机制,如果需要处理不同APP之间但是只是单线程的话只需使用Messager机制,即下面将介绍的一类情况。所以可以知道AIDL最佳使用情况是在不同APP之间访问同一个服务且处理多线程,因此可以知道AIDL可以用来处理不同APP之间的通信。
AIDL的使用:
一服务端:
1定义AIDL文件(该文件是一个接口,文件中的方法全部为抽象方法,如果格式正确,IDE会自动在gen目录下生成对应的java文件)
interface MyAIDL {
int plus(int a, int b);
}
2定义服务类(AIDL就是用来在多个APP之间访问同一个service的),在该服务类中定义对应的stub对象,在该stub对象中实现上述AIDL文件中定义的抽象方法,在服务的onBind(Intent intent)中返回该stub对象。AndroidManifest.xml配置相关属性。
public class MyService extends Service { ...... @Override
public IBinder onBind(Intent intent) {
return mBinder;//在onBind中返回该stub对象
} DemoAIDL.Stub mBinder = new Stub() {//在服务类中定义对应的stub对象,实现aidl中定义的抽象方法 @Override
public int plus(int a, int b) throws RemoteException {
return a + b;
}
}; }
AndroidManifest.xml配置相关属性如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest"
android:versionCode="1"
android:versionName="1.0" >
......
<service
android:name="com.example.servicetest.MyService"
android:process=":remote" >
<intent-filter>
<action android:name="com.example.servicetest.MyAIDLService"/>//注意action的android:name属性,该属性在客户端bindService中将会用到
</intent-filter>
</service>
</manifest>
上述定义服务的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属性。
<pre name="code" class="java">public class MainActivity extends Activity implements OnClickListener { ...
private MyAIDL myAIDL; private ServiceConnection connection = new ServiceConnection() { @Override
public void onServiceDisconnected(ComponentName name) {
} @Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAIDL = MyAIDL.Stub.asInterface(service);//在onServiceConnected中将IBinder转换为aidl对应的java类
try {
int result = myAIDL.plus(3, 5);
Log.d("TAG", "result is " + result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button bindService = (Button) findViewById(R.id.bind_service);
bindService.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("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方式时需要保证代码的线程安全。
首先我们来看一下其构造函数:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
可以看到Messenger的构造函数中的参数为Handler对象,Messager本质上就是跨进程使用Handler。
Messenger使用步骤:
客户端绑定服务端,在ServiceConnection类的onServiceConnection方法中将远程服务端传过来的binder对象转换为Messenger对象,调用Messenger的send函数,就可以把Message发送至服务端的Handler。同时,如果需要服务端回调客户端(往客户端的Handler发消息),则可以在send的Message中设置replyTo,服务端就可以往客户端发送消息了。
客户端代码:
public class MainActivity extends Activity {
protected static final String TAG = "MainActivity";
Messenger messenger;
Messenger reply;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
reply = new Messenger(handler);
Intent intent = new Intent("<span style="line-height: 25.2px; font-family: Verdana, Arial, Helvetica, sans-serif;">test.messenger.MessengerTestService</span><span style="line-height: 25.2px; font-family: Verdana, Arial, Helvetica, sans-serif;">");</span>
// 绑定服务
bindService(intent, new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(MainActivity.this, "bind success", 0).show();
messenger = new Messenger(service);//将远程服务端中返回的IBinder对象转换为Messenger对象
}
}, Context.BIND_AUTO_CREATE);
}
public void sendMessage(View v) {
Message msg = Message.obtain(null, 1);
// 设置回调用的Messenger
msg.replyTo = reply;//<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size:12px; line-height: 25.2px;">如果需要服务端回调客户端,<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size:12px; line-height: 25.2px;">则可以在send的Message中设置replyTo,将客户端的Messenger传递给服务端</span></span>
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private Handler handler = new Handler() {//回调Messenger处理的Handler
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "回调成功");
}
};
}
服务端通过Message的replyTo取出客户端传递过来的Messenger,这样就可以通过该Messenger与客户端通信。
服务端通过Messenger的getBinder方法将IBinder对象返给客户端,用于共享服务端的Messenger。
服务端代码:
public class MessengerTestService extends Service {
protected static final String TAG = "MessengerTestService";
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到消息");
//获取客户端message中的Messenger,用于回调
final Messenger callback = msg.replyTo;
try {
// 回调
callback.send(Message.obtain(null, 0));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
};
@Override
public IBinder onBind(Intent intent) {
return new Messenger(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对象,然后利用这个对象就可相互通信。
安卓中不同APP之间的消息通信的更多相关文章
- iOS 两个App之间调起通信
前言 假设需求是这样的:由一个app1跳转到app2之后,app2完成某项任务之后,怎么把app2的完成信息传到app1(自己的程序是app1),传的是什么类型的数据,怎么进行解析? 逻辑 本文章使用 ...
- 两个App之间调起通信
前言 经常使用一些app的分享功能,比如点击QQ分享,就从app打开(跳转到)QQ,然后分享完之后又回到我们的app,那么这是怎样实现的呢? 假设有这么一个需求,由app1跳转到app2,当app2完 ...
- 通过AIDL在两个APP之间Service通信
一.项目介绍 [知识准备] ①Android Interface definition language(aidl,android接口定义语言),其目的实现跨进程的调用.进程是程序在os中执行的载体, ...
- 消息通信机制NSNotificationCenter -备
消息通信机制NSNotificationCenter的学习.最近写程序需要用到这类,研究了下,现把成果和 NSNotificationCenter是专门供程序中不同类间的消息通信而设置的,使用起来极为 ...
- app之间的互相跳转
第一次写博客,给大家带来的是:iOS开发中不同app之间的跳转,相信很多人也有用过友盟的SDK或者其他的第三方的分享工具,原理都是一样的. 跳转的实现分为四步: 第一步:建立两个工程,模仿两个App的 ...
- NSNotificationCenter消息通信机制
作用:NSNotificationCenter是专门供程序中不同类间的消息通信而设置的. 注册通知:即要在什么地方接受消息 [[NSNotificationCenter defaultCenter] ...
- iOS中两个APP之间的跳转和通信
app间的跳转 一:在第一个app首先要做下面这些操作: 1.在info.plist文件中的Information Property List下添加一项:URL types. 2.点开URL type ...
- Android中实现跨app之间数据的暴露与接收
例如一个小项目:实现单词本的添加单词等功能 功能:不同的方式实现跨app之间数据的暴露与接收 暴露端app:实现单词的添加(Word.Translate),增删改查: 接收端app:模糊查询,得到暴露 ...
- 安卓中的消息循环机制Handler及Looper详解
我们知道安卓中的UI线程不是线程安全的,我们不能在UI线程中进行耗时操作,通常我们的做法是开启一个子线程在子线程中处理耗时操作,但是安卓规定不允许在子线程中进行UI的更新操作,通常我们会通过Handl ...
随机推荐
- [Project] Simulate HTTP Post Request to obtain data from Web Page by using Python Scrapy Framework
1. Background Though it's always difficult to give child a perfect name, parent never give up trying ...
- Map,HashMap,TreeMap
一.HashMap,TreeMap差别 1.两种常规Map性能 HashMap:适用于在Map中插入.删除和定位元素. Treemap:适用于按自然顺序或自定义顺序遍历键(key). 2.总结 Has ...
- jquery 引号问题
varFrozenColumns="[[{'field':'CZ','title':'操作','width':80,'align':'center','formatter':function ...
- RHEL(红帽七)的DNS配置
RHEL7的DNS配置 本文中用到的所有参数均位于文末附录中 查询bind-chroot这个安装包 Yum 安装 bind-chroot 进入named.conf文件 复制以下参数进去 进入这个文 ...
- 在循环列表的富文本里摘出每个item的img标签内容(适合vue渲染)
昨天在做公司项目的社区动态内容.后台接口返回的数据是数组套对象,对象里有富文本,然后需要摘出富文本里的img标签在列表里分开渲染(即图片九宫格样式).最终效果如图: 这个是后盾接口返回的json数据 ...
- popen() 使用举例 (转载)
函数原型: #include "stdio.h" FILE *popen( const char* command, const char* mode ) 参数说明: comman ...
- JVM类加载原理学习笔记
(1)类的生命周期包括了:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(Usin ...
- (译)openURL 在 iOS10中已弃用
翻译自:openURL Deprecated in iOS10 译者:Haley_Wong 苹果在iOS 2 推出了 openURL:方法 作为一种打开外部链接的方式.而与之相关的方法 canOpen ...
- Swift3中如何为Array写一个限定Type的扩展
我们知道Swift可以扩展已存在的类或结构,这些类或结构可以存在于标准库(或称为核心库)中.如果结构是一个集合类型(比如Array)就更有趣了.我们想尝试写一个限定Type数组的扩展,So我们就拿Ar ...
- PGM:贝叶斯网的参数估计2
http://blog.csdn.net/pipisorry/article/details/52599321 没时间看了,下次再看... 具有共享参数的学习模型 全局参数共享 局部参数共享 具有 共 ...