分类:C#、Android、VS2015;
创建日期:2016-03-03
一、Messager类简介
本章前面曾经说过,要在Android上执行带服务的进程间通信(IPC),既可以用Messenger类来实现,也可以用更高级的AIDL技术来实现(AIDL:Android接口定义语言)。
这一节我们学习首选的方案:用Messenger实现IPC的基本设计思路。
IPC:进程间通信(Inter-process Communication)。
IPC with Service:带服务的进程间通信。
实际上,IPC就是C/S模式的一个简单的实现。当用Messenger实现进程间通信时,它用一个单独的进程将从客户端发送的消息全部放入一个消息队列中,然后再从该队列中每次取出一个消息来处理。此外,Messenger并不是给客户端公开一个服务接口,而是让客户端发送消息对象给继承自Handler的类的实例,然后让该实例去处理消息对象来实现服务。
二、示例4功能和运行截图
1、例子功能
该例子演示如何利用消息服务实现不同进程间的消息传递,假定有进程A和进程B同时运行,并准备从进程B中向进程A发送消息。因此,在这个例子中,例子需要分别设计服务器端(进程A)和客户端(进程B)。
服务端(MessengerServiceDemo):表示接收消息的进程,该进程必须提供消息服务才能接收消息。
客户端(MessengerClientDemo):发送消息的进程,在该进程中,可发送消息给提供服务的进程。
分别实现服务器端代码和客户端代码后,就可以实现不同进程间的消息传递了。
2、运行截图
说明:本例子无法在Android 4.4.2(API 19)上运行,原因未知。
下面的截图是在Android 6.0(API 23)模拟器下运行的结果。先运行服务器端程序(效果见左侧图),单击服务器的【启动服务】后,再单击下方的小方块运行客户端程序(效果见右侧图)。
单击客户端程序的【发送消息】按钮,再单击下方的小方块切换到服务器程序,就会看到服务端已经接收到了客户端发送的消息,如下面的界面所示:
单击【停止服务】终止运行。
实际上,应用程序中使用最多的还是通过Messenger和Android系统服务或者Web服务进行消息传递,这个例子虽然已经尽可能简化了,但仍然搞的有点复杂,这样做的目的仅仅是为了让你看出这确实是两个进程之间在传递消息。
三、设计步骤
下面是相关的实现代码。
1、服务端代码
(1)添加ch1704_Main.xml文件
(2)添加ch1704MessengerService.cs文件
using Android.App;using Android.Content;using Android.OS;namespace MyDemos.SrcDemos{ [Service] [IntentFilter(new string[] { action })] public class ch1704MessengerService : Service { public const string action = "com.mydemos.ch1704MessengerService"; private Messenger messenger; public ch1704MessengerService() { messenger = new Messenger(new MessengerServiceHandler()); } public override IBinder OnBind(Intent intent) { ch1704MainActivity.adapter.Add("消息客户端已绑定到本消息服务"); return messenger.Binder; } private class MessengerServiceHandler : Handler { public override void HandleMessage(Message msg) { string text = msg.Data.GetString("InputText"); string s = string.Format("来自消息客户端的消息:\nWhat={0}\nInputText={1}", msg.What, text); ch1704MainActivity.adapter.Add(s); } } }}
(3)添加ch1704MainActivity.cs文件
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Android.App;using Android.Content;using Android.OS;using Android.Widget;namespace MyDemos.SrcDemos{ [Activity(Label = "ch1704MainActivity")] public class ch1704MainActivity : Activity { public static ArrayAdapteradapter; Intent serviceIntent; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.ch1704_Main); var listView1 = FindViewById (Resource.Id.listView1); adapter = new ArrayAdapter (this, Android.Resource.Layout.TestListItem); listView1.Adapter = adapter; var start = FindViewById
(4)运行服务端程序
运行后不要关闭它。
2、客户端代码
(1)新建客户端项目
项目和解决方案名:ch1704MessengerClientDemo
模板:Blank App(Android)
(2)Main.axml文件
(3)MainActivity.cs文件
using Android.App;using Android.Content;using Android.Widget;using Android.OS;namespace MessengerClientDemo{ [Activity(Label = "MessengerClientDemo", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity { bool isBound = false; Messenger messenger; ServiceConnection conn; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); var button = FindViewById
(4)运行客户端程序
运行后单击【发送消息】,再单击下方的小方块切换到服务器程序,就会看到截图所示的界面。
四、基本实现思路
1、在服务器端绑定消息服务
利用消息传递实现服务时(别忘了刚说过Messenger就是C/S模式的一个简单实现),需要在服务器端做两件事:
1. 创建一个继承自Service的类(例如:ch1704MessengerService),并在该类中创建消息服务类的实例提供服务。
2. 创建一个继承自Handler的消息服务类(例如:HandlerDemo)。
下面的代码演示了如何在服务器端实现这两件事:
[Service]
[IntentFilter(new String[]{"com.mydemos.ch1704MessengerService"})]
public class MessengerServiceDemo : Service
{
Messenger messengerDemo;
public MessengerServiceDemo ()
{
messengerDemo = new Messenger (new StockHandler());
}
public override IBinder OnBind (Intent intent)
{
return messengerDemo.Binder;
}
class HandlerDemo : Handler
{
public override void HandleMessage (Message msg)
{
...
}
}
}
继承者Handler的HanderDemo用于客户端每次调用时接收回调。此Handler用于创建一个Messenger对象(它是一个对Handler的引用)。
请注意Handler中的 HandleMessage() 方法,这里是服务接收输入Message 的地方,也是根据what数字来决定要执行什么操作的地方。
详细点说,你的实现代码必须重写下面的两个回调方法:
- OnServiceConnected():系统需要调用该方法将参数传递给服务中的OnBind()方法从而得到它返回的IBinder。
- OnServiceDisconnected():当客户端与服务的连接发生意外中断时,比如服务崩溃或者被杀死时,Android系统将会自动调用该方法。
通过调用BindService(),传入已实现的ServiceConnection。
当系统调用你的OnServiceConnected()回调方法时,你就可以利用接口中定义的方法调用服务了。
2、客户端绑定到服务
客户端用IBinder将Messenger(引用服务的Handler)实例化,客户端用它向服务发送Message对象。
服务接收Handler中的每个Message——确切的说,是在handleMessage()方法中接收。
通过这种方式,客户端不需要调用服务中的“方法”。取而代之的是,客户端通过发送“消息”(Message对象)给服务,服务则接收位于Handler中的这个消息。
当客户端绑定到服务并将消息发送到包含服务的Messenger对象中时,ch1704MessengerService类就会自动调用HandleMessage方法。
(1)在客户端实现ServiceConnection
应用程序组件(客户端)可以通过调用 BindService() 来绑定服务,此时Android系统会调用服务的 OnBind() 方法,返回一个用于和服务进行交互的 IBinder。
绑定是异步进行的。 调用BindService()以后该方法将立即返回,但是并不会向客户端返回 IBinder 。客户端为了接收 IBinder,必须创建一个 ServiceConnection 的实例,并把它传给 BindService()。 ServiceConnection 包含了一个回调方法,系统将会调用该方法来传递客户端所需的那个 IBinder。
注意:只有activity、服务和content provider才可以绑定到服务——你不能从广播接收器(broadcast receiver)中绑定服务。
客户端要断开与服务的联接,请调用UnbindService()。当客户端被销毁时,与服务的绑定也将解除。但与服务交互完毕后,或者你的activity进入pause状态时,你都应该确保解除绑定,以便服务能够在执行完毕后及时关闭。
利用这个ServiceConnection ,客户端就能够把它传入 BindService() 完成与服务的绑定。
以下是有关绑定服务的一些重要注意事项:
你应该确保捕获DeadObjectException异常,当客户端与服务的连接中断时会抛出该异常。这是远程方法唯一会抛出的异常。
对象的引用计数是跨进程的。你通常应该成对地进行绑定和解除绑定,并与客户端生命周期的启动和结束过程相呼应。比如:
(1)如果仅当你的activity可见时才需要与服务进行交互,则你应该在OnStart()中进行绑定,并在OnStop()中解除绑定。
(2)如果你的activity需要在Stopped后并进入后台期间仍然能接收响应,则你可以在OnCreate()中进行绑定,并在(1)中解除绑定。请注意这表明你的activity在整个运行期间都需要使用服务(即使在后台),因此假如服务位于其它进程中,则你会增加进程的重量级,进程也会更容易被系统杀死。
注意:你通常不应该在activity的OnResume()和OnPause()中绑定和解除绑定,因为这两个回调方法在每次切换生命周期状态时都会发生,这时你应该让处理工作最少化。而且,如果应用程序中有多个activity都绑定到同一个服务上,则在两个activity间切换时都会发生状态转换,因为当前activity解除绑定(在pause时)后,紧接着下一个activity又会进行绑定(resume时),所以服务也许在销毁后马上就重建。
更多展示绑定服务的示例代码,请参阅ApiDemos中的RemoteService类。
一旦服务被所有客户端解除绑定,则Android系统将会销毁它(除非它同时又通过调用OnStartCommand()被启动了)。因此,如果你的服务就是一个纯粹的bound服务,那你就不需要管理它的生命周期——Android系统会替你管理,根据是否还有客户端对其绑定即可。
不过,如果你选择实现OnStartCommand()回调方法,那么你就必须显式地终止服务,因为此服务现在已经被视为started Service了。这种情况下,无论是否还存在客户端与其绑定,此服务都会运行下去,直至自行用StopSelf()终止或由其它组件调用StopService()来终止。
此外,如果你的服务是started Service且允许被绑定,那么系统调用你的OnUnbind()方法时,你可以选择返回true。这样做的结果就是,下次客户端绑定时将会收到OnRebind()调用而不是收到OnBind()调用,不过,虽然OnRebind()返回void,但客户端仍然能在它的OnServiceConnected()回调方法中收到IBinder。
关于started服务生命周期的更多信息,请参阅服务文档。
3、从客户端发送消息
客户端若要用Messenger来调用服务端提供的服务,客户端需要创建一个Messenger对象,然后调用该对象的Send方法发送消息。换言之,客户端需要做的事有:
- 通过创建Messenger对象实现IServiceConnection接口。
- 创建Messenger对象并将数据添加到该对象中。
- 调用Messenger对象的Send方法发送消息。
(1)在客户端创建Messenger对象
如果你需要服务进行响应,那你还需要在客户端创建一个 Messenger。然后,当客户端接收到 OnServiceConnected() 回调后,再通过Send()方法发送一个Message 给服务,Send()方法中的replyTo参数中包含了客户端的Messenger。
总之,客户端要和服务打交道,要做的全部工作就是根据服务返回的IBinder创建一个 Messenger ,并用Send() 方法发送一个消息。
当客户端调用BindService时,客户端就会在IServiceConnection的实现中(红字部分)连接到服务并创建Messenger对象。例如:
protected override void OnStart ()
{
base.OnStart ();
var demoServiceIntent = new Intent ("com.mydemos.MessengerServiceDemo");
demoServiceConnection = new DemoServiceConnection (this);
BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate);
}
protected override void OnStop ()
{
base.OnStop ();
if (isBound)
{
UnbindService (demoServiceConnection);
isBound = false;
}
}
class DemoServiceConnection : Java.Lang.Object, IServiceConnection
{
DemoMessengerActivity activity;
public DemoServiceConnection (Activity1 activity)
{
this.activity = activity;
}
public void OnServiceConnected (ComponentName name, IBinder service)
{
activity.demoMessenger = new Messenger (service);
activity.isBound = true;
}
public void OnServiceDisconnected (ComponentName name)
{
activity.demoMessenger.Dispose ();
activity.demoMessenger = null;
activity.isBound = false;
}
}
此过程非常类似于本地绑定的服务(前面的示例中讲过了)。主要的区别是,此处的代码在OnServiceConnected中创建Messenger对象并将其绑定到Activity,然后,就可以用该对象来调用服务。
(2)创建和发送消息
客户端使用Message.Obtain方法创建一条消息后,该消息就会与Android.OS.Bundle捆绑在一起,然后就可以调用Send方法发送消息了。例如:
Message message = Message.Obtain ();
Bundle b = new Bundle ();
b.PutString ("InputText", "text from client");
message.Data = b;
messengerDemo.Send(message);
其中,Message.Data属性是一个可包含多种类型数据的“键/值”对的集合。在客户端发送的消息中,“键”是“InputText”,那么,服务器端在重写的HandleMessage方法中就可以利用该键获取对应的消息("text from client"),如下面对服务器端代码所示:
class StockHandler : Handler
{
public override void HandleMessage (Message msg)
{
Log.Debug ("MessengerServiceDemo", "What = " + msg.What.ToString());
string text = msg.Data.GetString ("InputText");
Log.Debug ("MessengerServiceDemo", "InputText = " + text);
}
}
如果消息仅包含整数值,客户端也可以用更高效的方式(不再用“键/值”对来发送了,而是直接发送“值”,但是这种方式仅适用于发送整数消息,不适用于发送字符串消息):
- 两个int类型的属性:Arg1和Arg2
- 一个名为“What”的int类型的属性:服务器利用它可区分发送的整数消息是干什么用的(what this message is about)。
客户端通过Arg1、Arg2发送不同的整数值,利用What和HandleMessage的实现区分该消息是发送给谁的,这样以来,就可以在服务器端实现不同的操作(Action)了。