服務(wù):Service
今天來學(xué)習(xí) Android 的另一個組件——Service,相比于 Activity,Service通常運(yùn)行在后臺,沒有任何 UI 界面,對用戶是透明感知。通常用來執(zhí)行一些后臺任務(wù),比如播放音樂、下載、加載一些數(shù)據(jù)等等,也可以用作一些進(jìn)程間通信(IPC)機(jī)制。
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 四大組件之一,通常用來執(zhí)行一些需要長時間運(yùn)行并且不需要和用戶發(fā)送交互的任務(wù),或者是要持續(xù)給其他 App 提供服務(wù)的場景。每一個服務(wù)和 Activity 一樣,需要在包下的 “AndroidManifest.xml”文件中添加注冊,Service可以通過
Context.startService()
或者Context.bindService()
兩種方式啟動。
簡而言之,Service適用于無 UI 界面并且長時間運(yùn)行或者專門給其他 App 提供服務(wù)的場景。
2. Service 的基本概念
為了更好的理解 Service的運(yùn)行機(jī)制,這里提出幾個容易混淆的概念:
- **進(jìn)程:**進(jìn)程是操作系統(tǒng)為一個 App 提供的獨(dú)立的運(yùn)行單元,是計算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位。
- **線程:**線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實際運(yùn)作單位。一條線程指的是
一般說來,在 Android 中一個 App 運(yùn)行在一個獨(dú)立進(jìn)程,而每一個進(jìn)程可以有多個線程。從編程的角度來看,多個線程是同時并行運(yùn)行的(是否是真并行依賴操作系統(tǒng)的調(diào)度以及 CPU 的核數(shù))。
我們的 Activity 就是運(yùn)行在主線程(UI線程),而 Service 默認(rèn)也是在主線程,所以如果需要做一些耗時操作仍然需要主動放到子線去運(yùn)行。
3. Service 的啟動方式及生命周期
在第 1 小節(jié)的最后講到過,Service 有兩種啟動方式:Context.startService()
和Context.bindService()
,所以也對應(yīng)著兩類 Service:
- Started Service
- Bound Service
2.1 Started Service
顧名思義,Started 類型的 Service 就是通過Context.startService()
方法啟動的 Service,此時 Service 會立即在后臺啟動,可以調(diào)用Context.stopService()
關(guān)閉。當(dāng)然,在 Service 內(nèi)也可以使用Context.stopService()
來關(guān)閉自己。
2.2 Bound Service
Service 進(jìn)入 Bound 狀態(tài)需要在 Activity 中調(diào)用Context.bindService()
方法,這樣這兩個組件就綁定到了一起,此后二者可以很方便的相互通信,調(diào)用Context.unbindService()
可以解除綁定。
其實以上兩種方式的最大差異就是,第 1 種在 start 之后,兩個組件之間就沒有太大關(guān)系了,而第 2 種是以“bind”形式啟動的,啟動之后兩者仍然是綁定關(guān)系,可以進(jìn)行數(shù)據(jù)的傳遞以及狀態(tài)的監(jiān)聽。
這兩種啟動方式的生命周期如下:
相比于 Activity,Service 的生命周期就簡化了很多,主要還是依賴于啟動方式,通常如果是一個相對獨(dú)立的 Service,未來不需要和 Activity 強(qiáng)關(guān)聯(lián),推薦使用第一種;當(dāng)然如果需要在 Activity 里面做一些交互甚至對 Service 做一些管理,那么必須使用 bind 的方式。
4. Service 使用示例
接下通過 Service 實現(xiàn)一個非常常見的功能——音樂播放器?,F(xiàn)在市面的絕大多數(shù)音樂播放器都是在一個 Service 里面實現(xiàn)的,它需要長時間在后臺運(yùn)行,所以天然就適合運(yùn)行在 Service 中。
4.1 播放器控制
這里主要是演示 Service 的用法,所以只對播放器進(jìn)行簡單的控制,大家課后感興趣的可以繼續(xù)補(bǔ)充,將示例做成一個更加完整的播放器。我們在 Service 創(chuàng)建的時候初始化播放器,在 Servce 啟動的時候啟動播放器,銷毀的時候關(guān)閉。首先創(chuàng)建“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()
中初始化播放器,設(shè)置音頻地址,將你喜歡的音樂放置在 raw 目錄,或者指定一個網(wǎng)絡(luò) Mp3 的 url 地址均可;然后在onStart()
中啟動播放器。
4.2 布局文件編寫
我們希望能夠隨時控制播放器的起播和停止,所以需要兩個 Button 分別進(jìn)行控制:
<?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
結(jié)束播放
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 中注冊(動態(tài)注冊除外),否則無法使用:
<?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>
**注意:**如果你的音頻文件是一個遠(yuǎn)程的 url,還需要增加網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
到此,整個初級的播放器就開發(fā)完成了。大家如果感興趣還可以考慮增加其他的功能,比如快進(jìn)、快退、切歌、增加通知欄、展示歌詞等等。
5. 小結(jié)
本節(jié)介紹了 Android 第二個組件,Service 的主要場景是運(yùn)行一些耗時且在后臺的任務(wù),并且相比 Activity 它更輕量且沒有用戶界面。有兩種啟動方式,通過startService()
啟動之后 Service 與啟動它的 Activity 再無任何關(guān)聯(lián),而bindService()
方式啟動之后二者還會綁定在一起,可以進(jìn)行相互的調(diào)用和數(shù)據(jù)傳遞。同時由于 Service 無 UI 界面,所以在用完后一定要記得要 stop,回收資源。