安卓中不同APP之间的消息通信
昨天在腾讯实习⽣招聘初试⾯试时⾯试官问道我关于两个APP之间相互通信的⽅式,当时⾃⼰回道到了contentProvider与BroadcastReceiver。但他接着问还有没有其它的⽅式,我跟他说可以使⽤AIDL,但是当时没说清楚,所以最后我说⽬前只知道这两种⽅式,然后他说可以使⽤⽂件的⽅式或云端存储的⽅式共享。⾯试回来后⾃⼰上⽹查了⼀下相关知识,根据⾃⼰的理解将安卓中不同APP之间消息通信总结如下:
⾸先要明⽩消息通信⼀般包括两种,⼀种是简单的数据访问,如ContentProvider,使⽤⽂件或云端⽅式共享,⼀种是消息的传递(传递的
BroadcastReceiver,Messenger。
Messenger。
任然是数据,但不再是单纯的数据访问,⽽是组件之间的相互通信),如AIDL,BroadcastReceiver,
⼀使⽤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.provider/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");//定义要访问的ContentProv
ider的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");
veToNext()){
Log.i("ContentTest", "personid="+ Int(0)+ ",name="+ String(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
三使⽤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
}
protected void onStart() {
// TODO Auto-generated method stub
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(Constants.ACTION_MSG);//指定响应<span >Constants.ACTION_MSG)的⼴播</span>
registerReceiver(MsgReceiver, intentFilter);//注册⼴播
}
@Override
protected void onStop() {
// TODO Auto-generated method stub
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=(SerializableExtra(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)
{
ClientInputThread();
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
通过上述⽂档叙述可以看到,当需要在不同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对象。l配置相关属性。
>安卓下载app
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论