參考
Android中的Service:默默的奉獻者 (1)
Android中的Service:Binder,Messenger翼馆,AIDL(2)
service深入解析
Android中Service的使用詳解和注意點(LocalService)
Android中Local Service最本質(zhì)的作用是什么管宵?
android后臺服務service全解析(上)--service的使用與本地通信
android后臺服務service全解析(下)--service遠程通信
把Service等同于thread或process是一個非常常見的誤解谨设。需要強調(diào)又強調(diào)的第一點是幽勒,Android的Service是一個Context洞翩。和Activity這種活躍周期相對短暫的component對比而言座舍,一旦用戶切換到其他應用沮翔,當前Activity就必須pause, stop甚至被destroy,不能在后臺處理其他事務曲秉。需要強調(diào)的事采蚀,嚴格來說你仍然可以在activity里創(chuàng)建自己的worker thread或async task之類做后臺的事情,但這樣做沒有任何保障——因為一旦你所有的activity都被切換到了后臺承二,系統(tǒng)隨時可能kill掉你的process榆鼠,你的后臺任務隨時可能悄無聲息的死掉。
Activity為什么這樣設計亥鸠?Windows下面的窗口在最小化的時候不是一樣可以處理消息或者繼續(xù)運行嗎妆够?這是另外一個話題识啦,不過回答也很簡單:這是移動設備,內(nèi)存/電池都有限神妹。
對你的應用而言颓哮,通過在manifest里聲明Service,把需要后臺相對長期運行的邏輯放在Service里鸵荠,你便獲得了這樣的保障:只要系統(tǒng)內(nèi)存不是極端不夠用冕茅,你的Service一定不會被kill掉。對系統(tǒng)而言蛹找,當看到一個進程里有Service在運行姨伤,這個進程就具有較高的優(yōu)先級,會在內(nèi)存不足被殺的行列里排得比較靠后熄赡。
可是前面不是強調(diào)了Service并不等于process或thread嗎姜挺?為什么上面又在說殺進程什么的?這里再次強調(diào)彼硫,你可以把Service看成一砣代碼炊豪,用來在后臺做些事情,僅此而已拧篮。至于這砣代碼在哪里運行词渤,完全是取決于你自己的喜好。你的local service串绩,如果不建立worker thread缺虐,仍然是在你的應用進程的主線程即UI線程里運行。如果你不想阻塞UI線程礁凡,你就建一個worker thread高氮。但這些細節(jié),Android framework并不怎么care顷牌,它只知道你聲明了一個service剪芍,然后在你的manifest里面找到這個service是聲明在哪個process里運行,那么這個process就不容易被kill窟蓝。
什么時候選擇local service(即不指定額外的進程)罪裹,什么時候選擇remote service(額外的進程)?通常我們會把真的需要長期運行的service(例如IM之類)放在單獨的進程里运挫,這樣UI所在的進程在必要的時候仍然可以被系統(tǒng)kill掉來騰出內(nèi)存状共。而local service通常用來處理一些需要短期運行但仍然超出activity活動周期的任務,打個比方谁帕,發(fā)送短信或彩信峡继。這樣的任務執(zhí)行完以后,service就可以stop自己匈挖,仍然不妨礙整個UI進程被回收掉碾牌。
一颠猴、Service的基本概念(四大組件之一)
Service是Android中實現(xiàn)程序后臺運行的解決方案,非常適合用于去執(zhí)行哪些不需要和用戶交互而且還要求長期運行的任務小染。不能運行在一個獨立的進程當中,而是依賴與創(chuàng)建服務時所在的應用程序進程贮折。當某個應用程序進程被殺掉時裤翩,所有依賴于該進程的服務也會停止運行。
Service可以在很多場合使用调榄,比如播放多媒體的時候用戶啟動了其他Activity踊赠,此時要在后臺繼續(xù)播放;比如檢測SD卡上文件的變化每庆;比如在后臺記錄你的地理信息位置的改變等等筐带,總之服務是藏在后臺的。
另外缤灵,不要被服務的后臺概念所迷惑伦籍,實際上服務并不會自動開啟線程,所有的代碼都是默認運行在主線程中的腮出。也就是說帖鸦,我們需要在服務內(nèi)部手動創(chuàng)建子線程,并在里面執(zhí)行具體的任務胚嘲,否則就有可能出現(xiàn)主線程被阻塞住的情況作儿。
二、定義Service
package com.example.servicetest;
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";
//第一次創(chuàng)建服務時調(diào)用
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
//每次啟動服務時調(diào)用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
//銷毀服務時調(diào)用
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
onCreate是第一次創(chuàng)建時調(diào)用馋劈,以后每次startService時調(diào)用onStartCommand,每次stopService時調(diào)用onDestory
三攻锰、啟動和停止服務
定義好服務之后,接下來看一下如何啟動和停止一個服務妓雾,這主要是借助Intent來實現(xiàn)的娶吞。注意startService()和stopService()方法都是定義在Context類當中的,所以可以在MainActivity中直接調(diào)用這兩個方法君珠。
package com.example.servicetest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener {
private Button button1_start_service;
private Button button2_stop_service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1_start_service = (Button) findViewById(R.id.button1_start_service);
button2_stop_service = (Button) findViewById(R.id.button2_stop_service);
button1_start_service.setOnClickListener(this);
button2_stop_service.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1_start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);
break;
case R.id.button2_stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
break;
default:
break;
}
}
}
需要注意的是:
- 服務對象同時只會有一個
- 默認情況下寝志,一個started的Service與啟動他的組件在同一個線程中。上面的實例中策添,服務就是在主線程中運行的材部,如果是在服務中完成耗時操作的話,容易造成主線程阻塞唯竹。
- 停止一個started服務有兩種方法:
- 在外部使用stopService()
- 在服務內(nèi)部(onStartCommand方法內(nèi)部)使用stopSelf()方法乐导。
四、活動和服務進行通信
有沒有什么辦法能讓活動和服務的關聯(lián)更多一些呢浸颓?比如說在Activity中指揮Service去干什么物臂,Service就去干什么旺拉。當然可以,只需要借助onBind()方法棵磷。
package com.example.servicetest;
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();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
return mBinder; //在這里返回新建的MyBinder類
}
//MyBinder類蛾狗,繼承Binder:讓里面的方法執(zhí)行下載任務,并獲取下載進度
class MyBinder extends Binder {
public void startDownload() {
Log.d("TAG", "startDownload() executed");
// 執(zhí)行具體的下載任務
}
public int getProgress(){
Log.d("TAG", "getProgress() executed");
return 0;
}
}
}
package com.example.servicetest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener {
private Button button1_start_service;
private Button button2_stop_service;
private Button button3_bind_service;
private Button button4_unbind_service;
private MyService.MyBinder myBinder;
//匿名內(nèi)部類:服務連接對象
private ServiceConnection connection = new ServiceConnection() {
//當服務異常終止時會調(diào)用仪媒。注意沉桌,解除綁定服務時不會調(diào)用
@Override
public void onServiceDisconnected(ComponentName name) {
}
//和服務綁定成功后,服務會回調(diào)該方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyBinder) service;
//在Activity中調(diào)用Service里面的方法
myBinder.startDownload();
myBinder.getProgress();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1_start_service = (Button) findViewById(R.id.button1_start_service);
button2_stop_service = (Button) findViewById(R.id.button2_stop_service);
button3_bind_service = (Button) findViewById(R.id.button3_bind_service);
button4_unbind_service = (Button) findViewById(R.id.button4_unbind_service);
button1_start_service.setOnClickListener(this);
button2_stop_service.setOnClickListener(this);
button3_bind_service.setOnClickListener(this);
button4_unbind_service.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1_start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);
break;
case R.id.button2_stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
break;
case R.id.button3_bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
break;
case R.id.button4_unbind_service:
unbindService(connection);
break;
default:
break;
}
}
}
可以看到算吩,這里我們首先創(chuàng)建了一個ServiceConnection的匿名類(24行)留凭,在里面重寫了onServiceConnected()方法和onServiceDisconnected()方法,如果當前Activity與服務連接成功后偎巢,服務會回調(diào)onServiceConnected()方法蔼夜,
在onServiceConnected()方法中,我們又通過向下轉(zhuǎn)型得到了MyBinder的實例(34行)压昼,有了這個實例求冷,Activity和Service之間的關系就變得非常緊密了。現(xiàn)在我們可以在Activity中根據(jù)具體的場景來調(diào)用MyBinder中的任何public方法(36巢音、37行)遵倦,即實現(xiàn)了Activity指揮Service干什么Service就去干什么的功能。
當然官撼,現(xiàn)在Activity和Service其實還沒關聯(lián)起來了呢梧躺,這個功能是在Bind Service按鈕的點擊事件里完成的“列澹可以看到掠哥,這里我們?nèi)匀皇菢嫿ǔ隽艘粋€Intent對象,然后調(diào)用bindService()方法將Activity和Service進行綁定秃诵。bindService()方法接收三個參數(shù)续搀,第一個參數(shù)就是剛剛構建出的Intent對象,第二個參數(shù)是前面創(chuàng)建出的ServiceConnection的實例菠净,第三個參數(shù)是一個標志位禁舷,這里傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯(lián)后會自動創(chuàng)建Service(即使之前沒有創(chuàng)建Service也沒有關系),這會使得MyService中的onCreate()方法得到執(zhí)行毅往,但onStartCommand()方法不會執(zhí)行牵咙。
然后如何我們想解除Activity和Service之間的關聯(lián)怎么辦呢?調(diào)用一下unbindService()方法就可以了攀唯,這也是Unbind Service按鈕的點擊事件里實現(xiàn)的邏輯洁桌。
另外需要注意,任何一個Service在整個應用程序范圍內(nèi)都是通用的侯嘀,即MyService不僅可以和MainActivity建立關聯(lián)另凌,還可以和任何一個Activity建立關聯(lián)谱轨,而且在建立關聯(lián)時它們都可以獲取到相同的MyBinder實例。
如果我們既點擊了Start Service按鈕吠谢,又點擊了Bind Service按鈕會怎么樣呢土童?這個時候你會發(fā)現(xiàn),不管你是單獨點擊Stop Service按鈕還是Unbind Service按鈕工坊,Service都不會被銷毀娜扇,必要將Unbind Service按鈕和Stop Service按鈕都點擊一下(沒有先后順序),Service才會被銷毀栅组。也就是說,點擊Stop Service按鈕只會讓Service停止枢析,點擊Unbind Service按鈕只會讓Service和Activity解除關聯(lián)玉掸,一個Service必須要在既沒有和任何Activity關聯(lián)又處理停止狀態(tài)的時候才會被銷毀。
什么時候用startService什么時候用bindService?
這個其實可以通過它們的特點很輕松的得到結論:它們之間的主要區(qū)別其實體現(xiàn)在兩點醒叁,能否交互司浪,以及生命周期。所以很顯然的把沼,startService適合那種啟動之后不顯式停止它就永遠在后臺運行啊易,并且不需要客戶端與服務端交互的service。比方說一條專門拿來存數(shù)據(jù)到本地數(shù)據(jù)庫的service饮睬,它就一直在后臺等著有別的組件startService租谈,然后把拿到的數(shù)據(jù)存入數(shù)據(jù)庫,這就顯然是用startService做的事情捆愁。而bindService呢割去,就適合那種可以交互的,可以掌控它什么時候停什么時候開始的昼丑。另外呻逆,如果有IPC的需求,那當然bindService是必不可少的了菩帝。
我們在上一篇博文里講過咖城,其實在大多數(shù)情況下,startService和bindService都是相輔相成的呼奢,它們并不是孤立的存在宜雀。比方說我這個時候要做一個音樂播放器,那么后臺播放是肯定要的吧控妻?總不能手機一熄屏音樂也沒了州袒。另外,控制音樂也是要的吧弓候?什么上一首下一首播放暫停什么的郎哭。這不就強強聯(lián)合了么他匪?當然要注意的是,在這兩種啟動方式同時存在去啟動一個service的時候夸研,service的生命周期會發(fā)生變化邦蜜,必須從兩種方法的角度看service均停止才能真正停止。
五亥至、IntentService
我們在第一段中就已經(jīng)說了悼沈,服務中的代碼默認運行在主線程中,如果直接在服務里執(zhí)行一些耗時操作姐扮,容易造成ANR(Application Not Responding)異常絮供,所以就需要用到多線程的知識了。
因此一個比較標準的服務可以這樣寫:
<pre>
package com.example.servicetest;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public static final String TAG = "MyService";
//服務執(zhí)行的操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
public void run() {
//處理具體的邏輯
stopSelf(); //服務執(zhí)行完畢后自動停止
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
</pre>
需要注意的是茶敏,如果沒有第17行的stopSelf()壤靶,服務一旦啟動后,就會一直處于運行狀態(tài)惊搏,必須調(diào)用stopService()或者stopSelf()方法才能讓服務停止下來贮乳;所以我們添加了17行的stopSelf(),服務執(zhí)行完畢后會自動停止恬惯。
雖說上面的這種寫法并不復雜向拆,但總會有一些程序猿忘記開啟線程,或者忘記調(diào)用stopSelf()方法酪耳。為了可以簡單地創(chuàng)建一個異步的浓恳、會自動停止的服務,Android專門提供了一個IntentService類碗暗,這個類就很好的解決了上面所提到的兩種尷尬奖蔓。另外,可以啟動IntentService多次讹堤,而每一個耗時操作會以工作隊列的方式在IntentService的onHandleIntent()回調(diào)方法中執(zhí)行吆鹤,并且每次只會執(zhí)行一個工作線程,執(zhí)行完第一個后洲守,再執(zhí)行第二個疑务,以此類推。IntentService內(nèi)部仍然是采用了HandlerThread來執(zhí)行任務梗醇。
package com.example.servicetest;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService{
public MyIntentService() {
super("MyIntentService");//調(diào)用父類有參構造函數(shù)知允。這里我們手動給服務起個名字為:MyIntentService
// TODO Auto-generated constructor stub
}
//該方法在會在一個單獨的線程中執(zhí)行,來完成工作任務叙谨。任務結束后温鸽,該Service自動停止
@Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub
for(int i = 0;i<3;i++) {
//打印當前線程的id
Log.d("MyIntentService","IntentService線程的id是:"+Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d("MyIntentService","onDestroy");
}
}
六、Service和Thread的關系
不少Android初學者都可能會有這樣的疑惑,Service和Thread到底有什么關系呢涤垫?什么時候應該用Service姑尺,什么時候又應該用Thread?答案可能會有點讓你吃驚蝠猬,因為Service和Thread之間沒有任何關系切蟋!
之所以有不少人會把它們聯(lián)系起來,主要就是因為Service的后臺概念榆芦。Thread我們大家都知道柄粹,是用于開啟一個子線程,在這里去執(zhí)行一些耗時操作就不會阻塞主線程的運行匆绣。而Service我們最初理解的時候驻右,總會覺得它是用來處理一些后臺任務的,一些比較耗時的操作也可以放在這里運行崎淳,這就會讓人產(chǎn)生混淆了旺入。但是,如果我告訴你Service其實是運行在主線程里的凯力,你還會覺得它和Thread有什么關系嗎?
其實礼华,后臺和子線程是兩個完全不同的概念:
Android的后臺就是指咐鹤,它的運行是完全不依賴UI的。即使Activity被銷毀圣絮,或者程序被關閉祈惶,只要進程還在,Service就可以繼續(xù)運行扮匠。比如說一些應用程序捧请,始終需要與服務器之間始終保持著心跳連接,就可以使用Service來實現(xiàn)棒搜。你可能又會問疹蛉,Service既然是運行在主線程里,在這里一直執(zhí)行著心跳連接力麸,難道就不會阻塞主線程的運行嗎可款?當然會,但是我們可以在Service中再創(chuàng)建一個子線程克蚂,然后在這里去處理耗時邏輯就沒問題了闺鲸。
既然在Service里也要創(chuàng)建一個子線程,那為什么不直接在Activity里創(chuàng)建呢埃叭?這是因為Activity很難對Thread進行控制摸恍,當Activity被銷毀之后,就沒有任何其它的辦法可以再重新獲取到之前創(chuàng)建的子線程的實例赤屋;而且在一個Activity中創(chuàng)建的子線程立镶,另一個Activity無法對其進行操作壁袄。但是Service就不同了,所有的Activity都可以與Service進行關聯(lián)谜慌,然后可以很方便地操作其中的方法然想,即使Activity被銷毀了,之后只要重新與Service建立關聯(lián)欣范,就又能夠獲取到原有的Service中Binder的實例变泄。因此,使用Service來處理后臺任務恼琼,Activity就可以放心地finish妨蛹,完全不需要擔心無法對后臺任務進行控制的情況。