众所周知,B站是个学习的网站,网页端和粉版移动端都非常的好用,不过,相对其它平台来说B站的PC客户端也算是大器晚成了。在有些场景PC客户端的优势也是显而易见的,比如,跓留电脑桌面的快捷、独立的应用窗口、特有的交互方式等,因此就有了很多喜欢B站的技术大佬整出了不少体验不错的第三方PC应用,比如云之幻版、逍遥橙子版等。
当然我站也有一个源自三方的UWP客户端,但是由于历史原因一直没有得到很好的维护,在21年底,一个需要在电脑端预装的需求让我们有了开发一个全新的PC客户端的想法。

选择一个什么样的技术,可以在降本增效的大背景下实现一个体验相对较好、易维护、跨平台且能快速推进的PC应用呢?

图2 PC端开发技术对比
调研了一些PC端应用开发的技术方案之后,基于开发效率、跨平台和成熟度等因素考虑,最终我们选择了使用WEB技术栈的Electron做为新PC客户端的开发框架。

图3 Electron框架的优势
在确定了开发框架后,我们先来看看Electron到底是什么。我们知道Electron是背靠微软基于WEB技术的开源项目,最初源自GitHub团队的Atom Shell项目,结合NodeJs和Chromium实现,并对系统API做了封装,比如系统对话框、系统托盘、系统菜单、剪切板等,开发者可以直接通过js访问这些API。

图4 Electron的构成

图5 一般Electron应用的运行时
我们知道Electron程序主要分为主进程(Main Process)和渲染进程(Render Process),主进程主要为NodeJs和原生API,渲染进程主要为前端技术,主进程和渲染进程之间通过IPC进行通信。
为了易维护、好拓展、可复用等目的,我们选择了NodeJs+NestJs+Esbuild的方案来开发主进程,选择了和WEB首页Laputa项目相同的技术栈Vue3+Vite2+TypeScript开发渲染进程。

图6 PC客户端技术选型
主进程的NestJs是一个受Angular启发的NodeJS后端框架,风格有点像SpringBoot,结合OOP (Object Oriented Programming)、FP (Functional Programming)和 FRP (Functional Reactive Programming)等编程范式,能够开发出高可测、好拓展、松耦合、易维护的应用。Esbuild是使用go语言实现的js极速打包工具,可以助力更高效的开发主进程。

图7 Esbuild官网展示的构建性能对比
渲染进程使用和WEB相同的技术栈,除了方便复用WEB现成的很多组件之外,同时WEB业务线的开发人力也可以无障碍的投入到PC客户端的业务开发中来,因此可以最大限度的降低开发成本、提高开发效率。另外,我们还在项目中实践和应用了我们自研的Vue3组件库vivid-ui、函数库@bilibili/b-utils和样式库@bilibili/b-style等工具库。

图8 PC客户端自研工具库
PC客户端是B站的一个全新的端,除了推荐、直播、热门、OGV、动态、空间、搜索等常规功能之外,还需具备电脑端本地软件的其它能力,包括安装升级、托盘隐藏、本地存储、专有窗口、离线缓存、自定义配置、唤端拉活等等。

图9 下载中心
(其中Windows版、MacOS版、微软商店版都是PC客户端)
我们先来看看PC客户端的业务功能设计,在1.0的版本我们其实就已经实现了安装卸载、开屏页、首页、动态、搜索、我的和空间、播放页、登录、主题和设置等功能,随着版本迭代,我们陆续完成了一键三连安装界面、离线缓存、播单、消息等功能。

图10 PC客户端业务功能设计
在明确业务功能后,结合框架和技术栈,我们设计了PC客户端的整体架构:由渲染进程实现业务功能的UI和交互,由主进程实现本地日志、存储、下载、上报、升级等服务和支持的功能,渲染进程和主进程之间封装IPC为统一的JSB接口进行通信。

图11 PC客户端整体架构设计
在渲染进程中,账号登录、大会员充值、直播间和开播页都是使用webview嵌WEB页的方式接入的,其它业务模块均为本地应用页面;在主进程中,本地日志、本地存储和下载SDK都是引入的模块,其它各个模块和服务之间通过依赖注入和rxjs主题订阅的方式实现相互调用;JSB则由主进程通过preload的方式注入到渲染进程各页面的window对象中,渲染进程不论是外嵌还是本地页面都能访问到JSB对象实现和主进程通信。
由于客户端基于Electron和WEB技术开发,一个窗口页面其实本质上就是一个HTML页面。当然,我们的主页面都是从由主进程创建的本地服务器上拉取的Local页面,因此断网的情况也能响应,像首页、动态网络强相关的页面,在断网时会显示失败提示,而离线缓存、设置等功能都能正常使用。
由于我们渲染层是基于Vue开发的SPA应用,主窗口和播放窗口在打开时会有一个比较耗时的加载过程,为了提升用户体验我们专门针对窗口创建和打开过程做了优化。
在启动时(首次打开主窗口),会先创建一个隐藏的主窗口,加载一个相对简单的开屏页。
当开屏页渲染完成后,调用主进程splashPageReady接口,主进程控制显示主窗口。(防止开屏页白屏)
主窗口和开屏页显示之后,在主窗口开屏页下方加载和渲染比较大的主页面。
主页面渲染完成时,调用主进程mainWindowReady接口,并关闭盖在主页面上面的开屏页。
主进程收到mainWindowReady后,创建一个隐藏的播放窗口。
当用户点击播放视频时,显示已渲染好的播放窗口,并加载和播放视频。
关闭主窗口和播放窗口时,只将窗口隐藏并不真实销毁窗口实例,当再次打开或播放视频时,就可以快速打开窗口,提升用户体验。

图12 PC客户端执行过程

图13 窗口创建和打开流程
事实上,采用隐藏的窗口进程做预渲染和并行处理是提升Electron应用体验的常用方法,在Electron中可以用ChildProcess、BrowserWindow、BrowserView、webview等方式开启子进程,突破进程限制,充分利用机器的性能。
可以通过主进程app.getAppMetrics()方法获取APP各进程的CPU和内存数据统计,PC客户端主要包含7个常跓进程:1个浏览器(Browser)进程、1个GPU进程、1个网络服务(Network Service)进程、1个音频服务(Audio Service)进程和3个页面(Tab)进程。

图14 PC客户端的进程
由于渲染进程占用资源比较大,如果用户长时间未使用又没有关闭的话就会造成资源浪费,因此我们还为APP设计了“节能模式”,当APP隐藏到托盘超过一定时间未点开后会进入到此模式,这时程序会杀掉主窗口和播放窗口对应的渲染进程,释放资源占用。进入节能模式之后,如果用户再打开APP则会进入前面提到的窗口创建和打开流程,不过这个过程没有APP初始化的任务,速度会比冷启动时要快很多。

图15 PC客户端节能模式
PC客户端使用Fawkes平台打包构建、使用北极星进行数据上报并在观远建立数据报表、使用魔镜平台进行自动化巡检测试、在info上建有专门的产品和开发文档,开发方面也有明确的代码规范要求

图16 项目设计开发平台工具
PC客户端渲染进程和主进程都是基于TypeScript进行开发的,Ts提供的类型定义和规范可以大幅度提高代码的可读性,在主进程和渲染进程的中间,我们还设计了一个common共享层,用来实现Ts类型和常量的共享。
PC客户端项目结构:
bilibili-electron
├── app/* # 构建生成代码
├── build/* # 构建资源目录
├── dist/* # 安装包生成目录
├── scripts/*
├── build.main.ts # 主进程构建配置
│
├── src
├── common/* # 共享类型
├── main/* # 主进程
└── render/* # 渲染进程
│
├── electron-builder.config.js # 安装包打包配置
├── vite.config.ts # 渲染进程构建配置
├── pckage.jsonPC客户端项目运行和构建都基于NPM Script,构建release包只需要运行: yarn build:prod。构建代码位于app目录下,由于构建产物也是js代码,很容易被人拿去套壳、植入或篡改。为了防范这些所知的风险,增加被篡改和攻击的难度,我们需要对构建包进行加密处理。经过不断的迭代优化,我们实现了一套使用结合代码混淆、对称加密、字节码处理、WASM解密和啥希校验的组合式客户端加密防破解方案。
在打Release包时,会把构建生成的代码先进行混淆、压缩处理,再将主进程主程序代码使用AES-256对称加密生成/app/main/.biliapp文件,将解密和执行入口使用字节码处理,最后计算出APP的哈希值。

图17 PC客户端主程序加密
当PC客户端启动时,会先根据操作系统和系统架构找到对应字节码入口、解密并运行主程序、主程序中校验APP的哈希值,在确定运行环境安全后再正常启动程序。

图18 pc客户端运行流程
PC客户端基于electron-builder进行打包,Mac的安装窗口通过electron-builder配置可以直接生成,Windows的专属一键三连安装程序则是使用NSIS + QT独立开发的。

图19 PC客户端安装引导界面

图20 基于NSIS+QT的专属一键三连界面
PC客户端前期版本基于electron-updater进行升级更新,每次进行版本升级后,都需要用户重新下载完整的安装包进行覆盖安装,由于安装包体积比较大在网络比较差的情况下,升级过程会比较耗时影响用户体验。
在1.9.x版本之后,我们已集成增量更新的功能,只需对app.asar代码包进行更新而不是包含框架的完整安装包,因此可以提高了升级更新效率和体验。

图21 PC客户端升级更新流程
类似爱奇艺和腾讯视频,我们的PC客户端也是双窗口模式,即主窗口和播放窗口是独立的,可以让用户一边播放视频一边刷动态、逛空间、看消息。
得益于PC客户端渲染进程使用WEB技术开发,稍加适配,PC客户端的播放器直接用上了WEB强大的Nano播放器,这使得PC客户端也拥有了高级弹幕、播放设置、播放器快捷键等功能。PC客户端还将UGC和OGV播放器集成在同一个播放窗口,让视频类型切换更加顺畅,还可以通过前进后退播放历史纪录。
PC客户端的播放质量对比WEB播放页来说,首帧和VV卡顿率会优于WEB,主要原因为能更多预载,单实例,编码支持统一;而百分钟卡顿率和错误率相对WEB来说要略高一些,新平台有更多case需要处理,此项会略高但重试机制尽量不影响体验。

图22 PC客户端和WEB播放页质量对比
对比B站其它端来说,PC客户端的人均播放时长是最高的,目前均值已超80分钟,峰值可达100分钟。
另外,PC客户端还实现了贴心的“小窗”播放模式,当点击播放窗口右上角的小窗模式按钮时或拖动改变播放窗口到小尺寸时便会进入小窗模式。
在小窗模式下:

图23 极致的小窗播放体验

图24 固定小窗在最前端
五、PC客户端的质量保证
PC客户端在每次上线之前都需要经过严格的测试流程,一般在官网上线前4个工作日需要进行集成测试,前2个工作日出总包并进行总包回归测试。
除了功能测试之外,构建也需要保证准确性,PC客户端使用Fawkes打包构建,但是人工构建每次都需要根据不同的渠道选择不同的环境变量,渠道也比较多构建容易出错,因此我们开发了CLI打包工具。
命令行打包工具打通了Fawkes构建平台和魔镜测试平台,通过命令行选择渠道、类型和其它参数,并使用配置列表发起构建。

图25 通过命令行工具发起构建

图26 魔镜PC客户端自动化测试
PC客户端于22年5月10日在官网正式上线,在B站WEB首页顶导、B站下载中心和常用的PC的软件平台如:微软商店、联想商店、华为应用市场、腾讯管家等搜索“哔哩哔哩”都能下载到最新版本。
PC客户端的发布渠道较多,不同的渠道也有不同的要求,新版本会根据渠道ID来构建并分别投放上架。

图27 PC客户端渠道汇总
上线以来,PC客户端DAU稳步增长,目前已达300万+。

图28 PC客户端DAU数据稳步增长
各渠道安装量稳步上长,对比官网包占比已达40%。

图29 微软商店~入选精选娱乐应用

图30 华为应用市场~闲暇时光首选

图31 联想应用商店~搜索“哔哩哔哩”
本期作者

沈涛
哔哩哔哩资深开发工程师
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:
我是一名决定学习Ruby和RubyonRails的ASP.NETMVC开发人员。我已经有所了解并在RoR上创建了一个网站。在ASP.NETMVC上开发,我一直使用三层架构:数据层、业务层和UI(或表示)层。尝试在RubyonRails应用程序中使用这种方法,我发现没有关于它的信息(或者也许我只是找不到它?)。也许有人可以建议我如何在RubyonRails上创建或使用三层架构?附言我使用ruby1.9.3和RubyonRails3.2.3。 最佳答案 我建议在制作RoR应用程序时遵循RubyonRails(RoR)风格。Rails
我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow
我已经使用Apartment设置了一个Rails5应用程序(1.2.0)和Devise(4.2.0)。由于某些DDNS问题,应用只能在app.myapp.com下访问(请注意子域app)。myapp.com重定向到app.myapp.com。我的用例是每个注册该应用的用户(租户)都应该通过他们的子域(例如tenant.myapp.com)访问他们的特定数据。用户不应限定在其子域内。基本上应该可以从任何子域登录。重定向到租户的正确子域由ApplicationController处理。根据Devise标准,登录页面位于app.myapp.com/users/sign_in。这就是问题开始的
我在关注RyanbatesRailsCast的devise和omniauth(第235集-devise-and-omniauth-revised)。当我尝试使用Twitter登录时,标题中不断出现错误。defself.new_with_session(params,session)ifsession["devise.user_attributes"]new(session["devise.user_attributes"],without_protection:true)do|user|user.attributes=paramsuser.valid?end完整跟踪:C:/Ruby20