5.1 Service使用大全

点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图

本节代码下载地址:WillFlowService

一、服务是什么?

服务(Service)是一种在后台运行的组件,是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的或为远程进程执行作业的任务。服务的运行不依赖于任何用户界面,例如,当用户位于其他应用中时,服务可能在后台播放音乐或者通过网络获取数据,但不会阻断用户与 Activity 的交互。所以即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。另外,也不要被服务的后台概念所迷惑, 实际上服务并不会自动开启线程,默认情况,如果没有显示的指 Service 所运行的进程,Service 和 Activity 是运行在当前 App 所在进程的 Mainthread(UI 主线程)里面的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,尤其是网络请求、拷贝数据库、大文件等类似的耗时操作,否则就有可能出现主线程被阻塞住的情况。

不过也有特殊情况,就是说我们可以在清单文件配置 Service 执行所在的进程,让 Service 在另外的进程中执行:

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

二、服务的生命周期

之前我们学习过了Activity以及Fragment的生命周期,与之相类似地,服务也有自己的生命周期,前面我们使用到的 onCreate()、onStartCommand()、onBind() 和 onDestroy() 等方法都是在服务的生命周期内可以回调的方法。

(1)服务的生命周期回调方法

要创建服务,我们必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,我们需要重写一些回调方法,以处理服务生命周期的某些关键方面并提供一种机制将组件绑定到服务,应重写的最重要的回调方法包括:

1、onStartCommand()

当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果我们实现此方法,则在服务工作完成后,需要由我们通过调用 stopSelf() 或 stopService() 来停止服务。(如果我们只想提供绑定,则无需实现此方法。)

2、onBind()

当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,我们必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。还是要务必实现此方法,但如果我们并不希望允许绑定,则应返回 null。

3、onCreate()

首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果服务已在运行,则不会调用此方法。

4、onDestroy()

当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

(2)控制服务的开启和停止方法

1、startService() 方法

一旦在项目的任何位置调用了 Context 的 startService() 方法,相应的服务就会启动起来,并回调 onStartCommand() 方法。如果这个服务之前还没有创建过, onCreate() 方法会先于 onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到 stopService() 或 stopSelf() 方法被调用。注意虽然每调用一次 startService() 方法, onStartCommand() 就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次 startService( )方法,只需调用一次 stopService() 或 stopSelf() 方法,服务就会停止下来了。

2、bindService() 方法

另外,还可以调用 Context 的 bindService() 来获取一个服务的持久连接,这时就会回调服务中的 onBind() 方法。类似地,如果这个服务之前还没有创建过, onCreate() 方法会先于 onBind() 方法执行。之后,调用方可以获取到 onBind() 方法里返回的 IBinder 对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

3、stopService() 方法

当调用了 startService() 方法后,又去调用 stopService() 方法,这时服务中的 onDestroy() 方法就会执行,表示服务已经销毁了。类似地,当调用了 bindService() 方法后,又去调用 unbindService() 方法, onDestroy() 方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了 startService()方法,又调用了 bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据 Android 系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用 stopService() 和 unbindService() 方法, onDestroy() 方法才会执行。

(3)创建启动服务

服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。 因此,服务应通过调用 stopSelf() 结束工作来自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。应用组件(如 Activity)可以通过调用 startService() 方法并传递 Intent 对象(指定服务幷包含待使用服务的所有数据)来启动服务,服务通过 onStartCommand() 方法接收此 Intent。

例如,假设某 Activity 需要将一些数据保存到在线数据库中,该 Activity 可以启动一个协同服务,并通过向 startService() 传递一个 Intent,为该服务提供要保存的数据。服务通过 onStartCommand() 接收 Intent,连接到互联网并执行数据库事务,事务完成之后,服务会自行停止运行并随即被销毁。

注意:默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。 因此,如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。所以为了避免影响应用性能,我们一般在服务内启动新线程。

一般来说,我们可以扩展两个类来创建启动服务:
1、Service

这是适用于所有服务的基类,扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,也就是我们说的UI线程,但这会降低应用正在运行的所有 Activity 的性能。

2、IntentService

这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果我们不要求服务同时处理多个请求,那么这是最好的选择。我们只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使我们能够执行后台工作。

三、服务的使用

了解了服务的生命周期方法和控制方法后,下面就让我们开始对服务的相关内容进行学习。作为 Android 四大组件之一,服务也少不了有很多非常重要的知识点,那我们自然要从最基本的用法开始学习了。

(1)定义一个服务

首先看一下如何在项目中定义一个服务。

新建 MyService 的类,代码如下所示:
/**
 * Created by   : WGH.
 */
public class MyService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

目前 MyService 中可以算是空空如也,但有一个 onBind() 方法特别醒目,这个方法是 Service 中唯一的一个抽象方法,所以必须要在子类里实现。我们会在后面使用到 onBind() 方法,目前可以暂时将它忽略掉。

既然是定义一个服务,自然应该在服务中去处理一些事情,那处理事情的逻辑应该写在哪里呢?这时就可以重写 Service 中的另外一些方法了,如下所示:

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate()");
    }

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

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy()");
        super.onDestroy();
    }

可以看到,这里我们又重写了 onCreate()、 onStartCommand() 和 onDestroy()这三个方法,它们是每个服务中最常用到的三个方法。其中 onCreate() 方法会在服务创建的时候调用,onStartCommand() 方法会在每次服务启动的时候调用, onDestroy() 方法会在服务销毁的时候调用。通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在 onStartCommand() 方法里。而当服务销毁时,我们又应该在 onDestroy()方法中去回收那些不再使用的资源。

另外需要注意,每一个服务都需要在 AndroidManifest.xml 文件中进行注册才能生效,不知道你有没有发现,这是 Android四大组件共有的特点。于是我们还应该修改 AndroidManifest.xml 文件,代码如下所示:

    <service android:name=".MyService">

    </service>

(2)启动和停止服务

定义好了服务之后,接下来就应该考虑如何去启动以及停止这个服务。启动和停止的方法当然我们也不会陌生,主要是借助 Intent 来实现的,下面就让我们尝试去启动以及停止 MyService 这个服务。

修改 activity_main.xml 中的代码,如下所示:
<android.support.constraint.ConstraintLayout 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:layout_height="match_parent"
    tools:context="com.wgh.willflowservice.MainActivity">

    <Button
        android:id="@+id/start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开启服务"
        android:textColor="#0d73f9"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.501"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.218" />

    <Button
        android:id="@+id/stop_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止服务"
        android:textColor="#ff3f3f"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.501"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.382" />

</android.support.constraint.ConstraintLayout>

这里我们在布局文件中加入了两个按钮,分别是用于启动服务和停止服务的。

然后修改 MainActivity 中的代码,如下所示:
/**
 * Created by   : WGH.
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button mButtonStart;
    private Button mButtonStop;

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

        initView();
    }

    private void initView() {
        mButtonStart = (Button) findViewById(R.id.start_service);
        mButtonStop = (Button) findViewById(R.id.stop_service);

        mButtonStart.setOnClickListener(this);
        mButtonStop.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                Log.i(TAG, "启动服务");
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                Log.i(TAG, "停止服务");
                break;
        }
    }
}

可以看到,这里在 onCreate() 方法中分别获取到了 Start Service 按钮和 Stop Service 按钮的实例,并给它们注册了点击事件。然后在 Start Service 按钮的点击事件里, 我们构建出了一个 Intent 对象,并调用 startService() 方法来启动 MyService 这个服务。 在 Stop Serivce 按钮的点击事件里,我们同样构建出了一个 Intent 对象,并调用 stopService() 方法来停止 MyService 这个服务。

startService() 和 stopService() 方法都是定义在 Context 类中的,所以我们在 Activity 里可以直接调用这两个方法。注意,这里完全是由Activity来决定服务何时停止的,如果没有点击 Stop Service 按钮,服务就会一直处于运行状态。那服务有没有什么办法让自已停止下来呢?当然可以,就像我们之前所说的,只需要在 MyService 的任何一个位置调用 stopSelf() 方法就能让这个服务停止下来了。

编译运行看效果:

**再次提醒:**onCreate()方法是在服务第一次创建的时候调用的,而 onStartCommand()方法则在每次启动服务的时候都会调用,由于刚开始我们是第一次点击”开启服务”按钮,服务此时还未创建过,所以两个方法都会执行,之后再连续多点击几次”开启服务”按钮,我们发现就只有 onStartCommand()方法可以得到执行了。

(3)Service和Activity进行通信

前面的篇幅中我们学习了启动和停止Service的方法,不知道你有没有发现,虽然Service是在Activity里启动的,但在启动了Service之后,Activity与Service基本就没有什么关系了。确实如此,我们在Activity里调用了 startService() 方法来启动 MyService 这个 Service,然后 MyService 的 onCreate() 和 onStartCommand() 方法就会得到执行。之后 Service 会一直处于运行状态,但具体运行的是什么逻辑, Activity 就控制不了了。这就类似于 Activity 通知了Service一下:“你可以启动了!”然后Service就去忙自己的事情了,但 Activity 并不知道 Service 到底去做了什么事情,以及完成的如何。

那么有没有什么办法能让 Activity 和 Service 的关系更紧密一些呢?例如在 Activity 中指挥 Service 去干什么,Service 就去干什么。当然可以,这就需要借助我们刚刚忽略的 onBind() 方法了。比如说目前我们希望在 MyService 里提供一个下载功能,然后在 Activity 中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理,修改 MyService 中的代码,如下所示:

    private DownloadBinder mBinder = new DownloadBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind()");
        return mBinder;
    }

    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.i(TAG, "开始下载!");
            Toast.makeText(getApplicationContext(), "开始下载!", Toast.LENGTH_SHORT).show();
        }
        public int getProgress() {
            Log.i(TAG, "获取进度!");
            Toast.makeText(getApplicationContext(), "获取进度!", Toast.LENGTH_SHORT).show();
            return 0;
        }
    }

首先,这里我们新建了一个 DownloadBinder 类,并让它继承自 Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。当然这只是两个模拟方法,并没有实现真正的功能,我们在这两个方法中分别打印了一行日志和弹出吐司。

接着,在 MyService 中创建了 DownloadBinder 的实例,然后在 onBind()方法里返回了这个实例,这样 MyService 中的工作就全部完成了。下面就要看一看,在Activity中如何去调用服务里的这些方法了。

修改 activity_main.xml 中的代码,如下所示:
    <Button
        android:id="@+id/bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="绑定服务"
        android:textColor="#63b400"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.454" />

    <Button
        android:id="@+id/unbind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="解绑服务"
        android:textColor="#823bed"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.58" />

这两个按钮分别是用于绑定服务和取消绑定服务的,那到底谁需要去和服务绑定呢?当然就是Activity了,当一个Activity和服务绑定了之后,就可以调用该服务里的 Binder 提供的方法了。

修改 MainActivity 中的代码,如下所示:
    private static boolean mBindFlag = false;
    private MyService.DownloadBinder mDownloadBinder;

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

        initView();
    }

    private void initView() {
        mButtonBind = (Button) findViewById(R.id.bind_service);
        mButtonUnbind = (Button) findViewById(R.id.unbind_service);
        mButtonBind.setOnClickListener(this);
        mButtonUnbind.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.bind_service:
                Intent bindIntent = new Intent(this, MyService.class);
                mBindFlag = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); // 绑定服务
                Log.i(TAG, "绑定服务");
                break;
            case R.id.unbind_service:
                if (mBindFlag) {
                    unbindService(serviceConnection);
                    Log.i(TAG, "解绑服务");
                    mBindFlag = false;
                }
                break;
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDownloadBinder = (MyService.DownloadBinder) service;
            mDownloadBinder.startDownload();
            mDownloadBinder.getProgress();
        }
    };

首先,这里我们创建了一个 ServiceConnection 的匿名类,在里面重写了 onServiceConnected() 方法和 onServiceDisconnected() 方法,这两个方法分别会在Activity与服务成功绑定以及解除绑定的时候调用。在 onServiceConnected() 方法中,我们又通过向下转型得到了 DownloadBinder 的实例,有了这个实例, Activity和服务之间的关系就变得非常紧密了。

现在我们可以在Activity中根据具体的场景来调用 DownloadBinder 中的任何 public 方法,即实现了指挥服务干什么,服务就去干什么的功能。这里仍然只是做了个简单的测试, 在 onServiceConnected() 方法中调用了 DownloadBinder 的 startDownload() 和 getProgress() 方法。

当然,现在Activity和服务其实还没进行绑定呢,这个功能是在 Bind Service 按钮的点击事件里完成的。可以看到,这里我们仍然是构建出了一个 Intent 对象,然后调用 bindService() 方法将 MainActivity 和 MyService 进行绑定。bindService() 方法接收三个参数,第一个参数就是刚刚构建出的 Intent 对象,第二个参数是前面创建出的 ServiceConnection 的实例,第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务,这会使得 MyService 中的 onCreate()方法得到执行,但 onStartCommand()方法不会执行。bindService() 方法返回一个boolean类型,这就让我们得知绑定的成功与否。然后如果我们想解除活动和服务之间的绑定该怎么办呢?调用一下 unbindService() 方法就可以了,这也是 Unbind Service 按钮的点击事件里实现的功能。

不过在解除绑定之前我们还是要判断一下 mBindFlag 这个标识位,否则可能出现这样的情况:在绑定服务成功后连续第两次点击“解绑服务”按钮或者没有绑定服务而直接点击“解绑服务”按钮,那么此时就会导致应用崩溃。所以我们用此标识位来判断服务绑定的成功与否或者是否解绑过,以此避免应用崩溃的情况发生。

编译运行看效果:

我们可以看到,首先是 MyService 的 onCreate() 方法得到了执行,然后是 onBind() 方法,最后 startDownload() 和 getProgress() 方法都得到了执行,说明我们确实已经在Activity里成功调用了服务里提供的方法了。

注意:任何一个服务在整个应用程序范围内都是通用的,即 MyService 不仅可以和 MainActivity 绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成后它们都可以获取到相同的 ownloadBinder 实例。

点此进入:GitHub开源项目“爱阅”,下面是“爱阅”的效果图:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章