Android绑定Service(含IPC)

前言:四大组件中的service是其中除了activity之外用得最多的可能就是它了,当然,其他两个组件有它们自己的应用场合,这个在每个应用中使用情况可能不同,需要根据应用的需要选择使用相应的组件来完成任务。这篇文章将介绍如何绑定一个服务Service,使得客户端和Service进行通讯。

一、绑定Service

 绑定一个服务,首先需要定义一个类继承系统的Service基类,然后必须重写onBind()方法并返回一个IBinder对象给客户端使用,这个返回的IBinder对象作为这个Service的代理对象,通过它可以使得客户端和这个服务进行通讯。通常,多个不同的客户端可以同时绑定这服务,但是需要注意的是onBind()方法并不会在每次客户端绑定服务的时候都会调用,它只会在第一个客户端绑定这个服务的时候调用,所以,如果是多个客户端绑定相同的服务,那么只会有一个相同的IBinder对象。因此,绑定服务关键在于定义一个自己的IBinder对象,然后在onBind()方法中返回给客户端使用。下面我们就做关键的一步,创建IBinder对象。

有以下三种方式可以得到IBinder对象:

  1. 继承Binder类:这种方式适用于你的服务只有你自己的应用或者和你的这app在相同的进程里面的客户端使用,如果要让不同进程的应用使用你的服务,那么这种方式就行不通了。
  2. 使用messenger:这种方式适用于你的服务需要在不同的进程间通讯(IPC),但这种方式有一个缺点就是所有处理请求(消息)都是在单一的线程中(没必要再将你的service设置成线程安全的了),也就是说不能处理多线程并发的情况。
  3. 使用AIDL:使用AIDL实现绑定服务就是为了解决上面两种情况的弊端,一个是service不能在进程间通讯,另一个就是service不能处理多线程的情况,但是,你的应用除非必须要使用AIDL,否则上面两种可以满足需求的情况下就没必要使用AIDL(消耗系统资源)。

1. 继承Binder类

 要绑定一个服务(和服务通讯),那么客户端需要得到service的一个实例(引用)或者一个接口,我们知道在客户端使用了bindService()绑定了一个服务后系统会调用该服务的onBind()方法(第一次绑定该服务)返回一个IBinder对象给客户端,那么我们可以通过这个IBinder对象(一种通讯接口)和service进行交互(通讯)。通常我们在自定义的Binder类中定义一些public方法,这样,客户端可以通过这个方法获取service的实例或者做其他的一些事情,下面,我们通过一个例子来理解这种方法绑定服务。

LocalService.java

package com.example.lt.boundservice;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;

public class LocalService extends Service{

    private String[] names = {"吕布","赵子龙","关羽"};

    private IBinder myBinder = new MyBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }

    public class MyBinder extends Binder{
        public LocalService getService(){
            return LocalService.this;
        }
    }

    public String getName(int postion){
        return names[postion];
    }
}

客户端端绑定服务:

package com.example.lt.boundservice;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private LocalService localService;
    private TextView textView;
    private boolean mBound;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);
    }

    public void getName(View view){
        if(mBound) {
            textView.setText(localService.getName(1));
        }
    }


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

    ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 这里的service就是onBind返回的那个IBinder对象
            System.out.println("ComponentName:"+name);
            LocalService.MyBinder myBinder = (LocalService.MyBinder) service;
            localService = myBinder.getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            System.out.println("onServiceDisconnected");
            System.out.println("ComponentName:"+name);
        }
    };

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(conn);
    }
}

这里需要注意的是绑定服务可能失败,所以要在确认绑定服务成功的情况下使用service,这种通过绑定启动一个服务的方式会将service和客户端(这里是activty)绑定在一起,所以需要在合适的时间绑定和取消绑定service(管理service的生命周期)。

2. 使用Messenger

 如果你的service需要在不同的进程间通讯,那么你可以使用Messenger去提供一个接口给你的service,使得你的service可以在不同的进程间通讯(IPC)。

通常,可以分为如下几个步骤来使用Messenger绑定服务:

  1. 在你的service里面提供一个Handler,这个Handler用来处理客户端发送过来的消息(响应);
  2. 创建这个Handler的对象,并用这个Handler对象创建一个Messenger对象(这个Messenger对象持有handler的引用);
  3. 在onBind()方法中返回这个Messenger对象创建的IBinder对象;
  4. 客户端绑定服务得到返回的IBinder对象,并用这个对象在客户端初始化Messenger对象,这个对象持有service的Handler对象的引用,这样客户端通过这个Messenger对象发送消息给service。

按照上面的步骤,咋们来写个测试代码来实践一下。由于是测试在进程间进行通讯,所以,我们需要创建两个项目(应用),一个包含那个远程服务,一个用来访问那个远程服务。

创建两个项目(应用),一个叫RemoteService,一个叫RemoteClient

(1)在RemoteService项目中创建远程服务RemoteService

package com.example.lt.messengerdemo;


import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.support.annotation.Nullable;
import android.widget.Toast;

public class RemoteService extends Service{

    private static final  int SAY_HELLO = 0;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 3. 通过Messenger对象返回一个IBinder对象
        return mMessenger.getBinder();
    }

    /**
     * 1. 创建一个Handler用来接收,处理客户端的消息
     */
    final Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case SAY_HELLO:
                    Toast.makeText(RemoteService.this,"hello,I am RemoteService",Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    /**
     * 2. 通过这个Handler创建一个Messenger对象
     */
    final Messenger mMessenger = new Messenger(mHandler);
}

在清单文件中注册这个服务,并给一个action,方便客户端通过这个action来绑定这个服务:

<service android:name="com.example.lt.messengerdemo.RemoteService">
    <intent-filter>
        <action android:name="com.example.lt.messengerdemo.RemoteService"> </action>
    </intent-filter>
</service>

可以看到,在这一步中完成了三面的三个步骤,接下来,我们需要在客户端来绑定这个远程服务。

(2)远程客户端绑定服务

package com.example.lt.remoteclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private Messenger mMessenger;
    private boolean mBound;


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

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent();
        intent.setAction("com.example.lt.messengerdemo.RemoteService");
        intent.setPackage("com.example.lt.messengerdemo");
        mBound = bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(conn);
    }

    public void hello(View view) throws RemoteException {
        if(mBound) {
            Message message = Message.obtain();
            message.what = 0;
            mMessenger.send(message);
        }else{
            Toast.makeText(MainActivity.this,"还没接通呢?",Toast.LENGTH_SHORT).show();
        }
    }
}

这里先在onStart()方法中绑定远程服务,然后在绑定服务成功后的那个回调方法onServiceConnected中通过得到的IBinder对象创建Messenger对象,然后用户点击hello按钮后向远程服务发送一个消息(调用hello()方法),最后在onStop()方法中取消绑定服务。

注意:android5.0后对service的隐式启动做了一些限制,隐式启动需要设置action和package(service所在的那个应用的包名)。

客户端布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:gravity="center"
    android:layout_height="match_parent"
    >

    <Button
        android:onClick="hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="hello" />

</LinearLayout>

这里就不贴出测试结果的动态图了,直接说个测试结果吧,测试结果是我们点击hello按钮后,系统弹出一个“hello,I am RemoteService”吐司,看到这个结果,我们就知道远程服务响应了客户端的请求(处理了接收到的消息)。

二、管理Service的生命周期

 我们知道,客户端可以通过bindService()绑定Service和startService()启动服务一个非常明显的区别就是前者会将Service的生命周期和客户端生命周期绑定在一起(通常是activity),后者Service的生命周期由这个服务自己管理,所以当我们绑定Service来启动服务的时候,我们需要在合适的时候(不需要和Service通讯)绑定和解除绑定服务,比如:当我们Activity在不可见的时候不需要服务(通讯),可见的时候需要服务(通讯),那么,我们就可以在Activity的onStart()中绑定服务,在onStop()中解除绑定;当我们需要Service一直运行在后台,直到Activity被销毁,那么,我们就可以在onCreate()onDestory()中分别绑定和解除绑定服务,但通常不会再onResume()onPause()中绑定和解除绑定服务,下面我们看一张图来理解Service生命周期:

这里写图片描述

这是android官网的一一张图片,这里这个说明,当客户端通过startService()方式启动服务的时候,系统会调用服务的onStartCommand()方法(我们一般在这个方法里面做任务)而不会调用onBind()方法;当客户端通过bindService()方法启动服务的时候,系统会调用onBind()方法将Service和客户端绑定在一起,而不会调用onStartCommand方法。

到这里,绑定一个服务的前2种方式基本介绍完了,关于AIDL绑定服务,大家可以参考:android使用AIDL实现跨进程通讯(IPC)

总结:

 绑定服务可以有很多种方式,具体通过什么方式,这里有一个原则,如果service只服务于自己的这个应用或者在同一进程(通常不同应用在不同的进程中)中共享,那么通过第一种方式就行了;如果你应用的service需要向远程客户端提供服务,但不需要在这个service中处理多线程并发,那么可以选择messenger方式绑定服务;如果你的service既要在不同的应用(进程)中通讯(IPC),又要在service中处理多线程,那么可以考虑使用AIDL绑定服务。不过,到这里,我好奇的是Messenger绑定远程服务实现IPC是否能够像AIDL一样传递数据(包括自定义类型)呢?还是只能跨进程传递消息?求解。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值