章节索引 :

服务:Service

今天来学习 Android 的另一个组件——Service,相比于 Activity,Service通常运行在后台,没有任何 UI 界面,对用户是透明感知。通常用来执行一些后台任务,比如播放音乐、下载、加载一些数据等等,也可以用作一些进程间通信(IPC)机制。

1. Service 的基本定义

我们还是先来看看官方文档的部分解释:

A Service is an application component representing either an application’s desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use. Each service class must have a corresponding declaration in its package’s AndroidManifest.xml. Services can be started with Context.startService() and Context.bindService().

还是用我蹩脚的英语给大家简单翻译一下:

Service 是 Android 四大组件之一,通常用来执行一些需要长时间运行并且不需要和用户发送交互的任务,或者是要持续给其他 App 提供服务的场景。每一个服务和 Activity 一样,需要在包下的 “AndroidManifest.xml”文件中添加注册,Service可以通过Context.startService()或者Context.bindService()两种方式启动。

简而言之,Service适用于无 UI 界面并且长时间运行或者专门给其他 App 提供服务的场景。

2. Service 的基本概念

为了更好的理解 Service的运行机制,这里提出几个容易混淆的概念:

  • **进程:**进程是操作系统为一个 App 提供的独立的运行单元,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
  • **线程:**线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是

一般说来,在 Android 中一个 App 运行在一个独立进程,而每一个进程可以有多个线程。从编程的角度来看,多个线程是同时并行运行的(是否是真并行依赖操作系统的调度以及 CPU 的核数)。
我们的 Activity 就是运行在主线程(UI线程),而 Service 默认也是在主线程,所以如果需要做一些耗时操作仍然需要主动放到子线去运行。

3. Service 的启动方式及生命周期

在第 1 小节的最后讲到过,Service 有两种启动方式:Context.startService()Context.bindService(),所以也对应着两类 Service:

  • Started Service
  • Bound Service

2.1 Started Service

顾名思义,Started 类型的 Service 就是通过Context.startService()方法启动的 Service,此时 Service 会立即在后台启动,可以调用Context.stopService()关闭。当然,在 Service 内也可以使用Context.stopService()来关闭自己。

2.2 Bound Service

Service 进入 Bound 状态需要在 Activity 中调用Context.bindService()方法,这样这两个组件就绑定到了一起,此后二者可以很方便的相互通信,调用Context.unbindService()可以解除绑定。

其实以上两种方式的最大差异就是,第 1 种在 start 之后,两个组件之间就没有太大关系了,而第 2 种是以“bind”形式启动的,启动之后两者仍然是绑定关系,可以进行数据的传递以及状态的监听。
这两种启动方式的生命周期如下:

service_lifecycle

相比于 Activity,Service 的生命周期就简化了很多,主要还是依赖于启动方式,通常如果是一个相对独立的 Service,未来不需要和 Activity 强关联,推荐使用第一种;当然如果需要在 Activity 里面做一些交互甚至对 Service 做一些管理,那么必须使用 bind 的方式。

4. Service 使用示例

接下通过 Service 实现一个非常常见的功能——音乐播放器。现在市面的绝大多数音乐播放器都是在一个 Service 里面实现的,它需要长时间在后台运行,所以天然就适合运行在 Service 中。

4.1 播放器控制

这里主要是演示 Service 的用法,所以只对播放器进行简单的控制,大家课后感兴趣的可以继续补充,将示例做成一个更加完整的播放器。我们在 Service 创建的时候初始化播放器,在 Servce 启动的时候启动播放器,销毁的时候关闭。首先创建“PlayerService”,代码如下:

package com.emercy.myapplication;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.IBinder;
import android.widget.Toast;

public class PlayerService extends Service {
    MediaPlayer myPlayer;

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

    @Override
    public void onCreate() {
        Toast.makeText(this, "Service Created", Toast.LENGTH_LONG).show();

        myPlayer = MediaPlayer.create(this, R.raw.mc_guitar);
        myPlayer.setLooping(false); // Set looping
    }

    @Override
    public void onStart(Intent intent, int startid) {
        Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
        myPlayer.start();
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service Stopped", Toast.LENGTH_LONG).show();
        myPlayer.stop();
    }
}

代码很简单,在 Service 的onCreate()中初始化播放器,设置音频地址,将你喜欢的音乐放置在 raw 目录,或者指定一个网络 Mp3 的 url 地址均可;然后在onStart()中启动播放器。

4.2 布局文件编写

我们希望能够随时控制播放器的起播和停止,所以需要两个 Button 分别进行控制:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/buttonStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="74dp"
        android:text="启动播放器" />

    <Button
        android:id="@+id/buttonStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="停止播放" />
</RelativeLayout>

4.3 主控逻辑编写

在 MainActivity 里主要要做两件事:

  1. 通过startService()启动 PlayerService,播放音乐;
  2. 通过stopService结束播放

package com.emercy.myapplication;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity implements View.OnClickListener {
    Button buttonStart, buttonStop;

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

        buttonStart = findViewById(R.id.buttonStart);
        buttonStop = findViewById(R.id.buttonStop);

        buttonStart.setOnClickListener(this);
        buttonStop.setOnClickListener(this);
    }

    public void onClick(View src) {
        switch (src.getId()) {
            case R.id.buttonStart:
                startService(new Intent(this, PlayerService.class));
                break;
            case R.id.buttonStop:
                stopService(new Intent(this, PlayerService.class));
                break;
        }
    }
}

4.4 清单文件

需要注意的是,Service 是一个组件,凡是添加组件都需要在 AndroidManifest.xml 中注册(动态注册除外),否则无法使用:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.emercy.myapplication">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

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

</manifest>

**注意:**如果你的音频文件是一个远程的 url,还需要增加网络权限:

    <uses-permission android:name="android.permission.INTERNET" />

到此,整个初级的播放器就开发完成了。大家如果感兴趣还可以考虑增加其他的功能,比如快进、快退、切歌、增加通知栏、展示歌词等等。

5. 小结

本节介绍了 Android 第二个组件,Service 的主要场景是运行一些耗时且在后台的任务,并且相比 Activity 它更轻量且没有用户界面。有两种启动方式,通过startService()启动之后 Service 与启动它的 Activity 再无任何关联,而bindService()方式启动之后二者还会绑定在一起,可以进行相互的调用和数据传递。同时由于 Service 无 UI 界面,所以在用完后一定要记得要 stop,回收资源。

环境搭建,开发相关
Android 系统背景及结构概述 Android 开发环境搭建 Genymotion 的安装与使用 Android 工程解析及使用 Android 程序签名打包
常用 UI 布局
Android 的 UI 根基 View与View Android 线性布局 LinearLayout Android相对布局RelativeLayout Android 表格布局 TableLayout Android 网格布局 GridLayout Android 帧布局 FrameLayout Android绝对布局AbsoluteLayout
基础控件
Android 文本框 TextView Android 文本输入框 EditText 按钮 Button/ImageButton 选择框 RadioButton/Check 开关控件ToggleButton/Switch Android 图片控件 ImageView Android 进度条 ProgressBar Android 拖动条 SeekBar Android 评分条 RatingBar Android 滚动条 ScrollView 轮播滚动视图 ViewFlipper
Adapter 相关控件
Android 适配器 Adapter Android 列表控件 ListView Android 网格视图 GridView Android 下拉选择框 Spinner 自动补全文本框 AutoCompleteText 折叠列表 ExpandableListView
提示类控件
吐司提示:Toast 的使用方法 状态栏通知:Notification 对话框:AlertDialog 悬浮窗:PopupWindow
菜单类控件
菜单:Menu
其他控件
视频页面:ViewPager 侧滑菜单:DrawerLayout
事件处理机制
基于监听的事件处理机制 Handler 消息传递机制 触摸事件分发处理 AsyncTask:异步任务 Android 手势处理
Android 四大组件
活动:Activity 服务:Service 广播接收器:Broadcast Receiver 内容提供者 - Content Provider
数据存储
文件存储 SharedPreferences 存储 数据库:SQLite 的使用
网络编程
HTTP 使用详解 xml 数据解析 JSON 数据解析 网页视图:WebView Socket 网络接口
绘图与动画
图片资源:Drawable 位图:Bitmap
多媒体开发
媒体播放器:MediaPlayer 相机:Camera 音频录制:MediaRecorder
并发编程
多线程