[WCF编程]10.操作:回调操作

一、回调操作概述

    WCF支持服务将调用返回给它的客户端。在回调期间,许多方面都将颠倒过来:服务将成为客户端,客户端将编程服务。回调操作可以用在各种场景和应用程序中,但在涉及事件或者服务发生时间需要通知客户端时,显得特别有用。

    回调操作通常被认为是双向操作。并非所有的绑定都支持回调操作,只有在具有了双向能力的绑定时,才支持回调操作。比如,HTTP协议本质上是与与连接无关的,所以他不能用于回调,所以,不能基于BasicHttpBingding绑定或WsHttpBingding绑定使用回调。为了让HTTP协议支持回调,WCF提供了WSDualHttpBingding绑定,它实际上设置了两个HTTP通道:一个用于从客户端到服务的调用,另一个则是服务到客户端的调用。WCF也为NetTcpBingding和NetNamePipeBingding绑定提供了对回调操作的支持。所以,TCP和IPC协议均支持双向通信。

    双工回调并不是标准的操作,因为没有对于的行业标准来规定客户端如何传递客户端地址给服务,或者服务如何实现发布回调契约。双工回调会损害WCF的性能。很少用到WSDualHttpBingding,因为它无法穿越客户端和服务端的重重阻碍。这种连接线问题由Windows  Azure AppFabric Service Bus解决了,在云上它支持双工对调,使用NetTcpRelayBingding绑定。

二、回调操作5步骤

1.创建标准契约和回调契约

    /// <summary>/// 回调契约/// </summary>public interface IMyContractCallback{[OperationContract]void OnCallback(string name);}[ServiceContract(CallbackContract = typeof(IMyContractCallback))]public interface IMyContract{[OperationContract]void DoSomething(string name);}

2.在服务端实现标准契约

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]public class MyContract : IMyContract{private IMyContractCallback callback;public void DoSomething(string name){callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();callback.OnCallback(name + "回来了");}}

3.创建服务端承载

class Program{static void Main(string[] args){ServiceHost host = new ServiceHost(typeof(WCF.CallbackOperation.Service.MyContract));host.Open();Console.WriteLine("服务启动成功......");int i = 0;foreach (ServiceEndpoint endpoint in host.Description.Endpoints){i++;Console.WriteLine("终结点序号:{0},终结点名称:{1},终结点地址:{2},终结点绑定:{3}{4}", i, endpoint.Name, endpoint.Address, endpoint.Binding, Environment.NewLine);}Console.Read();}}
    App.config
<system.serviceModel><services><service name="WCF.CallbackOperation.Service.MyContract" behaviorConfiguration="CallbackTcpBehavior"><endpoint name="tcp_IMyContract" address="MyContract" contract="WCF.CallbackOperation.Service.IMyContract" binding="netTcpBinding"></endpoint><endpoint address="MyContract/mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint><host><baseAddresses><add baseAddress="net.tcp://127.0.0.1:9000"/></baseAddresses></host></service></services><behaviors><serviceBehaviors><behavior name="CallbackTcpBehavior"><serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/><serviceDebug includeExceptionDetailInFaults="true"/></behavior></serviceBehaviors></behaviors><diagnostics performanceCounters="All"/></system.serviceModel>

4.实现客户端代理

    使用Visual Studio的SvcUtil工获取

5.在客户端实现并调用

class Program{static void Main(string[] args){MyCallback callback = new MyCallback();callback.CallService();callback.Dispose();Console.Read();}class MyCallback : IMyContractCallback, IDisposable{private MyContractClient m_Proxy;public void CallService(){InstanceContext context = new InstanceContext(this);m_Proxy = new MyContractClient(context);m_Proxy.DoSomething("zxj");}public void OnCallback(string name){Console.WriteLine(name);}public void Dispose(){m_Proxy.Close();}}}

    实例代码下载:下载

 

三、回调契约

    回调操作是服务契约的一部分,它取决于服务契约对回调契约的定义。一个服务契约最多只能包含一个回调契约。一旦定义了回调契约,就需要客户端支持回调,并在每次调用中提供执行服务的回调终结点。若要定义回调契约,则可以使用ServiceContract特性提供的Type类型的属性CallbackContract。

public sealed class ServiceContractAttribute : Attribute
{public Type CallbackContract { get; set; }
}

    在定义包含回调契约的服务契约时,需要为ServiceContract提供回调契约的类型,以及回调契约的定义,如下所示:

 public interface ISomeCallbackContract{[OperationContract]void OnCallback();}[ServiceContract(CallbackContract=typeof(ISomeCallbackContract))]public interface IMyContract{[OperationContract]void DoSomething();}

    注意,回调契约必须标记ServiceContract特性。因为只要类型定义为回调契约,就意味着它具有ServiceContract特性,并且在服务元数据中也将包含该特性。当然,我们仍然腰围所有的回调接口方法标记OperationContract特性。

    当客户端导入的回调接口与原来的服务端定义的名称不同时,它会被修改为服务契约的接口名后将后缀Callback。例如,如果客户端导入上个例子的定义,则客户端会获得以下的定义:

    /// <summary>/// 回调契约/// </summary>public interface IMyContractCallback{[OperationContract]void OnCallback(string name);}[ServiceContract(CallbackContract = typeof(IMyContractCallback))]public interface IMyContract{[OperationContract]void DoSomething(string name);}

    所以,建议在服务端使用命名规范,即在回调契约命名为服务契约接口后将后缀Callback。

四、客户端回调设置

    客户端负责托管回调对象以及公开回调终结点。服务实例最内层的执行范围是实例上下文。InstanceContext类定义的构造函数能够将服务实例传递给宿主。

public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext>
{// 实现服务实例的对象。public InstanceContext(object implementation);// 为实例上下文返回服务的实例。public object GetServiceInstance();......
}

    为了托管一个回调对象,客户端需要实例化回调对象,然后通过它创建一个上下文对象:

private MyContractClient m_Proxy;public void CallService()
{InstanceContext context = new InstanceContext(this);m_Proxy = new MyContractClient(context);m_Proxy.DoSomething("zxj");
}

    值得一提的是,虽然回调方法是在客户端,但它们仍然属于WCF操作,因此他们都是一个操作调用上下文,通过OperationContext.Current访问。

双向代理

    无论何时,只要服务终结点的契约定义了一个回调契约,客户端在与它进行交互时,都必须使用代理创建双向通信,并将回调终结点的引用传递给服务。因此没客户端使用的代理必须继承一个专门的代理类DuplexClientBase<T>,如下所示:

public abstract class DuplexClientBase<TChannel> : ClientBase<TChannel> where TChannel : class
{protected DuplexClientBase(InstanceContext callbackInstance);protected DuplexClientBase(object callbackInstance);protected DuplexClientBase(InstanceContext callbackInstance, ServiceEndpoint endpoint);protected DuplexClientBase(object callbackInstance, ServiceEndpoint endpoint);protected DuplexClientBase(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress);protected DuplexClientBase(object callbackInstance, Binding binding, EndpointAddress remoteAddress);//  获取内部双工通道。public IDuplexContextChannel InnerDuplexChannel { get; }......
}

    客户端需要提供实例上下文绑定给托管宿主的DuplexClientBase<T>构造函数。代理会根据回调上下文构建终结点,从服务终结点配置信息里推断回调终结点的信息:回调终结点契约是通过服务契约回调类型定义的。回调终结点会使用与外发调用相同的绑定。对于地址,WCF会使用客户端机器名。只是简单地传递实例上下文给双向代理,并使用代理来调用服务暴露的终结点。为了简化这个过程,DuplexClientBase<T>提供了可以接受回调对象的构函数,并将其包装在上下文里。无论出于什么原因,当客户端需要访问上下文时,DuplexClientBase<T>都会另外提供IDuplexContextChannel类型的InnerDuplexChannel属性。它提供了通过InnerDuplexChannel属性访问上下文的方式。

    使用Visual Studio能够为包含了回调契约的目标服务生成代理类,生成的类派生自DuplexClientBase<T>,如下所示:

public partial class MyContractClient : System.ServiceModel.DuplexClientBase<IMyContract>, IMyContract
{public MyContractClient(System.ServiceModel.InstanceContext callbackInstance) : base(callbackInstance){}public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) : base(callbackInstance, endpointConfigurationName){}public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : base(callbackInstance, endpointConfigurationName, remoteAddress){}public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : base(callbackInstance, endpointConfigurationName, remoteAddress){}public MyContractClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(callbackInstance, binding, remoteAddress){}public void DoSomething(){base.Channel.DoSomething();}public System.Threading.Tasks.Task DoSomethingAsync(){return base.Channel.DoSomethingAsync();}
}

    客户端可以使用派生的代理类创建回调实例,并将上下文作为它的宿主,创建代理、调用代理服务,这样就可以传递回调终结点的引用。

    class MyCallback : IMyContractCallback, IDisposable{private MyContractClient m_Proxy;public void CallService(){InstanceContext context = new InstanceContext(this);m_Proxy = new MyContractClient(context);m_Proxy.DoSomething("zxj");}public void OnCallback(string name){Console.WriteLine(name);}    }

    注意,只要客户端正在等待回调,就不能关闭代理。如果关闭回调终结点,当服务试图将调用返回时,就会导致服务端产生错误。

    最常见的实现方式是由客户端自身实现回调契约,此时,客户端通常可以将代理定义为成员变量,并在释放客户端时关闭它,如下所示:

class MyCallback : IMyContractCallback,IDisposable
{private MyContractClient m_Proxy;public void CallService(){InstanceContext context = new InstanceContext(this);m_Proxy = new MyContractClient(context);m_Proxy.DoSomething();}public void OnCallback(){throw new NotImplementedException();}public void Dispose(){m_Proxy.Close();}
}

 

五、服务端的回调调用

    随着服务端的每一次调用,客户端的回调终结点引用都会被传递到服务,组成传入消息的一部分。OperationContext类为服务提供了方便访问回调引用的途径,即调用泛型方法GetCallbackChannel<T>();

public sealed class OperationContext : IExtensibleObject<OperationContext>
{// 获取调用当前操作的客户端实例的通道。public T GetCallbackChannel<T>();
}

    服务如何处理回调引用及何时决定使用它,完全由服务选择,这一点毋庸置疑。服务能够从操作上下文中提取出回调引用,然后将它保存起来以备日后使用;或者可以在服务运行期间使用它,将调用返回客户端。如下所示:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyContract : IMyContract
{private IMyContractCallback callback;public void DoSomething(string name){callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();callback.OnCallback(name + "回来了");}
}

回调重入

    服务可能需要调用传入的回调引用。然而,这样的调用在默认情况下是禁止的,因为它受默认的服务并发管理的限制。在默认情况下,服务类被配置为单线程访问:服务实例上下文与锁关联,在任何时刻都只能有一个线程拥有锁,也只能有一个个线程能够访问上下文中的服务实例。在操作调用期间,在客户端发布的调用需要阻塞服务线程,并调用回调。问题是一旦回调返回它需要的重入的相同上下文及获取同一个锁的所有权,处理从相同通道的客户端返回的应答消息时就会导致死锁。注意,服务仍然可能调用到其它客户端的回调,或者调用其他服务,这属于正在调用的会导致死锁的客户端的回调。

    如果单线程的服务实例试图调用返回给他的客户端,为了避免死锁,WCF会抛出InvilidOperationException异常。对于这种情况,有三种可能的解决方案:

    第一种方案是配置服务,允许多线程访问。由于服务实例与锁无关,因此允许正在调用的客户端回调。但是这种情况也可能增加服务开发者的负担,因为它需要为服务提供同步。

    第二种方案是将服务配置为重入。一旦配置为重入,服务实例就与锁管理,同时只允许单线程访问。如果服务正在回调它的客户端,WCF就会先释放锁。就目前而言,如果服务需要回调它的客户端,则可以使用ServiceBehavior特性的ConcurrencyMode属性,将服务的并发行为配置为多线程或者重入。

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
public class MyContract : IMyContract
{private IMyContractCallback callback;public void DoSomething(string name){callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();callback.OnCallback(name + "回来了");}
}

    第三种方案是将回调契约操作配置为单向操作,这样服务就能够安全地将调用返回给客户端。因为没有任何应答消息会竞用锁,即使并发模式设置为单线程,服务也能够支持回调。注意,实例代码中使用第二种方案,如使用第三种方案,请重新生成客户端代理类。

public interface IMyContractCallback
{[OperationContract(IsOneWay = true)]void OnCallback(string name);
}[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
public interface IMyContract
{[OperationContract]void DoSomething(string name);
}public class MyContract : IMyContract
{private IMyContractCallback callback;public void DoSomething(string name){callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();callback.OnCallback(name + "回来了");}
}

六、回调连接管理

    如果客户端保持打开状态,则服务只能将调用返回给客户端。通常情况下,这建立在代理没有关闭的情况下。保证代理处于打开状态同样可以避免回调对象被垃圾回收期回收。如果服务维持了一个在对调终极点上的引用,而且客户端代理是关闭的,或者客户端应用程序已经推出,那么当服务调用回调时,就会从服务通道处获取一个ObjectDisposedException异常。因此,对于客户端而言,当它不再需要接收回调会客户端应用已经关闭,最好能够通知服务无。为此,可以在服务契约中显式添加一个Disconnect()方法。如果每个放哪广发调用都担忧回调引用,那么服务就能够在Disconnect()方法中将回调从内部存储结构中移除。

    当然,也建议开发者同时在服务契约中也显式提供一个Connect()方法。定义Connect()方法,能够是客户端重复地连接或断开,同时还提供一个明确的时间点,以判断何时需要一个回调,因为回调只能够发生在调用Connect()方法之后。

    [ServiceContract(CallbackContract = typeof(IMyContractCallback))]public interface IMyContract{[OperationContract]void DoSomething(string name);[OperationContract]void Connect();[OperationContract]void DisConnect();}[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]public class MyContract : IMyContract{private IMyContractCallback callback;public void DoSomething(string name){callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>();callback.OnCallback(name + "回来了");}public void Connect(){//可以实现为回调列表,将回调引用添加到列表中
        }public void DisConnect(){//可以实现为回调列表,将回调引用从列表中移除
        }}

连接管理与实例操作

单向服务

    只有在操作调用回调引用对象自身或者将它存储在某些全局变量(如静态变量)时,单调服务才能够使用 回调引用。既然服务可能使用的存储在引用对象中的任何实例状态在操作返回时都会丢失,那么单调服务就必须使用某些静态变量用以存储回调引用。因而,单调服务特别需要类似于Disconnect()方法。如果没有该放哪广发,共享存储就会随着时间的推移,而出现大量冗余的回调引用。

单例服务

    当然,单例服务也存在类似的情况。由与单例对象的生命周期不会结束,因而它会无限累计回调引用,随着时间的推移,回调的客户端不服存在,大多数回调引用也随之失效。定义DisConnect()方法可以保证单例服务只连接到对应的活动客户端。

会话服务

    会话服务即使没有DisConnect()方法也能获得引用,只要它将回调引用保存在某个实例成员的变量中。原因在于当会话结束时,服务实例会被自动释放,整个会话不存在保持引用的危险。这就能够保证回调引用总是有效的。但是,如果会话服务为了让其它宿主段或跨会话访问而将它的回调引用保存在某个全局变量中,必须添加DisConnect()方法,已达到移除回调引用的目的,这就是因为在调用Dispose()期间,不能使用回调引用。

    我们也可以为会话服务添加Connect()和DisConnect()方法,因为它允许客户端在会话期间决定何时启动或停止接受回调消息。

七、回调契约层级

    设计回调契约时,需要注意设计上的约束。如果一个服务契约的基契约可回调接口,则服务契约定义的回调结构必须是它的契约定义的所有回调接口的子接口。如下,以下回调契约是无效的:

interface ICallbackContract1
{......}interface ICallbackContract2
{......}[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract
{......}//无效
[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMySubContract
{......}

    IMySubContract不能指定ICanllbackContract2为它的回调契约,因为ICallbackContract2不能ICallbackContract1的子类,而IMyBaseContract(IMySubContract的父接口)定义了ICallbackContract1作为它的回调接口。

    满足约束的最直接方法实在回调契约层级反映服务契约的层次:

interface ICallbackContract1
{......}interface ICallbackContract2:ICallbackContract1
{......}[ServiceContract(CallbackContract = typeof(ICallbackContract1))]
interface IMyBaseContract
{......}[ServiceContract(CallbackContract = typeof(ICallbackContract2))]
interface IMySubContract
{......}

转载于:https://www.cnblogs.com/zxj159/p/3986137.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575202.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

float去掉小数点之后_float类型的存储方式

在c语言中float函数是单精度的。它在内存中以二进制的形式存储。分为符号位&#xff0c;阶码与尾数三部分。 符号位最为简单&#xff0c;如果你存储的是正数那么符号数就是0。如果是负数&#xff0c;则为1。下面&#xff0c;我以13.625为例说明阶码与尾数的表示方法。首先&…

idea 2020.2 如何设置classpath_开发属于自己的第一款IDEA插件!

作者&#xff1a;木杉 http://imushan.com/写Java代码的时候&#xff0c;经常会涉及到重复性的操作&#xff0c;这个时候就会想要是有这样一个插件就好了&#xff0c;如果是大家都会遇到的场景&#xff0c;IDE或许已经提供了&#xff0c;再不然也有可能有人编写了相关的插件。要…

java中Arrays类和Math类常用API简介

Arrays类&#xff1a; java.util.Arrays是一个与数组相关的工具类&#xff0c;提供了许多静态方法对数组进行操作&#xff0c;直接通过类点出来使用&#xff0c;无需创建对象。 // 1.Arrays类存在与java.util下&#xff0c;需要导包&#xff1a; import java.util.Arrays; pu…

按15分钟取数据_Python爬取猫眼电影《飞驰人生》4万多条评论并对其进行数据分析...

前言文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。作者&#xff1a; Yura不说数据说 &#xff0c;PYuraLPS&#xff1a;如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http://note.youdao.c…

UI设计

摘要 源于ajlr项目的验收&#xff0c;过程中ui的设计被屡次提到。所以就有了这样一个思考&#xff0c;什么是ui设计&#xff1f;对于开发人员来说&#xff0c;ui设计是否很重要&#xff1f;以及如何来进行ui设计&#xff1f; ui设计&#xff1f; User Interface&#xff0c;简称…

Java中继承、this关键字、super关键字

继承: 概述&#xff1a;当多个类中存在相同属性和行为时&#xff0c;将这些相同的内容抽取到单独一个类中&#xff0c;那么多个类无需再定义这些属性和行为&#xff0c;只要继承抽离出来的这个类即可。 子类&#xff1a;当某个类继承了另一个类的时候&#xff0c;可以把这个某…

java中抽象类,abstract关键字

抽象类&#xff1a;java语法规定&#xff0c;包含抽象方法的类为抽象类。 abstract关键字&#xff1a;abstract用来定义抽象方法和抽象类&#xff0c;定义抽象方法格式为&#xff1a;abstract修饰方法&#xff0c;并去掉大括号&#xff0c;直接以封号结束&#xff1b;定义抽象…

c++获取当前时间戳_python开发:python时间模块的使用

前言&#xff1a;今天元宵节&#xff0c;祝福大家元宵节快乐。今天在元宵节给各位朋友分享一下python时间模块。我们在开发中经常会与时间打交道&#xff0c;如&#xff1a;获取事件戳&#xff0c;时间戳的格式化等&#xff0c;这里简要记录一下python操作时间的方法。ython中常…

Linux重新编译内核指南

Linux的一个重要的特点就是其源代码的公开性&#xff0c;全世界任何一个软件工程师都可以将自己认为优秀的代码加入到其中&#xff0c;由此引发的一个明显的好处就是Linux修补漏洞的快速以及对最新软件技术的利用。而Linux的内核则是这些特点的最直接的代表。      为什么…

python 获取向上两级路径_Python学习第171课--相对路径和绝对路径

【每天几分钟&#xff0c;从零入门python编程的世界&#xff01;】这节我们补充2个概念&#xff1a;相对路径和绝对路径。●绝对路径就像一棵大树一样&#xff0c;从它的根开始&#xff0c;往上会有大的枝干&#xff0c;在大的枝干上面又会有小一点的树枝&#xff0c;小树枝上面…

Java接口、implements关键字、接口中抽象方法,默认方法,静态方法,私有方法,常量、final关键字

接口&#xff1a; 接口是Java语言中一种引用类型&#xff0c;是方法的集合&#xff0c;如果说类的内部封装了成员变量、构造方法和成员方法&#xff0c;那么 接口的内部主要就是封装了方法&#xff0c;包含抽象方法&#xff08;JDK 7及以前&#xff09;&#xff0c;默认方法和…

百度热力图颜色说明_大数据下的龙港、鳌江人口热力图

大数据下的龙港、鳌江人口热力图--趋势显示&#xff0c;人口逐步集中于区域中心地区与城市 2020-08-25 大数据(big data)&#xff0c;是指无法在一定时间内用常规软件工具对其内容进行抓取、管理和处理的数据集合。大数据有五大特点&#xff0c;即大量(Volume)、高速(Velocity)…

python 显示图片matplotlib_Python OpenCV ——Matplotlib显示图片

Color image loaded by OpenCV is in BGR mode.But Matplotlib displays in RGB mode.So color images will not be displayed correctly in Matplotlib if image is read with OpenCV.Please see the exercises for more details.(引自文档)import numpy as npimport cv2from …

什么是Spring?Spring是什么?

Spring概述&#xff1a; Spring是一个开源框架&#xff0c;是为了解决企业应用程序开发复杂性而开发的。 从简单性、可測试性和松耦合的角度而言&#xff0c;不论什么java应用都能够从Spring中受益。 简而言之&#xff0c;Spring就是一个轻量级的控制反转&#xff08;IOC&#…

java中多态,instanceof关键字

多态&#xff1a; 面向对象三大特征&#xff1a;继承、封装、多态&#xff0c;其中多态指的是有多种形态&#xff0c;可以使代码更加灵活&#xff0c;而非状态。继承或接口是多态的前提&#xff0c;比如一个对象狗是宠物类的实现类对象&#xff0c;而宠物类又继承了动物类&…

python语言的单行注释以井号开头_【学习】Python语言入门

Python是一门具有强类型(即变量类型是强制要求的)、动态性、隐式类型(不需要做变量声明)、大小写敏感(var和VAR代表了不同的变量)以及面向对象(一切皆为对象)等特点的编程语言。 语法 Python中没有强制的语句终止字符&#xff0c;且代码块是通过缩进来指示的。缩进表示一个代码…

python 参数个数 同名函数_Python——函数的参数

函数的参数定义函数的时候&#xff0c;我们把参数的名字和位置确定下来&#xff0c;函数的接口定义就完成了。对于函数的调用者来说&#xff0c;只需要知道如何传递正确的参数&#xff0c;以及函数将返回什么样的值就够了&#xff0c;函数内部的复杂逻辑被封装起来&#xff0c;…

webview的

问题描述我给WEBVIEW加了等待的圆圈,怎么不起作用?布局文件:<?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:lay…

java中final关键字、权限修饰符、内部类(成员内部类、局部内部类、匿名内部类)

final关键字&#xff1a; final表示最终的不可变的&#xff0c;在java中可以用来修饰类或方法、局部变量、成员变量。 // 1.final关键字修饰类需要放在class关键字前面&#xff0c;表示不可变的类&#xff0c;需要注意&#xff0c;这里表示当前类不能有子类&#xff0c;但是有…

python安装后无法使用_Python在终端通过pip安装好包以后在Pycharm中依然无法使用的问题(三种解决方案)...

在终端通过pip装好包以后&#xff0c;在pycharm中导入包时&#xff0c;依然会报错。新手不知道具体原因是什么&#xff0c;我把我的解决过程发出来&#xff0c;主要原因就是pip把包安装到了“解释器1”&#xff0c;但我们项目使用的是“解释器2”。我们新手怕字多&#xff0c;所…