Xamarin.Android其他類型的服務
一、前言
前面我們已經學了關於服務的很多知識,但是對於真實的開發那些遠遠不夠,通過這節我們將學習其他類型的服務,比如前台服務、IntentService和消息服務。下面我們開始進入正題。
二、前台服務
顧名思義,就是擁有前台的優先等級。當然服務還是不可見的。因為前面我們介紹過 Android系統會在低內存的情況下將一些長時間不用的應用關閉,如果還是不夠,那么就會通過關閉服務服務來達到目的,然而對於某些應用而言,這樣將會 影響用戶的正常使用。比如聽音樂,我們基本上都會打開應用選擇歌曲后將應用置為后台。但是你會發現通知欄中會存在這個通知並且無法移除,只有正確的退出這 個應用了才會消失,而這節我們就要實現這個功能。
首先我們必須要用一個通知,通過這個通知我們的服務才能夠變成前台服務,這里我們新建一個名為ForegroundService的服務,然后重寫OnStartCommand方法。
1 public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) 2 { 3 var notify = new Notification(Resource.Drawable.Icon, "前台服務"); 4 var activityIntent = new Intent(this, typeof(MainActivity)); 5 var activityPintent = PendingIntent.GetActivity(this, 0, activityIntent, PendingIntentFlags.UpdateCurrent); 6 notify.SetLatestEventInfo(this, "標題", "內容", activityPintent); 7 StartForeground((int)NotificationFlags.ForegroundService, notify); 8 return StartCommandResult.Sticky; 9 }
很多代碼都是我們在討論通知的時候都已經掌握的了,既然是前台服務,自然最后發送這個方法是不同的,我們需要使用服務的StartForeground來發送這個通知,同時第一個參數也要設置為前台服務,這樣我們就可以看到如圖的結果了(需要在MainActivity的OnCreate方法中開啟該服務)。
雖然已經是一個前台服務了,但是我們只能通過服務不斷的更新這個通知,而無法接收用戶的事件,下面我們還要實現一個自定義界面的通知,上面有一個Text和兩個Button用戶點擊不同的按鈕后將由服務去更新通知,從而改變Text中的值。
首先我們在Resources/layout/下新建一個NotificationLayout視圖,並在其中寫入如下的xml標記。
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:p1="http://schemas.android.com/apk/res/android" 3 p1:minWidth="25px" 4 p1:minHeight="25px" 5 p1:layout_width="match_parent" 6 p1:layout_height="match_parent" 7 p1:id="@+id/relativeLayout1"> 8 <TextView 9 p1:text="0" 10 p1:textAppearance="?android:attr/textAppearanceLarge" 11 p1:layout_width="wrap_content" 12 p1:layout_height="match_parent" 13 p1:id="@+id/textView1" /> 14 <Button 15 p1:text="顯示1" 16 p1:layout_width="wrap_content" 17 p1:layout_height="match_parent" 18 p1:layout_toRightOf="@id/textView1" 19 p1:id="@+id/button1" /> 20 <Button 21 p1:text="顯示2" 22 p1:layout_width="wrap_content" 23 p1:layout_height="match_parent" 24 p1:layout_toRightOf="@id/button1" 25 p1:id="@+id/button2" /> 26 </RelativeLayout>
打開ForegroundService並在其中新建一個CreateNotify方法,並在其中寫入如下代碼。
1 public Notification CreateNotify(string text) 2 { 3 notify = new Notification(Resource.Drawable.Icon, "前台服務"); 4 var sintent = new Intent(this, typeof(MainActivity)); 5 sintent.SetFlags(ActivityFlags.LaunchedFromHistory); 6 notify.ContentView = new RemoteViews(PackageName, Resource.Layout.NotificationLayout); 7 notify.ContentIntent = PendingIntent.GetActivity(this, 0, sintent, PendingIntentFlags.NoCreate); 8 9 var btn1Intent = new Intent(this, typeof(ForegroundService)); 10 btn1Intent.PutExtra("showBtn1", true); 11 var btn1Pintent = PendingIntent.GetService(this, 0, btn1Intent, PendingIntentFlags.UpdateCurrent); 12 notify.ContentView.SetOnClickPendingIntent(Resource.Id.button1, btn1Pintent); 13 14 var btn2Intent = new Intent(this, typeof(ForegroundService)); 15 btn2Intent.PutExtra("showBtn2", true); 16 var btn2Pintent = PendingIntent.GetService(this, 1, btn2Intent, PendingIntentFlags.UpdateCurrent); 17 notify.ContentView.SetOnClickPendingIntent(Resource.Id.button2, btn2Pintent); 18 19 notify.ContentView.SetTextViewText(Resource.Id.textView1, text); 20 return notify; 21 }
這里需要說明下,一旦通知發送出去了我們是無法同ContentView的Set去修改控件的,只能重新發送這個同時去更新舊的通知,所以筆者才需要一個單獨的方法負責創建通知。上面的代碼我們之前都已經學習過了,不理解的可以看這篇文件《Xamarin.Android通知詳解》。筆者設置按鈕的點擊事件是打開服務本身,同時還通過Intent傳遞了一個參數,因為后面我們需要通過這些參數去區分哪個按鈕按下了,同時還要注意PendingIntent的GetService方法的第二個參數,我們兩者都是0那么會造成按下按鈕1和按紐2都傳遞同樣的參數。下面我們在OnStartCommand中實現響應。
1 public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) 2 { 3 if (notify == null) 4 { 5 notify = CreateNotify("初始化"); 6 StartForeground((int)NotificationFlags.ForegroundService, notify); 7 } 8 bool isBtn1Click = intent.GetBooleanExtra("showBtn1", false); 9 bool isBtn2Click = intent.GetBooleanExtra("showBtn2", false); 10 if (isBtn1Click) 11 { 12 if (notify != null) 13 { 14 notify = CreateNotify("來自按鈕1"); 15 StartForeground((int)NotificationFlags.ForegroundService, notify); 16 } 17 } 18 else if (isBtn2Click) 19 { 20 if (notify != null) 21 { 22 notify = CreateNotify("來自按鈕2"); 23 StartForeground((int)NotificationFlags.ForegroundService, notify); 24 } 25 } 26 return StartCommandResult.Sticky; 27 }
可以看到我們通過GetBooleanExtra獲取了通過意圖傳遞的參數,當然筆者這里的用法不同於前面的方式,我還傳入了第二個參數,這樣做的目的就是在意圖中不存在該值的時候將會把第二參數返回,下面就是進行不同的判斷從而更新通知。
最終運行結果如下所示:
按下“顯示1”后
按下“顯示2”后
至此我們就完成了前台服務的學習。
三、IntentService
很多時候我們都需要利用服務進行耗時的操作,勢必需要創建新的線程去處理。但是普通的Service並不會主動創建而需要開發者自行在OnStartCommand中去創建,為此就繁衍出了IntentService類,它會為我們創建好線程去執行我們的代碼,從而避免一些代碼。但是我們不能重寫OnStartCommand方法而應該是OnHandleIntent方法。比如下面的代碼。
1 [Service] 2 public class MainIntentService : IntentService 3 { 4 protected override void OnHandleIntent(Android.Content.Intent intent) 5 { 6 Thread.Sleep(1000); 7 Toast.MakeText(this, "來自新線程" , ToastLength.Long).Show(); 8 } 9 }
通過下面的截圖我們可以看到OnHandleIntent中執行的代碼是新建的一個線程
關於IntentService的使用非常簡單。
四、通信服務
上一節關於綁定服務的學習中,活動必須確切的知道服務的類型才能使用,這樣就加大了他們之間的耦合度,而通過本節我們將會學習如何通過消息機制將他們解耦,首先我們需要理解Handler類,它將會負責處理發送過來的消息,我們需要繼承該類,並重寫HandleMessage方法,我們新建一個MainHandler類並繼承該類,然后重寫。
1 public class MainHandler : Handler 2 { 3 public override void HandleMessage(Message msg) 4 { 5 Toast.MakeText(Application.Context, "接收到的消息的what為" + msg.What.ToString() + " 內容為" + msg.Data.GetString("_str"), ToastLength.Short).Show(); 6 } 7 }
這里我們僅僅只是簡單的輸出了消息的類型以及消息傳遞的參數,下面我們還需要一個服務將這個消息傳遞給活動。
1 [Service] 2 public class MessengerService : Service 3 { 4 Messenger messenger; 5 6 public MessengerService() 7 { 8 messenger = new Messenger(new MainHandler()); 9 } 10 11 public override Android.OS.IBinder OnBind(Android.Content.Intent intent) 12 { 13 return messenger.Binder; 14 } 15 }
這里我們還需要Messenger去封裝MainHandler,因為MainHandler是無法在OnBind中直接返回的,只有Messenger的Binder屬性可以,自然活動那邊就需要接收這個接口,下面是IserviceConnection的實現。
1 public class MessengerServiceConnection : Java.Lang.Object , IServiceConnection 2 { 3 MainActivity mainActivity; 4 5 public MessengerServiceConnection(MainActivity ma) 6 { 7 mainActivity = ma; 8 } 9 10 public void OnServiceConnected(ComponentName name, Android.OS.IBinder service) 11 { 12 mainActivity.messenger = new Messenger(service); 13 } 14 15 public void OnServiceDisconnected(ComponentName name) 16 { 17 mainActivity.messenger.Dispose(); 18 mainActivity.messenger = null; 19 } 20 }
這里的方式依然是使用之前我們講述綁定服務時候的方法,只是在我們接收接口的時候是用Messenger的去封裝的,這樣就統一了。我們的活動只要有Messenger,並且對應的服務都滿足這個接口那么我們的活動就可以靈活的綁定任意服務,使用他們的功能了,最后是MainActivity的代碼(需要在Main.axml中拖拽兩個按鈕,以便發送消息給服務)。
1 [Activity(Label = "OtherService", MainLauncher = true, Icon = "@drawable/icon")] 2 public class MainActivity : Activity 3 { 4 public Messenger messenger; 5 6 protected override void OnCreate(Bundle bundle) 7 { 8 base.OnCreate(bundle); 9 SetContentView(Resource.Layout.Main); 10 BindService(new Intent(this, typeof(MessengerService)), new MessengerServiceConnection(this), Bind.AutoCreate); 11 Button btn1 = FindViewById<Button>(Resource.Id.button1); 12 btn1.Click += (e, s) => 13 { 14 Message msg = Message.Obtain(); 15 Bundle b = new Bundle(); 16 b.PutString("_str", "消息1"); 17 msg.Data = b; 18 msg.What = 1; 19 messenger.Send(msg); 20 }; 21 22 Button btn2 = FindViewById<Button>(Resource.Id.button2); 23 btn2.Click += (e, s) => 24 { 25 Message msg = Message.Obtain(); 26 Bundle b = new Bundle(); 27 b.PutString("_str", "消息2"); 28 msg.Data = b; 29 msg.What = 2; 30 messenger.Send(msg); 31 }; 32 } 33 }
唯一要說的就是發送消息,我們需要實例化Messager(不是Messenger),設置它的what,如果我們還需要傳遞更多的參數我們可以實例化一個Bundle,然后通過其PutXXX方法賦值,最后賦給Message的Data類型,最后要通過Messenger實例的Send方法發送這個消息,那么MainHandler就可以處理這個消息了。
下面是實際的運行圖。
點擊“發送消息1”按鈕后
點擊“發送消息2”按鈕后