Android 进程间通信

Android系统提供了一些通用服务,比如音乐打电话发短信,WIFI,定位,输入法,传感器等。应用程序与这些通用服务运行在不同的进程中,如果应用程序想要与这些通用服务交互就要涉及到进程间通信,Binder就是为了Android进程间通信而设计的。

Binder框架


Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。

服务端
Binder服务端相当于一个Binder类对象。当创建该对象时,其内部会启动一个线程不断接收Binder驱动发送的消息。收到消息后会执行Binder.onTransact()方法。所以要实现一个Binder服务就必须重载onTransact()方法。

onTransact()方法通常用来做数据格式转换,按约定的顺序取出Binder客户端发送来的数据并转换成服务端识别的数据格式。

Binder驱动
Binder驱动运行在内核态,其所有操作都是基于内存而非硬件,客户端与服务端通信时需要Binder驱动进行中转。

当一个服务端Binder被创建时其在Binder驱动中同时会创建一个mRemote引用指向服务端。客户端要访问服务端时首先要获取服务端在Binder中的引用mRemote,获取引用后就可以通过mRemote.transact()方法向服务端发送消息。

transact()方法实现了以下几个功能:

  • 以线程间消息通信的模式向服务端发送客户端传递过来的参数。
  • 挂起当前客户端线程,等待服务端线程执行完毕后的通知(notify)
  • 接收服务端线程通知,继续执行客户端线程,并返回执行结果

客户端
这里就是指我们需要和系统服务交互的应用程序。应用程序使用startService()与应用程序Service建立连接,使用getSystemService()与系统Service建立连接,从而进行通信。

数据格式
在进行IPC通信时需要约定客户端与服务端通信的数据格式。Android使用AIDL(Android Interface Definition Language)约定数据格式。

Android提供了一个AIDL工具,可以把AIDL文件转换成一个Java类,在该Java类中同时重载了onTransact()方法和transact()方法,统一了存入和读取参数。

实现Binder服务端


public class MusicService extends Binder{
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException{
        return super.onTransact(code,data,reply, flags);
    }
    
    public void start(String filePath){

    }
    public void stop(){}
}

以上代码基于Binder创建了一个Service,使用startService()方法启动后就可以看到DDMS中多了一个线程。

重载onTransact(int code, Parcel data, Parcel reply, int flags)方法:

switch(code){
     code 1000:
            data.enforceInterface("MusicService");// 数据校验与客户端writeInterfaceToken()对应
            String filePath = data.readString();//读取一个字符串
            start(filePath);
            // reply.writeXXX();//如果需要返回结果则写入reply中
            break;
}

data中保存着客户端传递过来的参数,onTransact()方法内部需要从data中读取客户端传递的参数。参数的位置格式需要在AIDL文件中约束。
reply中保存服务端返回客户端的结果,如果需要返回则调用Parcel提供的相关方法写入结果。参数的位置格式需要在AIDL文件中约束。
code变量标识客户端希望服务端执行哪个方法,这里假定1000执行start()方法。
flags表示IPC调用模式:0代表"双向模式",服务端在执行完指定服务后会返回数据;1代表"单向模式",服务端在执行完指定服务后不反回任何数据。

实现Binder客户端


首先在startService(),getSystemService()中获取服务端Binder的引用,然后将参数按AIDL定义的格式写入data中并调用mRemote.transact()方法传入参数,服务端会在onTransact()方法中取出这里出入的参数:

IBinder mRemote = null;
String filePath = "/sdcard/music/xxx.mp3";
int code = 1000;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("MusicService");
data.writeString(filePath);
mRemote.transact(code,data,reply,0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();

调用mRemote.transact()方法后,Binder驱动会挂起当前客户端线程,并向服务端发送一个消息,这个消息就包括客户端传递的参数。服务端接收到消息,解析数据执行完相关任务后会把执行结果写入reply中,然后向Binder驱动发送一个notify消息,Binder驱动从挂起处唤醒客户端线程继续执行。

系统Service与应用程序Service


Service也就是服务分为两种,一种是系统创建的Service,另一种是应用程序自定义的Service。这两种Service都是Binder服务端。

系统Service
系统Service在系统系统初始化时从SystemServer进程中启动。常见的有PowerManagerService(电源管理服务),VibratorService(振动传感器服务),WindowManagerService(窗口管理服务)NotificationManagerService等等,每个系统服务都是一个Binder,都运行在一个独立的线程中。

系统提供了一个ServiceManager类来管理系统服务ServiceManager本身也是一个Binder,运行在一个独立的进程中,其提供了一个全局Binder供应用程序使用,应用程序获取其他系统服务都是通过ServiceManager的全局Binder来获取的。

这设计的好处仅仅暴露一个全局Binder引用,其他系统服务则隐藏起来,从而有助于系统扩展,以及调用系统服务的安全检查。

系统Service在启动时首先向ServiceManager注册Service(注册自己),当调用getSystemService(serviceName)获取系统服务时,会间接调用到ServiceManager.getService(String name)方法。getService()实现如下所示:

public static IBinder getService(String name){
    try{
        IBinder service = sCache.get(name);
        if(service != null){
            return service;
        }else{
            return getIServiceManager().getService(name);
        }
    } catch(RemoteException e){
        Log.e(TAG,"error in get Service", e);
    }
}

首先从缓存中获取,没获取到则从getIServiceManager()中获取,getIServiceManager()返回的是系统唯一ServiceManager的Binder。

应用程序Service
系统在ActivityManagerService中提供了startService()方法来启动应用程序自定义Service。客户端使用以下方法和应用程序Service建立连接:

public ComponentName startService(Intent intent)
public boolean bindService(Intent service, ServiceConnection conn,int flags)

startService用于启动一个服务,bindService用于绑定一个服务,bindService的第二个参数是获取服务端Binder的关键所在:

public interface ServiceConnection{
     public void onServiceConnected(ComponentName name,IBinder service);
     public void onServiceDisconnected(ComponentName name);    
}

onServiceConnected()方法的第二个变量就是我们需要的服务端Binder的引用,当客户端请求AmS启动某个Service,如果该Service正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationThread对象,调用的参数包括Service的Binder引用,然后在ApplicationThread中会回调bindService()方法中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数Service保存为一个全局变量,从而在客户端任何地方都可以随时调用该服务。

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

推荐阅读更多精彩内容