web必备基础-3代码复用

一、css复用

二、UI 复用

1、目前实例(jquery的load方法)

将一个页面分成多个html文件(静态html分割页面)

参考文章:

2、其他方法

性能优化

iOS App启动优化(一)—— 了解App的启动流程

一、启动优化

主要是启动耗时,处理的主要方式是

代码方面:

  • 移除多余的:有些SDK是仅用来Debug测试的,没必要带到Release(条件编译)
  • 延迟非关键:如广告SDK
  • 分配到子线程处理,并发处理

其他方面:

  • +load
  • 动态库dylib合并

排查工具:Time Profiler

iOS性能启动优化相关问题

二、内存优化

app内存问题.md

  • 及时释放(不要只持有,但不释放,如避免单例持有大对象)

    autorelease

1、内存暴涨(Memory Spike)

大量小对象 → 一次性加载/渲染过多 cell、图片、富文本。(Cell 复用)

少量大对象 → 单个对象占用内存非常大(加载大图/视频,例如单张图片 100MB,或一个大 Data blob)

短时间内创建大量对象或加载大资源,造成内存占用过多,可能引发系统回收甚至崩溃。

  • 原因:循环创建对象、大量数据瞬时加载、大图加载等。
  • 工具检测:Instruments -> Allocations / Memory Usage。
  • 示例:

    JSON 解析时递归错误,不断循环创建对象;

    请求没做分页/单页数量极大(之前遇到为了省去Flutter中列表的数据处理,直接单页请求4000个数据)

    加载大图,大图直接解码显示原始位图,占用内存太大。优化:Downsampling(缩放解码),只解码需要显示的尺寸/缩略图,而不是原图全尺寸。

    1
    解码成位图 (bitmap): 4000 x 4000 图片 (RGBA8888) = 4000 * 4000 * 4 bytes  64 MB

    之前在发布内容时候(Flutter应用)涉及到多视频的压缩

2、内存泄漏(Memory Leak):

  • 原因:未释放的强引用循环、未释放的资源(解决循环引用导致释放不掉的问题)。
  • 工具检测:Xcode Instruments -> Leaks。

内存抖动(Memory Churn)

  • 原因:频繁的内存分配和释放。图片/数据频繁解码滑动列表时,每次都解码同一张图片,没有缓存。
  • 影响:性能下降、电池消耗增加。

APP 被系统 Kill(Jetsam)

  • 原因:使用内存超出系统限制。
  • 检查:控制台日志包含 JetsamEvent。

排查工具:Instruments 的 Leaks 和 Allocations 分析内存泄漏

1、正确的地方使用 reuseIdentifier

2、内存泄漏

1、使用Product-Analyze分析内存泄
利用Product-Analyze分析内存泄露,并不能把所有的内存泄露查出来,因为有的内存泄露是在运行时,用户操作时才产生的。那就需要用到Instruments了。

2、使用Instruments检测定位并解决iOS内存泄露

三、卡顿优化

卡顿监控.md

列表优化.md

四、网络优化

网络框架.md

分页规范

一、分页底层原理

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

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

常见方案:

备注
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

工作流程

前言

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

账号管理

一、开发相关

1、分支规范

分支开发+打包流程.graffle

2、目录结构规范

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

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

  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、代码规范

iOS代码开发规范.md

Flutter代码开发规范.md

模块功能划分清晰

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

打包保证

详见:app打包保证

主要处理问题:

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

质量保证

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

弱网测试:

业务提升

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

日常事项

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

分支规范

一、分支简介

项目应包含以下分支:

  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": "其他基础库"
}

弱网优化空间探索

前言

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

本节的其他参考文章:

背景

弱网下:如何让我们的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