Xamarin.FormsWebView 是一个在应用中显示 Web 和 HTML 内容的视图。 本文介绍如何创建一个扩展 WebView 以允许从 JavaScript 调用 C# 代码的自定义呈现器。
每个 Xamarin.Forms 视图都有一个附带的呈现器,适用于创建本机控件实例的各个平台。 当 iOS 中的 Xamarin.Forms 应用程序呈现 WebView 时,将实例化 WkWebViewRenderer 类,而该操作又会实例化本机 WkWebView 控件。 在 Android 平台上,WebViewRenderer 类实例化本机 WebView 控件。 在通用 Windows 平台 (UWP) 上,WebViewRenderer 类实例化本机 WebView 控件。 有关 Xamarin.Forms 控件映射到的呈现器和本机控件类的详细信息,请参阅呈现器基类和本机控件。
下图说明了 View 和实现它的相应本机控件之间的关系:

通过在每个平台上为 WebView 创建自定义呈现器,可使用呈现过程来实现平台自定义。 执行此操作的过程如下:
- 创建
HybridWebView自定义控件。 - 通过 Xamarin.Forms 使用
HybridWebView。 - 在每个平台上为
HybridWebView创建自定义呈现器。
现在,依次讨论每个项目以实现 HybridWebView 呈现器,该呈现器可增强 Xamarin.FormsWebView 以允许从 JavaScript 调用 C# 代码。 HybridWebView 实例将用于显示要求用户输入其名称的 HTML 页。 然后,当用户单击 HTML 按钮,JavaScript 函数将调用 C# Action 显示一个包含用户名称的弹出项。
要详细了解从 JavaScript 调用 C# 的过程,请参阅从 JavaScript 调用 C#。 有关 HTML 页面的详细信息,请参阅创建网页。
注意
WebView 可从 C# 调用 JavaScript 函数,并将任何结果返回给调用的 C# 代码。 有关详细信息,请参阅调用 JavaScript。
创建 HybridWebView
可通过对 WebView 类创建子类来创建 HybridWebView 自定义控件:
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
HybridWebView 自定义控件创建于 .NET Standard 库项目中,它为控件定义以下 API:
- 指定要加载的网页的地址的
Uri属性。 - 用于向控件注册
Action的RegisterAction方法。 已注册的操作将从通过Uri属性引用的 HTML 文件中包含的 JavaScript 调用。 - 移除对已注册的
Action的引用的CleanUp方法。 - 调用已注册的
Action的InvokeAction方法。 将从每个平台项目中的自定义呈现器中调用此方法。
使用 HybridWebView
通过在自定义控件上声明 HybridWebView 自定义控件位置的命名空间并使用命名空间前缀,可以在 .NET Standard 库项目的 XAML 中引用该自定义控件。 以下代码示例展示了 XAML 页可以如何使用 HybridWebView 自定义控件:
<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
x:Class="CustomRenderer.HybridWebViewPage"
Padding="0,40,0,0">
<local:HybridWebView x:Name="hybridWebView"
Uri="index.html" />
</ContentPage>
local 命名空间前缀可以命名为任何内容。 但是,clr-namespace 和 assembly 值必须与自定义控件的详细信息相匹配。 声明命名空间后,前缀用于引用自定义控件。
以下代码示例展示了 C# 页可以如何使用 HybridWebView 自定义控件:
public HybridWebViewPageCS()
{
var hybridWebView = new HybridWebView
{
Uri = "index.html"
};
// ...
Padding = new Thickness(0, 40, 0, 0);
Content = hybridWebView;
}
HybridWebView 实例将用于显示每个平台上的本地 Web 控件。 它的 Uri 属性设置为一个存储在每个平台项目中的 HTML 文件,并由本机 Web 控件显示。 呈现的 HTML 要求用户输入其名称,并使用 JavaScript 函数调用 C# Action 以对 HTML 按钮单击进行响应。
HybridWebViewPage 注册要从 JavaScript 调用的操作,如以下代码示例所示:
public partial class HybridWebViewPage : ContentPage
{
public HybridWebViewPage()
{
// ...
hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
}
}
此操作调用 DisplayAlert 方法以显示模式弹出项,该弹出项显示在 HybridWebView 实例显示的 HTML 页面中输入的名称。
现可将自定义呈现器添加到每个应用程序项目,以通过允许从 JavaScript 调用 C# 代码来增强平台 Web 控件。
在每个平台上创建自定义呈现器
创建自定义呈现器类的过程如下所示:
- 在 iOS 上创建
WkWebViewRenderer类的子类,并在 Android 和 UWP 上创建WebViewRenderer类,用于呈现自定义控件。 - 替代呈现
WebView的OnElementChanged方法,并编写逻辑对其进行自定义。 创建HybridWebView对象时,调用此方法。 - 向自定义呈现器类或 AssemblyInfo.cs 添加
ExportRenderer属性,以指定它将用于呈现 Xamarin.Forms 自定义控件。 此属性用于向 Xamarin.Forms 注册自定义呈现器。
注意
对于大多数 Xamarin.Forms 元素,都可选择在每个平台项目中提供自定义呈现器。 如果未注册自定义呈现器,将使用控件基类的默认呈现器。 但是,呈现视图元素时,每个平台项目中都需要自定义呈现器。
下图说明了示例应用程序中每个项目的职责,以及它们之间的关系:

HybridWebView 自定义控件由平台呈现器类呈现,这些类在 iOS 上派生自 WkWebViewRenderer 类,而在 Android 和 UWP 上,则派生自 WebViewRenderer 类。 这导致每个 HybridWebView 自定义控件都使用本机 Web 控件呈现,如以下屏幕截图所示:

WkWebViewRenderer 和 WebViewRenderer 类公开 OnElementChanged 方法,创建 Xamarin.Forms 自定义控件时会调用此方法以呈现对应的本机 Web 控件。 此方法采用 VisualElementChangedEventArgs 参数,其中包含 OldElement 和 NewElement 属性。 这两个属性分别表示呈现器“曾经”附加到的 Xamarin.Forms 元素和呈现器“现在”附加到的 Xamarin.Forms 元素。 在示例应用程序中,OldElement 属性将为 null,且 NewElement 属性将包含对 HybridWebView 实例的引用。
在每个平台呈现器类中,OnElementChanged 方法的替代版本可执行本机 Web 控件自定义。 可以通过 Element 属性获取正在呈现的 Xamarin.Forms 控件的引用。
每个自定义呈现器类均用 ExportRenderer 属性修饰,该属性向 Xamarin.Forms 注册呈现器。 该属性采用两个参数:要呈现的 Xamarin.Forms 自定义控件的类型名称和自定义呈现器的类型名称。 属性的 assembly 前缀指示属性适用于整个程序集。
以下各部分讨论每个本机 Web 控件加载的网页的结构、从 JavaScript 调用 C# 的过程以及在每个平台自定义呈现器类中的实现。
创建网页
下面的代码示例显示了将由 HybridWebView 自定义控件显示的网页:
<html>
<body>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<h1>HybridWebView Test</h1>
<br />
Enter name: <input type="text" id="name">
<br />
<br />
<button type="button" onclick="javascript: invokeCSCode($('#name').val());">Invoke C# Code</button>
<br />
<p id="result">Result:</p>
<script type="text/javascript">function log(str) {
$('#result').text($('#result').text() + " " + str);
}
function invokeCSCode(data) {
try {
log("Sending Data:" + data);
invokeCSharpAction(data);
}
catch (err) {
log(err);
}
}</script>
</body>
</html>
网页允许用户在 input 元素中输入他们的名称,并在单击时提供引用 C# 代码的 button 元素。 实现此操作的过程如下:
- 当用户单击
button元素时,调用invokeCSCodeJavaScript 函数,并将input元素的值传递到函数。 invokeCSCode函数调用log函数以显示发送给 C#Action的数据。 然后它调用invokeCSharpAction方法以调用 C#Action,传递来自input元素的参数。
invokeCSharpAction JavaScript 函数未在网页中定义,并将由每个自定义渲染器注入其中。
在 iOS 上,此 HTML 文件位于平台项目的“内容”文件夹中,包含 BundleResource 的生成操作。 在 Android 上,此 HTML 文件位于平台项目的“资产/内容”文件夹中,包含 AndroidAsset 的生成操作。
从 JavaScript 调用 C#
从 JavaScript 调用 C# 的过程在每个平台上完全相同:
- 自定义呈现器创建本机 Web 控件并加载
HybridWebView.Uri属性指定的 HTML 文件。 - 加载网页后,自定义呈现器将
invokeCSharpActionJavaScript 函数注入到网页中。 - 当用户输入其名称并单击 HTML
button元素时,会调用invokeCSCode函数,并随之调用invokeCSharpAction函数。 invokeCSharpAction函数调用自定义呈现器中的方法,该方法随之调用HybridWebView.InvokeAction方法。HybridWebView.InvokeAction方法调用已注册的Action。
以下各节将讨论此过程在每个平台上的实现方式。
在 iOS 上创建自定义呈现器
以下代码示例展示了适用于 iOS 平台的自定义呈现器:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.iOS
{
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
userController = config.UserContentController;
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
userController.RemoveAllUserScripts();
userController.RemoveScriptMessageHandler("invokeAction");
HybridWebView hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
string filename = Path.Combine(NSBundle.MainBundle.BundlePath, $"Content/{((HybridWebView)Element).Uri}");
LoadRequest(new NSUrlRequest(new NSUrl(filename, false)));
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
((HybridWebView)Element).InvokeAction(message.Body.ToString());
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
HybridWebViewRenderer 类将 HybridWebView.Uri 属性中指定的网页加载到本机 WKWebView 控件中,并将 invokeCSharpAction JavaScript 函数注入到网页中。 用户输入其名称并单击 HTML button 元素后,执行 invokeCSharpAction JavaScript 函数,并在收到来自网页的消息后调用 DidReceiveScriptMessage 方法。 然后此方法将调用 HybridWebView.InvokeAction 方法,该方法调用已注册的操作以显示弹出项。
此功能以下述方式实现:
- 呈现器构造函数创建一个
WkWebViewConfiguration对象,并检索其WKUserContentController对象。WkUserContentController对象允许发布消息并将用户脚本注入网页。 - 呈现器构造函数创建一个
WKUserScript对象,该对象在加载网页后将invokeCSharpActionJavaScript 函数注入到该网页中。 - 呈现器构造函数调用
WKUserContentController.AddUserScript方法,以将WKUserScript对象添加到内容控制器中。 - 呈现器构造函数调用
WKUserContentController.AddScriptMessageHandler方法,以将名为invokeAction的脚本消息处理程序添加到WKUserContentController对象,这将导致 JavaScript 函数window.webkit.messageHandlers.invokeAction.postMessage(data)在使用WKUserContentController对象的所有WebView实例中的所有框架中定义。 - 如果已将自定义呈现器附加到新的 Xamarin.Forms 元素:
WKWebView.LoadRequest方法加载HybridWebView.Uri属性指定的 HTML 文件。 该代码指定该文件存储在Content项目的文件夹中。 显示网页后,invokeCSharpActionJavaScript 函数将注入到网页中。
- 当呈现器附加到的元素发生更改时,便会发布资源。
- 释放呈现器时,将清理 Xamarin.Forms 元素。
注意
WKWebView 类仅支持 iOS 8 及更高版本。
此外,Info.plist 必须更新,以包含以下值:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
在 Android 上创建自定义呈现器
以下代码示例展示了适用于 Android 平台的自定义呈现器:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
HybridWebViewRenderer 类将 HybridWebView.Uri 属性中指定的网页加载到本机 WebView 控件中,并在网页加载完成后使用 JavascriptWebViewClient 类中 OnPageFinished 的替代方法将 invokeCSharpAction JavaScript函数注入到网页中:
public class JavascriptWebViewClient : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
用户输入其名称并单击 HTML button 元素后,执行 invokeCSharpAction JavaScript 函数。 此功能以下述方式实现:
- 如果已将自定义呈现器附加到新的 Xamarin.Forms 元素:
SetWebViewClient方法将新的JavascriptWebViewClient对象设置为WebViewClient的实现。WebView.AddJavascriptInterface方法将新的JSBridge实例注入到 WebView 的 JavaScript 上下文的主框架中,并将其命名为jsBridge。 这允许从 JavaScript 访问JSBridge类中的方法。WebView.LoadUrl方法加载HybridWebView.Uri属性指定的 HTML 文件。 该代码指定该文件存储在Content项目的文件夹中。- 在
JavascriptWebViewClient类中,页面加载完成后会将invokeCSharpActionJavaScript 函数注入到网页中。
- 当呈现器附加到的元素发生更改时,便会发布资源。
- 释放呈现器时,将清理 Xamarin.Forms 元素。
当执行 invokeCSharpAction JavaScript 函数时,它会调用 JSBridge.InvokeAction 方法,该方法如下面的代码示例所示:
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}
该类必须派生自 Java.Lang.Object 且必须使用 [JavascriptInterface] 和 [Export] 属性修饰公开给 JavaScript 的方法。 因此,当 invokeCSharpAction JavaScript 函数注入到网页中并执行时,由于使用 [JavascriptInterface] 和 [Export("invokeAction")] 属性进行了修饰,它将调用 JSBridge.InvokeAction 方法。 然后,InvokeAction 方法调用 HybridWebView.InvokeAction 方法,这将调用已注册的操作以显示弹出项。
重要
使用 [Export] 属性的 Android 项目必须包含对 Mono.Android.Export 的引用,否则将导致编译器错误。
请注意,JSBridge 类保持对 HybridWebViewRenderer 类的 WeakReference。 这是为了避免在两个类之间创建循环引用。 有关详细信息,请参阅弱引用。
在 UWP 上创建自定义呈现器
以下代码示例展示了适用于 UWP 的自定义呈现器:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.UWP
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.external.notify(data);}";
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.NavigationCompleted -= OnWebViewNavigationCompleted;
Control.ScriptNotify -= OnWebViewScriptNotify;
}
if (e.NewElement != null)
{
Control.NavigationCompleted += OnWebViewNavigationCompleted;
Control.ScriptNotify += OnWebViewScriptNotify;
Control.Source = new Uri($"ms-appx-web:///Content//{((HybridWebView)Element).Uri}");
}
}
async void OnWebViewNavigationCompleted(Windows.UI.Xaml.Controls.WebView sender, WebViewNavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
// Inject JS script
await Control.InvokeScriptAsync("eval", new[] { JavaScriptFunction });
}
}
void OnWebViewScriptNotify(object sender, NotifyEventArgs e)
{
((HybridWebView)Element).InvokeAction(e.Value);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
HybridWebViewRenderer 类将 HybridWebView.Uri 属性中指定的网页加载到本机 WebView 控件中,并在加载网页后使用 WebView.InvokeScriptAsync 方法将 invokeCSharpAction JavaScript函数注入到网页中。 用户输入其名称并单击 HTML button 元素后,执行 invokeCSharpAction JavaScript 函数,并在收到来自网页的通知后调用 OnWebViewScriptNotify 方法。 然后此方法将调用 HybridWebView.InvokeAction 方法,该方法调用已注册的操作以显示弹出项。
此功能以下述方式实现:
- 如果已将自定义呈现器附加到新的 Xamarin.Forms 元素:
- 已注册用于
NavigationCompleted和ScriptNotify事件的事件处理程序。 当本机WebView控件已完成加载当前内容或导航失败时,将触发NavigationCompleted事件。 当本机WebView控件中的内容使用 JavaScript 传递字符串到应用程序时,会激发ScriptNotify事件。 网页在传递string参数时通过调用window.external.notify激发ScriptNotify事件。 WebView.Source属性设置为HybridWebView.Uri属性指定的 HTML 文件的 URI。 该代码假定该文件存储在项目Content文件夹中。 显示网页后,会激发NavigationCompleted事件,并调用OnWebViewNavigationCompleted方法。 导航成功完成后,会使用WebView.InvokeScriptAsync方法将invokeCSharpActionJavaScript 函数注入到网页。
- 已注册用于
- 当呈现器附加到的元素更改时,取消订阅事件。
- 释放呈现器时,将清理 Xamarin.Forms 元素。