原文:Xamarin.Android開發實踐(七)html
學習了前面的活動與服務後,你會發現服務對於活動而言彷佛就是透明的,相反活動對於服務也是透明的,因此咱們還須要一中機制可以將服務和活動之間架起一座橋樑,經過本節的學習,你將會學到廣播與綁定服務,這兩種方式偏偏是解決上面問題的關鍵。java
實現一個最簡單的廣播接收器須要繼承BroadcastReceiver類,而且還要實現OnReceive方法,咱們能夠在項目中新建一個MainReceiver類,而後寫入以下代碼:c#
1 public class MainReceiver : BroadcastReceiver 2 { 3 public override void OnReceive(Context context, Intent intent) 4 { 5 6 } 7 }
上面其實已經實現了一個簡單的廣播接收器,而且可使用。咱們還須要註冊廣播接收器,不然廣播接收器就沒法接收廣播,因此咱們須要在MainActivity.cs中註冊這個廣播接收器。固然爲了可以接近現實,咱們須要在OnResume中註冊,在OnPause中註銷。異步
首先咱們在OnResume中註冊ide
1 protected override void OnResume() 2 { 3 base.OnResume(); 4 receiver = new MainReceiver(); 5 RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver")); 6 }
接着咱們在OnPause中註銷post
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 }
註冊好了廣播接收器,咱們還須要一個可以發送廣播的地方,既然咱們說了這節重點解決的是服務與活動的通訊,那麼咱們就實現一個服務來發送廣播。爲了可以貼近現實,咱們的服務中將會新建一個線程,讓這個線程發送一個廣播給這個廣播接收器。this
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方法,以便獲取傳遞過來的字符串並顯示。url
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方法來實現。spa
最後有了服務還不行,咱們還須要開啓這個服務。固然咱們依然仍是要在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 }
經過上面的步驟咱們還不能看到實際的效果,下面咱們須要在服務中實現一個簡單的方法,只是返回一段字符串。
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 }
這樣咱們就完成了活動調用服務中的方法,可是現實開發中。若是是耗時的任務。都是活動調用服務公開的方法後當即返回,而後服務在完成以後經過廣播將處理的結果返回給活動,整個過程都是異步的。