Android 进程间通信——Service、Messenger

发布时间:2017-07-13
技术:Android4.4+jdk1.8

概述

介绍绑定服务端的三种方式:同一进程绑定服务、跨进程绑定服务(Messenger)、跨进程绑定服务(aidl)。 重点说一下通过Messenger、Service实现的进程间通信。

详细

一、准备工作

开发环境:

    jdk1.8 

    Eclipse Luna Service Release 1 (4.4.1)

运行环境:

    华为荣耀6(Android4.4)、华为p9(Android7.0)

实现功能:

    Messenger、Service实现的进程间通信

二、基础知识

bound服务是客户端 - 服务器结构中的服务器。 bound服务允许组件(如Activity)绑定到服务,发送请求,接收响应,甚至执行进程间通信(IPC)。 绑定的服务通常仅服务于另一个应用程序组件,并且不会无限期地在后台运行。 

bound服务是Service类的一个实现,它允许其他应用程序绑定到它并与之交互。 要提供绑定服务,您必须实现onBind()回调方法。 此方法返回一个IBinder对象,该对象定义了客户端与服务进行交互的编程接口。

客户端可以通过调用bindService()绑定到该服务。当它这样做时,它必须实现ServiceConnection,监视与服务的连接。 bindService()方法立即返回,没有返回值,但是当Android系统创建客户端和服务之间的连接时,它会回调ServiceConnection上的onServiceConnected()来传递给客户端IBinder对象,客户端可通过IBinder对象与服务通信。 

多个客户端可以全部连接到该服务。但是,系统只会在第一个客户端绑定时调用您的服务的onBind()方法来检索IBinder。系统然后将相同的IBinder传递给绑定的任何其他客户端,而无需再次调用onBind()。 

当最后一个客户端解除绑定服务时,系统会销毁该服务(除非该服务也由startService()启动)。

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

三、扩展Binder类

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

1、扩展Binder类 

如果您的Service对您自己的应用程序是私有的,并且与客户端在相同的进程中运行(这是常见的),则应该通过扩展Binder类并创建其实例,onBind()返回该实例。 客户端收到Binder,可以使用它直接访问Binder实现或甚至Service中可用的公共方法。 

当您的服务只是您自己的应用程序的后台工作者时,这是首选技术。您不会以这种方式创建界面的唯一原因是因为您的服务被其他应用程序或单独的进程使用。

2、使用Messenger 

如果您需要Service 和客户端位于不同的进程,则可以使用Messenger为服务创建一个interface。 以这种方式,服务定义响应不同类型的Message对象的Handler。 该Handler是Messenger的基础,可以与客户端共享IBinder,允许客户端使用Message对象向服务发送命令。 此外,客户端可以定义自己的Messenger,因此服务可以发回消息。 

这是执行进程间通信(IPC)的最简单的方法,因为Messenger将所有请求排队到单个线程中,以便您不必将服务设计为线程安全。

3、使用AIDL 

AIDL(Android Interface Definition Language)执行所有的工作,将对象分解为基元,操作系统可以在进程之间了解和编组它们以执行IPC。 之前使用的Messenger技术实际上是基于AIDL作为其底层结构。 如上所述,Messenger在单个线程中创建所有客户端请求的队列,因此服务一次接收一个请求。 但是,如果您希望您的服务同时处理多个请求,则可以直接使用AIDL。 在这种情况下,您的服务必须能够进行多线程并建立线程安全。 

要直接使用AIDL,您必须创建一个定义编程接口的.aidl文件。 Android SDK工具使用此文件生成一个实现接口并处理IPC的抽象类,然后您可以在服务中扩展它。

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

四、扩展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;
        }
    };
}

五、进程间通信(使用Messenger)

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

以下是Messenger使用总结:

    A、Service实现一个Handler,接收客户端发送的消息。

    B、Handler用于创建一个Messenger对象(这是对Handler的引用)。

    C、Messenger创建一个IBinder,Service将IBinder 从onBind()返回给客户端。

    D、客户端使用IBinder来实例化Messenger (引用服务的Handler),客户端利用Messenger将Message对象发送到服务。

    E、Service接收 Message在其Handler的handleMessage()方法中进行处理。

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

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

image.png

image.png


服务端

服务端没有Activity(没有界面),主要是一个Service——RemoteService。 

服务端没有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对象

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

4 服务端接收客户端发送的消息,并在Handler对象的hanleMessage方法中进行处理。 

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()。然后,

应用程序组件(客户端)可以通过调用绑定到服务 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发送消息(控制播放音乐、停止播放)。

六、管理绑定服务的生命周期

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

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

image.png

七、程序代码截图

1、客户端

image.png

2、服务端

image.png

八、运行效果

运行,右键项目:Run as -》Android Application 

image.png

image.png


本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢