Xamarin.Android廣播接收器與繫結服務
一、前言
學習了前面的活動與服務後,你會發現服務對於活動而言似乎就是透明的,相反活動對於服務也是透明的,所以我們還需要一中機制能夠將服務和活動之間架起一座橋樑,通過本節的學習,你將會學到廣播與繫結服務,這兩種方式恰恰是解決上面問題的關鍵。
二、簡單的廣播接收器
實現一個最簡單的廣播接收器需要繼承BroadcastReceiver類,並且還要實現OnReceive方法,我們可以在專案中新建一個MainReceiver類,然後寫入如下程式碼:
1 public class MainReceiver : BroadcastReceiver 2 { 3 publicoverride void OnReceive(Context context, Intent intent) 4 { 5 6 } 7 }
上面其實已經實現了一個簡單的廣播接收器,並且可以使用。我們還需要註冊廣播接收器,否則廣播接收器就無法接收廣播,所以我們需要在MainActivity.cs中註冊這個廣播接收器。當然為了能夠接近現實,我們需要在OnResume中註冊,在OnPause中登出。
首先我們在OnResume中註冊
1 protected override void OnResume() 2 {3 base.OnResume(); 4 receiver = new MainReceiver(); 5 RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver")); 6 }
接著我們在OnPause中登出
1 protected override void OnPause() 2 { 3 base.OnPause(); 4 UnregisterReceiver(receiver);5 }
全部程式碼如下所示
1 [Activity(Label = "BroadcastStudy", MainLauncher = true, Icon = "@drawable/icon")] 2 public class MainActivity : Activity 3 { 4 private MainReceiver receiver; 5 6 protected override void OnCreate(Bundle bundle) 7 { 8 base.OnCreate(bundle); 9 SetContentView(Resource.Layout.Main); 10 } 11 12 protected override void OnResume() 13 { 14 base.OnResume(); 15 receiver = new MainReceiver(); 16 RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver")); 17 } 18 19 protected override void OnPause() 20 { 21 base.OnPause(); 22 UnregisterReceiver(receiver); 23 } 24 }View Code
註冊好了廣播接收器,我們還需要一個能夠傳送廣播的地方,既然我們說了這節重點解決的是服務與活動的通訊,那麼我們就實現一個服務來發送廣播。為了能夠貼近現實,我們的服務中將會新建一個執行緒,讓這個執行緒傳送一個廣播給這個廣播接收器。
1 [Service] 2 public class MainService : Service 3 { 4 public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) 5 { 6 new Thread(() => 7 { 8 Thread.Sleep(1000); 9 var sintent = new Intent("xamarin-cn.main.receiver"); 10 sintent.PutExtra("_str", "來自服務"); 11 SendBroadcast(sintent); 12 }).Start(); 13 return StartCommandResult.Sticky; 14 } 15 16 public override IBinder OnBind(Intent intent) 17 { 18 return null; 19 } 20 }
這裡我們通過意圖傳遞了一個引數,而在服務中傳送廣播的方法是SendBroadcast。其實我們可以看到在建立意圖的時候傳入了一個字串,而這個字串必須與註冊廣播接收器時指定的字串一致,否則對應的廣播接收器是無法接收到這個廣播的,下面我們修改廣播接收器的OnReceive方法,以便獲取傳遞過來的字串並顯示。
1 public override void OnReceive(Context context, Intent intent) 2 { 3 string str = intent.GetStringExtra("_str"); 4 new Handler().Post(() => 5 { 6 Toast.MakeText(Application.Context, str, ToastLength.Long).Show(); 7 }); 8 }
其中我們通過意圖的GetXXXX方法獲取傳遞過來的引數,然後建立了一個Handler物件並使用Toast傳送了一個提示,這裡使用Handler是為了與UI執行緒同步。因為前面講過只用UI執行緒才能夠訪問控制元件等等物件,而這裡並沒有RunOnUiThread方法,所以我們需要使用Handler物件的Post方法來實現。
最後有了服務還不行,我們還需要開啟這個服務。當然我們依然還是要在OnResume中開啟,在OnPause中暫停。
1 protected override void OnResume() 2 { 3 base.OnResume(); 4 receiver = new MainReceiver(); 5 RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver")); 6 StartService(new Intent(this, typeof(MainService))); 7 } 8 9 protected override void OnPause() 10 { 11 base.OnPause(); 12 UnregisterReceiver(receiver); 13 StopService(new Intent(this, typeof(MainService))); 14 }
最後我們執行之後的結果如下所示
三、服務向活動傳送訊息
上面的例子我們僅僅只是打通了服務與廣播接收器的通訊,而我們今天的主題是服務與活動的雙向通訊,但是為了能夠循序漸進學習,所以我們先學習了服務與廣播接收器怎麼通訊,而這節我們將學習廣播接收器如何與活動通訊。
因為c#並沒有java的部分語言的特性,所以我們沒法直接通過匿名的方法建立一個繼承自BroadcastReceiver類的例項,所以我們需要先建立一個繼承自BroadcastReceiver的具體類,然後在其中定義活動需要響應的方法的委託(Action或者Func),這樣我們可以在例項化這個具體類的同時將活動中的方法賦給廣播接收器,這樣廣播接收器在OnReceive中就可以呼叫活動中的方法了,自然而言就打通了廣播接收器與活動的通訊。當然還有其他的方法,希望讀者可以在留言中留下,以便更多的人進行學習。
首先修改MainReceiver類:
1 public class MainReceiver : BroadcastReceiver 2 { 3 public Action<string> Alert; 4 5 public override void OnReceive(Context context, Intent intent) 6 { 7 string str = intent.GetStringExtra("_str"); 8 if (Alert != null) 9 { 10 Alert(str); 11 } 12 } 13 }
在這裡我們定義了一個委託(Action<string> Alert)以便活動可以重寫,同時還修改了OnReceive中的程式碼,從而使用活動的方法來顯示提示,有了介面之後,我們就可以回到活動中進行重寫了。因為廣播被例項化的步驟是在OnResume中,所以我們這裡直接給出這個方法中的程式碼(這裡我們使用了一個TextView控制元件tv讀者可以需要自行新增下)。
1 protected override void OnResume() 2 { 3 base.OnResume(); 4 receiver = new MainReceiver() 5 { 6 Alert = (s) => 7 { 8 RunOnUiThread(() => 9 { 10 tv.Text = s; 11 }); 12 } 13 }; 14 RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver")); 15 StartService(new Intent(this, typeof(MainService))); 16 }
現在我們就打通了廣播接收器與活動的橋樑,如果有多個方法也是一樣的道理,我們現在執行程式可以發現一切正常,下面筆者還要介紹另一種使用介面的方法,首先我們需要一個介面去規定活動需要實現哪些方法,然後在初始化廣播接收器的同時將活動的例項賦廣播接收器的對應介面變數。下面我們將上面的例子改寫,先定義個含有Alert的介面。
1 public interface IMainInterface 2 { 3 void Alert(string s); 4 }
然後讓活動實現該介面
public class MainActivity : Activity, IMainInterface { private MainReceiver receiver; private TextView tv; public void Alert(string s) { RunOnUiThread(() => { tv.Text = s; }); }
接著我們修改廣播接收器,公開一個該接收的屬性,一遍在廣播接收器被初始化的時候可以複製。
1 public class MainReceiver : BroadcastReceiver 2 { 3 public IMainInterface mainInterface; 4 5 public override void OnReceive(Context context, Intent intent) 6 { 7 string str = intent.GetStringExtra("_str"); 8 if (mainInterface != null) 9 { 10 mainInterface.Alert(str); 11 } 12 } 13 }
回到MainActivity中修改OnResume方法。
1 protected override void OnResume() 2 { 3 base.OnResume(); 4 receiver = new MainReceiver() 5 { 6 mainInterface = this 7 }; 8 RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver")); 9 StartService(new Intent(this, typeof(MainService))); 10 }
最後效果一樣的,讀者可以根據實際的情況選擇。畢竟他們各自都有或多或少的缺點。
四、繫結服務
其實繫結服務就是將服務中的功能公開給活動,只有這樣活動才能呼叫服務中的方法。而這一過程需要經過一個繫結。首先我們需要一個繼承自Binder的類,這樣才能將服務通過介面傳遞給活動。以下為繼承自Binder的類,其中我們需要在初始化時將服務傳入,然後公開一個方法將服務的例項返回。
1 public class MainBinder : Binder 2 { 3 MainService mainService; 4 5 public MainBinder(MainService ms) 6 { 7 mainService = ms; 8 } 9 10 public MainService GetService() 11 { 12 return mainService; 13 } 14 }
接下來我們開啟MainService檔案,實現OnBind方法,並將上面類返回。
1 [Service] 2 public class MainService : Service 3 { 4 public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) 5 { 6 return StartCommandResult.Sticky; 7 } 8 9 public override IBinder OnBind(Intent intent) 10 { 11 return new MainBinder(this); 12 } 13 }
到此為止,服務這邊已經做好了準備。既然是繫結自然不能通過簡單的StartService方法開啟,因為我們還需要OnBind返回的介面,否則活動無法與服務溝通。這就需要在活動中通過BindService方法進行繫結,但是該方法還需要一個實現了IserviceConnection介面的類,因為通過BindService方法進行繫結的操作是非同步的,也就意味著不會阻塞當前呼叫該方法的執行緒,而是在服務成功開啟並並且OnBind方法返回介面後會回撥IserviceConnection中的方法,我們可以看下該介面的方法。
1 public interface IServiceConnection : IJavaObject, IDisposable 2 { 3 void OnServiceConnected(ComponentName name, IBinder service); 4 void OnServiceDisconnected(ComponentName name); 5 }
關於介面的方法,大致的解釋如下:
OnServiceConnected:當服務中的OnBind方法返回介面後將回調該方法,並且通過service引數將OnBind返回的值傳遞給這個方法。
OnServiceDisconnected:當服務被關閉或者主動斷開連線後回撥該方法,如果我們利用這個方法重新恢復連線,或者發出異常並關閉對應的活動。
下面我們實現該介面
1 public class MainServiceConnection : Java.Lang.Object , IServiceConnection 2 { 3 public void OnServiceConnected(ComponentName name, Android.OS.IBinder service) 4 { 5 6 } 7 8 public void OnServiceDisconnected(ComponentName name) 9 { 10 11 } 12 }
這裡我們沒有實現任何程式碼,該類與活動還沒有關聯起來,所以我們需要在活動中新建一個公開的變數去儲存服務的介面。
1 [Activity(Label = "BroadcastStudy", MainLauncher = true, Icon = "@drawable/icon")] 2 public class MainActivity : Activity 3 { 4 private TextView tv; 5 public MainBinder mainBinder;
接著我們就可以實現MainServiceConnection類了。
1 public class MainServiceConnection : Java.Lang.Object , IServiceConnection 2 { 3 MainActivity mainActivity; 4 public MainServiceConnection(MainActivity ma) 5 { 6 mainActivity = ma; 7 } 8 9 public void OnServiceConnected(ComponentName name, Android.OS.IBinder service) 10 { 11 mainActivity.mainBinder = (MainBinder)service; 12 } 13 14 public void OnServiceDisconnected(ComponentName name) 15 { 16 mainActivity.mainBinder = null; 17 } 18 }
最後我們在活動中就可以進行綁定了。
1 [Activity(Label = "BroadcastStudy", MainLauncher = true, Icon = "@drawable/icon")] 2 public class MainActivity : Activity 3 { 4 private IServiceConnection serviceConnection; 5 private TextView tv; 6 public MainBinder mainBinder; 7 8 9 protected override void OnCreate(Bundle bundle) 10 { 11 base.OnCreate(bundle); 12 SetContentView(Resource.Layout.Main); 13 tv = FindViewById<TextView>(Resource.Id.textView1); 14 } 15 16 protected override void OnResume() 17 { 18 base.OnResume(); 19 serviceConnection = new MainServiceConnection(this); 20 BindService(new Intent(this, typeof(MainService)), serviceConnection, Bind.AutoCreate); 21 } 22 23 protected override void OnPause() 24 { 25 base.OnPause(); 26 UnbindService(serviceConnection); 27 } 28 }View Code
通過上面的步驟我們還不能看到實際的效果,下面我們需要在服務中實現一個簡單的方法,只是返回一段字串。
1 public string GetString() 2 { 3 return "來自服務"; 4 }
然後在Main.axml中拖放一個按鈕,並在活動中進行繫結。
1 protected override void OnCreate(Bundle bundle) 2 { 3 base.OnCreate(bundle); 4 SetContentView(Resource.Layout.Main); 5 Button btn = FindViewById<Button>(Resource.Id.button1); 6 btn.Click += (e, s) => 7 { 8 if (mainBinder != null) 9 { 10 string str = mainBinder.GetService().GetString(); 11 Toast.MakeText(this, str, ToastLength.Long).Show(); 12 } 13 }; 14 }
這樣我們就完成了活動呼叫服務中的方法,但是現實開發中。如果是耗時的任務。都是活動呼叫服務公開的方法後立即返回,然後服務在完成之後通過廣播將處理的結果返回給活動,整個過程都是非同步的。