第十讲:绑定(信道)

代码

https://yunpan.cn/cPns5DkGnRGNs   密码:3913



绑定简介

从整个基础架构的层次结构上讲,WCF可以分成两个部分:服务模型层和信道层。

服务模型层建立在信道层之上,提供了一个统一的、可扩展的编程模型。

信道层则通过绑定创建的信道栈为消息通道提供了一个传输、处理的通道。

信道(Channel)与信道栈(Channel Stack)

在信道层,若干信道逐个相连,成为一个信道栈。WCF采用基于消息的通信手段,信道栈提供一个消息传输和处理的通道。

我们举个例子:

比如有一个为居民提供饮用水的自来水厂,它的任务是抽取自然水源,进行必要的净化处理,最终输送到居民区。净化的流程可能是这样的:天然水源被汲取到一个蓄水池中进行杂质的过滤(过滤池);被过滤后的水流到第二个池子进行消毒处理(消毒池);被消毒的水流到流到第三个池子中进行水质软化处理(软化池);最终水通过自来水管流到居民的家中。

WCF的信道栈就相当于一个自来水厂,而构成信道栈的一个个信道就相当于上面提到的过滤池,消毒池,软化池等。唯一不同的是,自来水厂处理的是水,而信道栈处理的是消息。这样设计的最大好处就是具有很强的可扩展性,水的净化需要进行多次处理,那么对于消息处理来说,不可能,也没有必要设计出一种万能信道完成所有的消息处理任务。我们更希望的方式是让一个信道专注于某一个单一功能的实现,通过对信道有序,合理的组合去实现实际消息处理功能。



对于WCF的信道栈来说,有两种信道是必须的:传输信道和消息编码信道。原因很简单,信道栈的最终任务总是实现对消息的传输,传输信道是必须的;在传输之前需要对消息进行编码,消息编码功能通过消息编码信道实现。所以,最简单的信道栈由传输信道和消息编码信道组成。

当然,在我们之前的例子当中,使用的 绑定  (即  EndPoint 中的 B) 都包含 传输信道和消息编码信道,只不过这一步是微软帮我们已经封装好的,我们在配置B的时候 感觉不到。

绑定与信道栈

在上面我们提到,整个WCF的体系结构可以分为两个层次:服务模型层和信道层。绑定:在整个结构体系中扮演着中间人的角色。从层次隶属来讲,绑定属于服务模型层,同时又是整个信道层的缔造者。在服务端,当服务被成功寄宿时,WCF通过服务终结点的绑定对象创建一个或多个信道监听器(ChannelListener),绑定到监听端口进行请求的侦听。当请求消息抵达,则利用信道监听器创建的信道栈进行消息的接受。服务操作执行的结果最终封装到回复消息中,通过相同的信道栈被回送。在客户端,通过绑定创建信道工厂(ChannelFactory),借助信道工厂创建的信道栈进行请求消息的发送与回复消息的接收。

我们看一个小Demo,代码在云盘

用于我们更加了解WCF的信道栈的工作,如何直接通过绑定进行消息通信。通过这个案例让大家对如何通过绑定创建信道栈,如何通过信道栈进行消息的发送和接收有一个感性的认识。

首先先看 结构,这个结构非常简单

[ 10-01 ]

再看监听端:

[ 10-02 ]

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.ServiceModel.Channels;
 6 using System.ServiceModel;
 7
 8 namespace Listener
 9 {    /// <summary>
10     /// 创建请求监听的应用程序
11     /// </summary>
12     class Program
13     {
14         static void Main(string[] args)
15         {
16
17             Uri listenUri = new Uri("http://127.0.0.1:9999/listener");
18             Binding binding = new BasicHttpBinding();
19             IChannelListener<IReplyChannel> channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri);
20             //执行完Open方法,表示信道监听器对象channel。
21             channelListener.Open();
22             //信道监听器对象通过调用AcceptChannel创建信道栈进行请求的监听,可以看成信息接收后的载体
23             IReplyChannel channel = channelListener.AcceptChannel(TimeSpan.MaxValue);
24             //一旦信道栈被成功创建,那么就可以利用它对请求消息进行接收,处理了。
25             channel.Open();
26             Console.WriteLine("开始监听");
27             //通过一个无限循环来处理来自不同客户端的消息请求。
28             while (true)
29             {
30                 /*
31                  * 请求的接收通过IReplyChannel的ReceiveRequest方法实现,该方法接受一个TimeSpan类型参数,代表该方法从执行开始成功接受请求的时间,由于客户端
32                  * 请求的频率不确定,在这里给它指定了一个最大值。ReceiveRequest并不像我们想象的一样返回一个代表请求消息的Message对象,而是返回一个RequestContext对象,
33                  * 并通过该对象将创建的回复消息回复给请求方。注意:在请求/回复消息交换模式中,RequestContext是连接请求和回复的纽带,
34                  * RequestContext不仅仅是对请求消息的封装,还可以用于回复消息的发送。
35                  * 在本例中,我们通过它的RequestMessage属性得到请求消息,然后通过CreateReplyMessage方法创建一个回复消息,通过Reply方法回复给发送方.
36                  */
37                 RequestContext requestContext = channel.ReceiveRequest(TimeSpan.MaxValue);
38                 Console.WriteLine("接受到请求消息:\n{0}", requestContext.RequestMessage);
39                 requestContext.Reply(CreateReplyMessage(binding));
40             }
41         }
42
43         static Message CreateReplyMessage(Binding binding)
44         {
45             string action = "urn:ibeifeng.com/reply";
46             string body = "这是一个简单的回复消息";
47             return Message.CreateMessage(binding.MessageVersion, action, body);
48         }
49
50
51     }
52 }

最后看我们的发送端:

[ 10-03 ]

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.ServiceModel;
 6 using System.ServiceModel.Channels;
 7 namespace Sender
 8 {
 9    /// <summary>
10    /// 创建消息发送端应用程序
11    /// </summary>
12     class Program
13     {
14         static void Main(string[] args)
15         {
16             Uri listenUri = new Uri("http://127.0.0.1:9999/listener");
17             Binding binding = new BasicHttpBinding();
18             IChannelFactory<IRequestChannel> channelFactory = binding.BuildChannelFactory<IRequestChannel>();
19             channelFactory.Open();
20             //对请求消息的发送
21             IRequestChannel channel = channelFactory.CreateChannel(new EndpointAddress(listenUri));
22             channel.Open();
23
24             //channel.Request 不光发送消息,并且 接收 服务端消息
25             Message replyMessage = channel.Request(CreateRequestMessage(binding));
26             Console.WriteLine("接收到回复消息\n{0}",replyMessage);
27             Console.Read();
28         }
29         static Message CreateRequestMessage(Binding binding)
30         {
31             string action = "urn:ibeifeng.com/request";
32             string body = "这是一个简单的请求消息";
33             return Message.CreateMessage(binding.MessageVersion,action,body);
34         }
35     }
36 }

道管理器

在WCF中,信道通过信道管理器(ChannelManager)创建。对于信道管理器,大家可能有点陌生,不过应该还记得这两个对象:信道监听器(ChannelListener)和信道工厂(ChannelFactory).实际上,信道管理器是信道工厂和信道监听器的统称。由于它在客户端和服务端的作用不尽相同,信道管理器在服务端和客户端具有不同的名称。服务端的信道管理器的作用在于创建信道栈监听请求、接收消息,所以称为信道监听器;而客户端的信道管理器在于创建信道进行请求消息的发送和回复消息的接收,所以被称为信道工厂。

绑定编程

对于WCF来说,服务端和客户端采用基于终结点的通信手段。作为终结三要素之一,绑定实现了所有消息通信的细节。无论是在服务端对服务的寄宿,还是在客户端对服务的访问,都要创建相应的终结点,在终结点的创建过程中,对绑定的指定是必须的。

1).服务寄宿对绑定的指定

当我们通过一个托管的应用程序对一个服务进行自我寄宿的时候,一般会为服务创建一个ServiceHost对象,然后通过ServiceHost的AddServiceEndpoint方法为之添一个或多个终结点。

例如 下面的例子:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))
 6             {
 7                 //BasicHttpBinding绑定方式
 8                 serviceHost.AddServiceEndpoint(typeof(ICalculator), new BasicHttpBinding(), "http://127.0.0.1:6666/CalculatorService");
 9                 //WSHttpBinding绑定方式
10                 serviceHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "http://127.0.0.1:6666/CalculatorService");
11                 //NetTcpBinding绑定方式
12                 serviceHost.AddServiceEndpoint(typeof(ICalculator), new NetTcpBinding(), "net.tcp://127.0.0.1:6666/CalculatorService");
13                 serviceHost.Open();
14                 Console.Read();
15             }
16         }
17     }

当我们采用基地址/相对地址的方式对服务进行寄宿的时候,对于终结点中指定的相对地址,WCF会在对应的ServiceHost基地址列表中寻找传输协议和与绑定类型相匹配的基地址,两者相互组合构成完整的终结点地址。比如,在下面的代码中,为服务CalculatorService添加了两个基地址:http://127.0.0.1:8888和net.tcp://127.0.0.1:9999.并通过AddServiceEndpoint方法添加了两个终结点,这不同的终结点具有不同的绑定类型和相同的相对地址(CalculatorService)。WCF会根据绑定的具体

类型,在基地址列表中寻找与传输协议匹配的基地址,基地址和相对地址相互组合,构成最终的终结点地址。

例如:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Uri[] baseAddresses = new Uri[] { new Uri("http://127.0.0.1:8888"), new Uri("net.tcp://127.0.0.1:9999") };
 6             using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService), baseAddresses))
 7             {
 8                 serviceHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "CalculatorService");
 9                 serviceHost.AddServiceEndpoint(typeof(ICalculator), new NetTcpBinding(), "CalculatorService");
10                 serviceHost.Open();
11                 Console.Read();
12             }
13         }
14     }

我们再看下 AppConfig的绑定写法规范

所有的绑定对象都具有一些共同的属性,比如OpenTimeout,CloseTimeout,SendTimeout,ReceiveTimeout等。此外,不同的绑定对象也有一些属于自己的属性。如果没有显式地对这些属性进行设置,那么将采用这些属性的默认值。如果希望通过配置的方式对

绑定对象进行定制,需要将相关的设置定义在binding列表中。下面的配置中,在<bindings>节点下添加了一个<basicHttpBinding>的节点,并为其指定了一系列的属性,并将该<basicHttpBinding>命名为”MyBinding”,在具体的终结点中,通过bindingConfiguration根据名称对绑定进行引用.

例如:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3   <system.serviceModel>
 4     <bindings>
 5       <basicHttpBinding>
 6         <binding name="MyBinding" openTimeout="00:05:00" receiveTimeout="00:15:00" sendTimeout="00:15:00"/>
 7       </basicHttpBinding>
 8     </bindings>
 9     <services>
10       <service name="Services.CalculatorService">
11         <endpoint address="http://127.0.0.1:6666/CalculatorService" binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="Contracts.ICalculator"></endpoint>
12       </service>
13     </services>
14   </system.serviceModel>
15 </configuration>

我们再谈谈  绑定的类型:

绑定类型 配置名 描述 补充
BasicHttpBinding basicHttpBinding
一个web服务绑定,

用与早起的诸如WebService等Web服务相兼容


这里主要是与早起的WebService兼容

如果你现在要做一个与之前的WebService交互

就要使用这种绑定类型

WSHttpBinding wsHttpBinding
一个web服务绑定,

用于支持最新的可互操作Web服务标准,

并与最近的Web服务相兼容


比较常用的HTTP方式绑定

在安全性与事物上都比BasicHttpBinding强

推荐使用

WSDualHttpBinding wsDualHttpBinding
一个web服务绑定,

用于支持双向通信

 支持HTTP协议的双工通信
NetNamedPipeBinding netNamedPipeBinding
一个面向连接的绑定

用于再同一台机器上通过命名管道进行通信

 没什么说的
NetTcpBinding netTcpBinding
一个面向连接的绑定

用于通过TCP进行跨进程和跨机器边界的通信

 也可以支持双工通信,是关于TCP的双工通信
NetMsmqBinding netMsmqBinding 基于支持MSMQ的持久可靠通信  消息队列的 绑定模式

说下这些绑定的类型 在什么场合使用:

BasicHttpBinding:

在默认的情况下采用HTTP和基于文本的消息编码方式.同时还提供了对安全的支持,无论是基于传输的安全还是基于消息的安全(后面讲,这里记住就可以了),都可以通过对绑定进行相应的设置实现。

将安全模式设为传输安全模式:

BasicHttpBinding binding=new BasicHttpBinding(BasicHttpSecurityMode.Transport)

如果我们将其设置成基于消息的安全模式,并将客户端的凭证类型设为证书:(Certificate,这对于基于消息安全模式的BasicHttpBinding是必须的).

BasicHttpBinding binding=new BasicHttpBinding(BasicHttpSecurityMode.Message)

binding.Security.Message.ClientCredentialType=BasicHttpMessageCredentialType.Certificate;

BasicHttpBinding还提供基于MTOM编码的支持。

方式对消息编码方式进行显示指定。代码如下:

BasicHttpBinding binding=new BasicHttpBinding();

binding.MessageEncoding=WSMessageEncoding.Mtom;

BasicHttpBinding可以和传统的ASP.NET ASMX Web Service进行互操作.

WsHttpBinding:

默认情况下采用基于文本的编码方式和基于Http的传输协议。同时实现对事务和实现基于消息的安全。

WsHttpBinding在默认的情况下就提供了基于消息安全的支持,此外WsHttBinding仍然提供基于HTTPS的传输安全。

下面的代码,为基于传输的安全的安全模式:

WsHttpBinding binding=new WsHttpBinding(SecurityMode.Transport)

除了提供对传输和消息安全的支持之外,还对传输的可靠性提供支持。可靠性消息确保网络环境不好的情况下消息能有效,有序地抵达目的地。

WsHttpBinding binding=new WsHttpBinding(SecurityMode.Message,true);

编码也可以是MTOM.

WsDualHttpBinding:

该绑定模式是专门为HTTP传输下双工消息交换模式设计的。除了基于传输的安全之外,WsHttpBinding所有的特性都被WsDualHttpBinding继承下来,这包括基于HTTP的传输,基于文本和MTOM的消息编码等。

为什么WsDualHttpBinding不支持基于传输的安全呢?
原因很简单,因为HTTP协议下的传输安全通过HTTPS(SSL)实现,HTTPS依赖于一个真正意义上的WEB站点,也就是说只有访问一个真正意义上WEB站点资源的前提下,HTTPS才会有意义。而对于双工通信来说,由于客户端满足这样要求,所以从服务端回调客户端的传输安全是无法确保的。

HTTP的通信通道不支持双工通信,WsDualHttpBinding采用了两个HTTP通道

到目前位置,我们以供介绍了3种类型的绑定。从对于传输协议的支持来看,它们都是基于HTTP和HTTPS的绑定;从消息的编码的角度来看,它们均支持基于纯文本的消息编码和MTOM编码。除了WsDualHttpBinding(受到双向通信的限制),这些属性都决定了这两种绑定具有较好的互操作性,也就是说,对于前两种绑定的应用并不限于基于.NET平台应用的交互,如果通过这些绑定寄宿服务,其他平台的客户端可以调用服务,同理我们也可以利用基于这些绑定的客户端访问其他非.NET平台的WEB服务,只要对方支持相应的标准.

NetTcpBinding:

由于NetTcpBinding采用TCP作为传输协议,所以它一般应用于企业网(局域网)中;由于采用二进制的消息编码方式,在性能上较基于文本的编码会有较大的提高;此外,由于和HTTP协议不同,TCP本身就是一个基于双工通信的协议,所以它和WsDualBinding一样,可以用于基于双工消息交换模式的WCF应用中.

除了对传输安全模式的支持(默认),也提供对消息安全模式的支持。

NetTcpBinding binding=new NetTcpBinding(SecurityMode.Message);

和WsHttpBinding一样,NetTcpBinding也提供对可靠会话的支持(后面讲,这里记住就可以了),以保障数据包或消息的可靠、有序传递。不过与WsHttpBinding的实现机制不同的是,NetTcpBinding采用的是TCP协议固有的可靠性传输机制,比如消息确认机制、重发机制等。

下面的代码,通过ReliableSession.Enabled属性让绑定实现对可靠会话的支持:
NetTcpBinding binding=new NetTcpBinding();
binding.ReliableSession.Enabled=true;

NetNamedPipeBinding:

用于同一台机器的跨进程通信(IPC),是性能最好的绑定.

以上所有的属性设置是通过代码方式展示的,也可以通过配置:

 1   <system.serviceModel>
 2     <bindings>
 3       <wsHttpBinding>
 4         <binding name="wsHttpSecureAndReliable" maxReceivedMessageSize="100000">
 5           <security mode="Transport"/>
 6         </binding>
 7       </wsHttpBinding>
 8     </bindings>
 9     <services>
10       <service name="HelloIndigo.HelloIndigoService">
11         <endpoint contract="HelloIndigo.IHelloIndigoService" binding="wsHttpBinding"  bindingConfiguration="wsHttpSecureAndReliable"></endpoint>
12       </service>
13     </services>
14   </system.serviceModel>

绑定中增加了消息的大小和数组的长度来适应最大可到100KB的数据。这个设置并不打算适应极端大型的消息负载,但是,它确实为一般消息超出64KB默认值的可能性留出了较大的回旋余地.

WCF默认接受信息就是为64KB,如果超出了 64KB 将出现异常信息.

声明式地定制BasicHttpBinding默认值

1       <basicHttpBinding>
2         <binding name="basicHttpSecure" maxBufferSize="100000" maxReceivedMessageSize="100000">
3           <readerOuotas maxArrayLength="100000" maxStringContentLength="100000"/>
4           <security mode="Transport"/>
5         </binding>
6       </basicHttpBinding>

用代码设置

BasicHttpBinding binding=new BasicHttpBinding(BasicHttpSecurityMode.Transport);

binding.MaxBufferSize=100000;

binding.MaxReceivedMessageSize=100000;

binding.ReaderQuotas.MaxArrayLength=100000;

binding.ReaderQuotas.MaxStringContentLength=1000000;

EndpointAddress address=new EndpointAddress(“https://localhsot/........”);

2).服务调用对绑定的指定

通过添加服务引用的方式,生成的核心类是继承自System.ServiceModel.ClientBase<TChannel>的子类,TChannel为服务契约类型

1 [ServiceContract(NameSpace="http://www.HulkXu.com")]
2 Public interface ICalculator
3 {
4   [OperationContract]
5   double Add(double x,double y);
6 }

针对上面这个服务契约,引用对应的该服务,将会生成3个类型:ICalculator,ICalculatorChannel,CalculateClient.

ICalculator:可以看成是客户端的等效服务契约

ICalculatorChannel:定义了客户端信道的基本行为

CalculateClient:是最终用于服务访问的服务代理类,并实现了服务契约

为ChannelFactory<TChannel>指定绑定从远程调用的角度来理解WCF的客户端程序,其目的在于创建一个代理对象实现远程调用;从消息交换的角度来讲,WCF客户端程序的目的在于创建一个信道栈用于向服务端发送消息和接收回复。所以我们需要的就是通过一个对象来创建这样的服务代理或是信道栈,从编程模型的角度来看,这个对象就是System.ServiceModel.ChannelFactory<TChannel>

服务调用对绑定的指定通过前面的介绍,我们知道了WCF服务调用具有两种典型的方式:一种通过添加服务引用的方式,另一种是通过ChannelFactory直接创建服务代理对象进行服务调用.

时间: 2024-08-05 16:08:35

第十讲:绑定(信道)的相关文章

Stanford机器学习---第十讲. 数据降维

本文原始地址见http://blog.csdn.net/abcjennifer/article/details/8002329,在此添加了一些自己的注释方便理解 本栏目(Machine learning)包括单参数的线性回归.多参数的线性回归.Octave Tutorial.Logistic Regression.Regularization.神经网络.机器学习系统设计.SVM(Support Vector Machines 支持向量机).聚类.降维.异常检测.大规模机器学习等章节.内容大多来自

Java Web快速入门——全十讲

Java Web快速入门——全十讲 这是一次培训的讲义,就是我在给学生讲的过程中记录下来的,非常完整,原来发表在Blog上,我感觉这里的学生可能更需要. 内容比较长,你可以先收藏起来,慢慢看. 第一讲(参考<Java Web程序设计基础教程>第1章)1 JSP 和 Java的关系 一般Java指的标注版 Java SE   另外两个版本:Java EE 和 Java ME JSP属于Java EE的一部分.   Java EE:     组件:Web层组件(JSP+Servlet)+业务层组件

机器学习中使用的神经网络第十讲笔记

Geoffery Hinton教授的Neuron Networks for Machine Learning的第十讲介绍了如何combine模型,并进一步从实际应用的角度介绍了完全贝叶斯方法. Why it helps to combine models 这一小节,我们讨论在做预测时为什么要结合许多模型.使用多个模型可以在拟合真正的规则和拟合样本错误之间做一个很好的折中. 我们已经知道,当训练数据比较少时容易出现过拟合,如果我们平均一下许多不同模型的预测,那我们就可以降低过拟合的程度.对于回归来

【笔记】得到-《梁宁&#183;产品思维三十讲》

ps:偶然从[得到]上听到梁宁的<产品思维三十讲],感觉很棒,抽时间听完了所有的课程,特整理笔记如下. 01发刊词|产品能力是每个人的底层能力 产品能力就是训练一个人:判断信息,抓住要点,整合有限的资源,把自己的价值打包成一个产品向世界交付,并且获得回报. 通过这30讲,希望拥有三个东西: 1.一双眼睛.发现痛点.找到破局点的敏锐之眼: 2.一双手.动手优化,着手改变的行动之手: 3.一颗心.洞察人性的同理心,懂得自己与用户,懂得产品上每个细节给到人的满足感.确认感和依赖感. 02案例:用户体验

《上古天真论》第十讲文字版

上古天真论篇第一讲主讲:徐文兵  主持:梁  冬播出时间:2008-12-06  23:00—24:00  经文:夫言人之阴阳,则外为阳,内为阴:言人身之阴阳,则背为阳,腹为阴:言人身之藏腑中阴阳,则藏者为阴,腑者为阳,肝.心.脾.肺.肾五藏皆为阴,胆.胃.大肠.小肠.膀胱.三焦六腑皆为阳. 梁冬:重新发现中医太美,大家好,欢迎收听今天晚上的国学堂,依然是和厚朴中医学堂堂主徐文兵老师一起来学习<素问?金匮真言论篇第四>,徐老师好! 徐文兵:梁冬好,听众朋友们,大家好! 梁冬:上一周的时候我们讲

第六十讲:四大组件之BroadcastReceiver(三)

如果把生活比喻为创作的意境,那么阅读就像阳光. 本讲内容:举几个常见的例子加深一下对BroadcastReceiver广播的理解和应用: 一.开机启动服务 我们经常会有这样的应用场合,比如消息推送服务,需要实现开机启动的功能.要实现这个功能,我们就可以订阅系统"启动完成"这条广播,接收到这条广播后我们就可以启动自己的服务了.我们来看一下BootCompleteReceiver和MsgPushService的具体实现: public class BootCompleteReceiver

16汇编第十讲完结Call变为函数以及指令的最后讲解

16汇编完结Call变为函数以及指令的最后讲解 学了10天的16位汇编,这一讲就结束了,这里总结一下昨天的LOOP指令的缺陷,因为lOOP指令的缺陷,所以我们都改为下面的汇编代码使用了,自己去写,其中条件是你自己写的 请看汇编代码: do while 的汇编代码 WHILE: mov ax,ax cmp ax, 10 jl WHILE while 的汇编代码 WHILE: cmp ax, 10 jge WHILE_END mov ax,ax jmp WHILE WHILE_END: 一丶Call

第三十讲:Android之Animation(五)

天行健,君子以自强不息.--<周易·乾·象> 本讲内容:逐帧动画 Frame Animation 逐帧动画 Frame Animation就是说一帧一帧的连起来播放就变成了动画,和放电影的机制非常相似. 我们通过一个样例感受一下,代码的解说都写在凝视里了 以下是res/layout/activity_main.xml 布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&quo

第五十讲:Android之Nothfication(三)

勤能补拙是良训,一分辛劳一分才. 本讲内容:通知 Notification 和 通知管理器 NotificationManager 我们通过一个例子实现一个可更新进度的通知,代码的讲解都写在注释里了 下面是res/layout/activity_main.xml 布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schema