Android Service介绍
背景
先從日常用戶體驗說起,用過蘋果的iOS系統都知道,凡是音頻播放,在下滑菜單都能看到是哪個應用在播放,音頻的標題,用戶還可以直接在下滑菜單操作,而安卓手機則不然,因為Android系統使用本文介紹的Service進行后臺音樂播放,而Service不提供和用戶交互接口,因此在安卓手機上,當用戶打開音樂程序并后臺播放音樂,想關閉音樂只有再打開該播放音樂程序或者直接殺死所有程序才能關閉音樂,如果用戶手機上有多個音樂程序(國內用戶大概率會這樣),有時候可能會忘記打開的是哪個音樂播放程序,勤快點的一個一個打開播放程序,哦,對了,順便得看一遍開屏廣告,懶一點的直接殺死所有程序,不過,有時候直接殺死所有程序方式也沒用。
導致這些不好的用戶體驗,多是因為使用Service不當所致,為了更好的使用Service,還是得對其進去了解。
1.什么是Service
官方對Service定義如下:
服務是可以在后臺執(zhí)行長時間運行的操作的應用組件,并且不提供用戶界面。另一個應用程序組件可以啟動服務,并且即使用戶切換到另一個應用程序時,Service也可以在后臺繼續(xù)運行。此外,組件可以綁定到服務以與其進行交互,甚至可以執(zhí)行進程間通信(IPC)。例如,一項服務可以從后臺處理網絡事務,播放音樂,執(zhí)行文件I/O或與內容提供者進行交互。
從官方的定義,我們可以總結出Service的特性
- Service是沒有UI的Android組件,用戶是無法直接和其進行交互;
- Service用于在后臺執(zhí)行長時間運行的操作。除非明確停止或銷毀服務,否則它們將無限期運行,直到系統資源短缺而被Android終止;
- Service可以由任何其他應用程序組件啟動。組件甚至可以實際上綁定到服務以執(zhí)行進程間通信;
官方文檔還提到Service不是什么:
- Service不是獨立的進程。除非另有說明,Service并沒有自己獨立的進程,而是和所屬的應用程序在同一進程中運行。Service是以一個獨立程序的形式安裝,這樣容易讓人誤認為Service在運行的時候也是一個獨立的程序,因此有自己的進程,實際上不是的,Service是在調用它的程序的進程里;
- Service也不是線程,意味著啟動一個Service并沒開啟另外一個線程。
2.什么時候使用Service
Service的特性是在后臺運行,因此去執(zhí)行需要長期運行的任務都可采用Service,這樣任務放在后臺運行,用戶可以去做其他操作,例如開始提到的音樂播放,還有下載文件,或者提供外置設備的接口,例如外置打印機調用接口等等。
3.如何使用Service
3.1 Service生命周期
不同于Activity,Service具有兩種生命周期形式,分別成為Started狀態(tài)和Bound狀態(tài),兩種狀態(tài)生命周期見圖1,他們的區(qū)別如下;
-
Started
其他組件通過startService啟動Service,一旦啟動后,Service可以無限地運行下去,除非Service自身調用stopSelf()方法或者其他組件調用stopService()方法來停止它,當Service被停止時,系統會銷毀它,這種狀態(tài)下,Service和調用它的組件相對獨立,一般情況下,兩者之間沒有太多的交互,形同陌路。Android手機常發(fā)生的程序關閉了音樂還在播放的窘態(tài)正是Service進入這個狀態(tài)所導致的。
-
Bound
其他組件調用bindService來創(chuàng)建,與Started狀態(tài)不同的是,在這個狀態(tài)下,組件可以通過IBinder接口和Service進行通信,組件想解綁,則通過unbindService()方法來關閉連接,一個Service可以同時和多個客戶綁定,當多個組件都解除綁定之后或者組件銷毀后,系統會銷毀。和Started形同陌路的狀態(tài)相比,Bound狀態(tài)下Service和調用它的組件不僅感情親密,而且進退共存亡,播放音頻的程序如果bindService()調用Service,則不會出現程序已經銷毀,音頻還在播放的情況。
問題1.什么時候使用startService啟動Service比較合適, 什么時候使用bindService綁定Service比較合適?
- 如果只是要啟動Service長時間在后臺執(zhí)行一個任務,期間不需要和它交互,則使用startService比較合適;
- 如果啟動service之后要與它進行交互則使用bindService比較合適;
問題2.想讓Service長期在后臺執(zhí)行,期間又想和其進行交互怎么辦?
這個需求在日常使用中經常碰到,例如在后臺播放音樂時,想獲取當前播放音樂的信息。這個時候,可以兩種開啟Service方式混著用,先startService,然后再bindService。在這種情況下,要終止Service,必須調用了stopSelf或者某個組件調用了stopService,而且所有綁定到該Service的組件都已解綁或者被銷毀,理解起來并不復雜,即有怎么樣的開始,就得怎么樣結束,即以startService開始,必須使用stopSelf或者stopService結束,使用bindService開始,必須使用unbindService結束。
3.2 代碼示例
3.2.1 startService方式
1.繼承Service定義一個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中注冊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>//在這里注冊Service服務<service android:name=".MyService"></service></application></manifest>3.修改activity_main.xml布局文件,在其中添加兩個按鈕,分別用于開啟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對象來開啟和停止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("啟動服務");//構建Intent對象,并調用startService()啟動ServiceIntent startIntent = new Intent(this, MyService.class);startService(startIntent);break;case R.id.btnStop:tvShow.setText("停止服務");//構建Intent對象,并調用stopService()停止ServiceIntent stopIntent = new Intent(this, MyService.class);stopService(stopIntent);break;default:break;}} }完成以上步驟后,這樣就可以開始運行了,界面效果如下:
1.點擊開啟時,logcat打印的log如下:
com.pm.servicedemo D/MyService: 執(zhí)行onCreate()
com.pm.servicedemo D/MyService: 執(zhí)行onStartCommand()
此時Service已經創(chuàng)建,如果此時再次點擊開啟,則只執(zhí)行onStartCommand,不會再執(zhí)行onCreate(),logcat只增加一行打印信息:
com.pm.servicedemo D/MyService: 執(zhí)行onStartCommand()
2.點擊停止時,logcat打印的log如下:
com.pm.servicedemo D/MyService: 執(zhí)行onDestroy()
以上是startService創(chuàng)建Service方式,接下來看bindService方式。
3.2.2 bindService方式
1.在已創(chuàng)建MyService基礎上,新建一個子類繼承自Binder類、重寫父類的onBind和onUnbind,以提供給Activity綁定該服務,并實現一個虛擬的播放音樂函數。
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);}//新建一個子類繼承自Binder類class MyBinder extends Binder {public void playMusic() {Log.d(TAG,"播放音樂");}} }2.修改activity_main.xml布局文件,添加綁定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="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,實現綁定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解除關聯的時候調用@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//實例化Service的內部類myBindermyBinder = (MyService.MyBinder) service;//在Activity調用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("啟動服務");//構建Intent對象,并調用startService()啟動ServiceIntent startIntent = new Intent(this, MyService.class);startService(startIntent);break;case R.id.btnStop:tvShow.setText("停止服務");//構建Intent對象,并調用stopService()停止ServiceIntent stopIntent = new Intent(this, MyService.class);stopService(stopIntent);break;case R.id.btnBindService:tvShow.setText("綁定服務");//仍然需要構建Intent對象Intent bindIntent = new Intent(this, MyService.class);//參數說明//第一個參數:Intent對象//第二個參數:ServiceConnection實例//第三個參數:標志位//這里傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯后自動創(chuàng)建Service//這會使得MyService中的onCreate()方法得到執(zhí)行,但onStartCommand()方法不會執(zhí)行bindService(bindIntent, connection, BIND_AUTO_CREATE);break;case R.id.btnUnbindService:tvShow.setText("解綁服務");//不需要構建Intent對象//調用unbindService()解綁服務//參數為ServiceConnection實例unbindService(connection);break;default:break;}} }從代碼可以看出,首先需要創(chuàng)建一個ServiceConnection匿名類,從名字可以知道,這是一個Activity和Service的連接,類似一個通訊機制,類中的onServiceConnected()方法和onServiceDisconnected()方法分別用于綁定和解綁,而綁定后Activity指揮Service操作則通過MyBinder里的方法。
綁定中,除了連接類,仍然是構建出了一個Intent對象,然后調用bindService()方法將Activity和Service進行綁定。這和上面的startService一樣,bindService方式比startService方式更緊密正是因為多了一個ServiceConnection。
我們注意到還有第三個參數BIND_AUTO_CREATE,該參數是一個標志位,BIND_AUTO_CREATE表示在Activity和Service建立關聯后自動創(chuàng)建Service,這會使得MyService中的onCreate()方法得到執(zhí)行,但onStartCommand()方法不會執(zhí)行。
到這里代碼準備好了,現在來測試一下:
1.點擊綁定Service按鈕,logcat打印如下信息:
com.pm.servicedemo D/MyService: 執(zhí)行onCreate()
com.pm.servicedemo D/MyService: 執(zhí)行onBind()
com.pm.servicedemo D/MyService: 播放音樂
2.此時再點擊一遍綁定Service按鈕則不會新增信息打印出來,因為已經綁定了服務;
3.點擊解綁Service打印信息如下,表示執(zhí)行了解綁并銷毀Service:
com.pm.servicedemo D/MyService: 執(zhí)行onUnbind()
com.pm.servicedemo D/MyService: 執(zhí)行onDestroy()
4.如果先點擊開啟Service,再點擊綁定Service,根據上面的討論,應該是先執(zhí)行startService生命周期,然后在進入bindService生命周期,logcat打印信息如下,說明我們的理解是對的
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,還是前面討論的,怎樣開始,就得怎樣結束,如果直接點停止Service按鈕,logcat不會打印銷毀服務的信息,證明銷毀不成功,需先點解綁Service按鈕(也只顯示onUnbind(),沒有onDestory()),再點停止Service按鈕打印onDestory(),說明此時才正常銷毀Service
com.pm.servicedemo D/MyService: 執(zhí)行onUnbind()
com.pm.servicedemo D/MyService: 執(zhí)行onDestroy()
總結
以上是生活随笔為你收集整理的Android Service介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android回调的简单理解
- 下一篇: Android AIDL使用介绍(1)基