分页规范

[toc]

分页规范

一、分页底层原理

后台得到符合指定条件的所有记录,然后再截取位置。常见的截取位置方法为分页和指定第几页。

为避免请求非第一页数据时候,后台在得到符合指定条件的所有记录和第一页得到的不一样,需要前台或者后台需要记录与第一页一样的请求时间。

常见方案:

备注
pageIndex
pageSize 缺点:只能index变,size不能变,不然返回的数据会错误
timestamp 此为优化点,为的是避免请求非第一页数据时候,后台在得到符合指定条件的所有记录和第一页得到的不一样
没此参数的时候,后台应该是缓存了第一次请求后所有记录,只有再次重新请求或过期才释放这些所有记录。

timestamp 的记录方式:

1、后端记录(需要结合过期时间)

需要结合过期时间,以处理为每个用户记录的这个第一页请求时间。有额外开销

2、前端记录

二、优化方案:解决size的变化

方法1:后端记录下发的数据,新请求时候进行过滤

缺点:需要集合过期时间

方法2:前端下发上次请求的最后一条记录信息

信息方法1:上次最后一条的id,此方法不通。因为新增的记录后台id目前已经不是自增的了

信息方法2:上次最后一条的modifyTime,此方法可以,前提需要后台的时间单位为更为精确的mm级或者um等。

结论:此时所需字段如下

pageIndex 不需要,已通过最后一条的modifyTime 处理
timestamp 不需要,已通过最后一条的modifyTime 处理
最后一条的modifyTime
pageSize 可灵活变动

End

工作流程

[toc]

工作流程

前言

管理相关:详见管理相关 主要包含以下内容。

账号管理

一、开发相关

1、分支规范

分支开发+打包流程

2、目录结构规范

项目目录结构规范 见《架构模式-①概览》

目录结构规范的必要性在于:

  1. 便于团队协作:在团队协作中,规范的目录结构可以让所有人都能够快速地找到他们需要的文件和资源,提高开发效率。
  2. 便于维护:有一致的目录结构可以让项目更易于维护。例如,如果您需要添加一个新的功能,您可以很快地找到添加到哪个文件夹中。
  3. 可读性更高:良好的目录结构可以让代码更易于阅读和理解,这对于代码的维护和升级非常重要。

3、基础框架分层规范及解耦

在项目开发中,基础框架分层是非常重要的,可以帮助我们将代码分成多个层次,每个层次都有自己的职责和责任,从而提高代码的可维护性、可扩展性、可重用性和可测试性。同时,基础框架分层也可以帮助我们解耦不同部分的代码,减少代码之间的依赖关系,从而提高代码的灵活性和可靠性。

解耦的必要性在于:

  1. 减少代码之间的依赖关系:解耦可以帮助我们减少代码之间的依赖关系,降低代码的耦合度,从而提高代码的灵活性和可靠性。
  2. 提高代码的可维护性:解耦可以帮助我们将代码分成多个独立的部分,每个部分都有明确的职责和责任。这样,我们可以更容易地测试、修改和维护应用程序的不同部分,从而提高代码的可维护性。
  3. 提高代码的可扩展性:解耦可以帮助我们将应用程序的不同部分分离开来,以便于单独测试和修改。这样,我们可以更容易地扩展应用程序的功能和特性,而不会影响到其他部分的代码。
  4. 提高代码的可重用性:解耦可以帮助我们将可复用的代码和组件集中在公用组件层中,方便其他部分的代码进行复用。

4、项目空安全升级的必要性

Flutter 空安全(Null Safety)是指在代码中添加了对空值的非空断言、空值检查等机制,从而使代码更加健壮和安全性。Flutter 空安全升级的必要性主要体现在以下几个方面:

  1. 提高代码的健壮性和安全性:空值是程序中常见的 bug 和异常来源,它们可能会导致应用程序的崩溃和数据丢失等问题。Flutter 空安全升级可以帮助我们在编码时更加谨慎和严谨地处理空值,从而提高代码的健壮性和安全性。
  2. 改进开发体验:Flutter 空安全升级可以使开发人员更加自信地编写代码,减少了在运行时意外发现空值异常的可能性。此外, Flutter 空安全还提供了更好的类型推断和代码提示,使代码编写更加高效。
  3. 为未来的 Flutter 版本做好准备:Flutter 空安全是未来 Flutter 版本的必备功能,通过升级到 Flutter 空安全,我们可以为未来的 Flutter 版本做好准备,并避免在未来的升级中遇到问题。
  4. 适应 Dart 语言的发展:Dart 语言从一开始就支持空安全,Flutter 空安全升级可以使我们更加适应 Dart 语言的发展,以便更好地利用 Dart 语言的新特性和功能。

因此,Flutter 空安全升级是非常必要的,它可以提高代码的健壮性和安全性,改进开发体验,为未来的 Flutter 版本做好准备,并适应 Dart 语言的发展。

举几个例子说明不升级空安全容易出现的问题或者隐患

以下是一些在不升级 Flutter 空安全的情况下可能出现的问题或隐患:

  1. 空指针异常:在使用空值时,如果没有进行空值检查或非空断言操作,可能会导致空指针异常。
  2. 运行时错误:在使用空值时,如果没有进行空值检查或非空断言操作,可能会导致应用程序在运行时出现错误或异常,从而导致应用程序崩溃或数据丢失等问题。
  3. 不良用户体验:在应用程序中使用空值时,如果没有进行空值检查或非空断言操作,可能会导致应用程序出现不良的用户体验,例如界面卡顿、延迟等。
  4. 难以维护和修改代码:在没有使用 Flutter 空安全的情况下,代码中可能存在大量的空值检查和非空断言操作,这会使代码变得冗长和复杂,从而使代码难以维护和修改。
  5. 降低代码质量:在不使用 Flutter 空安全的情况下,可能会存在一些代码质量问题,例如代码重复、逻辑混乱等问题,这会降低代码的可读性、可维护性和可扩展性。

因此,升级 Flutter 空安全是非常必要的,它可以提高应用程序的健壮性和安全性,改进用户体验,提高代码的可读性、可维护性和可扩展性,并减少开发过程中出现的错误和异常。

5、代码规范

项目代码编写规范

模块功能划分清晰

  • 页面接口整理 见《请求规范》 中的【请求接口的整理规范】

打包保证

详见:app打包保证

主要处理问题:

  • 避免外部人员使用非正式包

质量保证

1、操作日志:app记录所有的记录,方便排查,接入游戏的地方也要有日志 [p1]
2、崩溃率、卡顿检测 [p1]
3、app接入apm [p2]

弱网测试:

业务提升

5、全站埋点:有现成工具最好,从核心业务开始 [p2]

日常事项

4、值班:记录每个人的能力,碰到问题找对应的人,值班热线电话号码,24小时随时待命 【p0】

分支规范

[toc]

分支规范

一、分支简介

项目应包含以下分支:

  1. master

    该分支为项目主分支,此分支只作为稳定版本发布使用,不允许在此分支上修改 bug ,开发功能等。并且该分支每次发布时需根据项目的版本号打上 tag ,例如 v1.0.0。

  2. develop

    该分支为各开发人员的开发合并分支,各开发人员在各自开发分支/功能分支/bug分支上开发/调试代码,完成相关工作后即可合并到 develop 分支,不允许各位在此分支上直接开发。此分支无需打 tag 。

  3. qa-test

    该分支是交由 QA 进行测试的分支,每次交由 QA 测试时,从 dev 分支合并到该分支,合并的最后一次 commit 需要打 tag ,例如 v1.0.0-test-num,num 为 1.0.0 版本提交测试的序号。QA 测试稳定后,merge 到 master 并打上 tag,待发布。

  4. feture_xxx

    该分支用于开发新功能,比如新版本迭代时,有一个较大的功能模块,可以不在 dev_hzxxx 分支开发,相关开发同学可在此类分支上开发,有两个优点:(1)可以多人协同 (2)可以防止模块较大时导致原来代码混乱。

  5. issue_xxx

    该分支用于解决 jira 上 bug,修改完成并验证后合并到 dev 分支,等待 QA 验证。

说明:关于以上 feature_xxx 和 issue_xxx 分支的使用说明可以根据实际情况进行参考,不是规定死的,在开发过程中有疑问可以跟大家讨论。

二、分支命名规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
master

develop

分支命名方式:模块/基础_功能_开发者
形如:
feature:新功能(feature)。
-------feature/auth
-------feature/auth_normal
-------feature/auth_ban
-------feature/auth_ban_qian
perf:优化相关,比如提升性能、体验。
-------perf/apm
-------perf/apm_dokit
chore:构建过程或辅助工具的变动。
-------chore/pack

附1:已知模块选项如下:
"strong_business": {
"auth": "登录模块(登录/注册、退出)",
"profile": "用户模块(个人首页/个人中心)",
"member": "会员模块(会员中心)",
"wish": "愿望单模块(愿望单列表、愿望单详情)",
"mall": "商城模块(商城首页、商品详情)",
"publish": "发布模块(愿望单发布)",
"order": "订单模块(订单列表、订单详情)",
"search": "搜索模块(大搜索)",
"im": "IM模块(会话列表、会话窗口)",
"live": "直播模块",
"comment": "评论模块"
}

附2:已知基础选项:
"package": {
"network": "网络模块",
"route": "路由模块",
"webview": "网页模块(js)",
"version": "版本模块(版本检查(含蒲公英)、应用内下载)",
"location": "定位模块",
"media": "媒体模块(选择、编辑、降噪增益)",
"share": "分享模块",
"apm": "应用性能管理(dokit、)",
"package": "其他基础库"
}

弱网测试

[toc]

第1节:弱网测试

本文中有引用的文章:

本文核心:

前言

网络测试:

序号 类型 问题示例 期望结果
1 无网 没有提示 提示”请求失败”
2 弱网 没有提示 提示”请求超时”
3 复网 显示的还是无网的页面 自动发起请求,恢复显示正常页面
4 切网 —- —-

1、无网

问题示例:没有提示

优化方案:

1、Toast提示”请求失败”(与2不共存)

2、页面显示无网络页面,内含刷新按钮(与1不共存)

没有退出操作页面,支持退出。而不是无网络时候,卡住不动。

序号 描述 举例 正常 异常 其他
1 没有退出操作页面 网页游戏加载页 OK 新增退出

2、弱网

具体可优化问题:详见下节 弱网问题与优化

一、弱网测试的关注点

1、关注点

弱网测试的目的就是尽可能保证用户体验,关注的关键点包括:

以下摘自:腾讯-AllTests软件测试-弱网测试工具-QNET

1、页面响应时间是否可以接受,关注包括热启动、冷启动时间、页面切换、前后台切换、首字时间,首屏时间等。

2、页面呈现是否完成一致。

3、超时文案是否符合定义,异常信息是否显示正常。

4、是否有超时重连。

5、安全角度:是否会发生DNS劫持、登陆IP更换频繁、单点登陆异常等。

6、大流量事件风险:是否会在弱网下进行更新Apk包、下载文件等大流量动作。

2、实际测试点总结

以下摘自:浅谈APP如何进行弱网测试

弱网环境测试点总结

完成弱网环境搭建后,一起来梳理下弱网测试场景和测试点。

场景一

弱网环境下某个操作响应时间

「原因」APP
用户对等待时间容忍度低,如果弱网环境Loading
超过5s,用户很容易Kill
应用后再次进入应用

「测试点」性能测试中,加入弱网环境测试点,检测各个场景网络请求的API
消耗时间

场景二

弱网环境下直至超时,UI
界面友好度&APP
是否稳定

「原因」容错机制主要是考虑弱网情况下带来的不稳定,常见的问题是:Loading
超时导致ANR or Crash

「测试点」弱网环境直至超时,判定为断网状态,界面提示友好且理解无歧义

场景三

断网环境下,是否自动重发请求

「原因」不同模块,开发对请求处理不同。测试前可了解,代码是否支持自动重复请求,自动重发请求的频率是什么?

「测试点」断网后恢复网络,是否堆积网络请求(目前来说理财模块10s左右无返回,则会重发请求),此时请求和返回正常情况下,是否出现异常情况。比如1次支付操作,断网后堆积多个支付请求,恢复网络后因堆积多个支付请求,是否完成多次支付

PS
:断网后恢复网络,考虑APP
进行操作目的是否伤害用户体验,通过哪种手段可以达到操作目的同时用户体验无感或者低伤害

比如,微信希望在线升级某些内容,会自动监听用户是否插着电or
连着WiFi
,一旦监听符合上述场景,APP
自动升级:

  • 插电场景:确保升级过程中,耗电不会导致手机低电量甚至没电
  • WiFi
    场景:确保升级过程中,流量消耗不会使用用户话费中流量包,不会导致因消耗话费流量伤害用户体验

场景四

网络请求中,kill
进程 (导致APP登录状态掉线)

  • 登录同一个账号成功,应该不继续相同网络请求
  • 登录不同账号成功,应该不继续相同网络请求

常见弱网问题和原因分析

场景一

上传大图或者多图时,在弱网络环境下出现进度条走到一半卡住,然后又从头开始

「原因」采用分段上传方式,直至请求超时,分段传输没有结束,代码逻辑不对,导致每次重试都重头上传,一直循环

场景二

在弱网络环境下,容易出现登录不上或者登录后立即掉线

「原因」登录没有缓冲机制,而请求超时时间的设置没有区分同网络情况

「解决方案」建议开发针对WiFi、2G、3G、4G、5G
设置不同的超时时间

场景三

弱网络环境下,请求的数据返回时间较长,等待的过程中,如果页面上的相关控件仍然可以操作,则容易出现闪退现象、触发底部时获得原页面请求数据。

「原因」依赖数据的控件操作,在数据返回前没有做兼容处理

场景四

搜索时输入关键字会连续发请求,停下时,显示最终的关键字搜索结果,但很快又会被前面的关键字搜索结果覆盖了。

「原因」中间的请求返回较慢,显示了最终的结果后,之前的请求返回的数据应不做处理。

其他问题场景补充:

1、用户登录应用时下载初始化数据,下载过程中因网速太慢点击取消并重新登录,数据下载过程中、下载失败后,未进行数据回滚,中止后重新下载,出现数据重复。

2、用户点击数据上传,数据上传过程中网络弱且不稳定,基于联网状态自动触发数据上传,导致出现数据重复写入,形成脏数据

3、在弱网环境下,用户输入用户名和密码点击登录,应用链接超时后,按照强网业务逻辑处理,导致返回超时异常。

4、在弱网环境下,用户输入用户名和密码后点击登录,数据下载超时,加载数据严重依赖于后来的异步加载。数据还没来得及返回,应用跳转到下个activity,导致崩溃。

二、弱网网络测试方案

常见方案:

序号 方案 描述 缺点
1 前往弱网环境 电梯、地下室、隧道 不实际
2 搭建弱网环境 购买一些路由网络设备,在全公司范围搭建一个弱网环境。
(相关的技术方案有Facebook的ATC和腾讯的WeTest-WiFi。)
搭建成本高
3 使用网络代理 在PC上安装网络抓包工具,将设备的网络代理到PC上。
通过设置PC上抓包工具的延时来进行弱网络模拟。
(比如Fiddler,Charles,NET-Simulator)
4 安装弱网工具 苹果弱网测试工具-Network Link Conditioner(支持手机和Mac)
腾讯弱网测试工具-QNET(目前仅支持Android手机)

三、弱网相关术语

1、衡量网络好坏的标准

(1)带宽(吞吐量):单位时间内传输的数据量(bps),反映网络的传输能力

(2)丢包:数据丢包个数=发送的数据包数-接受的数据包数,反映为网络的可靠性

(3)时延:数据包从发送开始到接受到该数据所耗费的时间,反映网络速度

(4)抖动:指时延的变化,反映网络的稳定性

(5)乱序:指接受到的数据包顺序和发送顺序不一致的次数,反映网络稳定性。乱序比较 严重时,丢包也会比较严重,所以一般以丢包

2、术语

2.1、上/下行带宽Bandwidth(kbps—千比特每秒)

上行带宽(上行速率):本地信息上传到网络的速率
下行带宽(下行速率):网络信息下载的本地的速率
注:上行速率不等于下行速率,在大多数情况下,下行与上行带宽的比率可达到10:1

2.2、上/下行丢包率packet loss(%)

数据在网络上是以数据包未单位传输的,由于一些原因不能百分百得完成,这时网络会自动根据协议来补办,网速快线路好得时候,包得损失会非常小,补包就会很容易完成,但是线路较差得时候,数据得损失量就会很大,补包就不可能百分之百完成,这种情况下就会造成丢包。
丢包率 = 1 - 单位时间内接收得数据包数 / 发送的数据包数

查看丢包率可以通过ping命令来查看。
在这里插入图片描述
在这里插入图片描述

2.3、上/下行延迟delay(ms)

1-30ms:极快,几乎察觉不出有延迟
31-50ms:良好,没有明显的延迟情况
52-100ms:普通,能感觉出网络有明显延迟
大于100ms:差,有卡顿,丢包并掉线现象

在这里插入图片描述

2.4、DNS延迟(ms)

DNS是域名解析服务器,延迟就是和这个服务器的连接速度,DNS的作用就是把网址解析成IP地址,因为电脑网络连接只能通过IP连接。

3、各个网络环境参数

常见网络设置参考:

网络环境 上/下行带宽(kbps) 上/下行丢包率(%) 上/下行延迟(ms) DNS延迟(ms)
2G 20/50 0/0 500/400 0
3G 330/2000 0/0 100/100 0
wifi 33000/40000 0/0 1/1 0
4G 40000/80000 0/0 15/10 0
无网(飞行模式或关闭网络)
带宽有限环境 32/32 0/0 200/100 0

网络超时设置参考:

网络环境 上/下行带宽(kbps) 上/下行丢包率(%) 上/下行延迟(ms) DNS延迟(ms)
网络超时(请求) 33000/40000 100/0 100/100 200
网络超时(完全丢包) 33000/40000 100/100 100/100 200
网络超时(响应) 33000/40000 0/100 100/100 200

特殊网络设置参考:

网络环境 上/下行带宽(kbps) 上/下行丢包率(%) 上/下行延迟(ms) DNS延迟(ms)
低丢包率、低时延的环境(上行) 33000/40000 10/0 100/100 200
低丢包率、高时延的环境(上行) 33000/40000 10/0 350/350 350
低丢包率、低时延的环境(下行) 33000/40000 0/10 100/100 200
低丢包率、高时延的环境(下行) 3000/40000 0/10 350/350 350
低丢包率、低时延的环境 33000/40000 10/10 100/100 200
低丢包率、高时延的环境 33000/40000 10/10 350/350 350
高丢包率的环境(上行) 33000/40000 90/0 100/100 200
高丢包率的环境(下行) 33000/40000 0/90 100/100 200
高丢包率的环境 33000/40000 90/90 100/100 200

四、网络测试工具

1、可直接安装的弱网工具

如下图:

1、进入设置找到开发者(Developer)选项。(如果没有该选项,请将手机接入开发人员电脑Xcode,即可自动生成。)

2、在开发者(Developer)选项中 –> Network Link Conditioner

2、打开Enable,并选择需要测试的网路开始测试

3、选择完毕后ios手机及会变更成指定网络环境(全局变更),可以开始测试弱网情况。

image-20230516164829914

4、如果需要自定义
①、选择某个Profile,进入其详情

②、在该Profile的详情里,选择Duplicate Profile,修改对应数值后,即新增完成。

image-20230518120557481

每个类型中可以查看对应的数据。数据内容包括如下部分。

序号 类型 描述 其他
1 in Bandwidth 下行带宽,即下行网络速度
2 In packet loss 下行丢包率
3 in delay 下行延迟,单位ms
4 out bandwidth 上行带宽
5 out packet loss 上行丢包率
6 out delay 上行延迟
7 DNS delay DNS解析延迟
8 protocol 支持Any,IPV4、IPV6
9 interface 支持Any,WI-Fi,cellular(蜂窝网)

Xcode弱网测试工具的下载及安装过程如下:

1、进入 https://developer.apple.com/download/all/

2、搜索 Network Link Conditioner

image-20230516171948118

虽然没有Network Link Conditioner的选项。但其实其实包含在Additional Tools for Xcode 中的。

3、下载与自己xcode版本匹配的插件。如 Additional_Tools_for_Xcode_14.1.dmg

4、安装 Additional_Tools_for_Xcode 。安装过程如下图:

image-20230516171241524

5、安装完后,即会在你的系统设置里新增一个Network Link Conditioner选项。

image-20230516171406364

6、Mac版的Network Link Conditioner使用与iOS版没什么差别。

image-20230516171549486 image-20230516171650795

1.2、腾讯弱网测试工具-QNET(目前仅支持Android)

1、工具下载:

QNET官网(已失效):https://wetest.qq.com/products/qnet

其他地址(亲测有效-2023.05.17):http://www.5577.com/s/563515.html

2、工具使用/测试步骤:

①打开工具,授权登录

②选择应用,开启模拟

③在应用内,可自行按需切换模拟环境

image-20230518110844670 image-20230518105919986 image-20230518111702681

2、网络抓包代理工具

序号 工具名 工具描述 其他
1 Charles 抓包工具,PC端安装,作为代理服务器,可以支持延迟、丢包、带宽等弱网配置。弱网配置项只支持HTTP/HTTPS。
2 Fiddler 抓包工具,PC 端安装,作为代理服务器,设置延迟参数,模拟不同的网络情况。只能模拟延迟,如丢包、带宽等等是无法支持的。

2.1、Charles

详见:Charles弱网模拟

2.2、

五、移动端下各类诊断方法与工具

以下内容摘自:移动端下各类诊断方法与工具

工具/方法 OS 使用场景及使用说明 优点 缺点 工具地址
华佗ping诊断 android/ios/pc 获取客户端IP,ldns,域名请求ip,请求耗时
https://cloud.tencent.com/developer/article/1489036
无需客户端,直接浏览器请求 有时会获取不到ip,dns信息,或不准确 https://ping.huatuo.qq.com/
腾讯云诊断APP android 分析dns劫持,http 302劫持。获取客户端IP,ldns,域名请求ip,请求耗时,可靠性高,信息较全
https://cloud.tencent.com/developer/article/1489043
无需root 需要安装app http://imgcache.gtimg.cn/huatuo/apks/capture.apk
iNetTools ios 获取dns解析,延迟,分析dns劫持
https://cloud.tencent.com/developer/article/1608239
可准确获取ping数据和域名解析信息 无法对url进行诊断,需要安装app https://itunes.apple.com/cn/app/inettools-ping-dns-portscan/id561659975?mt=8
笔记本共享热点,通过fiddler抓包 android/ios/pc pc共享热点,通过fiddler来进行抓包,分析业务的http请求 fiddler可抓https包 自己模拟可以,让用户来用比较困难 https://www.telerik.com/fiddler
手机WIFI接入,走云主机代理抓包 android/ios/pc 检查手机端问题,还是网络问题 对用户侧操作比较简单有些公司wifi有限制代理 网络环境变了,请求网络是云主机到服务端 https://www.telerik.com/fiddler
PC andorid模拟器 Nox android 在pc下用模拟器进行测试,抓数据包,分析业务的请求 可轻松root安装抓包软件 自己模拟可以,让用户来用比较困难 https://cn.bignox.com/
华佗抓包工具 android 需要真实获取用户请求数据包
https://cloud.tencent.com/developer/article/1489050
可直接移动端抓包,无需root 部分app无法进行抓包 http://imgcache.gtimg.cn/huatuo/apks/capture.apk
Stream 抓包工具【推荐】 IOS 需要真实获取用户请求数据包,可抓https请求
https://cloud.tencent.com/developer/article/2116252
可直接移动端抓包,无需root 部分app无法进行抓包 https://apps.apple.com/cn/app/stream/id1312141691
QuickSource 抓包工具 https://apps.apple.com/cn/app/quicksource/id1449472048

End

弱网优化空间探索

[toc]

第3节:弱网优化空间探索

前言

在前一节 弱网测试 中,我们罗列了网络测试的各种问题。而本节我们针对弱网单独的进行说明。

本节的其他参考文章:

背景

弱网下:如何让我们的app不受弱网的影响,以使得用户能够继续”正常”使用我们的网络?

1、网络优化的必要性分析/竞品分析

通过竞品分析,App网络测试&竞品对比.xlsx,测试各APP在各相同网络环境下的表现。对存在的不足进行发现和优化。

看看其他APP都是在什么网络(什么值)下大概不行?

最佳网络质量 开始出现问题 基本不可用的网络
带宽(kbps) > > & < > & <
丢包率(%) < > & < > & <
延迟(ms) < > & < > & <

2、网络优化的切入点整理(开始前、执行中、完成后)

对此我们从弱网下的事件开始前、事件执行中及事件执行结束对结果的处理三个阶段来分析。

阶段 概述 处理位置 优化方案
1 弱网下,事件开始前可考虑的优化 数据上 用户体验优化 + 网络状态/网络质量
2 弱网下,事件执行过程中可考虑的优化 数据上 缓存 + 降低质量
3 弱网下,事件执行结束对结果的处理可考虑的优化 界面上 UI和Toast的显示

一、弱网下,事件开始前可考虑的优化

1、进入页面时候就优化用户体验,避免弱网无数据的页面异常

下图摘自:《用户加载体验优化》中的【一、页面初始加载优化】

image-20240725001944956

从上图中,可以看出弱网情况下,我们可以针对不同的网络:做不同超时,重试设置,以及取不同质量的图片数据。

2、事件开始前,网络状态、质量的获取、同步及联动

1、网络类型从wifi切换到移动网络/无网络时,全局Toast提示用户当前联网类型切换为移动网络/无网络。

2、弱网情况下(下行网络质量低于某个阀值)提示用户当前网络较差,请检查网络状态。

二、弱网下,事件执行结束对结果的处理可考虑的优化

弱网下的事件结果,主要体现在界面上。尤其是UI提示、toast提示

1、弱网问题在界面上的体现 UI/toast的显示不同存、不同失

针对请求超时/请求失败,常见的提示方式有UI提示、或者toast提示。而常出现的可考虑优化的场景如下:

序号 问题类型/可优化点 优化建议 优化方案具体描述
1 弱网view、弱网toast同时都存在 只保留弱网view 请求接口时,自己处理业务失败的toast,而非底层自动toast
接口异常:①业务异常仍然toast、②超时异常去除toast
2 弱网view、弱网toast同时不存在 至少有弱网toast 页面接口请求可允许使用底层自动toast机制
3 同接口弱网超时,连弹弱网toast 优化连弹/重叠 接口针对异常区分超时类型,toast使用对应单例优化连弹但不重弹
4 弱网超时\无网,页面空数据兼容 兼容空数据 ①补充空页面
②点击无反应 –> 改为提示”后台数据异常”

为了找到确实需要处理的优化点,我们可以在以下表格中先整体罗列弱网下的所有可能是异常的页面,然后再评估哪些需要处理。表格样式如下:

优化点概述 优化点详细说明 待优化处截图/视频 页面路径 错误重现 是否需要处理 备注 状态

待优化处截图/视频示例:

ui_toast_both

ui_toast_none

2、toast提示语尽量后台化

1、提取APP中现有的本地toast。

为此我专门写了个脚本:获取文本中的某些文本并将其整理到excel中:https://gitee.com/dvlpCI/package-size-resource/blob/master/Flutter/example_get_file_some_text.sh

整理的表格完成版示例如下:

https://gitee.com/dvlpCI/package-size-resource/blob/master/Flutter/APP提示语清单表.xlsx

APP提示语清单表:

文件(简) 行号 提示文案 文案类型 触发路径及条件 提示方式 isUse 是否使用中
(//判断)
创建者 修改者
xxx_page.dart 128 复制成功 成功 TRUE
xxx_page.dart 81 抱歉:图片上传失败 失败 TRUE
xxx_page.dart 105 请选择物流公司 提示 TRUE
xxx_page.dart 891 正在开发中… 未确定 TRUE

三、弱网下,事件执行过程中可考虑的优化

网络区分的方式,主要有网络类型、网络质量。

1、网络类型、网络质量必备基础知识

切入点类型(及其原理) 适用的使用场景 参考文档
1 实时检测网络类型变化
(移动网络、wifi网络、无网络)
可在网络类型变化,提示用户注意当前网络环境 connectivity_plus Flutter检查连接网络connectivity_plus实现步骤
2 检测网络的可达性和网络延迟时间,探测信号强度。附:Ping测试不能测量网络的带宽容量,只反映延迟。
原理:通过发送ICMP(Internet Control Message Protocol)回显请求到服务器,并测量回显响应的时间,来评估网络的延迟(Ping值)。
dart_ping Flutter Ping检查服务器通讯信号强度实现步骤
3.1 测试设备的网络速度(包括上传和下载速度)
原理:从选定的服务器下载一定大小的数据包来测试下载速度。测试时会记录下载所需的时间,并据此计算出下载速度。上传计算同理。
flutter_internet_speed_test 见库自身地址
3.2 测试设备的网络速度(包括上传和下载速度)
NERTC_SDK
NERTC SDK 通话前网络质量探测

附:上述部分库的使用示例详见: 附1:网络类型、网络质量部分库的使用示例

2、网络类型、网络质量在流程中的使用场景

流程中的优化空间:我们以网络类型、网络质量决定体验的原则,来分析,使得在不同网络状态下,可以有不同的用户体验。

image-20230725152850177

体验举例:

处理内容 低弱网下处理 强弱网下处理
图片 加载抵制图:1倍图 直接全不加载
视频 降低清晰度 直接不播放

3、从加载流程看网络质量

我们从页面加载流程探索弱网优化的空间。

image-20230714144045136

其中以上述图片加载渲染流程为例(加载视频同理),其过程如下:

image-20230725152850177

附1:网络类型、网络质量部分库的使用示例

1.1、使用 connectivity_plus,实时检测网络类型变化(移动网络、wifi网络、无网络)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 初始化
Future<void> _init() async {
try {
// 方式1:单次请求检查
// final connectivityResult = await _connectivity.checkConnectivity();
// _updateConnectionStatus(connectivityResult);
// 方式2:状态订阅
_subscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
} on PlatformException catch (e) {
print(e);
print('连接网络出现了异常');
}
}

Future<void> _updateConnectionStatus(ConnectivityResult result) async {
setState(() {
_connectivityStatus = result;
});
if (result == ConnectivityResult.mobile) {
print('成功连接移动网络');
} else if (result == ConnectivityResult.wifi) {
print('成功连接WIFI');
} else if (result == ConnectivityResult.ethernet) {
print('成功连接到以太网');
} else if (result == ConnectivityResult.vpn) {
print('成功连接vpn网络');
} else if (result == ConnectivityResult.bluetooth) {
print('成功连接蓝牙');
} else if (result == ConnectivityResult.other) {
print('成功连接除以上以外的网络');
} else if (result == ConnectivityResult.none) {
print('没有连接到任何网络');
}
}

1.2、使用 dart_ping 库来执行Ping操作,探测信号强度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   final ping = Ping('baidu.com', count: 5, interval: 1); // count: ping的次数   interval:每几秒ping一次
ping.stream.listen((event) {
if (event.error != null) {
print("网络较差或网络断开:${event.error.toString()}")
} else {
int pingDelay = event.response?.time?.inMillisecondes ?? 0;
_signalStrength = calculateSignalStrength(pingDelay); // 信号强度
print("网络状态信息:${pingDelay}");
}
});

// 信号强度 0 ~ 5 , 5 最好,0 最差
int calculateSignalStrength(int pingDelay) {
if (pingDelay < 0) return 0; // 无网络连接
if (pingDelay < 100) return 5; // 延迟 < 100ms,信号强度为 5
if (pingDelay < 200) return 4; // 延迟 < 200ms,信号强度为 4
if (pingDelay < 300) return 3; // 延迟 < 300ms,信号强度为 3
if (pingDelay < 500) return 2; // 延迟 < 500ms,信号强度为 2
return 1; // 延迟 >= 500ms,信号强度为 1
}

打印结果:

dart_ping_reslut

H5-APP开发小技巧

[TOC]

一、div设置多个class

在 HTML 中,一个 class 值中可能包含一个词列表,各个词之间用空格分隔。例如,如果希望将一个特定的元素同时标记为重要(important)和警告(warning),就可以写作:

1
2
3
<p class="important warning">
This paragraph is a very important warning.
</p>

这两个词的顺序无关紧要,写成 warning important 也可以。
我们假设 class 为 important 的所有元素都是粗体,而 class 为 warning 的所有元素为斜体,class 中同时包含 important 和 warning 的所有元素还有一个银色的背景 。就可以写作:

1
2
3
.important {font-weight:bold;}
.warning {font-weight:italic;}
.important.warning {background:silver;} //相当于两个class有相同的样式。

其实是一个DIV拥有两个Class 空格并不是一个CLASS,用jquery你只需要(".import")或%E6%88%96)(“.warning”)就可将其选定。

H5-APP开发环境搭建与运行

[TOC]

一、启动

1、启动服务

1、cd 到 package.json 所在层

2、执行npm install/yarn install

​ => 执行后会生成node_modules

3、通过npm run执行package.jsonscripts里提供的命令(里面的命令是自己可以改的)。如:

1
npm run dev:client

执行结果在终端成功的话,显示为:Compiled successfully.

如:

image-20200401004428926

分析本步骤的命令执行的内部原理

观察得执行该命令,其实会去执行当前目录下的bulid中的webpack.dev.js文件

image-20200401003619216

打开该文件

image-20200401004814191

2、URL主路径的获取

http://localhost:9000/ 其中9000即是上面的端口号

3、URL路径的获取

如何得知你的界面的URL地址

image-20200401011247816

4、浏览器访问

在浏览器中输入地址

http://localhost:9000/inspection/HomeEntrance

document.documentElement.clientWidth;
document.documentElement.clientHeight;

用window.screen.height;window.screen.width;并不准确,当部分手机页面本身有超出屏幕宽度的元素时,会将超出部分也计算进内。目前遇到的机型有ZTE。

进程线程

react-异常状态记录

来源:react-异常状态记录

1.

日志信息:React does not recognize thefieldTypeprop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercasefieldtypeinstead. If you accidentally passed it from a parent component, remove it from the DOM element.
问题来源:react
原因:DOM节点中被传入了组件属性

1
2
3
4
5
6
7
8
9
const resPorps={
fieldType:'.txt',
marginLeft:10,
};
return(
<div {...resProps}>
test
</div>
)

如上,fieldType不属于DOM的默认属性,对于React来说,这是一种冗余的写法。
解决办法:将不属于DOM的属性提取出来

1
2
3
4
5
6
7
8
const resPorps={
marginLeft:10,
};
return(
<div {...resProps} fieldType='.txt'>
test
</div>
)

第2节:h5_open_app

[toc]

测试app外的h5直接打开app

前言:app路由地址生成器

网页地址及内容:请点击dvlp_h5_open_app_app_route_url_demo.html查看。

demo使用的json数据dvlp_h5_open_app_app_route_url_demo.json

一、app外h5地址生成及解析器网页

网页地址及内容:请点击dvlp_h5_open_app_browser_url_demo.html查看。

demo使用的json数据dvlp_h5_open_app_browser_url_demo.json

1、网址组成元素

1.1、主地址(必传)

1.2、app参数信息(非必传)

1.3、其他参数信息(非必传)

2、网址组成方式

2.1、直接地址

2.2、自己构成

3、网址解析器功能

4、网址生成器功能

5、执行按钮

5.1、如果本地址生成及解析器,是正常在app外的浏览器打开的

那点击按钮,直接打开到指定页面。

5.2、如果本地址生成及解析器,是正常在app内的WebView打开的

那点击按钮,打开外部浏览器,浏览指定页面。

iOS:无法打开外部浏览器。

Android:可以直接打开外部浏览器。

二、测试工具网页

埋点原理与实现

埋点原理与实现

一、曝光埋点

1、使用 VisibilityDetector 监听子组件可见性的变化(什么时候触发变化)

2、

二、tableview的曝光埋点

  1. 使用UIViewwindow属性:可以通过检查视图的window属性是否为空来判断视图是否在屏幕上可见。如果window属性为非空值,表示视图正在显示。
  2. 使用CGRectCGRectIntersectsRect函数:可以通过将视图的frame转换到屏幕坐标系中,然后与屏幕的bounds进行比较,来确定视图是否在屏幕上可见以及可见的比例。
  3. 使用CADisplayLink:可以通过CADisplayLink来定期检查视图的可见性。在每次屏幕刷新时,都可以检查视图的window属性和frame,从而实现对视图可见性的实时监控。
  4. 使用isDisplayedInScreen类别方法:可以通过为UIView添加一个类别,并在该类别中实现isDisplayedInScreen方法,来判断视图是否在屏幕上可见。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// UIView+isDisplayedInScreen.h


// 使用CADisplayLink定期检查
- (void)startMonitoringViewVisibility:(UIView *)view {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(checkViewVisibility:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)checkViewVisibility:(CADisplayLink *)displayLink {
// 在这里检查视图的可见性
}

// 检查视图是否在屏幕上可见
- (BOOL)isViewVisible:(UIView *)view {
CGRect viewRect = [view convertRect:view.bounds toView:nil];
CGRect screenRect = [UIScreen mainScreen].bounds;
CGRect intersectionRect = CGRectIntersection(viewRect, screenRect);
CGFloat visibleArea = CGRectGetArea(intersectionRect);
CGFloat viewArea = CGRectGetArea(view.bounds);
return (visibleArea / viewArea) > 0.5;
}

曝光(曝光结束才计算曝光。曝光过程不计算,那初始曝光的怎么办)

当然我们对滑动曝光有一些额外的要求:

  • 需要滑出一定比例(只出现一点点不算)、一定时间(太快划走不算)的时候才出发曝光
  • 滑出视野的模块,再次滑入视野时需要再次上报
  • 模块在视野中上下反复移动只触发一次曝光(还未实现)

1、Flutter visibility_detector 原理解析

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@override
Widget build(BuildContext context) {
return VisibilityDetector(
key: Key('exposure_$hashCode'),
onVisibilityChanged: (visibilityInfo) {
var visiblePercentage = visibilityInfo.visibleFraction * 100;
var inScreen = visiblePercentage >= widget.exposeFactor;
if (inScreen != show) {
if (inScreen) {
_onExpose();
} else {
_onHide();
}
}
},
child: widget.child,
);
}

底层核心逻辑代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 1、VisibilityDetector 继承自 SingleChildRenderObjectWidget 类
class VisibilityDetector extends SingleChildRenderObjectWidget

// 2、 重写其 createRenderObject(BuildContext context) 方法 返回RenderVisibilityDetector
@override
RenderVisibilityDetector createRenderObject(BuildContext context) {
return RenderVisibilityDetector(
key: key,
onVisibilityChanged: onVisibilityChanged,
);
}
}


class RenderVisibilityDetector extends RenderProxyBox {
RenderVisibilityDetector({
RenderBox child,
@required this.key,
@required VisibilityChangedCallback onVisibilityChanged,
}) : assert(key != null),
_onVisibilityChanged = onVisibilityChanged,
super(child);

// 省略一些代码...

// 3、在 RenderVisibilityDetector 中,重写绘制方法 paint(),push 一个用于监听可见状态的 Layer(VisibilityDetector)
@override
void paint(PaintingContext context, Offset offset) {
if (onVisibilityChanged == null) {
VisibilityDetectorLayer.forget(key);
super.paint(context, offset);
return;
}

final layer = VisibilityDetectorLayer(
key: key,
widgetSize: semanticBounds.size,
paintOffset: offset,
onVisibilityChanged: onVisibilityChanged);
context.pushLayer(layer, super.paint, offset);
}
}

在 RenderVisibilityDetector 中,在绘制方法 paint() 里 push 了一个 Layer,这个Layer就是 VisibilityDetector 用于监听可见状态的关键。

二、埋点的数据上报

将监听到的数据添加到 final List<Map<String, dynamic>> _buriedPointModels = []; 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> 	_init() {
> // print('BuriedPointManager _init');
> /// 每5秒上传一次,避免请求太多
> _uploadTimer = Timer.periodic(const Duration(milliseconds: 5000), (timer) {
> _uploadBuriedPoints();
> });
> }
>
> addEvent(String eventName, Map<String, dynamic> eventAttr) async {
> Map<String, dynamic> eventMap = {};
>
> eventMap.addAll({
> "event_name": eventName,
> "event_attr": eventAttr,
> "request_time": DateTime.now().millisecondsSinceEpoch, // 单条记录生成时间,精确到毫秒
> });
>
> _buriedPointModels.add(eventMap);
> }
>

三、曝光率

曝光率(曝光率= 点击量 / 曝光量)来分析用户(比如用户的喜好推荐数据的统计)

参考文档: