一、前言

学习了活动与服务后,你会发现服务对于活动而言似乎就是透明的,相反活动对于服务也是透明的,所以我们还需要一中机制能够将服务和活动之间架起一座桥梁,通过本节的学习,你将会学到广播与绑定服务,这两种方式恰恰是解决上面问题的关键。

二、简单的广播接收器

实现一个最简单的广播接收器需要继承BroadcastReceiver类

[Activity(Label = "BroadcastStudy", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        private MainReceiver receiver;


        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
        }


        protected override void OnResume()
        {
            base.OnResume();
            receiver = new MainReceiver();
            RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver"));
        }


        protected override void OnPause()
        {
            base.OnPause();
            UnregisterReceiver(receiver);
        }
  }

注册好了广播接收器,我们还需要一个能够发送广播的地方,既然我们说了这节重点解决的是服务与活动的通信,那么我们就实现一个服务来发送广播。为了能够贴近现实,我们的服务中将会新建一个线程,让这个线程发送一个广播给这个广播接收器。

[Service]
    public class MainService : Service
    {
        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
        {
            new Thread(() =>
            {
                Thread.Sleep(1000);
                var sintent = new Intent("xamarin-cn.main.receiver");
                sintent.PutExtra("_str", "来自服务");
                SendBroadcast(sintent);
            }).Start();
            return StartCommandResult.Sticky;
        }


        public override IBinder OnBind(Intent intent)
        {
            return null;
        }
     }

这里我们通过意图传递了一个参数,而在服务中发送广播的方法是SendBroadcast。其实我们可以看到在创建意图的时候传入了一个字符串,而这个字符串必须与注册广播接收器时指定的字符串一致,否则对应的广播接收器是无法接收到这个广播的,下面我们修改广播接收器的OnReceive方法,以便获取传递过来的字符串并显示。

public override void OnReceive(Context context, Intent intent)
        {
            string str = intent.GetStringExtra("_str");
            new Handler().Post(() =>
            {
                Toast.MakeText(Application.Context, str, ToastLength.Long).Show();
            });
        }

其中我们通过意图的GetXXXX方法获取传递过来的参数,然后创建了一个Handler对象并使用Toast发送了一个提示,这里使用Handler是为了与UI线程同步。因为前面讲过只用UI线程才能够访问控件等等对象,而这里并没有RunOnUiThread方法,所以我们需要使用Handler对象的Post方法来实现。

最后有了服务还不行,我们还需要开启这个服务。当然我们依然还是要在OnResume中开启,在OnPause中暂停。

protected override void OnResume()
        {
            base.OnResume();
            receiver = new MainReceiver();
            RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver"));
            StartService(new Intent(this, typeof(MainService)));
        }


        protected override void OnPause()
        {
            base.OnPause();
            UnregisterReceiver(receiver);
            StopService(new Intent(this, typeof(MainService)));
        }

三、服务向活动发送消息

上面的例子我们仅仅只是打通了服务与广播接收器的通信,而我们今天的主题是服务与活动的双向通信,但是为了能够循序渐进学习,所以我们先学习了服务与广播接收器怎么通信,而这节我们将学习广播接收器如何与活动通信。

因为c#并没有java的部分语言的特性,所以我们没法直接通过匿名的方法创建一个继承自BroadcastReceiver类的实例,所以我们需要先创建一个继承自BroadcastReceiver的具体类,然后在其中定义活动需要响应的方法的委托(Action或者Func),这样我们可以在实例化这个具体类的同时将活动中的方法赋给广播接收器,这样广播接收器在OnReceive中就可以调用活动中的方法了,自然而言就打通了广播接收器与活动的通信。当然还有其他的方法,希望读者可以在留言中留下,以便更多的人进行学习。

首先修改MainReceiver类:

public class MainReceiver : BroadcastReceiver
{
        public Action<string> Alert;


        public override void OnReceive(Context context, Intent intent)
        {
            string str = intent.GetStringExtra("_str");
            if (Alert != null)
            {
                Alert(str);
            }
        }
}

在这里我们定义了一个委托(Action<string>  Alert)以便活动可以重写,同时还修改了OnReceive中的代码,从而使用活动的方法来显示提示,有了接口之后,我们就可以回到活动中进行重写了。因为广播被实例化的步骤是在OnResume中,所以我们这里直接给出这个方法中的代码(这里我们使用了一个TextView控件tv读者可以需要自行添加下)。

protected override void OnResume()
        {
            base.OnResume();
            receiver = new MainReceiver()
            {
                Alert = (s) =>
                {
                    RunOnUiThread(() =>
                    {
                        tv.Text = s;
                    });
                }
            };
            RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver"));
            StartService(new Intent(this, typeof(MainService)));
        }

现在我们就打通了广播接收器与活动的桥梁,如果有多个方法也是一样的道理,我们现在运行程序可以发现一切正常,下面笔者还要介绍另一种使用接口的方法,首先我们需要一个接口去规定活动需要实现哪些方法,然后在初始化广播接收器的同时将活动的实例赋广播接收器的对应接口变量。下面我们将上面的例子改写,先定义个含有Alert的接口。

public interface IMainInterface
{
     void Alert(string s);
}

然后让活动实现该接口

public class MainActivity : Activity, IMainInterface
    {
        private MainReceiver receiver;
        private TextView tv;


        public void Alert(string s)
        {
            RunOnUiThread(() =>
            {
                tv.Text = s;
            });
        }

接着我们修改广播接收器,公开一个该接收的属性,一遍在广播接收器被初始化的时候可以复制。

public class MainReceiver : BroadcastReceiver
{
        public IMainInterface mainInterface;


        public override void OnReceive(Context context, Intent intent)
        {
            string str = intent.GetStringExtra("_str");
            if (mainInterface != null)
            {
                mainInterface.Alert(str);
            }
        }
}

回到MainActivity中修改OnResume方法。

protected override void OnResume()
        {
            base.OnResume();
            receiver = new MainReceiver()
            {
                mainInterface = this
            };
            RegisterReceiver(receiver, new IntentFilter("xamarin-cn.main.receiver"));
            StartService(new Intent(this, typeof(MainService)));
        }

 

最后效果一样的,读者可以根据实际的情况选择。毕竟他们各自都有或多或少的缺点。