Android Service-Local Service

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)窗口)纸肉。具體效果如下所示:

demo效果

分析栗子

這里簡(jiǎn)單的通過(guò)一個(gè)流程圖,看看Service的啟動(dòng)和停止的過(guò)程吧喊熟,如下所示:

service_life_cycle

為了更好的理解上面的過(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í)不然乎婿,我們看看下面的效果:

one_case

我在最近中把當(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)选酗!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阵难,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芒填,更是在濱河造成了極大的恐慌呜叫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殿衰,死亡現(xiàn)場(chǎng)離奇詭異朱庆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)闷祥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)娱颊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)傲诵,“玉大人,你說(shuō)我怎么就攤上這事箱硕∷┲瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵剧罩,是天一觀(guān)的道長(zhǎng)栓拜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)惠昔,這世上最難降的妖魔是什么幕与? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮镇防,結(jié)果婚禮上啦鸣,老公的妹妹穿的比我還像新娘。我一直安慰自己来氧,他們只是感情好诫给,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著饲漾,像睡著了一般蝙搔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上考传,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天吃型,我揣著相機(jī)與錄音,去河邊找鬼僚楞。 笑死勤晚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泉褐。 我是一名探鬼主播赐写,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼膜赃!你這毒婦竟也來(lái)了挺邀?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跳座,失蹤者是張志新(化名)和其女友劉穎端铛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疲眷,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡禾蚕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狂丝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片换淆。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哗总,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倍试,到底是詐尸還是另有隱情讯屈,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布易猫,位于F島的核電站耻煤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏准颓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一棺妓、第九天 我趴在偏房一處隱蔽的房頂上張望攘已。 院中可真熱鬧,春花似錦怜跑、人聲如沸样勃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)峡眶。三九已至,卻和暖如春植锉,著一層夾襖步出監(jiān)牢的瞬間辫樱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工俊庇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狮暑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓辉饱,卻偏偏與公主長(zhǎng)得像搬男,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彭沼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言:本文所寫(xiě)的是博主的個(gè)人見(jiàn)解缔逛,如有錯(cuò)誤或者不恰當(dāng)之處,歡迎私信博主姓惑,加以改正褐奴!原文鏈接,demo鏈接 Serv...
    PassersHowe閱讀 1,423評(píng)論 0 5
  • 本文出自 Eddy Wiki 挺益,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-androi...
    eddy_wiki閱讀 3,277評(píng)論 0 20
  • 服務(wù)基本上分為兩種形式 啟動(dòng) 當(dāng)應(yīng)用組件(如 Activity)通過(guò)調(diào)用 startService() 啟動(dòng)服務(wù)時(shí)...
    pifoo閱讀 1,274評(píng)論 0 8
  • 上篇我們講解了Android中的5中等級(jí)的進(jìn)程歉糜,分別是:前臺(tái)進(jìn)程、可見(jiàn)進(jìn)程望众、服務(wù)進(jìn)程匪补、后臺(tái)進(jìn)程伞辛、空進(jìn)程。系統(tǒng)會(huì)按照...
    徐愛(ài)卿閱讀 3,864評(píng)論 6 33
  • Scikit 中的樂(lè)趣 加載示例數(shù)據(jù)集 道瓊斯股票聚類(lèi) 使用 statsmodels 執(zhí)行正態(tài)性測(cè)試 角點(diǎn)檢測(cè) 邊界檢測(cè)
    布客飛龍閱讀 252評(píng)論 0 4