1. 引言
現(xiàn)實生活中的服務(wù)很常見兼都,各式各樣的人都在從事服務(wù)這個行業(yè)旨涝。但是Android中的服務(wù)呢?Android中的服務(wù)在哪里喇伯?我們什么時候需要服務(wù)呢喊儡?Android 中的服務(wù)怎么用?這就是這篇文章想要表述的內(nèi)容稻据。
2. 服務(wù)
服務(wù)(Service)是Android 實現(xiàn)程序后臺運行的解決方案艾猜。看完這句話捻悯,馬上就知道匆赃,服務(wù)在后臺。沒錯今缚,Android 中的服務(wù)是存在后臺中的算柳,它適合去執(zhí)行哪些不需要與用戶交互的但是還被要求長期運行的任務(wù)。比如姓言,你在聊QQ的時候瞬项,喜歡聽音樂蔗蹋,這時候你的音樂就是在后臺掛著,即使你開了另外一個應(yīng)用程序囱淋,當前的應(yīng)用程序還是不會被關(guān)閉猪杭。
??不過需要說明的是,Service服務(wù)不是運行在一個獨立的程序中的妥衣,而是依賴于創(chuàng)建服務(wù)的這個應(yīng)用程序中的皂吮。如果這個程序被殺死,服務(wù)也會停止税手。
??前面說服務(wù)是在后臺的蜂筹。但是實際上服務(wù)不會自動開啟線程,所有的服務(wù)都是在主線程中運行的芦倒。而我們知道主線程不能執(zhí)行耗時的操作艺挪,否則會引起線程阻塞,所以我們需要在服務(wù)上創(chuàng)建子線程熙暴,由子線程去實現(xiàn)需要的具體工作闺属。
3. 服務(wù)的用法
3.1 服務(wù)的生命周期
因為我們知道Service是依賴于應(yīng)用程序的,應(yīng)用程序終止了,服務(wù)也終止了。所以了解Service的生命周期非常重要捌显。
Service的生命周期有兩種:
- Started Service的生命周期:
- onCreate() 創(chuàng)建服務(wù)。
- onStartCommand() 服務(wù)開始運行
- onDestory() 服務(wù)停止
【說明】 - 程序調(diào)用:context.startService()會觸發(fā)執(zhí)行生命周期中的onCreate(),onStartCommond()的回調(diào)方法国瓮,此時服務(wù)正式運行。
- 如果service沒有運行狞谱,則安卓會先調(diào)用onCreate()然后再調(diào)用onStartCommond()方法乃摹,所以onStartCommand()可能會被調(diào)用很多次。
- 如果在程序中調(diào)用:context.stopService()會觸發(fā)執(zhí)行Service生命周期中的onDestroy()回調(diào)方法跟衅,會讓服務(wù)停止孵睬。
- stopService()的時候直接onDestroy,如果是調(diào)用者自己直接退出而沒有調(diào)用stopService()的話伶跷,Service會一直在后臺運行掰读。該Service的調(diào)用者再啟動該Service后可以通過stopService關(guān)閉Service;stopSelf()
- 所以StartService的生命周期為:onCreate --> onStartCommand(可多次調(diào)用) --> onDestroy叭莫。
- Bound Service的生命周期:
- onCreate()創(chuàng)建服務(wù)
- onBind() 綁定服務(wù)蹈集,服務(wù)開始運行
- onUnbind() 解綁服務(wù)
- onDestory() 服務(wù)停止
【說明】 - 在程序中調(diào)用:context.bindService()會觸發(fā)執(zhí)行Service生命周期中的onCreate()、onBind()回調(diào)方法雇初,此時服務(wù)開始運行拢肆;
- onBind將返回給客戶端一個IBind接口實例,IBind允許客戶端回調(diào)服務(wù)的方法,比如得到Service運行的狀態(tài)或其他操作郭怪。此后調(diào)用者(Context支示,例如Activity)會和Service綁定在一起;
- 如果調(diào)用Service的調(diào)用者Context退出了移盆,那么會依次調(diào)用Service生命周期中的onUnbind()悼院、onDestroy()回調(diào)方法,會讓服務(wù)停止咒循;
- 所以BindService的生命周期為:onCreate --> onBind(只一次,不可多次綁定) --> onUnbind --> onDestory绞愚。
【備注:】 - Service是不能自己啟動的叙甸,只有通過 Context 對象調(diào)用startService() 和bindService() 方法來啟動。
- 在Service每一次的開啟關(guān)閉過程中位衩,只有onStartCommand()可被多次調(diào)用(通過多次startService調(diào)用)裆蒸,其他onCreate()、onBind()糖驴、onUnbind()僚祷、onDestory()在一個生命周期中只能被調(diào)用一次。
- Service可以在和多場合的應(yīng)用中使用贮缕,比如播放多媒體的時候用戶啟動了其他Activity這個時候程序要在后臺繼續(xù)播放辙谜,比如檢測SD卡上文件的變化,再或者在后臺記錄你地理信息位置的改變等等感昼,總之服務(wù)總是藏在后頭的装哆。
3.2 服務(wù)的概念總結(jié)
- Service在后臺運行,不可以與用戶直接交互定嗓;
- 一個服務(wù)不是一個單獨的進程蜕琴。服務(wù)對象本身并不意味著它是在自己的進程中運行,除非另有規(guī)定宵溅,否則它與運行程序是同在一個進程中凌简;
- 一個服務(wù)不是一個單獨的線程。Service和其他組件一樣恃逻,默認情況下雏搂,Service中的所有代碼都是運行在主線程中;
- Service存在的價值雖然不如Activity那么清晰辛块。但是一般都讓Service執(zhí)行耗時較長的操作畔派。例如:播放音樂、下載文件润绵、上傳文件等等线椰。但是因為Service默認運行在主線程中,因此不能直接用它來做耗時的請求或者動作尘盼,最好在Service中啟動新線程來運行耗時的任務(wù)憨愉;
- 需要通過某一個Activity或其他Context對象來啟動Service烦绳。context.startService() 或 context.bindService();
- Service很大程度上充當了應(yīng)用程序后臺線程管理器的角色配紫。(如果Activity中新開啟一個線程径密,當該Acitivyt關(guān)閉后,該線程依然在工作躺孝,但是與開啟它的Activity失去聯(lián)系享扔。也就是說此時的這個線程處于失去管理的狀態(tài)。但是使用Service植袍,則可以對后臺運行的線程有效地管理惧眠。)
3.3 服務(wù)的面試總結(jié)點
- 為什么不使用后臺線程而使用Service?
- Service可以放在獨立的進程中于个,所以更安全氛魁;
- 使用Service可以依賴現(xiàn)有的binder機制,不需要在應(yīng)用層面上處理線程同步的繁雜工作厅篓;
- 系統(tǒng)可以重新啟動異常死去的Service秀存。
- Service 與 Activity 的相同點與不同點:
- 不同點:Activity是與用戶交互的組件,即可以看到UI界面羽氮,而Service是在后臺運行或链、無需界面;
- 相同點:使用Activity 時我們需要在配置文件中聲明<activity>標簽乏苦,同樣的使用Service 也需要在配置文件中聲明<service>標簽株扛。都具有一定的生命周期。
3.4 服務(wù)的基本用法
3.4.1 定義一個服務(wù)
項目新建一個服務(wù)汇荐,包名->New ->Service->Service ,然后彈出一個界面洞就,
這里將創(chuàng)建的Service命名為MyService,Exproted屬性表示是否允許其他程序訪問這個服務(wù)掀淘,Enable屬性表示是否啟用這個服務(wù)旬蟋,將兩個都勾選然后Finish()創(chuàng)建。
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");
}
}
定義的這個類繼承Service,創(chuàng)建完之后只有一個onBind()方法革娄。這個方法是Service中的唯一的一個抽象方法倾贰。定義完了服務(wù)之后,自然要再服務(wù)中處理需要的操作拦惋。然后重寫Service的另一些方法匆浙。
public class MyService extends Service {
....
@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()是在創(chuàng)建服務(wù)的時候調(diào)用,onStartCommand()方法會在啟動服務(wù)的時候調(diào)用厕妖,onDestory()方法會在服務(wù)銷毀的時候調(diào)用首尼。
??通常情況下,我們在啟動服務(wù)之后,在onStartCommand()的方法里面編寫代碼邏輯软能,當服務(wù)銷毀之后迎捺,應(yīng)該在onDestory()的方法里面回收哪些不再使用的資源。
??最后還必須得在AndroidMainfest.xml文件中注冊查排。安卓四大組件都必須要再這里注冊凳枝。因為我們創(chuàng)建代碼的時候,Android Studio已經(jīng)幫我們創(chuàng)建好了跋核。
<application>
....
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
</application>
到這里就定義完了一個Service服務(wù)了岖瑰。
3.4.2 啟動和停止服務(wù)
定義完成服務(wù)之后,就需要考慮怎么去啟動和停止這個服務(wù)砂代。主要是借助Intent的方法來啟動和停止服務(wù)锭环。
1.activity_main.xml文件中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/btn_start_service"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="開啟服務(wù)"/>
<Button
android:id="@+id/btn_stop_service"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="停止服務(wù)"/>
</LinearLayout>
2.MainActivity.java
public class MainActivity extends AppCompatActivity implements OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_start_service= (Button) findViewById(R.id.btn_start_service);
Button btn_stop_service= (Button) findViewById(R.id.btn_stop_service);
btn_start_service.setOnClickListener(this);
btn_stop_service.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_start_service:
Intent intent=new Intent(this,MyService.class);
startService(intent);
break;
case R.id.btn_stop_service:
Intent intent1=new Intent(this,MyService.class);
stopService(intent1);
break;
}
}
}
通過startService()和stopService這兩個方法來啟動和停止service。
3.MyService.java 顯示執(zhí)行步驟
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: " );
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: " );
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: " );
}
}
打印結(jié)果:
當然你可以多次點擊開啟服務(wù)的按鈕發(fā)現(xiàn)onStartCommand()的方法被多次執(zhí)行泊藕,點擊關(guān)閉服務(wù)的按鈕,發(fā)現(xiàn)沒動靜难礼。
3.4.2 活動和服務(wù)進行通信
之前在MyService中娃圆,我介紹了新添加的幾個方法,而沒有說onBind()方法蛾茉。那這個方法是干什么用的呢讼呢?在上面介紹定義服務(wù)的時候,點擊開始服務(wù)的時候谦炬,MyService中的onCreate() 和onStartCommand()方法就會執(zhí)行悦屏,直到onDestory()方法調(diào)用才銷毀,那么在這之間的過程怎么控制呢键思?假設(shè)我想控制活動去指揮服務(wù)怎么辦础爬,這時候就需要用到onBind()方法。
1.新建一個MyBindService的方法吼鳞,然后定義一個內(nèi)部類看蚜,在內(nèi)部類中定義兩個方法,然后實例化內(nèi)部類赔桌,最后在onBind()方法返回內(nèi)部類的實例供炎。
public class MyBindService extends Service {
private static final String TAG = "MyBindService";
public MyBindService() {
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: " );
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: " );
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: " );
}
private DownloadBinder mBinder=new DownloadBinder();
class DownloadBinder extends Binder{
public void startDownload(){
Log.e(TAG, "startDownload: 開始下載");
}
public int getProgress(){
Log.e(TAG, "getProgress: 當前下載量" );
return 0;
}
}
}
2.在activity_main2.xml,兩個按鈕,分別的綁定和取消服務(wù)的疾党。這時候就需要Activity來和Service進行綁定了音诫。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/btn_start_service"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="綁定服務(wù)"/>
<Button
android:id="@+id/btn_stop_service"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="取消服務(wù)"/>
</LinearLayout>
3.定義一個Main2Activity,當Activity和Service進行綁定后雪位,就可以調(diào)用它Binder提供的方法竭钝。
public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
private MyBindService.DownloadBinder downloadBinder;
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyBindService.DownloadBinder) iBinder;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Button btn_start_service= (Button) findViewById(R.id.btn_start_service);
Button btn_stop_service= (Button) findViewById(R.id.btn_stop_service);
btn_start_service.setOnClickListener(this);
btn_stop_service.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_start_service:
Intent intent = new Intent(this, MyBindService.class);
bindService(intent,connection,BIND_AUTO_CREATE);//綁定服務(wù)
break;
case R.id.btn_stop_service:
unbindService(connection); //解綁服務(wù)
break;
}
}
}
這里首先創(chuàng)建一個ServiceConnection的匿名類,然后在里面重寫onServiceConnected()和onServiceDisconnected()方法,這兩個方法會在Activity與Service成功綁定和連接斷開的時候調(diào)用蜓氨。在onServiceConnected()方法中聋袋,我們通過向下轉(zhuǎn)型得到DownloadBinder的實例,有了這個實例穴吹,活動和服務(wù)之間的關(guān)系就變得非常緊密了∮睦眨現(xiàn)在我們可以在Activity中根據(jù)具體的場景來調(diào)用DownloadBinder中的任何一個public 方法,實現(xiàn)了Activity指揮Service去干的功能港令。
??當然啥容,現(xiàn)在Activity和Service還沒有綁定,這個功能是在點擊 綁定按鈕的時候觸發(fā)點擊事件時完成的顷霹。我們?nèi)匀挥肐ntent咪惠,然后調(diào)用bindService()方法將Main2Activity和MyService進行綁定。bindService()方法接收3個參數(shù)淋淀。遥昧,第一個參數(shù)是intent對象,第二個是之前創(chuàng)建的ServiceConnection實例朵纷。第三個參數(shù)是一個標志位實例 BIND_AUTO_CREATE表示Activity和Service綁定之后自動創(chuàng)建service炭臭。這會使得onCreate()方法得到實現(xiàn),onStartCommand()方法不會得到執(zhí)行袍辞。
4. 服務(wù)的高級技巧使用
4.1 使用前臺服務(wù)
服務(wù)幾乎都是在后臺運行的鞋仍,一直以來都是吃的少,干得多搅吁,啥臟活累死都攬著干威创,但是它的優(yōu)先級還是相當?shù)偷摹.斚到y(tǒng)的內(nèi)存比較少的時候谎懦,就會回收掉正在后臺運行的服務(wù)肚豺。如果你想保持服務(wù)的運行狀態(tài),而不會由于系統(tǒng)的內(nèi)存不足而回收掉党瓮。這時候就可以考慮前臺服務(wù)详炬。前臺服務(wù)和普通服務(wù)的最大區(qū)別,它會一直有一個運行圖標在狀態(tài)欄顯示寞奸,例如QQ呛谜,微信,網(wǎng)易云音樂等枪萄。需要下拉狀態(tài)欄才能看到隐岛,非常類似于通知效果。當然有時候它的作用可不僅僅是為了防止服務(wù)被回收掉才使用前臺服務(wù)的瓷翻,有些項目由于特殊的要求必須使用前臺服務(wù)聚凹,例如網(wǎng)易云的點擊播放按鈕割坠,顯示當前播放的曲目。
創(chuàng)建一個前臺服務(wù):
1.修改MyService的服務(wù)
public class MyBindService extends Service {
···
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: " );
Intent intent= new Intent(this,Main2Activity.class);
PendingIntent pi=PendingIntent.getActivity(this,0,intent,0);
Notification notification= new NotificationCompat.Builder(this)
.setContentTitle("標題")
.setContentText("內(nèi)容")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher_round)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
}
這里只是修改onCreate()方法中的代碼妒牙,通過構(gòu)建Notification對象彼哼,來將通知顯示出來。調(diào)用startForeground()方法會讓MyService變成一個前臺服務(wù)湘今,并在系統(tǒng)狀態(tài)欄顯示敢朱。運行效果如下所示。
4.2 使用 IntentService
在本文的開頭我們知道Service服務(wù)的代碼都是在主程序中運行的摩瞎,如果直接在服務(wù)中去處理一些耗時操作拴签,很容易出現(xiàn)ANR(Application Not Responsing)程序無響應(yīng)。所以這時候就需要用到Android 多線程編程的技術(shù)旗们,我們應(yīng)該在服務(wù)的每個具體的方法里面開啟一個子線程蚓哩。然后在這里去處理哪些耗時的操作。所以標準的服務(wù)應(yīng)該寫如下形式:
public class MyBindService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: " );
new Thread(new Runnable() {
@Override
public void run() {
//處理具體的邏輯
stopSelf();//停止服務(wù)
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
}
這種服務(wù)一旦啟動之后上渴,就會一直處于運行狀態(tài)岸梨,所以調(diào)用stopSelf()或stopService()方法才能讓服務(wù)停止。這樣寫就實現(xiàn)了一個服務(wù)在執(zhí)行完畢之后自動關(guān)閉服務(wù)的功能稠氮。
??但是如果忘記開線程了或者忘記調(diào)用 stopSelf()方法了怎么辦盛嘿。為了可以簡單創(chuàng)建一個異步的會自動停止的功能。Android 提供了IntentService類括袒。
1.新建一個MyIntentService類
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService"); //調(diào)用父類的有參構(gòu)造函數(shù)
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//打印當前線程的id
Log.d("MyIntentService","當前線程的id="+Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService","onDestroy");
}
}
這里首先提供了一個無參的構(gòu)造函數(shù),然后必須在內(nèi)部調(diào)用父類的有參構(gòu)造函數(shù)稿茉。然后在子類中實現(xiàn)onHandleIntent()這個抽象方法锹锰,在這方法中處理抽象的邏輯,因為這個方法是在子線程中執(zhí)行的漓库,所以不需要單選ANR的問題恃慧。這里打印當前線程的id,另外為了驗證服務(wù)運行結(jié)束之后會自動關(guān)閉,重寫onDestory()的方法渺蒿,打印下日志痢士。
2.修改activity_main2.xml文件,添加一個按鈕
<Button
android:id="@+id/btn_log_service_id"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="打印線程的id"/>
3.修改Main2Activity中的代碼茂装,使用方法和前面的按鈕一樣
public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
···
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
····
Button btn_log_service_id= (Button) findViewById(R.id.btn_log_service_id);
btn_log_service_id.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
···
case R.id.btn_log_service_id:
//打印主線程的id
Log.d("Main2Activity", "主線程的id= "+Thread.currentThread().getId());
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);
break;
}
}
}
4.在AndroidMainfes.xml文件中洪注冊
<service android:name=".bindservice.MyIntentService"/>
運行結(jié)果:
可以看到MyIntentService 的線程id和主線程的id不一樣怠蹂,而且MyIntentService 的onDestory()方法也得到了執(zhí)行。說明MyIntentService在運行完畢之后確實自動停止了少态。集開縣城和自動停止與一身城侧。
項目github地址:https://github.com/wangxin3119/myServiceDemo