Service為Android四大組件之一
- 目錄
- 什么是Service?
- 服務(wù)的基本用法
- Android-8.0的行為變更
什么是Service?
- 服務(wù)是Android中實現(xiàn)程序后臺運行的方案,他非常適合去執(zhí)行那些不需要和用戶交互而且還要求長期運行的任務(wù)。
- 服務(wù)的運行不依賴于任何用戶界面,即使程序被切換到后臺,或者用戶打開了其他應(yīng)用程序,服務(wù)仍然能夠保持正常運行。
- 服務(wù)并不是運行在一個獨立的進程當(dāng)中的,而是依賴于創(chuàng)建服務(wù)時所在的應(yīng)用程序進程。當(dāng)某個應(yīng)用程序被殺掉時,所有依賴于該進程的服務(wù)也會停止運行(當(dāng)然,設(shè)置為多進程狀況除外)
- 不要被服務(wù)的后臺概念所迷惑,實際上服務(wù)并不會自動開啟線程,所有的代碼都是默認在運行在主線程當(dāng)中的。也就是說,我們需要在服務(wù)內(nèi)部手動創(chuàng)建子線程,并在里面執(zhí)行具體的任務(wù),或者就有可能出現(xiàn)主線程被阻塞的情況出現(xiàn)。
- 服務(wù)的超時時間是20s。
Android中的多線程編程
① 使用Thread類
② 使用Runnable類
③ 使用Handler類
④ 使用AsyncTask類
服務(wù)的基本用法
一、定義一個服務(wù)
新建一個Service,命名為MyService,

Exported屬性表示是否允許除了當(dāng)前程序之外的其他程序訪問這個服務(wù)
Enabled屬性表示是否啟動這個服務(wù)
將兩個屬性都勾選,點擊Finish完成創(chuàng)建,現(xiàn)在觀察MyService中的代碼:
package com.sl.demo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
可以看見MyService繼承自Service類的,說明這是一個服務(wù);現(xiàn)在看里面只有一個構(gòu)造方法和一個onBind()方法,這個onBind()方法時Service中的唯一的一個抽象方法,必須在子類實現(xiàn),現(xiàn)在我們先忽略這個方法,后面再講解這個方法;
服務(wù)里面的邏輯應(yīng)該在哪里寫呢?這就需要我們重寫Service中另外一些方法了,如下:
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
我們重寫了三個方法:
- onCreate() : 此方法會在服務(wù)創(chuàng)建時調(diào)用
- onStartCommand() : 此方法會在每次服務(wù)啟動時調(diào)用
- onDestroy() : 此方法會在程序銷毀時調(diào)用
通常情況下,如果希望服務(wù)一啟動就立刻去執(zhí)行某個動作,就可以將邏輯寫在onStartCommand()方法中。而當(dāng)服務(wù)銷毀時,我們又應(yīng)該在onDestroy()方法中去回收那些不再使用的資源。
注意:每一個服務(wù)都需要在AndroidManifest.xml中進行注冊才能生效。這也是Android四大組件共有的特點,當(dāng)通過Android studio通過new 創(chuàng)建服務(wù)時,Android studio已幫我們在Androidmanifest .xml中將這一步完成了
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sl.demo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
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=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
這樣一個服務(wù)就定義好了。
二、啟動和停止服務(wù)
現(xiàn)在先在Service中的幾個方法中加入打印日志:
public class MyService extends Service {
public MyService() {
Log.d("MyService","MyService() executed");
}
@Override
public IBinder onBind(Intent intent) {
Log.d("MyService","onBind executed");
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService","onCreate executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService","onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d("MyService","onDestroy executed");
super.onDestroy();
}
}
在MainActivity中添加兩個按鈕,并綁定點擊事件,一個用于啟動服務(wù),一個用于停止服務(wù),布局就不貼了,現(xiàn)在看MainActivity中代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button start_Service ,stop_service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start_Service = findViewById(R.id.start_Service);
stop_service = findViewById(R.id.stop_service);
start_Service.setOnClickListener(this); //添加點擊事件
stop_service.setOnClickListener(this); //添加點擊事件
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_Service: //啟動服務(wù)
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
break;
case R.id.stop_service: //停止服務(wù)
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
}
}
可以看見,啟動和停止都是通過Intent來實現(xiàn)的;startService(Intent)和stopService(Intent)方法都是定義在Context類中,這里完全時由活動來開控制服務(wù)的啟動和停止的。
服務(wù)自身也是可以讓自己停止下來的,只需要在需要的位置調(diào)用stopSelf()方法就能讓該服務(wù)停止下來;
現(xiàn)在我們來運行程序,查看服務(wù)的運行日志:
點擊開始服務(wù)按鈕:
03-13 11:50:20.692 1744-1744/com.sl.demo D/MyService: MyService() executed
03-13 11:50:20.692 1744-1744/com.sl.demo D/MyService: onCreate executed
03-13 11:50:20.692 1744-1744/com.sl.demo D/MyService: onStartCommand executed
看結(jié)果說明這個服務(wù)啟動了,onCreate和onStartCommand方法都執(zhí)行了,我們也可以在手機中查看該服務(wù)是否啟動:設(shè)置——》開發(fā)者選項——》正在運行的服務(wù),能再列表中看到該應(yīng)用,點擊后可以看見詳情:

不要糾結(jié)我這個項目名為Notification,因為我是在一個已有的項目上進行測試滴,很明顯的看見Myservice是啟動的
在這里在驗證一下onCreate和onStartCommand方法,我們多次對啟動服務(wù)按鈕點擊,查看:
03-13 12:05:53.742 1744-1744/com.sl.demo D/MyService: MyService() executed
03-13 12:05:53.742 1744-1744/com.sl.demo D/MyService: onCreate executed
03-13 12:05:53.742 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:54.899 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:55.697 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:56.331 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:56.863 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:57.397 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:57.965 1744-1744/com.sl.demo D/MyService: onStartCommand executed
03-13 12:05:58.662 1744-1744/com.sl.demo D/MyService: onStartCommand executed
從這里可以看見,onCreate只運行了一次,而每次啟動服務(wù)時,onStartCommand都會運行。
點擊停止服務(wù)按鈕:
03-13 11:50:25.915 1744-1744/com.sl.demo D/MyService: onDestroy executed
再進入設(shè)置里面查看的時候,已經(jīng)沒有這個服務(wù)了。
至此就完成了服務(wù)的啟動和停止
三、活動與服務(wù)通信
前面只是通過活動來啟動和停止服務(wù),但是活動并不知道服務(wù)做了什么,以及完成得如何。如果想讓活動和服務(wù)的關(guān)系更緊密一些,就可以借助我們剛才忽略的onBind()方法了.
例,我們需要在MyService里main提供一個下載功能,然后在活動中可以決定何時下載,以及隨時查看下載進度。實現(xiàn)這個功能的思路是創(chuàng)建一個專門的Binder對象來對下載功能進行管理,修改MyService中的代碼:
public class MyService extends Service {
...... //省略前面的一些代碼
private DownLoadBinder mBinder = new DownLoadBinder() ;
class DownLoadBinder extends Binder{
public void startDownLoad(){
Log.d("MyService","startDownLoad executed");
}
public int getProgress(){
Log.d("MyService","getProgress executed");
return 0 ;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder ;
}
}
這里主要做了一下三個工作:
① 新建了一個DownLoadBinder繼承Binder類,然后在它的內(nèi)容提供了開始下載和查看進度的模擬方法
② 在MyService中創(chuàng)建了DownLoadBinder實例
③ 在onBind()方法中返回這個實例
至此完成了MyService的修改;
現(xiàn)在在對MainActivity進行修改,將兩個按鈕修改為綁定服務(wù)和解綁服務(wù),當(dāng)一個活動和服務(wù)綁定了后,就可以調(diào)用該服務(wù)里的Binder提供的方法了,修改MainActivity如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button bind_Service ,unBind_service;
private MyService.DownLoadBinder downLoadBinder ;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected executed");
downLoadBinder = (MyService.DownLoadBinder)service ;
downLoadBinder.startDownLoad();
downLoadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected executed");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind_Service = findViewById(R.id.bind_Service);
unBind_service = findViewById(R.id.unBind_service);
bind_Service.setOnClickListener(this); //添加點擊事件
unBind_service.setOnClickListener(this); //添加點擊事件
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bind_Service: //綁定服務(wù)
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
break;
case R.id.unBind_service: //解綁服務(wù)
unbindService(connection);
break;
}
}
}
在MainActivity中主要實現(xiàn)了
① 創(chuàng)建了一個ServiceConnection類,在里面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在活動和服務(wù)成功綁定和解除綁定的時候調(diào)用。在onServiceConnected()方法中,我們通過向下轉(zhuǎn)型得到了DownLoadBinder的實例,通過這個實例活動和服務(wù)就變得非常緊密了,在活動中可以根據(jù)具體的場景來調(diào)用DownLoadBinder中的任何public方法了。
② 在綁定服務(wù)的按鈕點擊事件中,構(gòu)建Intent兌現(xiàn),調(diào)用bindService()方法,將MainActivity和MyService進行綁定;
③ 在解除綁定服務(wù)中按鈕點擊事件中,直接調(diào)用unbindService()方法即可
點擊綁定服務(wù)查看日志:
03-21 14:15:55.918 30849-30849/com.sl.demo D/MyService: onCreate executed
03-21 14:15:55.927 30849-30849/com.sl.demo D/MainActivity: onServiceConnected executed
03-21 14:15:55.927 30849-30849/com.sl.demo D/MyService: startDownLoad executed
03-21 14:15:55.927 30849-30849/com.sl.demo D/MyService: getProgress executed
點擊解綁服務(wù)查看日志:
03-21 14:17:18.371 31155-31155/com.sl.demo D/MyService: onDestroy executed
發(fā)現(xiàn)onServiceDisconnected()方法時沒有被調(diào)用的,在連接正常關(guān)閉的情況下是不會被調(diào)用的, 該方法只在Service 被破壞了或者被殺死的時候調(diào)用. 例如, 系統(tǒng)資源不足, 要關(guān)閉一些Services, 剛好連接綁定的 Service 是被關(guān)閉者之一, 這個時候onServiceDisconnected() 就會被調(diào)用
注:任何一個服務(wù)在整個應(yīng)用程序范圍內(nèi)都是通用的,即MyService不僅可以和MainActivity綁定,還可以和任何一個其他的活動綁定,而且綁定完成后他們都可以獲取到相同的DownLoadBinder實例
四、服務(wù)的生命周期

1)啟動Service服務(wù)
單次:startService() —> onCreate() —> onStartCommand()
多次:startService() —> onCreate() —> onStartCommand() —> onStartCommand()
2)停止Service服務(wù)
stopService() —> onDestroy()
3)綁定Service服務(wù)
bindService() —> onCreate() —> onBind()
4)解綁Service服務(wù)
unbindService() —> onUnbind() —> onDestroy()
5)啟動綁定Service服務(wù)
startService() —> onCreate() —> onStartCommand() —> bindService() —> onBind()
6)解綁停止Service服務(wù)
unbindService() —> onUnbind() —> stopService() —> onDestroy()
7)解綁綁定Service服務(wù)
unbindService() —> onUnbind(ture) —> bindService() —> onRebind()
五、前臺服務(wù)
服務(wù)幾乎都是在后臺運行的,但是服務(wù)的優(yōu)先級還是比較低,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足的時候,就有可能會回收正在后天運行的服務(wù)。如果希望服務(wù)可以一直保持運行狀態(tài),而不會由于系統(tǒng)內(nèi)存不足的原因?qū)е卤换厥?,就可以考慮前臺服務(wù);
前臺服務(wù)后后臺服務(wù)最大的區(qū)別就在于,他會一直有一個正在運行的圖片在系統(tǒng)狀態(tài)欄顯示,下拉狀態(tài)欄可以看到更加詳細的信息,非常類似于通知的效果。
修改MyService的onCreate()方法如下:
@Override
public void onCreate() {
Log.d("MyService","onCreate executed");
super.onCreate();
int channelId = 1 ;
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder ;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ //Android 8.0適配
NotificationChannel channel = manager.getNotificationChannel(String.valueOf(channelId));
if (channel == null){
channel = new NotificationChannel(String.valueOf(channelId),
"channel_name",
NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel);
}
builder = new NotificationCompat.Builder(this,String.valueOf(channelId));
}else{
builder = new NotificationCompat.Builder(this);
}
Notification notification = builder
.setContentTitle("this is content title")
.setContentText("this is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
創(chuàng)建一個Notification對象,調(diào)用startForeground()方法就會讓MyService變?yōu)橐粋€前臺服務(wù),并在系統(tǒng)欄顯示出現(xiàn),現(xiàn)在調(diào)用StartService()或BindService()方法,MyService就會以前臺服務(wù)的模式啟動了
六、使用IntentService
服務(wù)中的代碼默認都是運行在主線程當(dāng)中的,如果直接在服務(wù)里去處理一些耗時的邏輯,就很容易出現(xiàn)ANR的情況(Application Not Responding)
這種情況我們可以在具體實現(xiàn)的方法中開啟一個子線程,讓后在里面處理耗時邏輯,但是這種服務(wù)一旦啟動起來就會一直處于運行狀態(tài),所以,如果想要讓一個服務(wù)在執(zhí)行完畢后自動停止,就必須在執(zhí)行完耗時操作后調(diào)用stopSelf();
IntentService的目的是為了簡單的創(chuàng)建一個異步的、會自動停止的服務(wù)。他是繼承Service的一個抽象類,下面看看它的用法。
新建一個MyIntentService類集成子IntentService:
public class MyIntentService extends IntentService {
public MyIntentService(){
super("MyIntentService"); //調(diào)用父類有參構(gòu)造
}
/** 實現(xiàn)父類的這個抽象方法,此方法為異步線程,也為主要Service的主要運行方法 */
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d("MyIntentService", "onHandleIntent: Theard id is " + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}
修改主界面布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/bind_Service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="啟動服務(wù)"
android:onClick="startIntentService"/>
</LinearLayout>
MainActivity代碼:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startIntentService(View view){
Log.d(TAG, "startIntentService: Theard id is " + Thread.currentThread().getId());
Intent intent = new Intent(this,MyIntentService.class);
startService(intent);
}
}
當(dāng)然,不能忘記在AndroidManifest.xml里注冊服務(wù):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.sl.demo">
<application
android:name=".app.DemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:label">
<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=".MyIntentService" />
</application>
</manifest>
運行項目,查看log日志:
03-21 17:28:12.300 23843-23843/com.sl.demo D/MainActivity: startIntentService: Theard id is 2
03-21 17:28:12.316 23843-24604/com.sl.demo D/MyIntentService: onHandleIntent: Theard id is 5820
03-21 17:28:12.318 23843-23843/com.sl.demo D/MyIntentService: onDestroy executed
可以看見,MyIntentService和MainActivity所在的線程id不一樣,而且MyIntentService的onDestory()方法在運行完畢后也自動停止了。
Android 8.0的行為變更
Android 8.0為提高電池續(xù)航時間,引入了 后臺執(zhí)行限制,當(dāng)您的應(yīng)用進入 已緩存 的狀態(tài)時,如果沒有活動的組件,系統(tǒng)將解除應(yīng)用具有的所有喚醒鎖。
此外,為提高設(shè)備性能,系統(tǒng)會限制未在前臺運行的應(yīng)用的某些行為。具體而言:
- 現(xiàn)在,在后臺運行的應(yīng)用對后臺服務(wù)的訪問受到限制。
- 應(yīng)用無法使用其清單注冊大部分隱式廣播(即,并非專門針對此應(yīng)用的廣播)。
默認情況下,這些限制僅適用于針對 O 的應(yīng)用。不過,用戶可以從 Settings 屏幕為任意應(yīng)用啟用這些限制,即使應(yīng)用并不是以 O 為目標平臺。
Android 8.0 還對特定函數(shù)做出了以下變更:
- 如果針對 Android 8.0 的應(yīng)用嘗試在不允許其創(chuàng)建后臺服務(wù)的情況下使用
startService()函數(shù),則該函數(shù)將引發(fā)一個IllegalStateException。 - 新的
Context.startForegroundService()函數(shù)將啟動一個前臺服務(wù)?,F(xiàn)在,即使應(yīng)用在后臺運行,系統(tǒng)也允許其調(diào)用Context.startForegroundService()。不過,應(yīng)用必須在創(chuàng)建服務(wù)后的五秒內(nèi)調(diào)用該服務(wù)的startForeground()函數(shù)。
針對Service的行為變更定義Service如下:
public class MyService extends Service {
public static final String ACTION = "abcdefg";
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("serviceId", "serviceName", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(getApplicationContext(), "serviceId").
setSmallIcon(R.drawable.ic_launcher_foreground).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background)).
setContentText("服務(wù)運行中")
.setContentTitle("測試服務(wù)")
.setWhen(System.currentTimeMillis()).build();
startForeground(1, notification);
}
}
private int time = 0 ;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
time ++ ;
Log.d(MyService.this.getClass().getName(), "計時:" + time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
public static void startService(Context context) {
Intent intent = new Intent(context, MyService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
}