前言
现在常用的方案
Duilib/(Duilib+CEF)
只支持Windows的选择,优点是打包文件小(使用C++)(QQ、微信、有道精品课)。Qt/(Qt+CEF)
支持跨平台,缺点是打包文件大(使用C++)这是很多客户端跨平台的首选,UI 库和各种功能的类库非常丰富,但是学习成本比较高。(WPS)。WinForm/(WinForm+CEF)
微软第一代的 Desktop 版本,但是从开发体验角度来说自定义、美化控件会比较麻烦。不推荐。WPF/(WPF+CEFSharp)
打包文件小,但是性能相比前两者弱,但比Electron强,内存占用高,只支持Windows。Electron
打包文件大,但是性能弱,内存占用高,支持跨平台,开发效率最高。(迅雷)。
上面各种技术只要集成CEF的打包都大,所以上面所说的是在不集成CEF情况下对比的。
几种方案都各有利弊,可以根据团队的情况选用,都是相对可用的,其他的方案比如Flutter,Java就不太推荐。
目前因为C++的技术栈的原因,我们的团队主要用WPF或者是Electron来做桌面端的开发。
网易云信团队基于原 Duilib 魔改的 NIM Duilib
库下载地址:https://github.com/netease-im/NIM_Duilib_Framework/
有些界面用WEB开发会更美观和快速,所以这里就来集成CEFSharp来加载WEB页面。
注意
添加CEF会大幅增加安装包大小。
为什么使用CEF
- .NET 自带的 WebBrowser 是WEB 开发人员最讨厌的 IE,性能低下而且兼容性差
- Webkit: 项目已经不再支持
- Cef 是 Chrome 内核,性能和兼容性杠杠的。缺点就是带的 DLL 太多太大,一个发布版应该在150M左右,X86+X64一块就得快300M了。另外EXE加载速度也会稍慢。
CEF官方文档
https://github.com/cefsharp/CefSharp
安装WPF版本依赖
通过Nuget安装,右击项目 -> 管理Nuget程序包 -> 在打开的界面中搜索CefSharp,依次安装 CefSharp.Common
和 CefSharp.Wpf
,至于 cef.redist.x64
和 cef.redist.x86
会自动安装。
配置解决方案平台
因为CefSharp不支持Any CPU
所以要配置x86、x64,点击菜单 生成
-> 配置管理器
。
选择解决方案平台,点击编辑,先将x64和x86删掉,再重新新建,重新配置比较容易些。
Any CPU的支持
如果我们要支持Any CPU
就要自己实现了。
1 | using System.Windows; |
使用
使用时可以直接在xaml文件中直接添加ChromiumWebBrowser控件,不过ChromiumWebBrowser控件特别消耗内存,所以代码里动态添加也是一种不错的选择。
Xaml中添加
xmal文件头部插入引用
1 | xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" |
添加控件如下:
1 | <Grid x:Name="ctrlBrowerGrid"> |
cs文件中操作控件访问网址:
1 | Browser.Load("https://www.psvmc.cn"); |
代码中添加
添加浏览器类:
1 | using CefSharp.Wpf; |
动态添加和操作控件:
1 | private CollapsableChromiumWebBrowser MyBrowser = null; |
获取Cookie和Html
添加Cookie访问类
1 | using CefSharp; |
浏览器控件访问网址,并设置回调
1 | private CollapsableChromiumWebBrowser MyBrowser = null; |
加载本地页面和JS回调
添加HTML
项目下添加html路径html\index.html
1 |
|
复制页面
复制页面到目标目录
方式1
项目
->属性
->生成事件
->生成前事件命令行
添加如下
1 | xcopy /Y /i /e $(ProjectDir)\html $(TargetDir)\html |
方式2
文件右键点击属性,设置复制到输出目录和生成操作。
如果文件较多建议用方式1 。
代码
全局初始化
Application中初始化默认配置
1 | CefSettings cSettings = new CefSettings() |
注意
上面的代码(
Cef初始化
)项目中只能调用一次。
页面中初始化
1 | private ChromiumWebBrowser MyBrowser = null; |
调用JS方法
1 | private void Button_Click(object sender, RoutedEventArgs e) |
事件回调类
1 | public class CallbackObjectForJs |
禁用右键菜单的类
1 | public class MenuHandler : IContextMenuHandler |
原窗口打开链接的类
1 | public class LifeSpanHandler : ILifeSpanHandler |
注意项
API变更
1 | //Old Method |
本地文件路径
文件路径中不能包含特殊字符,否则不能加载,之前我的项目在
C#
目录下,就一直加载不了页面。
性能
在使用CEF的过程中,我发现了一个现象:
WPF版的CEF比Chrome性能要差:一些有动画的地方会掉帧(例如,CSS动画,全屏图片拖动等),视频播放的效果也没有Chrome流畅。
查了一下相关资料,发现CEFSharp.WPF不是直接渲染在控件上的,它的大概流程如下:
- CEFSharp.WPF的ChromiumWebBrowser控件本质上是一个图片
- 而是通过离屏渲染的方式渲染在缓冲区里,
- 绘制完成后,然后将缓冲区的数据传递到InteropBitmap中去
- 将InteropBitmap作为ChromiumWebBrowser的图源更新
这个基本上是类似于WPF的视频播放器的做法:先离屏渲染出图片,在将图片更新到界面。这个做法由于是使用的WPF的原生渲染方案,可以说是WPF的原生控件的,本身是有不少好处的:
- 可以支持透明背景
- 可以在上面叠加其它WPF控件
- 可以支持WPF的变形,动画,裁剪等特效
简单一句话,是可以和WPF程序无缝集成的,如果将WEB界面作为控件嵌入式再方便不过的。
但是,它这个实现是有代价的:
- 离屏渲染本身需要多一层工序,
- 有切换上下文和内存拷贝的开销。
- 更要命的是,貌似目前GPU离线渲染视频效果还不是很好,因此默认还把gpu加速给关了,性能更下降了一截。
另外,InteropBitmap传递图片内存的效率本身就不高,不光吃cpu,还吃内存,网上也有人讨论过。
针对这些问题,有人建议使用WritableBitmap替换InteropBitmap,但貌似作者认为InteropBitmap的效率更好些。
我使用过WritableBitmap离屏渲染地图,应该是能做到比当前更好的性能的。
也有人建议使用效率更高的D3DImage(WPF原生支持这个,不过麻烦些),可能作者觉得目前它的这个性能问题不是首要解决的目标吧,也一直没有采纳。
最后说一下解决方法吧,虽然在大部分的情况下,当前的解决方案是能满足我们的需求的,不过如果遇到非要解决的情况下,可以使用下WinFrom版的CEFSharp,通过WinFormHost来集成到WPF程序中去。
WinFrom版的CEFSharp应该是直接渲染的,我试了一下,效率基本上接近Chrome,并且由于他们的基础库是公用的,在WPF程序中WinFrom版和WPF版的CEF是可以并存的,用起来还算方便。
WPF使用WindowsFormsHost加载Winform控件
WindowsFormsHost
是置顶的,不能像WPF控件那样层叠实现。
WindowsFormsHost使用起来相当简单。 它有一个Child属性,你可以在其中定义一个WinForms控件,就像WPF Window只保存一个根控件一样。 如果在WindowsFormsHost中需要来自WinForms的更多控件,可以使用WinForms中的Panel控件或任何其他容器控件。
使用方法:
首先,我们需要向项目中的引用(reference)中添加两个动态库dll
一个是.NET库中的
System.Windows.Forms
另外一个是
WindowsFormsIntegration
添加完两个动态dll以后,就可以在控件库中找到WindowsFormsHost这个控件;
将这个控件放入窗体,放置完以后在xmal代码中会自动生成相应代码:
1 | <Grid> |
然后,需要在xmal的开始处添加两行代码 :
1 | xmlns:WinFormHost="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration" |
这样就可以在WindowsFormsHost下放置需要的Windows Form控件了。
1 | <WindowsFormsHost> |
WPF中加载WinForm版的CEF
WinForm版的CEF加载页面的显示效果比较流畅,这里就介绍怎么在WPF中加载WinForm版的CEF
添加如下依赖(圈中的两个)
添加头
1 | xmlns:winforms="clr-namespace:CefSharp.WinForms;assembly=CefSharp.WinForms" |
添加组件
1 | <Grid> |
加载页面
1 | private async void initMyBrowser() |
注意
默认直接加载页面会闪一下黑色的背景,所以这里先隐藏,延迟200毫秒再显示就不会有黑屏的现象了。
注意如果程序有多个窗口,不要在窗口关闭时调用下面的方法,会导致其他窗口也不可用。
1 | Cef.Shutdown(); |
启动加速
在实际使用过程中,发现有的客户端会出现chrome加载网页过慢问题,定位后发现很多是因为设置系统代理所致,此时可以通过如下启动参数禁止系统代理。
1 | {"proxy-auto-detect", "0"}, |
另外一个小技巧是: 由于cef本身是一个独立的进程,我们不需要等待主窗口加载完成后再创建ChromiumWebBrowser,单独启动它也不影响主程序启动速度,
因此可以将ChromiumWebBrowser和主窗口一并启动。
1 | public MainWindow() |
主窗口加载完成后,再将chrome放置到相应的控件上。
需要说明的是,ChromiumWebBrowser只有防止到窗口才开始渲染,要想预先渲染,可以先新建一个临时窗口,把这个临时窗口显示到屏幕外面去。要用ChromiumWebBrowser的时候再放置到我们的实际窗体中。
Flash支持
找到本地flash的dll(pepflashplayer.dll)
项目中新建plugins,添加pepflashplayer.dll,右击属性,改为始终复制
CEF初始化配置
1 | CefSettings settings = new CefSharp.CefSettings() |
Chrome插件支持
CEF只是集成了Chromium的Content API,对Chrome插件支持不完整,只支持部分功能。