Android Service介绍
背景
先從日常用戶體驗(yàn)說起,用過蘋果的iOS系統(tǒng)都知道,凡是音頻播放,在下滑菜單都能看到是哪個(gè)應(yīng)用在播放,音頻的標(biāo)題,用戶還可以直接在下滑菜單操作,而安卓手機(jī)則不然,因?yàn)锳ndroid系統(tǒng)使用本文介紹的Service進(jìn)行后臺(tái)音樂播放,而Service不提供和用戶交互接口,因此在安卓手機(jī)上,當(dāng)用戶打開音樂程序并后臺(tái)播放音樂,想關(guān)閉音樂只有再打開該播放音樂程序或者直接殺死所有程序才能關(guān)閉音樂,如果用戶手機(jī)上有多個(gè)音樂程序(國內(nèi)用戶大概率會(huì)這樣),有時(shí)候可能會(huì)忘記打開的是哪個(gè)音樂播放程序,勤快點(diǎn)的一個(gè)一個(gè)打開播放程序,哦,對(duì)了,順便得看一遍開屏廣告,懶一點(diǎn)的直接殺死所有程序,不過,有時(shí)候直接殺死所有程序方式也沒用。
導(dǎo)致這些不好的用戶體驗(yàn),多是因?yàn)槭褂肧ervice不當(dāng)所致,為了更好的使用Service,還是得對(duì)其進(jìn)去了解。
1.什么是Service
官方對(duì)Service定義如下:
服務(wù)是可以在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行的操作的應(yīng)用組件,并且不提供用戶界面。另一個(gè)應(yīng)用程序組件可以啟動(dòng)服務(wù),并且即使用戶切換到另一個(gè)應(yīng)用程序時(shí),Service也可以在后臺(tái)繼續(xù)運(yùn)行。此外,組件可以綁定到服務(wù)以與其進(jìn)行交互,甚至可以執(zhí)行進(jìn)程間通信(IPC)。例如,一項(xiàng)服務(wù)可以從后臺(tái)處理網(wǎng)絡(luò)事務(wù),播放音樂,執(zhí)行文件I/O或與內(nèi)容提供者進(jìn)行交互。
從官方的定義,我們可以總結(jié)出Service的特性
- Service是沒有UI的Android組件,用戶是無法直接和其進(jìn)行交互;
- Service用于在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行的操作。除非明確停止或銷毀服務(wù),否則它們將無限期運(yùn)行,直到系統(tǒng)資源短缺而被Android終止;
- Service可以由任何其他應(yīng)用程序組件啟動(dòng)。組件甚至可以實(shí)際上綁定到服務(wù)以執(zhí)行進(jìn)程間通信;
官方文檔還提到Service不是什么:
- Service不是獨(dú)立的進(jìn)程。除非另有說明,Service并沒有自己獨(dú)立的進(jìn)程,而是和所屬的應(yīng)用程序在同一進(jìn)程中運(yùn)行。Service是以一個(gè)獨(dú)立程序的形式安裝,這樣容易讓人誤認(rèn)為Service在運(yùn)行的時(shí)候也是一個(gè)獨(dú)立的程序,因此有自己的進(jìn)程,實(shí)際上不是的,Service是在調(diào)用它的程序的進(jìn)程里;
- Service也不是線程,意味著啟動(dòng)一個(gè)Service并沒開啟另外一個(gè)線程。
2.什么時(shí)候使用Service
Service的特性是在后臺(tái)運(yùn)行,因此去執(zhí)行需要長(zhǎng)期運(yùn)行的任務(wù)都可采用Service,這樣任務(wù)放在后臺(tái)運(yùn)行,用戶可以去做其他操作,例如開始提到的音樂播放,還有下載文件,或者提供外置設(shè)備的接口,例如外置打印機(jī)調(diào)用接口等等。
3.如何使用Service
3.1 Service生命周期
不同于Activity,Service具有兩種生命周期形式,分別成為Started狀態(tài)和Bound狀態(tài),兩種狀態(tài)生命周期見圖1,他們的區(qū)別如下;
-
Started
其他組件通過startService啟動(dòng)Service,一旦啟動(dòng)后,Service可以無限地運(yùn)行下去,除非Service自身調(diào)用stopSelf()方法或者其他組件調(diào)用stopService()方法來停止它,當(dāng)Service被停止時(shí),系統(tǒng)會(huì)銷毀它,這種狀態(tài)下,Service和調(diào)用它的組件相對(duì)獨(dú)立,一般情況下,兩者之間沒有太多的交互,形同陌路。Android手機(jī)常發(fā)生的程序關(guān)閉了音樂還在播放的窘態(tài)正是Service進(jìn)入這個(gè)狀態(tài)所導(dǎo)致的。
-
Bound
其他組件調(diào)用bindService來創(chuàng)建,與Started狀態(tài)不同的是,在這個(gè)狀態(tài)下,組件可以通過IBinder接口和Service進(jìn)行通信,組件想解綁,則通過unbindService()方法來關(guān)閉連接,一個(gè)Service可以同時(shí)和多個(gè)客戶綁定,當(dāng)多個(gè)組件都解除綁定之后或者組件銷毀后,系統(tǒng)會(huì)銷毀。和Started形同陌路的狀態(tài)相比,Bound狀態(tài)下Service和調(diào)用它的組件不僅感情親密,而且進(jìn)退共存亡,播放音頻的程序如果bindService()調(diào)用Service,則不會(huì)出現(xiàn)程序已經(jīng)銷毀,音頻還在播放的情況。
問題1.什么時(shí)候使用startService啟動(dòng)Service比較合適, 什么時(shí)候使用bindService綁定Service比較合適?
- 如果只是要啟動(dòng)Service長(zhǎng)時(shí)間在后臺(tái)執(zhí)行一個(gè)任務(wù),期間不需要和它交互,則使用startService比較合適;
- 如果啟動(dòng)service之后要與它進(jìn)行交互則使用bindService比較合適;
問題2.想讓Service長(zhǎng)期在后臺(tái)執(zhí)行,期間又想和其進(jìn)行交互怎么辦?
這個(gè)需求在日常使用中經(jīng)常碰到,例如在后臺(tái)播放音樂時(shí),想獲取當(dāng)前播放音樂的信息。這個(gè)時(shí)候,可以兩種開啟Service方式混著用,先startService,然后再bindService。在這種情況下,要終止Service,必須調(diào)用了stopSelf或者某個(gè)組件調(diào)用了stopService,而且所有綁定到該Service的組件都已解綁或者被銷毀,理解起來并不復(fù)雜,即有怎么樣的開始,就得怎么樣結(jié)束,即以startService開始,必須使用stopSelf或者stopService結(jié)束,使用bindService開始,必須使用unbindService結(jié)束。
3.2 代碼示例
3.2.1 startService方式
1.繼承Service定義一個(gè)MyService類,并重寫方法父類的方法
package com.pm.servicedemo;import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log;public class MyService extends Service {public static final String TAG = "MyService";@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "執(zhí)行onCreate()");}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d(TAG, "執(zhí)行onStartCommand()");return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "執(zhí)行onDestroy()");}@Overridepublic IBinder onBind(Intent intent) {return null;}}2.在AndroidManifest.xml中注冊(cè)Service,否則無法使用Service
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.pm.servicedemo"><applicationandroid: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>//在這里注冊(cè)Service服務(wù)<service android:name=".MyService"></service></application></manifest>3.修改activity_main.xml布局文件,在其中添加兩個(gè)按鈕,分別用于開啟Service和停止Service
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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=".MainActivity"><TextViewandroid:id="@+id/tvLog"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btnStartService"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="104dp"android:layout_marginTop="36dp"android:text="開啟Service"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btnStop"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="35dp"android:layout_marginEnd="79dp"android:text="停止Service"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>4.在MainActivity添加開啟和停止Service邏輯,具體來說,使用Intent對(duì)象來開啟和停止Service
package com.pm.servicedemo;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener {Button bStart;Button bStop;TextView tvShow;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tvShow=(TextView)findViewById(R.id.tvLog);bStart = (Button) findViewById(R.id.btnStartService);bStop = (Button) findViewById(R.id.btnStop);bStart.setOnClickListener(this);bStop.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btnStartService:tvShow.setText("啟動(dòng)服務(wù)");//構(gòu)建Intent對(duì)象,并調(diào)用startService()啟動(dòng)ServiceIntent startIntent = new Intent(this, MyService.class);startService(startIntent);break;case R.id.btnStop:tvShow.setText("停止服務(wù)");//構(gòu)建Intent對(duì)象,并調(diào)用stopService()停止ServiceIntent stopIntent = new Intent(this, MyService.class);stopService(stopIntent);break;default:break;}} }完成以上步驟后,這樣就可以開始運(yùn)行了,界面效果如下:
1.點(diǎn)擊開啟時(shí),logcat打印的log如下:
com.pm.servicedemo D/MyService: 執(zhí)行onCreate()
com.pm.servicedemo D/MyService: 執(zhí)行onStartCommand()
此時(shí)Service已經(jīng)創(chuàng)建,如果此時(shí)再次點(diǎn)擊開啟,則只執(zhí)行onStartCommand,不會(huì)再執(zhí)行onCreate(),logcat只增加一行打印信息:
com.pm.servicedemo D/MyService: 執(zhí)行onStartCommand()
2.點(diǎn)擊停止時(shí),logcat打印的log如下:
com.pm.servicedemo D/MyService: 執(zhí)行onDestroy()
以上是startService創(chuàng)建Service方式,接下來看bindService方式。
3.2.2 bindService方式
1.在已創(chuàng)建MyService基礎(chǔ)上,新建一個(gè)子類繼承自Binder類、重寫父類的onBind和onUnbind,以提供給Activity綁定該服務(wù),并實(shí)現(xiàn)一個(gè)虛擬的播放音樂函數(shù)。
package com.pm.servicedemo;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 static final String TAG = "MyService";private MyBinder mBinder = new MyBinder();@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "執(zhí)行onCreate()");}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d(TAG, "執(zhí)行onStartCommand()");return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "執(zhí)行onDestroy()");}@Overridepublic IBinder onBind(Intent intent) {Log.d(TAG, "執(zhí)行onBind()");return mBinder;}@Overridepublic boolean onUnbind(Intent intent) {Log.d(TAG, "執(zhí)行onUnbind()");return super.onUnbind(intent);}//新建一個(gè)子類繼承自Binder類class MyBinder extends Binder {public void playMusic() {Log.d(TAG,"播放音樂");}} }2.修改activity_main.xml布局文件,添加綁定Service和解綁Service按鈕,用于給用戶操作綁定和解綁操作,布局內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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=".MainActivity"><TextViewandroid:id="@+id/tvLog"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btnStartService"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="104dp"android:layout_marginTop="36dp"android:text="開啟Service"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btnStop"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="36dp"android:layout_marginEnd="56dp"android:text="停止Service"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btnBindService"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="8dp"android:text="綁定Service"app:layout_constraintBottom_toBottomOf="@+id/btnUnbindService"app:layout_constraintStart_toStartOf="@+id/btnStartService" /><Buttonandroid:id="@+id/btnUnbindService"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="56dp"android:layout_marginTop="108dp"android:layout_marginEnd="56dp"android:text="解綁Service"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="1.0"app:layout_constraintStart_toEndOf="@+id/btnStartService"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>3.修改MainActivity,實(shí)現(xiàn)綁定Service和解綁Service邏輯,代碼如下:
package com.pm.servicedemo;import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.View; import android.widget.Button; import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener {Button bStart;Button bStop;Button bBind;Button bUnbind;TextView tvShow;private MyService.MyBinder myBinder;//創(chuàng)建ServiceConnection的匿名類,該連接用于本Activity和Service溝通private ServiceConnection connection = new ServiceConnection() {//重寫onServiceConnected()方法和onServiceDisconnected()方法@Overridepublic void onServiceDisconnected(ComponentName name) {}//在Activity與Service解除關(guān)聯(lián)的時(shí)候調(diào)用@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//實(shí)例化Service的內(nèi)部類myBindermyBinder = (MyService.MyBinder) service;//在Activity調(diào)用Service類的方法myBinder.playMusic();}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tvShow=(TextView)findViewById(R.id.tvLog);bStart = (Button) findViewById(R.id.btnStartService);bStop = (Button) findViewById(R.id.btnStop);bBind = (Button) findViewById(R.id.btnBindService);bUnbind = (Button) findViewById(R.id.btnUnbindService);bStart.setOnClickListener(this);bStop.setOnClickListener(this);bBind.setOnClickListener(this);bUnbind.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btnStartService:tvShow.setText("啟動(dòng)服務(wù)");//構(gòu)建Intent對(duì)象,并調(diào)用startService()啟動(dòng)ServiceIntent startIntent = new Intent(this, MyService.class);startService(startIntent);break;case R.id.btnStop:tvShow.setText("停止服務(wù)");//構(gòu)建Intent對(duì)象,并調(diào)用stopService()停止ServiceIntent stopIntent = new Intent(this, MyService.class);stopService(stopIntent);break;case R.id.btnBindService:tvShow.setText("綁定服務(wù)");//仍然需要構(gòu)建Intent對(duì)象Intent bindIntent = new Intent(this, MyService.class);//參數(shù)說明//第一個(gè)參數(shù):Intent對(duì)象//第二個(gè)參數(shù):ServiceConnection實(shí)例//第三個(gè)參數(shù):標(biāo)志位//這里傳入BIND_AUTO_CREATE表示在Activity和Service建立關(guān)聯(lián)后自動(dòng)創(chuàng)建Service//這會(huì)使得MyService中的onCreate()方法得到執(zhí)行,但onStartCommand()方法不會(huì)執(zhí)行bindService(bindIntent, connection, BIND_AUTO_CREATE);break;case R.id.btnUnbindService:tvShow.setText("解綁服務(wù)");//不需要構(gòu)建Intent對(duì)象//調(diào)用unbindService()解綁服務(wù)//參數(shù)為ServiceConnection實(shí)例unbindService(connection);break;default:break;}} }從代碼可以看出,首先需要?jiǎng)?chuàng)建一個(gè)ServiceConnection匿名類,從名字可以知道,這是一個(gè)Activity和Service的連接,類似一個(gè)通訊機(jī)制,類中的onServiceConnected()方法和onServiceDisconnected()方法分別用于綁定和解綁,而綁定后Activity指揮Service操作則通過MyBinder里的方法。
綁定中,除了連接類,仍然是構(gòu)建出了一個(gè)Intent對(duì)象,然后調(diào)用bindService()方法將Activity和Service進(jìn)行綁定。這和上面的startService一樣,bindService方式比startService方式更緊密正是因?yàn)槎嗔艘粋€(gè)ServiceConnection。
我們注意到還有第三個(gè)參數(shù)BIND_AUTO_CREATE,該參數(shù)是一個(gè)標(biāo)志位,BIND_AUTO_CREATE表示在Activity和Service建立關(guān)聯(lián)后自動(dòng)創(chuàng)建Service,這會(huì)使得MyService中的onCreate()方法得到執(zhí)行,但onStartCommand()方法不會(huì)執(zhí)行。
到這里代碼準(zhǔn)備好了,現(xiàn)在來測(cè)試一下:
1.點(diǎn)擊綁定Service按鈕,logcat打印如下信息:
com.pm.servicedemo D/MyService: 執(zhí)行onCreate()
com.pm.servicedemo D/MyService: 執(zhí)行onBind()
com.pm.servicedemo D/MyService: 播放音樂
2.此時(shí)再點(diǎn)擊一遍綁定Service按鈕則不會(huì)新增信息打印出來,因?yàn)橐呀?jīng)綁定了服務(wù);
3.點(diǎn)擊解綁Service打印信息如下,表示執(zhí)行了解綁并銷毀Service:
com.pm.servicedemo D/MyService: 執(zhí)行onUnbind()
com.pm.servicedemo D/MyService: 執(zhí)行onDestroy()
4.如果先點(diǎn)擊開啟Service,再點(diǎn)擊綁定Service,根據(jù)上面的討論,應(yīng)該是先執(zhí)行startService生命周期,然后在進(jìn)入bindService生命周期,logcat打印信息如下,說明我們的理解是對(duì)的
com.pm.servicedemo D/MyService: 執(zhí)行onCreate()
com.pm.servicedemo D/MyService: 執(zhí)行onStartCommand()
com.pm.servicedemo D/MyService: 執(zhí)行onBind()
com.pm.servicedemo D/MyService: 播放音樂
如果想銷毀Service,還是前面討論的,怎樣開始,就得怎樣結(jié)束,如果直接點(diǎn)停止Service按鈕,logcat不會(huì)打印銷毀服務(wù)的信息,證明銷毀不成功,需先點(diǎn)解綁Service按鈕(也只顯示onUnbind(),沒有onDestory()),再點(diǎn)停止Service按鈕打印onDestory(),說明此時(shí)才正常銷毀Service
com.pm.servicedemo D/MyService: 執(zhí)行onUnbind()
com.pm.servicedemo D/MyService: 執(zhí)行onDestroy()
總結(jié)
以上是生活随笔為你收集整理的Android Service介绍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android回调的简单理解
- 下一篇: Android AIDL使用介绍(1)基