Android 进程间通信 Service、Messenger

转载请注明出处:http://www.jianshu.com/p/e6867aa8f267

Android四大组件(二)Service
接着上一篇Service基础知识,这一篇主要说下介绍下绑定的服务端的三种方式:同一进程绑定服务、跨进程绑定服务(Messenger)、跨进程绑定服务(aidl)。
重点说一下通过Messenger、Service实现的进程间通信(demo下载地址见最下面)。


1 基础

bound服务是客户端 - 服务器结构中的服务器。 bound服务允许组件(如Activity)绑定到服务,发送请求,接收响应,甚至执行进程间通信(IPC)。 绑定的服务通常仅服务于另一个应用程序组件,并且不会无限期地在后台运行。
bound服务是Service类的一个实现,它允许其他应用程序绑定到它并与之交互。 要提供绑定服务,您必须实现onBind()回调方法。 此方法返回一个IBinder对象,该对象定义了客户端与服务进行交互的编程接口。

客户端可以通过调用bindService()绑定到该服务。当它这样做时,它必须实现ServiceConnection,监视与服务的连接。 bindService()方法立即返回,没有返回值,但是当Android系统创建客户端和服务之间的连接时,它会回调ServiceConnection上的onServiceConnected()来传递给客户端IBinder对象,客户端可通过IBinder对象与服务通信。
多个客户端可以全部连接到该服务。但是,系统只会在第一个客户端绑定时调用您的服务的onBind()方法来检索IBinder。系统然后将相同的IBinder传递给绑定的任何其他客户端,而无需再次调用onBind()。
当最后一个客户端解除绑定服务时,系统会销毁该服务(除非该服务也由startService()启动)。

实现绑定服务时,最重要的部分是定义onBind()回调方法返回的接口。您可以通过几种不同的方法来定义服务的IBinder接口,以下部分将讨论每种技术。


2 绑定服务方式

在创建提供绑定的Service时,必须提供一个IBinder (客户端可以用来与服务进行交互的编程接口)。有三种方法可以定义接口:

  • 扩展Binder类
    如果您的Service对您自己的应用程序是私有的,并且与客户端在相同的进程中运行(这是常见的),则应该通过扩展Binder类并创建其实例,onBind()返回该实例。 客户端收到Binder,可以使用它直接访问Binder实现或甚至Service中可用的公共方法。
    当您的服务只是您自己的应用程序的后台工作者时,这是首选技术。您不会以这种方式创建界面的唯一原因是因为您的服务被其他应用程序或单独的进程使用。
  • 使用Messenger
    如果您需要Service 和客户端位于不同的进程,则可以使用Messenger为服务创建一个interface。 以这种方式,服务定义响应不同类型的Message对象的Handler。 该Handler是Messenger的基础,可以与客户端共享IBinder,允许客户端使用Message对象向服务发送命令。 此外,客户端可以定义自己的Messenger,因此服务可以发回消息。
    这是执行进程间通信(IPC)的最简单的方法,因为Messenger将所有请求排队到单个线程中,以便您不必将服务设计为线程安全。
  • 使用AIDL
    AIDL(Android Interface Definition Language)执行所有的工作,将对象分解为基元,操作系统可以在进程之间了解和编组它们以执行IPC。 之前使用的Messenger技术实际上是基于AIDL作为其底层结构。 如上所述,Messenger在单个线程中创建所有客户端请求的队列,因此服务一次接收一个请求。 但是,如果您希望您的服务同时处理多个请求,则可以直接使用AIDL。 在这种情况下,您的服务必须能够进行多线程并建立线程安全。
    要直接使用AIDL,您必须创建一个定义编程接口的.aidl文件。 Android SDK工具使用此文件生成一个实现接口并处理IPC的抽象类,然后您可以在服务中扩展它。

注意:大多数应用程序不应该使用AIDL创建绑定的服务,因为它可能需要多线程功能,并可能导致更复杂的实现。因此,AIDL不适用于大多数应用程序。

3 扩展Binder类

如果您的服务仅由本地应用程序使用,并且不需要跨进程工作,那么您可以实现自己的Binder类,为客户端直接访问服务中的公共方法。

注意:只有当客户端和服务处于相同的应用程序和进程中时,这是最常见的。 例如,将Activity绑定到其自己的Service对于需要在后台播放音乐的音乐应用程序将是有效的。

具体使用方法:

  1. 在您的服务中,创建一个或多个实例Binder:
    包含客户端可以调用的公共方法
    返回当前Service实例,该实例具有客户端可以调用的公共方法
    或者,使用客户端可以调用的公共方法返回由服务托管的另一个类的实例
  2. Binder从onBind()回调方法返回此实例。
  3. 在客户端中,Binder从onServiceConnected()回调方法接收并使用提供的方法调用绑定的服务。

以下是一种通过Binder实现为客户端访问服务中的方法的服务:

public class LocalService extends Service {
    // 创建IBinder对象
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /**
     * 用于客户端绑定的类。 因为我们知道这个服务总是和客户端一样运行在统一进程,
     * 所以我们不需要处理IPC
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            //返回此LocalService实例,以便客户端可以调用公共方法
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        //返回LocalBinder实例
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

LocalBinder类继承了Binder,并提供了getService()方法,该方法返回LocalService实例。
LocalServcie的onBind方法返回LocalBinder实例。
客户端获取该LocalBinder实例,然后通过调用getService()方法获取LocalServcie。这允许客户端调用该Service中的公共方法。例如,客户端可以从服务调用getRandomNumber()。
以下是Activity的代码,

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 绑定LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    //单击按钮时调用。
    public void onButtonClick(View v) {
        if (mBound) {
            // 调用LocalService的一个方法。
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // 绑定到LocalService,转换IBinder,获取LocalService实例。
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName com) {
            mBound = false;
        }
    };
}

4 进程间通信(使用Messenger)

如果您需要服务与远程进程通信,那么可以使用Messenger为服务提供接口。这种技术允许执行进程间通信(IPC),而无需使用AIDL。

以下是Messenger使用总结:

  • Service实现一个Handler,接收客户端发送的消息。
  • Handler用于创建一个Messenger对象(这是对Handler的引用)。
  • Messenger创建一个IBinder,Service将IBinder 从onBind()返回给客户端。
  • 客户端使用IBinder来实例化Messenger (引用服务的Handler),客户端利用Messenger将Message对象发送到服务。
  • Service接收 Message在其Handler的handleMessage()方法中进行处理。

以这种方式,客户端没有“方法”可以调用该服务。相反,客户端提供Service在其Handler中接收的“消息”(Message对象)。

如下是一个使用Messenger实现进程间通信的实例。由两个应用程序,一个是服务端工程——RemoteService,另一个是客户端工程——LocalClient。


服务创建
客户端操作服务端

服务端

服务端没有Activity(没有界面),主要是一个Service——RemoteService。
1 service创建Handler对象。

/**
 * 处理来自客户端的消息
 */
//创建Handler对象
private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        Log.i(TAG, "msg.what="+msg.what);
        Log.i(TAG, "mHandler Thread ="+Thread.currentThread());
        switch (msg.what) {
            case MSG_SAY_HELLO:
                Toast.makeText(getApplicationContext(), "hello,remote service", Toast.LENGTH_SHORT).show();
                //通过message对象获取客户端传递过来的Messenger对象。
                Messenger messenger = msg.replyTo;
                if(messenger != null){
              Message messg = Message.obtain(null, MSG_SAY_HELLO);
              try {
                //向客户端发送消息
            messenger.send(messg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
                }
                break;
            case MSG_MUSIC_PLAY:
                //播放音乐
                startPlay();
                break;
            case MSG_MUSIC_STOP:
                //停止播放
                stopPlay();
                break;
            default:
                break;
        }
    }
};

2 创建Messenger对象

//创建Mesenger 对象
Messenger mMessenger = new Messenger(mHandler);

3 onBind()返回IBinder对象

@Override
public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind");
    //通过Mesenger对象获取IBinder实例,并返回
    return mMessenger.getBinder();
}

4 服务端接收客户端发送的消息,并在Handler对象的hanleMessage方法中进行处理。
Handler接收客户端发来的Message消息,并进行处理。可以通过msg.replyTo获取客户端传递的Messenger对象(前提是客户端设置了该对象),通过该Messenger对象可以实现服务端向客户端发送消息。
播放音乐和停止播放的代码就不贴了。

清单文件AndroidManifest.xml

<service
    android:name="com.zpengyong.service.RemoteService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.zpengyong.messanger"></action>
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

exported要设置为true,否则别的进程不能使用该Service。

客户端

应用程序组件(客户端)可以通过调用绑定到服务 bindService()。然后,Android系统调用该Service的onBind()方法,该方法返回一个IBinder用于与服务进行交互的方法。

绑定是异步的。bindService()立即返回,并不会返回IBinder给客户端。要接收IBinder,客户端必须创建一个实例ServiceConnection并将其传递给bindService()。在ServiceConnection包括系统调用提供的回调方法IBinder。

注意:只有活动,服务和内容提供商可以绑定到服务 - 您无法从广播接收方绑定到服务。

1 绑定Service
其中intent的action和Service注册的action保持一致。

private void bind() {
    mBindState = STATE_CONNECTING;
    Intent intent = new Intent();
    // 设置action 与service在清单文件中的action保持一致
    intent.setAction("com.zpengyong.messanger");
    // 绑定服务
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    mStateText.setText("connecting");
}

2 连接成功回调
实现ServiceConnection,必须覆盖两个回调方法:

  1. onServiceConnected()
    系统调用此方法来传递IBinder由服务onBind()方法返回的结果。
  2. onServiceDisconnected()
    当与服务的连接意外丢失时,例如服务已崩溃或已被杀死时,Android系统会调用此操作。当客户端解除绑定(unbindService)时,这不被调用。

客户端与远端Servcie连接成功,系统会回调onServiceConnected()。通过IBinder参数获取Messenger,该Messenger用来向Service发送消息。

private ServiceConnection mConnection = new ServiceConnection() {

    // 当与服务端连接成功时,回调该方法。
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected");
        //通过IBinder参数获取Messenger
        mRemoteMessenger = new Messenger(service);
        mStateText.setText("connected");
        mBindState = STATE_CONNECTED;
        mBtnBind.setText("解绑");
    }

    // 当与服务端连接异常断开时,回调该方法。
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected");
        mRemoteMessenger = null;
        mStateText.setText("disconnected");
        mBindState = STATE_DISCONNECTED;
        mBtnBind.setText("绑定");
    }
};

3 向服务端发送消息
Message属性replyTo不是必须的,如果希望Service向客户端发送消息则需要。

private void sendMsg(int what){
    if (mRemoteMessenger == null)
        return;
    //创建消息,设置what属性
    Message msg = Message.obtain(null, what);
    //replyTo不是必须的,如果希望Service向客户端发送消息则需要设置。
    msg.replyTo = mMessenger;
    try {
        //发送消息
        mRemoteMessenger.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

sendMsg(MSG_SAY_HELLO); 向服务端打招呼。
sendMsg(MSG_MUSIC_PLAY); 向服务端发送消息 播放音乐
sendMsg(MSG_MUSIC_STOP); 向服务端发送消息 停止播放

4 接收Service的回复
Handler中接收到Service发送到消息,进行处理。

private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
        case MSG_SAY_HELLO:
            Log.i(TAG,"MSG_SAY_HELLO");
            break;
        default:
            break;
        }
    };
};
//向Service发送消息所用
private Messenger mRemoteMessenger = null;
//接收Service的回复所用
private Messenger mMessenger = new Messenger(mHandler);

5 解除绑定
需要断开与服务的连接时,请调用unbindService()。

private void unbind() {
    mBindState = STATE_DISCONNECTING;
    //解除与Service的连接
    unbindService(mConnection);
    mStateText.setText("disconnecting");
    mBindState = STATE_DISCONNECTED;
    mStateText.setText("disconnected");
    mBtnBind.setText("绑定");
}

unbindService与Service断开连接之后,由于我RemoteService是通过client的绑定开启的,所以解除绑定后,RemoteService 会走onUnbind --》onDestroy。但是还有远端Messenger对象,依然可以向向Service发送消息(控制播放音乐、停止播放)。


5 管理绑定服务的生命周期

当一个Service从所有客户端解除绑定时,Android系统会销毁它(除非它是通过startServiceon创建的)。因此,您无需管理服务的生命周期,如果它完全是一个有限的服务,Android系统将根据是否绑定到任何客户端来管理您的服务。

另外,如果您的Service启动并接受绑定,那么当系统调用您的onUnbind()方法时,如果希望在下次客户端绑定到服务时接收调用onRebind()(而不是调用onBind()),则可以选择返回 true。但客户端仍然在onServiceConnected()回调中收到IBinder。下图说明了这种生命周期的逻辑。


绑定服务生命周期

进程间通信demo下载地址:http://www.demodashi.com/demo/10611.html

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

推荐阅读更多精彩内容