標籤:
分類:C#、Android、VS2015;
建立日期:2016-03-01 一、簡介
Started Service是指被同一個應用程式的某個對象顯式啟動,或者在裝置引導時就已經啟動了(配置了服務的情況)。 二、Started Services的生命週期
前面我們說過,Service只是一種被分離出來的組件(例如從某個Activity中分離出來),可被單獨啟動啟動和停止。因此不論是Started Services還是Bound Services,這些Services都有它自己獨立的生命週期。
示範了Started Services的生命週期期間調用的方法。
一旦服務被啟動(started),它就擁有了自己的生命週期,這是獨立於啟動它的組件的。並且它能夠在後台一直運行下去,即使啟動它的組件已被銷毀也是如此。因此,服務應該能夠在完成工作後自行終止,通過調用StopSelf()即可終止服務自身,或者由其它組件通過調用StopService()也可以終止服務。
對於activity之類的應用程式組件,可以通過調用StartService()啟動服務,並傳入一個給出了服務和服務所需資料的Intent對象。服務將在OnStartCommand()方法中接收到該Intent對象。舉個例子,假定某activity需要把一些資料儲存到線上資料庫中,此activity可以啟動一個守護服務並通過傳入StartService()的一個intent把需要儲存的資料發送給該服務,該服務在OnStartCommand()內接收intent、串連Internet,再進行資料庫交易處理。當事務完成後,服務自行終止,並被系統銷毀。
警告:預設情況下,運行服務的進程與應用程式相同,並且運行在應用程式的主線程中。 因此,如果你的服務要執行計算密集或阻塞的操作,而同時使用者又需要與同一個應用程式中的activity進行互動,那麼服務將會降低activity的效能。為了避免對應用程式效能的影響,你應該在服務中啟動一個新的線程。
使用服務時,最重要的、需要重寫的回調方法有下面幾種。
1、OnStartCommand()
任何一個對象請求開始服務時,系統都會調用這個方法。比如一個activity通過調用StartService()請求服務時,系統將會調用本方法。調用StartService()、重啟系統等也會調用該方法。
一旦本方法執行,服務就被啟動,並在後台一直運行下去。 如果你的代碼實現了本方法,你就有責任在完成工作後通過調用StopSelf()或StopService()終止服務。
OnStartCommand要求必須返回一個StartCommandResult枚舉類型的值,它告訴安卓系統如果擷取的服務已停止是否應該重新啟動這個服務。例如,下面的代碼返回StartResultCommand.Sticky枚舉值,此時執行OnStartCommand方法時將自動重新啟動該服務:
public override StartCommandResult OnStartCommand (……)
{
// start a task here
……
return StartCommandResult.Sticky;
}
StartCommandResult枚舉值可以是下面的選項之一:
- Sticky – 此選項表示將重新啟動指定的服務,同時傳遞給OnStartCommand方法一個值為null的Intent類型的參數。這種服務常用於不斷執行一個需要長時間啟動並執行操作(比如股票行情)。
- RedeliverIntent – 此選項用於正常執行服務時Intent包含有關鍵的附加資訊(extra information)的情況。如果在最後一個Intent發送前停止了服務,此時將重新啟動該服務,並將這個Intent傳遞給OnStartCommand方法。
- NotSticky –該服務不會自動重新啟動。
- StickyCompatibility – 該選項僅僅是為了與API 5之前的版本相容而提供的,其含義與Sticky的含義相同,現在的項目中很少用到它。
在這些返回的選項中,最常用的是StartCommandResult.Sticky。當然其他選項也會在不同的場合下用到,否則提供不同的選項就沒有意義了。
註:Android 1.6及更低版本使用的是OnStart()方法而不是OnStartCommand()方法。從Android 2.0開始,OnStart()已經過時,改為用OnStartCommand()取而代之。
2、OnBind()
當其它組件需要通過BindService()綁定服務時(比如執行RPC),系統會調用本方法。 在本方法的實現代碼中,你必須返回IBinder來提供一個介面,用戶端用它來和服務進行通訊。 你必須確保實現本方法,不過假如你不需要提供綁定,那就返回null即可。
3、OnCreate()
當初次開機服務時被調用一次,一般用它實現初始化工作。
注意僅在第一次啟動服務時,才會調用一次這個方法。如果服務已經運行,則不會再調用本方法。
4、OnDestroy()
當服務用不上了並要被銷毀時,系統會調用本方法。你的服務應該在這個方法中進行清理服務佔用的資源,比如線程、登入的接聽程式listener和接收器receiver等等。這將是服務收到的最後一個調用。
如果組件通過調用StartService()(這會導致onStartCommand()的調用)啟動了服務,那麼服務將一直保持運行,直至自行用stopSelf()終止或由其它組件調用StopService()來終止它。
如果組件調用BindService()來建立服務(此時OnStartCommand()就不會被調用),則服務的生存期就與被綁定的組件一致。一旦所有用戶端都對服務解除了綁定,系統就會銷毀該服務。
僅當記憶體少得可憐、且必須覆蓋擁有使用者焦點的activity的系統資源時,Android系統才會強行終止一個服務。 如果服務被擁有使用者焦點的activity綁定著,則它一般不會被殺死。 如果服務聲明為“在前台運行服務”,則它幾乎永遠不會被殺死。 否則,如果服務已被啟動並且已運行了很長時間,那麼系統將會隨時間推移而降低它在背景工作列表中的層級, 此類服務將很有可能會被殺死——如果服務已經啟動,那你必須好好設計代碼,使其能完美地應付被系統重啟的情況。如果系統殺死了你的服務,只要資源再度夠用,系統就會再次啟動服務(當然這還取決於OnStartCommand()的傳回值)。
關於系統可能會在何時銷毀服務的詳細資料,請參閱進程和線程。 三、建立、啟動和停止Started服務
1、建立自訂的Service
建立服務的第一步是建立繼承自Service的子類(Service類是所有服務的基類)。
與自訂的Activities相對應,通過ServiceAttribute特性聲明(用C#聲明特性時先去掉Attribute尾碼然後再用中括弧將其括起來)可告訴系統這是一個自訂的服務:
[Service]
public class MyService : Service
{
...
}
用ServiceAttribute類聲明Service特性後,它就會自動在AndroidManifest.xml中註冊這個服務,而不需要我們去手工配置AndroidManifest.xml檔案。例如,假定項目名為ServiceDemo1,用[Service]聲明後,它就會自動在AndroidManifest.xml中添加下面的代碼:
<service android:name="servicedemo1.ServiceDemo1"></service>
當然也可以手工在AndroidManifest.xml中直接添加配置代碼,但一般不這樣做(18.1已經說過一遍了),這是因為在設定檔中添加時沒有智能提示,特別是對於初學者來說比較容易出錯。而用繼承自Service的子類實現時,在.cs檔案中添加特性時有智能提示,既免去了配置的麻煩,用起來也非常簡單、直觀、方便。
2、啟動Service
在上下文中(例如某個Activity)調用StartService()方法可以初始化Started Services。
如果該服務正在從某項活動中啟動,那麼可以直接在該活動中調用StartService()方法,否則,可先通過Android.App.Application.Context擷取當前上下文,然後再調用該方法。
要啟動一個服務,需要傳遞一個Intent指定要啟動的服務類型以及當前上下文。
例如,下面的代碼在一個活動中啟動MyService類型的服務:
this.StartService (new Intent (this, typeof(MyService)));
從Started Services生命週期中我們已經知道,調用StartService()方法將導致Android調用服務中提供的OnStartCommand()方法。同時也知道了OnStartCommand()要求必須返回一個StartCommandResult枚舉類型的值,它告訴安卓系統如果擷取的服務已停止是否應該重新啟動這個服務。
例如,下面的代碼返回StartResultCommand.Sticky枚舉值,此時執行OnStartCommand方法時將自動重新啟動該服務:
public override StartCommandResult OnStartCommand (Intent intent, StartCommandFlags flags, int startId)
{
// start a task here
new Task (() => {
// long running code
DoWork();
}).Start();
return StartCommandResult.Sticky;
}
【注意】:在該方法中必須使用Task或者自訂的Thread來執行服務的初始化工作。這是因為服務是運行在UI線程上的,任何長時間啟動並執行任務都會讓UI渲染停頓,從而導致應用程式無響應。而使用Task或自訂的Thread來執行服務的初始化工作,不會引起介面停頓的現象。
3、停止Service
除非任務開始後打算無限期地運行下去,否則一個已啟動的服務應調用StopSelf方法停止它無休止地長時間運行。這很重要,因為Started Services是一個獨立啟動並執行組件,運行期間將繼續佔用系統的繪製資源,直到它被顯式停止或被作業系統關閉。
下面的代碼示範了如何在完成任務後調用StopSelf()方法停止服務:
public void DoWork ()
{
var t = new Task (() => {
Thread.Sleep (5000); //類比長時間執行的任務
StopSelf ();
});
t.Start();
}
或者:
public void DoWork ()
{
var t = new Thread (() => {
Log.Debug ("DemoService", "Doing work");
Thread.Sleep (5000);
Log.Debug ("DemoService", "Work complete");
StopSelf ();
});
t.Start ();
}
另外,為了避免無限期地繼續服務的可能性,調用方還可以通過調用StopService方法請求停止該服務,如下所示:
StopService (new Intent (this, typeof(MyService)));
當服務停止時,Started Service會自動調用服務中的OnDestroy方法,在這個方法中應該做一些清理服務所佔用的資源的工作。
在服務類中,只需要重寫OnDestroy方法即可:
public override void OnDestroy ()
{
base.OnDestroy ();
// 在此處編寫清理資源的代碼
}
多個調用方都可以請求啟動服務,如果某個外部請求啟動服務,也可以將startId傳遞到OnStartCommand方法,以防止該服務被過早地停止。StartId對應最後一次調用的StartService方法,每次執行OnStartCommand方法都會遞增該值。因此,如果對StartService後面的請求還沒有導致對OnStartCommand的調用,此時服務可以調用StopSelfResult方法並傳遞它收到的startId最新值。如果調用StartServic沒有導致運行OnStartCommand,則系統不會停止該服務,因為startId調用中所使用的方法將不會對應於最新的StartService調用。
一個started服務必須自行管理生命週期。也就是說,系統不會終止或銷毀這類服務,除非必須恢複系統記憶體並且服務返回後一直維持運行。 因此,服務必須通過調用stopSelf()自行終止,或者其它組件可通過調用stopService()來終止它。
再強調一遍:當服務完成工作後,你的應用程式應該及時終止它,這非常重要。因為這樣可以避免系統資源的浪費,並能節省電池電量的消耗。必要時,其它組件可以通過調用StopService()來終止服務。即使你的服務允許綁定,你也必須保證它在收到對OnStartCommand()的調用時能夠自行終止。
用StopSelf()或StopService()的終止請求一旦發出,系統就會儘快銷毀服務。
不過,如果你的服務要同時處理多個OnStartCommand()請求,那麼,在處理啟動請求的過程中,你就不應該去終止服務,因為你可能接收到了一個新的啟動請求(在第一個請求處理完畢後終止服務將停止第二個請求的處理。為了避免這個問題,你可以用StopSelf(int)來確保終止服務的請求總是根據最近一次的啟動請求來完成。也就是說,當你調用StopSelf(int) 時,你把啟動請求ID(發送給OnStartCommand()的startId)傳給了對應的終止請求。這樣,如果服務在你可以調用StopSelf(int)時接收到了新的啟動請求,則ID將會不一樣,服務將不會被終止。 四、樣本1--StartedServiceDemo1
運行
主要設計步驟
(1)添加ch1601_Main.axml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/ch1601StartService" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="啟動服務" /> <Button android:id="@+id/ch1601StopService" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="停止服務" /></LinearLayout>
(2)添加ch1601ServiceDemo.cs
using Android.App;using Android.Content;using Android.OS;using Android.Runtime;using Android.Widget;using System.Threading;namespace MyDemos.SrcDemos{ [Service] public class ch1601ServiceDemo : Service { Thread thread; [return: GeneratedEnum] public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId) { //擷取主線程的訊息迴圈後,就可以在主線程中顯示來自服務的訊息了 var myHandler = new Handler(MainLooper); //在此處執行需要長時間處理的服務 thread = new Thread(() => { //處理過程中,還可以告訴使用者處理的狀態 //這裡用每隔3秒顯示一次訊息來類比,此服務可隨時被MainActivity終止 for (int i = 1; i <= 10; i++) { var msg = string.Format("這是來自服務的第{0}個訊息", i); Thread.Sleep(3000); myHandler.Post(() => { Toast.MakeText(this, msg, ToastLength.Long).Show(); }); } StopSelf(); }); thread.Start(); return StartCommandResult.NotSticky; } public override void OnDestroy() { base.OnDestroy(); thread.Abort(); var myHandler = new Handler(MainLooper); myHandler.Post(() => { Toast.MakeText(this, "服務已停止", ToastLength.Long).Show(); }); } //基類要求實現的介面 public override IBinder OnBind(Intent intent) { return null; } }}
注意,如果你在運行中發現中文顯示為亂碼,別忘了你需要在AssemblyInfo.cs檔案中指定地區語言(前面章節已經介紹過),即修改下面的語句(在參數中指定“zh-CN”):
[assembly: AssemblyCulture("zh-CN")]
網上介紹的什麼更改進階儲存選項都是挖坑的,千萬別信。
(3)添加ch1601MainActivity.cs
using Android.App;using Android.Content;using Android.OS;using Android.Widget;namespace MyDemos.SrcDemos{ [Activity(Label = "ch1601MainActivity")] public class ch1601MainActivity : Activity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.ch1601_Main); Intent intent = new Intent(this, typeof(ch1601ServiceDemo)); var start = FindViewById<Button>(Resource.Id.ch1601StartService); start.Click += delegate { StartService(intent); Toast.MakeText(this, "服務已啟動!", ToastLength.Short).Show(); }; var stop = FindViewById<Button>(Resource.Id.ch1601StopService); stop.Click += delegate { StopService(intent); Toast.MakeText(this, "服務被強行請求停止!", ToastLength.Short).Show(); }; } }}
【Android】16.2 Started Services