今天来学习 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”形式启动的,启动之后两者仍然是绑定关系,可以进行数据的传递以及状态的监听。
这两种启动方式的生命周期如下:
相比于 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 里主要要做两件事:
- 通过
startService()
启动 PlayerService,播放音乐; - 通过
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,回收资源。