美文网首页
IPC - Binder还要从Service说起

IPC - Binder还要从Service说起

作者: 千山万水迷了鹿 | 来源:发表于2017-08-05 11:12 被阅读332次

一、 什么是Service

第一次明显感觉到Binder的存在实在写Service的时候,虽然那些系统服务都是通过Binder实现的,但是一般来说对开发者并无感知。

像后台音乐播放,后台下载都离不开Service,一般来说Service具有较长时间的运行特性。但是它仍然是Android中的四大组件之一,是Context的子类,他是运行在UI线程中的。

Service可以分为(本文提到的都是本地服务):

  • 本地服务:指的是服务和启动服务的activity在同一个进程中
  • 远程服务: 指的是服务和启动服务的activity不在同一个进程中。

二、Service的使用

一般来说我们把开启Service的一端叫做客户端,把Service叫做服务端

使用一个Service的步骤

继承Service类,实现其抽象方法

package me.febsky.aidltest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

从上面代码可以看到,要想实现Service,有个方法必须实现,这个onBind方法的返回值是一个IBinder类型。

AndroidManifest.xml中声明上一步创建的Service

 <service
      android:name=".MyService"
      android:enabled="true"
      android:exported="true" />

注意这时候,Service和Activity运行在同一个进程中

启动Service
Service的启动有两种方式,一种是通过Context的startService(Intent)方法,另一种是通过Context的bindService()的方式打开,这两种不同的启动方式很重要,分别对应后面的不同的生命周期。

三、 Service的生命周期

Service的生命周期图.png

上面这张图是官网给出的,Service的生命周期主要看懂这张图就懂了。

1. 以startService的方式开启Service,也即是上图的左边生命周期

可以写一个Activity,在这个Activity中有个button名字叫做START,button的点击事件如下:

    public void startMyService(View view) {
        startService(new Intent(MainActivity.this, MyService.class));
    }

为了方便观察生命周期,我们把MyService类的一些生命周期函数实现一些,并打印一下log:

public class MyService extends Service {
    public MyService() {
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Q_M", "onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Q_M", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Q_M", "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

当第一次点击START按钮的时候,打印结果调用顺序如下:
onCreate--> onStartCommand
当再次点击START按钮的时候,打印结果调用顺序如下:
onStartCommand

这种service理论上可以无限地运行下去(除非关机,或者系统内存不够,后者用户手动杀死进程),必须Service内部调用stopSelf()方法或者其他组件调用stopService()方法来停止它。

但是startService的开启方式有个小小的缺陷,这种方式下,只能其他组件像MyService发消息,MyService无法直接将结果反馈给调用者(当然可以借助广播发送反馈)。有个非常常见的应用场景,比如我要下载一个文件,这个下载过程最好放到Service中(为啥不放到Activity中,因为有时候用户不想一直盯着这个下载进度看,想关掉当前页面,但是这个时候下载没有完成,这个下载这时候还要后台进行的啊),但是用户还想看到下载进度怎么办,那Service没下载一点,都要向展示下载进度的Activity反馈当前下载的进度。这个时候就引出了Service的另一种开启方式,bindService

2. bindService(intent, connection, flag)对应上图右面的生命周期

这个bindService 只看方法签名就比较麻烦,比上面startService的方式多了两个参数。
一个Service可以被多个Activity绑定,当所有的activity(客户端)调用unbindService()之后(并且这个MyService没有通过startService的方式开启过),这个Service将自动被系统销毁。要想bind的方式开启Service,必须实现以下几步:

  • 客户端Service实现ServiceConnection接口
  • 服务端实现IBinder接口,并在onBind中返回
  • 客户端调用bindService,并接收服务端返回的IBinder子类对象

这个地方引出了Binder和AIDL的概念,主要用在上面第二部在Service中的onBind方法中返回一个IBinder的子类。这里先假设Service和他的绑定对应的Activity在同一个进程中,要不然就设计到进程间通信,需要复写Binder的一些方法,比如onTransact等。这些都是重复并且麻烦的工作,Android将其封装成AIDL,然后编译的时候自动生成。【aidl下篇再说】

客户端Activity的实现

package me.febsky.aidltest;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class TestActivity extends AppCompatActivity {

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

    //bind button的点击事件
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    //unbind button的点击事件
    public void unBind(View view) {
        unbindService(mServiceConnection);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("Q_M", "onServiceConnected invoked");

            //这里能这么写是因为在同一个进程中
            MyService.MyBinder myBinder = (MyService.MyBinder) service;
            myBinder.invokeTest();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
        }
    };
}

布局文件就是两个按钮,就不再粘代码了。一个叫bind,一个叫unbind。

服务端Service的实现

package me.febsky.aidltest;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Q_M", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("Q_M", "onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("Q_M", "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d("Q_M", "onBind");
        return new MyBinder();
    }

    class MyBinder extends Binder {

        public void invokeTest() {
            Log.d("Q_M", "MyBinder内的方法被调用");
        }

    }
}

点击bind按钮,可以根据日志,观察调用顺序:

08-05 13:44:47.763 1213-1213/me.febsky.aidltest D/Q_M: onCreate
08-05 13:44:47.765 1213-1213/me.febsky.aidltest D/Q_M: onBind
08-05 13:44:47.771 1213-1213/me.febsky.aidltest D/Q_M: onServiceConnected invoked
08-05 13:44:47.771 1213-1213/me.febsky.aidltest D/Q_M: MyBinder内的方法被调用

然后,点击unbind按钮:

08-05 13:45:49.164 1213-1213/me.febsky.aidltest D/Q_M: onDestroy

3. 混合开启Service

Service混合开启生命周期.png

一般来说startServicebindService的开启方式并不是单独存在的,一般会混合使用。比如,一个后台音乐service可能因调用 startService()方法而被开启了,稍后,可能用户想要控制播放器或者得到一些当前歌曲的信息,可以通过 bindService()将一个activity和service绑定。这种情况下,stopService()stopSelf()实际上并不能停止这个service,除非所有的客户都解除绑定。但是onCreate方法只会执行一次。

四、 前台Service和IntentService

相关文章

网友评论

      本文标题:IPC - Binder还要从Service说起

      本文链接:https://www.haomeiwen.com/subject/wjzvlxtx.html