Service這個(gè)組件好歹也是四大組件之一,但是因?yàn)闃I(yè)務(wù)的需求不同很可能有很多的小伙伴并沒(méi)有用過(guò)∪袂停或者用過(guò)之后也感覺(jué)還是不是了解的很透徹停忿,今天我就來(lái)說(shuō)說(shuō)我的理解吧驾讲,這里我主要是基于Local Service,對(duì)于Remote Service我會(huì)在下一篇文章中探討席赂。
從一個(gè)栗子開(kāi)始
對(duì)于Service看看它的源碼還是比較簡(jiǎn)單的吮铭,方法也不是太多。為了更好的理解Local Service的使用和注意事項(xiàng)在這里寫(xiě)一個(gè)簡(jiǎn)單的Demo颅停,通過(guò)對(duì)這個(gè)Demo的分析逐步理解它谓晌。這個(gè)Demo的主要功能是開(kāi)啟一個(gè)后臺(tái)服務(wù),然后回到主界面有一個(gè)浮動(dòng)窗口癞揉,通過(guò)浮動(dòng)窗口可以開(kāi)啟一個(gè)Dialog(類(lèi)似于360內(nèi)存清理等浮動(dòng)窗口)纸肉。具體效果如下所示:
分析栗子
這里簡(jiǎn)單的通過(guò)一個(gè)流程圖,看看Service的啟動(dòng)和停止的過(guò)程吧喊熟,如下所示:
為了更好的理解上面的過(guò)程柏肪,現(xiàn)在通過(guò)上面的栗子驗(yàn)證上圖所示的過(guò)程。浮動(dòng)窗口主要是使用WindowsManager添加View實(shí)現(xiàn)芥牌,主要的代碼如下所示:
/**
* initialize the {@link #mWindowManager} and
* {@link #mFloatView} which is floating view
* */
private void init(){
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mFloatView = LayoutInflater.from(mContext).inflate(R.layout.float_window_layout, null);
mWindowPosition = new Point();
mTouchPosition = new PointF();
mFloatView.findViewById(R.id.start_activity).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(mContext, LocalService.TipActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
}
/**
* show the floating view,
* just add view to {@link WindowManager}
* */
public void show(){
if (!mIsShowed){
WindowManager.LayoutParams wl = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_TOAST,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
wl.gravity = Gravity.LEFT | Gravity.TOP;
mWmLayoutParams = wl;
mWindowManager.addView(mFloatView, wl);
mFloatView.setOnTouchListener(this);
mIsShowed = true;
}
}
/**
* if there is view already added to {@link WindowManager}
* remove this view immediately
* */
public void hide(){
if (mIsShowed){
mWindowManager.removeViewImmediate(mFloatView);
mIsShowed = false;
}
}
LocalService.java中的onCreate()和onStartCommand()方法如下:
@Override
public void onCreate() {
super.onCreate();
mFloatWindow = new FloatWindow(this);
Toast.makeText(this, "Service onCreate", Toast.LENGTH_LONG).show();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, " onStartCommand intent " + intent);
if (intent == null){
Toast.makeText(this, "null intent", Toast.LENGTH_LONG).show();
}else {
String action = intent.getAction();
if (ACTION_SHOW_FLOATING_WINDOW.equals(action)){
mFloatWindow.show();
}else if (ACTION_HIDE_FLOATING_WINDOW.equals(action)){
mFloatWindow.hide();
}
}
return super.onStartCommand(intent, flags, startId);
}
如果我們想使用Service烦味,第一步需要做的就是啟動(dòng)它,就像我們使用startActivity()等一系列的方法啟動(dòng)一個(gè)新的Activity一樣壁拉,啟動(dòng)Service當(dāng)然是使用startService()谬俄,當(dāng)調(diào)用startService()后柏靶,如果Service為第一次啟動(dòng),會(huì)順序調(diào)用onCreate()和onStartCommand()方法凤瘦。如果當(dāng)前Service已經(jīng)啟動(dòng)則只會(huì)調(diào)用onStartCommand()方法宿礁。onCreate()方法主要就是做一些初始化的工作,這里實(shí)例化了一個(gè)浮動(dòng)窗口類(lèi)蔬芥,onStartCommand()方法可以通過(guò)startService多次調(diào)用梆靖,利用該方法可以實(shí)現(xiàn)浮動(dòng)窗口的現(xiàn)實(shí)和隱藏。這里做了一個(gè)非空的判斷笔诵,那么為什么要做這個(gè)工作呢返吻,不是通過(guò)調(diào)用startService()方法才會(huì)觸發(fā)該方法嗎,其實(shí)不然乎婿,我們看看下面的效果:
我在最近中把當(dāng)前的應(yīng)用kill掉测僵,但是系統(tǒng)卻幫我們重新啟動(dòng)了Service(這里要注意一下,這個(gè)和手機(jī)的系統(tǒng)有關(guān)系的谢翎,我這里使用的是模擬器捍靠,但是我手頭上的華為P7是不會(huì)出現(xiàn)這種現(xiàn)象的,同時(shí)這個(gè)關(guān)于重新創(chuàng)建的問(wèn)題森逮,我在實(shí)際開(kāi)發(fā)的過(guò)程中發(fā)現(xiàn)有的手機(jī)使用360清理內(nèi)存的時(shí)候會(huì)把開(kāi)啟在后臺(tái)的Service給kill掉但是系統(tǒng)會(huì)重新啟動(dòng)榨婆,這時(shí)的情況和這個(gè)現(xiàn)象一樣),這個(gè)時(shí)候Intent就是為null值褒侧,那么為什么會(huì)重新啟動(dòng)呢良风,是通過(guò)什么控制的呢,這個(gè)是和onStartCommand()方法的返回值有關(guān)系的闷供,該方法的返回值有三個(gè)參數(shù)烟央,它們對(duì)應(yīng)的功能如下:
START_NOT_STICKY
如果系統(tǒng)在正常調(diào)用onStartCommand()方法返回后終止了服務(wù)(stopService),則除非有掛起 Intent 要傳遞(就是在其他地方還有沒(méi)來(lái)得及調(diào)用傳遞Intent的地方歪脏,例如在第一次啟動(dòng)Service時(shí)疑俭,然后在停止的時(shí)候立馬又啟動(dòng)Service),否則系統(tǒng)不會(huì)重建服務(wù)唾糯。這是最安全的選項(xiàng)怠硼,可以避免在不必要時(shí)以及應(yīng)用能夠輕松重啟所有未完成的作業(yè)時(shí)運(yùn)行服務(wù)。
START_STICKY
如果系統(tǒng)在onStartCommand()返回后終止服務(wù)移怯,則會(huì)重建服務(wù)并調(diào)用onStartCommand(),但絕對(duì)不會(huì)重新傳遞最后一個(gè)Intent这难。相反舟误,除非有掛起 Intent 要啟動(dòng)服務(wù)在這種情況下,將傳遞這些 Intent)姻乓,否則系統(tǒng)會(huì)通過(guò)空Intent調(diào)用onStartCommand()嵌溢。這適用于不執(zhí)行命令眯牧、但無(wú)限期運(yùn)行并等待作業(yè)的媒體播放器(或類(lèi)似服務(wù))。(如果默認(rèn)情況下沒(méi)有修改onStartCommand()方法的返回值赖草,該值為默認(rèn)值)
START_REDELIVER_INTENT
如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù)学少,則會(huì)重建服務(wù),并通過(guò)傳遞給服務(wù)的最后一個(gè) Intent 調(diào)用 onStartCommand()秧骑。任何掛起 Intent 均依次傳遞版确。這適用于主動(dòng)執(zhí)行應(yīng)該立即恢復(fù)的作業(yè)(例如下載文件)的服務(wù)。
這里我們使用多次調(diào)用startService方法觸發(fā)onStartCommand()方法顯示和隱藏浮動(dòng)窗口乎折,所以可以通過(guò)startService()來(lái)和Service進(jìn)行交互绒疗,Service與其他組件的通信也都是通過(guò)廣播來(lái)實(shí)現(xiàn)。骂澄。但是這個(gè)startService()和廣播也是有自己的缺陷的吓蘑,那就是如果需要傳遞參數(shù)都是需要通過(guò)Intent進(jìn)行攜帶的,用起來(lái)就不是那么方便了坟冲,現(xiàn)在大家用EventBus來(lái)解決這塊通信的問(wèn)題磨镶,這樣就比較的方便了。
?對(duì)于Service的停止必須調(diào)用stopService()方法(bind方式啟動(dòng)的就是unbind方法)才可以健提,對(duì)于一開(kāi)始的Demo效果圖大家可以看到琳猫,如果我直接按下返回鍵,啟動(dòng)的Service并沒(méi)有調(diào)用OnDestroy方法(如果調(diào)用會(huì)彈出Toast)矩桂,說(shuō)明Service并沒(méi)有停止沸移。這個(gè)大家還是自己實(shí)踐一下認(rèn)識(shí)的深刻一點(diǎn)。
Servcie侄榴、Activity和Thread
Thread:開(kāi)啟一個(gè)線(xiàn)程主要是為了異步處理耗時(shí)的事件雹锣;
Activity:運(yùn)行在UI線(xiàn)程,主要是為了用戶(hù)界面的顯示和處理事件的分發(fā)癞蚕;
Service:運(yùn)行在UI線(xiàn)程蕊爵,處理一些不需要及時(shí)的顯示用戶(hù)界面,但是在有處理結(jié)果后可以及時(shí)的通過(guò)UI給用戶(hù)反饋(暫時(shí)我是這么理解的桦山,但是豈止這么點(diǎn)呢_)攒射。
這里的Service、Activity是在同一個(gè)線(xiàn)程中恒水,我們稱(chēng)這個(gè)線(xiàn)程為UI線(xiàn)程会放,該線(xiàn)程的主要作用就是處理與用戶(hù)的交互,主要包括UI顯示以及觸摸事件的處理钉凌。在開(kāi)始的時(shí)候我曾經(jīng)很困惑咧最,總是感覺(jué)Service就是為了做一些耗時(shí)操作的,和UI交互沒(méi)有太多的關(guān)系,為什么Android系統(tǒng)要搞出這么一個(gè)東西呢矢沿,后來(lái)我發(fā)現(xiàn)滥搭,說(shuō)Service沒(méi)有UI其實(shí)是不完全正確的,對(duì)于Service來(lái)說(shuō)是可以獲取Context對(duì)象捣鲸,獲取Context對(duì)象之后就可以獲取相應(yīng)的服務(wù)瑟匆、啟動(dòng)Activity、彈出Dialog等一系列UI界面栽惶。我們通常說(shuō)的下載就需要一個(gè)Service愁溜,那么為什么不直接在Activity當(dāng)中啟動(dòng)一個(gè)Thread呢,我們知道因?yàn)锳ctivity是有生命周期的媒役,并且是為了界面切換所使用的祝谚,一旦退出后,Thread所依賴(lài)的上下文環(huán)境就沒(méi)有了酣衷。但是對(duì)于Service就不會(huì)了交惯,它的生命周期是可以在外部控制的,并且常駐在內(nèi)存中穿仪,隨時(shí)使用隨時(shí)喚醒席爽。
Service使用注意事項(xiàng)
1.不使用的時(shí)候及時(shí)的stop或者unbind
當(dāng)Service在后臺(tái)運(yùn)行的時(shí)候是占用一定的內(nèi)存的,如果你不使用了啊片,最好及時(shí)的將其stop只锻,這樣就可以釋放一定的內(nèi)存。其實(shí)啟動(dòng)Service就像在酒店開(kāi)了一個(gè)房間一樣紫谷,如果你不住了齐饮,但是你不把它退掉,你就需要付錢(qián)笤昨。其實(shí)Android系統(tǒng)中如果在內(nèi)存不夠用的情況下祖驱,系統(tǒng)會(huì)優(yōu)先將處于后臺(tái)的Activity對(duì)象kill掉,這里就涉及到當(dāng)Activity和Service同時(shí)處于后臺(tái)時(shí)瞒窒,它們的級(jí)別那一個(gè)更高的問(wèn)題捺僻。我們可以看看這里對(duì)不同類(lèi)型的process介紹。這里面有一個(gè)visible process崇裁,那么哪些是visible process呢匕坯,例如我們啟動(dòng)一個(gè)應(yīng)用,當(dāng)前的應(yīng)用處于用戶(hù)交互狀態(tài)拔稳,那么它就是visible process葛峻,相應(yīng)的處于后臺(tái)的應(yīng)用就是invisible process。其實(shí)這里我們可以把service 所在的process提升為visible級(jí)別巴比, 通過(guò)調(diào)用startForeground(id, notification)方法實(shí)現(xiàn)泞歉,對(duì)于這個(gè)方法的詳細(xì)使用大家可以看看源碼的注釋吧逼侦,這里就不再討論了匿辩。對(duì)于visible process 在所有的process當(dāng)中級(jí)別是最高的腰耙,所以在內(nèi)存不夠用的情況下不會(huì)輕易的被kill掉。
2.防止Service被殺掉
對(duì)于Service來(lái)說(shuō)铲球,當(dāng)用戶(hù)退到后臺(tái)時(shí)挺庞,如果手機(jī)的內(nèi)存不夠時(shí)會(huì)觸發(fā)Android系統(tǒng)殺掉處于后臺(tái)的應(yīng)用,但是在內(nèi)存緊張時(shí)會(huì)都用如下的方法:
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
其中onTrimMemory(int levle)方法中的level代表當(dāng)前進(jìn)程的memory處于哪個(gè)級(jí)別稼病,這里主要有下面幾種級(jí)別:
static final int TRIM_MEMORY_COMPLETE = 80;
static final int TRIM_MEMORY_MODERATE = 60;
static final int TRIM_MEMORY_BACKGROUND = 40;
static final int TRIM_MEMORY_UI_HIDDEN = 20;
static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
static final int TRIM_MEMORY_RUNNING_LOW = 10;
static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
這里的每個(gè)級(jí)別對(duì)應(yīng)的注釋可以看ComponentCallbacks2接口(這里需要注意的是這個(gè)接口是在Android3.0以后才用的选侨,并且Activity也實(shí)現(xiàn)了該接口),對(duì)于onLowMemory()方法則表示系統(tǒng)沒(méi)有太多內(nèi)存可使用了然走,如果沒(méi)有足夠的內(nèi)存使用將會(huì)kill掉當(dāng)前的進(jìn)程(這個(gè)時(shí)候要看當(dāng)前進(jìn)程的級(jí)別了援制,主要是1里面分析的),這個(gè)時(shí)候我們要將我們cache的內(nèi)容釋放掉芍瑞,例如使用ImageLoader緩存在內(nèi)存中的圖片晨仑、優(yōu)先級(jí)不是很高的Service或者Activity kill掉。很多人或許都和我一樣拆檬,感覺(jué)自己好像從來(lái)沒(méi)有遇到過(guò)手機(jī)內(nèi)存很緊張的情況洪己,其實(shí)當(dāng)我們壓縮圖片或者使用WebView打開(kāi)網(wǎng)頁(yè)的時(shí)候都會(huì)消耗很大的內(nèi)存的。
總結(jié)
今天通過(guò)一個(gè)小demo簡(jiǎn)單的講解了Service的內(nèi)容竟贯,主要也添加了自己的一些理解吧答捕,對(duì)于Service的另一種啟動(dòng)方法——bind大家可以自行分析,主要的思路都差不多的屑那。其實(shí)關(guān)于Service的東西還有很多拱镐,大家只有多實(shí)踐,或者在實(shí)際的項(xiàng)目開(kāi)發(fā)中解決相應(yīng)的問(wèn)題才會(huì)對(duì)它有更深刻的理解吧持际。這里的demo源碼可以到這里去看看沃琅。
希望在Android學(xué)習(xí)的路上,大家共同成長(zhǎng)选酗!