切为H5的标准

针对指示的h5实现方式和空态页面体验优化问题,我们经讨论,处理方案如下

问题一:对目前app中存在的H5页面,哪些是需要处理成h5的,初步定为如下标准:

1、黄金主流程的必须保证flutter实现,更稳定;

2、对于强运营的、改动量比较频繁的采用h5,一般为活动页;

3、对于纯展示交互少的采用h5,如隐私协议、玩法说明等;

按以上标准,目前app中,需要处理成h5页面的有:

状态1:已由H5实现,无需修改;

状态2:待开发,直接开发成H5;

状态3:原生实现,后续优化时调整为H5;

1、注册协议、隐私政策、玩法说明:待开发,直接开发成H5;

2、频道页:目前只有潮物是h5,其他已用flutter实现,后续待频道页优化时调整为H5;

3、活动规则(百愿清单、愿望星规则等):目前已有部分由flutter实现,后续逐步调整为H5;

4、大转盘、动物运动会、4大场景(生日、节日、结婚、生娃):已由H5实现;

5、首页banner跳转的落地页:跳转百愿清单、愿望单为黄金主流程已由flutter实现;跳转电子邀请函、动物运动会已由H5实现;

问题二:对app中无数据的空态页面太空的体验优化问题,晚上产品会把所有页面都列出来,再跟您对一遍,然后我们再统一做修改。

方案陈述以上,如有描述不当或需要再补充的,感谢指点!

加固选型-1调研

参考文档:

腾讯云加固功能介绍-官网

一、确定需求

1、痛点/所需功能/期望

序号 影响/目的 所需功能
1
2

2、预算成本

二、调研市场

了解市场上已有的产品和解决方案,分析其特点、优劣势和市场占有率等。

1、调研包含内容/考虑点

序号 考虑项/因素 内容 其他
1 包含的功能 ①必须/主要功能
②补充功能
2 后台数据 控制台数据呈现、维度
3 收费标准 免费功能、免费额度、免费门槛

2、调研产品列表

厂家 产品 官网 支持列表
360 360加固 https://jiagu.360.cn/#/global/vip/desc
腾讯 腾讯乐固 https://cloud.tencent.com/document/product/283/14002

3、调研包含内容/考虑点对比表

三、制定评估标准

将APM产品的功能清单转化为具体的评估指标,并根据用户需求和期望,给予不同指标不同的权重。例如,对于监控指标,可以考虑启动时间、页面加载时间、卡顿率、崩溃率等方面,对于诊断指标,可以考虑问题定位时间、错误率、准确性等方面。

序号 类型 功能【含Flutter上的支持】 影响/目的 需求程度(1-10)
1

根据上述评估标准,我们将《XXX》和《XXX》列入备选方案。

四、筛选备选方案、实地数据对比

根据评估标准,筛选出符合要求的备选方案,并进行初步比较和评估。

1、筛选方案,详见:

2、筛选方案下的实地数据对比,详见:

五、选型决策和采购

六、跟踪和评估

End

技术选型流程

一、选型流程

产品选型流程通常包含以下步骤:

  1. 确定需求:明确产品选型的目的和需求,包括所需功能、性能、规格、成本等方面的要求。

    在确定需求的过程中,需要明确用户的痛点、需求和期望,以及应用场景和系统规模等因素。

  2. 调研市场:了解市场上已有的产品和解决方案,分析其特点、优劣势和市场占有率等。

  3. 制定评估标准:根据需求和市场调研结果,制定产品选型的评估标准,包括技术指标、产品性能、价格、服务等方面的标准。

    制定评估指标和权重:将APM产品的功能清单转化为具体的评估指标,并根据用户需求和期望,给予不同指标不同的权重。例如,对于监控指标,可以考虑启动时间、页面加载时间、卡顿率、崩溃率等方面,对于诊断指标,可以考虑问题定位时间、错误率、准确性等方面。

  4. 筛选备选方案:根据评估标准,筛选出符合要求的备选方案,并进行初步比较和评估。

  5. 进行实地考察:对备选方案进行实地考察,包括参观厂家、现场考察、试用产品等。

  6. 编制报告:根据实地考察和评估结果,编制产品选型报告,包括备选方案的优缺点、技术指标、价格、服务等方面的比较和评估结果。

  7. 决策和采购:根据产品选型报告,进行决策和采购,选定最终方案,并进行合同签订、交货、安装、调试等工作。

  8. 跟踪和评估:对选定的产品进行跟踪和评估,及时发现和解决问题,提高产品使用效率和维护质量。

End

设备相关

一、分辨率与尺寸

最新iPhone全机型分辨率和尺寸收录

手机机型 (iPhone) 屏幕尺寸 (inch) 逻辑分辨率 (pt) 设备分辨率 (px) 缩放因子 (Scale Factor)
3G(s) 3.5 320x480 320x480 @1x
4(s) 3.5 320x480 640x960 @2x
5(s/se) 4 320x568 640x1136 @2x
6(s)/7/8 4.7 375x667 750x1334 @2x
6(s)/7/8 Plus 5.5 414x736 1242x2208 @3x
X/Xs /11 Pro 5.8 375x812 1125x2436 @3x
Xr /11| 6.1 6.1 414x896 828×1792 @2x
Xs Max /11 Pro Max 6.5 414x896 1242×2688 @3x
12 mini 5.4 360x780 1080x2340 @3x
12/12 Pro 6.1 390x844 1170x2532 @3x
12 Pro Max 6.7 428x926 1284x2778 @3x
13 mini 5.4 360x780 1080x2340 @3x
13/13 Pro 6.1 390x844 1170x2532 @3x
13 Pro Max 6.7 428x926 1284x2778 @3x

iPhone X系列随着机型增多,屏幕尺寸也发生了改变,安全距离也不同。竖屏状态下有44、48、47等(可能还有其他数值);横竖屏切换时也不相同:横屏时左右的安全距离相同,由原来竖屏下底部安全距离34改为与顶部安全距离相同。即横屏时左右安全距离相同,都为竖屏下顶部安全距离。
适配方案:如果项目中带SceneDelegate文件可添加以下代码,不带该文件的项目可去除iOS13的判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (UIEdgeInsets)getIphoneSafeInsets {
UIEdgeInsets safeInsets = UIEdgeInsetsMake(20, 0, 0, 0);
if (@available(iOS 11.0, *)) {
if (@available(iOS 13.0, *)) {
safeInsets = [UIApplication sharedApplication].windows.firstObject.safeAreaInsets;
}
else {
safeInsets = [[UIApplication sharedApplication] delegate].window.safeAreaInsets;
}
}

if (safeInsets.top < 20) {
safeInsets.top = 20;
}

return safeInsets;
}

iphone_device_height

1
2
3
4
5
6
7
let screenHeight = UIScreen.main.bounds.size.height
let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
let navigationBarHeight = self.navigationController?.navigationBar.frame.size.height ?? 0
let tabbarHeight = self.tabBarController?.tabBar.frame.height ?? 0
let screenWidth = UIScreen.main.bounds.size.width
let iphoneX = screenWidth >= 375.0 && screenHeight >= 812.0
let bottomGuideHeight: CGFloat = iphoneX ? 34 : 0

End

视频优化

前言

一、视频上传

文件分片上传与分片下载.md

二、常见视频播放

1、视频地址/素材获取网站

mov_website1

2、m3u8文件认识

m3u8 是 HLS 协议中使用的播放列表文件格式,它是 M3U 文件格式的扩展,专门用于 HLS。

在 HLS(HTTP Live Streaming)中,主播放列表(Master Playlist)媒体播放列表(Media Playlist) 通常是 分开的两个文件,而不是合并到一个文件中。这种分离的设计是为了支持自适应码率切换和多分辨率适配。

HLS 的 m3u8 文件有两种主要类型:

主播放列表.m3u8(Master Playlist)用于描述多个不同码率(分辨率)的视频流,每个流对应一个独立的媒体播放列表 .m3u8 文件。它是一个”目录”,不包含实际的 TS 片段,只包含指向不同分辨率/码率的子播放列表的链接。

一般命名为: master.m3u8index.m3u8 (包含 EXT-X-STREAM-INF

媒体播放列表.m3u8(Media Playlist)一定会提供。用于描述一个视频流的具体分段(切片)信息,每个分段通常是一个 .ts 文件。

一般命名为:low/index.m3u8mid/index.m3u8high/index.m3u8 (包含 EXTINF.ts

在某些情况下,可能会直接提供媒体播放列表给 App,而不是主播放列表。例如:单一码率:如果视频只有一种码率,可能会直接提供媒体播放列表。不然正常情况下一般提供一个主播放列表(Master Playlist)文件,然后播放器根据网络条件自动选择最合适的媒体播放列表。

  • 主播放列表媒体播放列表 的文件后缀都是 .m3u8
  • 区分它们的关键在于文件内容:
    • 主播放列表包含 #EXT-X-STREAM-INF 标签,描述多个视频流。
    • 媒体播放列表包含 #EXTINF 标签,描述一个视频流的分段信息。
  • 文件后缀相同是为了保持格式统一和简化解析。

现在比较常见的视频流媒体,大部分都是 m3u8 格式的,而对于 m3u8 格式的视频而言,如果你下载过,你会发现它就是一个文本文件,大概也就只有几十 kb,从磁盘大小来看,应该也知道它并不是一个直接的视频文件。即如果我们想要下载对应的视频文件,直接下载 m3u8,当然是达不到目的的。

m3u8 是一种视频的播放格式,它是将一个完整的视频切割成多个 ts 后缀的视频,然后当我们的进度条被移动或者按时间顺序移动的时候,就会下载对应的片段来加载。

触发的方法在iOS中如下,其中 loadingRequest 每一小块数据的请求,请求地址如m3u8格式中的ts文件链接地址。

1
2
3
4
5
6
7
8
9
10
/*  连接视频播放和视频断点下载的桥梁
* 必须返回Yes,如果返回NO,则resourceLoader将会加载出现故障的数据
* 这里会出现很多个loadingRequest请求,需要为每一次请求作出处理
* 该接口会被调用多次,请求不同片段的视频数据,应当保存这些请求,在请求的数据全部响应完毕才销毁该请求
* @param resourceLoader 资源管理器
* @param loadingRequest 每一小块数据的请求
*/
- (BOOL)resourceLoader:(AVAssetResourceLoader*)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest*)loadingRequest{
// TODO:在这里面开始我们的网络下载请求,也就是得到AVAssetResourceLoadingRequest对象
}

实现代码摘自:KJPlayerDemo 其说明文档见: iOS 音频视频播放器实现边下载边播放缓存视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 开始请求下载数据 
NS_INLINE void kStartDownloading(KJDownloader * downloader, AVAssetResourceLoadingRequest * request){
AVAssetResourceLoadingDataRequest *dataRequest = request.dataRequest;
NSInteger length = dataRequest.requestedLength;

NSInteger offset;
if (dataRequest.currentOffset != 0) {
offset = (NSInteger)dataRequest.currentOffset;
} else {
offset = (NSInteger)dataRequest.requestedOffset;
}
if (@available(iOS 9.0, *)) {
if (dataRequest.requestsAllDataToEndOfResource) {
[downloader kj_downloadTaskRange:NSMakeRange(offset, length) whole:YES];
return;
}
}
[downloader kj_downloadTaskRange:NSMakeRange(offset, length) whole:NO];
}

2.1、m3u8文件示例

多码率自适应流示例(Master Playlist)

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000,RESOLUTION=720x480
low/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000,RESOLUTION=1280x720
mid/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5120000,AVERAGE-BANDWIDTH=4000000,RESOLUTION=1920x1080
high/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=10240000,AVERAGE-BANDWIDTH=8000000,RESOLUTION=3840x2160
uhd/video.m3u8

m3u8文件示例1:ts片段是完整地址

1
2
3
4
5
6
7
8
9
10
11
#EXTM3U
#EXT-X-TARGETDURATION:19
#EXT-X-VERSION:2
#EXTINF:10,
http://122.225.31.106/6775ED76AE94E84581E2FA4D03/0300080B0053D05A49A22808DF700EC607AF80-D3EF-E08A-FB37-918809D8CF4C.mp4.ts?ts_start=0&ts_end=10&ts_seg_no=0
#EXTINF:10,
http://122.225.31.106/6775ED76AE94E84581E2FA4D03/0300080B0053D05A49A22808DF700EC607AF80-D3EF-E08A-FB37-918809D8CF4C.mp4.ts?ts_start=10&ts_end=20&ts_seg_no=1
…………..
#EXTINF:12,
http://122.225.31.85/657DD4BEC15488401753C7200C/0300080B0A53D05A49A22808DF700EC607AF80-D3EF-E08A-FB37-918809D8CF4C.mp4.ts?ts_start=320&ts_end=336&ts_seg_no=396
#EXT-X-ENDLIST

m3u8文件示例2:这里的片段,全部是基于域名的相对地址,

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-VERSION:2
#EXT-X-MEDIA-SEQUENCE:102
#EXT-X-TARGETDURATION:12
#EXTINF:10,
57b3f432.ts
#EXTINF:12,
57b3f43c.ts
#EXTINF:9,
57b3f446.ts

2.2、m3u8文件字段说明

#EXTINF:10表示的是这段TS的时长是10秒。

57b3f432.ts这里表示的是每一个TS的文件名,有的M3U8这里直接是一个完成的http链接。

其他参考文章:

iOS解析M3U8文件及TS文件下载与合并

iOS 拼接m3u8文件 苹果m3u8怎么转换成mp4

1
self.playerUrl = @"http://cctv2.vtime.cntv.wscdns.com:8000/live/no/204_/seg0/index.m3u8?begintime=1469509516000";

附:边下边播

1、解码m3u8:拿到一个M3U8链接后可以解析出M3U8索引的具体内容,包括每一个TS的下载链接、时长等;

下载:拿到每一个TS文件的链接就可以逐个下载了,下载后存储为xxx.ts到手机里;
打包:将下载的TS数据按照播放顺序打包,供客户端播放;
播放:数据打包完成,就可以播放了。

Android——实现m3u8视频缓存

iOS流媒体开发之三:HLS直播(M3U8)回看和下载功能的实现

iOS 视频边下边播(缓存,预加载)

点播:

服务器将直播内容使用FFMPEG转码成MP4和3GP等点播源,生成播放连接返回给客户端播放就可以了。

2、播放器选择之路

音视频播放对于现在的互联网应用来说,已经是不可或缺的功能之一。作为一个 App 开发者,开发一个音视频播放功能,说难不难,说简单也不简单,我们常常会面临几个抉择:

  1. 使用原生视频组件(如:MediaPlayer)
  2. 使用原生硬解码/FFmpeg软解,定制视频播放组件
  3. 使用完全开源的第三方组件(如:ijkplayer)
  4. 使用商业第三方组件(如:腾讯云播放器,阿里云播放器)

2.1、腾讯云播放器

flutter_tencentplayer_plus

三、直播礼物播放

直播礼物类型详解

VAP动效实现方案

SVGA VS VAP

SVGA 是矢量动画方案,对简单动画支持更好。

VAP是视频方法,对复杂动画支持更好。相比SVGA,VAP具有更高的压缩率和硬件解码的优势,使得动画播放更加流畅。VAP的素材大小较小,仅为1.5M,远小于Apng和Webp格式,且采用硬件解码,解码速度更快。

VAP在文件大小和内存占用方面优于SVGA,这可以极大的节约文件包的空间并加快用户的读取速度

四、视频预加载

上中下三个player组成类似轮播的效果。播放当前视频时候,预加载下一个视频的数据。

视频播放延迟

由于分段和缓冲机制,HLS的延迟通常高于实时流媒体协议(如RTMP)。HLS的延迟有多少,怎么优化延迟

1. HLS延迟的来源

HLS延迟主要由以下几个因素引起:

  • 分段长度:HLS将视频分割成多个小片段(通常为10秒),播放器需要等待第一个完整的片段下载完成后才能开始播放。
  • 缓冲机制:播放器通常会预加载多个片段以应对网络波动,这会增加延迟(实际是并发的加载影响了第一个片段的加载时间)。

缓冲机制会预加载多个片段,为的是播放的流畅性。延迟的来源是第一个片段什么时候加载完。但由于有缓冲机制所以会同时加载第一个片段和后续的片段,所以假设是3个片段缓冲,则第一个片段在这种并发的情况相爱可能是最后才加载完的。所以 HLS延迟的典型值

  • 默认配置:10秒片段长度 + 3个片段缓冲 = 30秒左右延迟
  • 优化配置(减少片段长度+减少缓冲区个数):2秒片段长度 + 1个片段缓冲 = 3秒到5秒延迟

五、视频帧

上下两个区域。页面下部分区域为视频的帧图,页面上部分为视频滑动到指定位置显示的画面,以此来形成滑动页面下部分滚动条时候,能实时看到视频帧的效果,实际最终帧为点击确认后才截取。

业务流程图:视频帧获取.graffle

六、视频播放暗掉

核心:

1
2
3
4
5
// 1.打开常亮
[[UIApplication sharedApplication] setIdleTimerDisabled:YES] ;

// 2.关闭长亮
[[UIApplication sharedApplication] setIdleTimerDisabled:NO] ;

视频解码

音频降噪

【岗位核心价值】
通过技术手段实现沉浸式短剧播放体验,推动产品在海外市场的用户增长与留存。
【岗位职责】
负责短剧App的iOS原生模块开发及Flutter跨平台功能实现
优化视频流加载、多分辨率适配及低延迟播放技术方案
实现海外支付(Google Pay/Apple Pay/本地钱包)、多语言动态切换等全球化功能构建AB测试框架支持海外市场本地化运营
持续优化应用性能,确保百万级DAU下的稳定性
【技术要求】
必备能力:
3年以上iOS原生开发经验,精通Swift/
Obiective-0
,2年以上Flutter商业项目经验,熟悉Dart语言特性
深刻理解MVC/MVVM架构,具备模块化开发能
熟悉App上架流程及海外合规要求(GDPR/COPPA)
加分项:
熟悉Android开发及Flutter与Native混合开发
有出海项目经验(AWS/GCP部署、多CDN调度)

枚举值名称 枚举值 枚举类型描述
V2TIM_ELEM_TYPE_NONE 0 没有元素
V2TIM_ELEM_TYPE_TEXT 1 文本消息 V2TimTextElem
V2TIM_ELEM_TYPE_CUSTOM 2 自定义消息 V2TimCustomElem
V2TIM_ELEM_TYPE_IMAGE 3 图片消息 V2TimImageElem
V2TIM_ELEM_TYPE_SOUND 4 语音消息 V2TimSoundElem
V2TIM_ELEM_TYPE_VIDEO 5 视频消息 V2TimVideoElem
V2TIM_ELEM_TYPE_FILE 6 文件消息 V2TimFileElem
V2TIM_ELEM_TYPE_LOCATION 7 地理位置消息 V2TimLocationElem
V2TIM_ELEM_TYPE_FACE 8 表情消息 V2TimFaceElem
V2TIM_ELEM_TYPE_GROUP_TIPS 9 群 Tips 消息(存消息列表) V2TimGroupTipsElem
V2TIM_ELEM_TYPE_MERGER 10 合并消息 V2TimMergerElem

End

Flutter代码开发规范

一、目录

目录结构请查看:项目目录结构规范

二、文件+类命名

1、类文件命名

查看dart源码发现,源文件名都是小写英文加上下划线组成,如app_bar.dart;

使用小写加下划线来命名库和源文件

1
2
3
4
5
// good 
app_bar.dart

// bad
AppBar.dart

2、资源文件命名

命名:功能名称 _ 控件类型 _ 自定义参数*(标准或者大字版).png

1
2
标准:share_btn_weixin.png
选中:share_btn_weixin_selected.png

三、类

1、class命名

一般情况下,类名是源文件名的大驼峰写法,如源文件app_bar.dart,类名AppBar;源文件bottom_app_bar,类名BottomAppBar;

1
2
3
class AppBar {

}

2、import

2.1、import顺序

为了使你的文件前言保持整洁,我们有规定的命令,指示应该出现在其中。每个“部分”应该用空行分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ①dart库
import 'dart:async';
import 'dart:html';

// ②三方库
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

// ③项目文件
import 'package:my_package/util.dart';

// ④相对文件
export 'src/error.dart';

2.2、import as

使用小写加下划线来命名导入前缀

1
2
3
4
5
6
7
8
9
// good
import 'dart:math' as math;
import 'package:angular_components/angular_components'
as angular_components;

// bad
import 'dart:math' as Math;
import 'package:angular_components/angular_components'
as angularComponents;

3、构造函数

3.1、构造函数定义

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
class Point {
num x, y;

// good
Point2(this.x, this.y);

// bad
Point2(num x, num y) {
this.x = x;
this.y = y;
}
}

3.2、构造函数设计技巧

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

class WishPublishPage extends BJHBasePage {
final bool isUpdateBusiness;
final WishDetailModel wishDetailModel;
final bool reEditPublish;
final Function backCallBack;// 从哪个页面跳转过来的 用于区分游戏中心的跳转

WishPublishPage({
Key key,
this.isUpdateBusiness = false,
this.reEditPublish = false,
this.wishDetailModel,
this.backCallBack,
}) : super(key: key);

WishPublishPage.fromOtherUser({
Key key,
this.reEditPublish = false,
this.isUpdateBusiness = false,
this.wishDetailModel,
List<WishGood> selectGoodsList,
this.backCallBack,
}) : super(key: key) {
wishDetailModel.wishGoods = selectGoodsList; // 暂时只平移代码,不修改(原本可能有问题)
}

@override
_WishPublishPageState createState() => _WishPublishPageState();
}

四、其他命名

1、常量、变量命名

使用小驼峰法命名

1.1、常量

1
2
3
const num pi = 3.14;
const int defaultTimeout = 1000;
final RegExp urlScheme = RegExp('^([a-z]+)

1.2、变量

1
2
3
List<String> bookList;
Map<String, dynamic> bookMap;
bool isShowDetail; // 使用is开头+具体的业务场景命名

非特殊需要,禁止使用dynamic。(无法发挥空安全优势)

1
2
3
4
5
6
7
8
9
10
// 错误代码
dynamic string1 = sp.getString();
if ( string1.length == 2 )

// bugly报错
string1 出现 null

// 正确修改
String? string1 = sp.getString();
if ( string1 != null && string1!.length == 2 )

1.3、枚举命名

1
2
3
4
5
/* 枚举的用途 */
enum Direction {
top, //类型的用途 xxxx
left,//类型的用途 xxxx
}

五、基础接口使用规范

判空(不使用length)

1
2
3
4
5
6
7
// good: isEmpty/isNotEmpty
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

// bad:length
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');

代码编写技巧

一、泛型

T是一个抽象类型,因此无法直接使用T(a: "hello")来创建实例。我们需要稍微修改一下代码,以便在test()方法中正确创建T的实例。

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
abstract class A {
String? a;
A({this.a});
}

class A1 extends A {
A1({String? a}) : super(a: a);
}

class A2 extends A {
A2({String? a}) : super(a: a);
}

class B<T extends A> {
// 生成泛型T的实例ben。即达到B<A1>().test(); 输出A1,而B<A2>().test(); 输出A2
T createInstance() {
if (T == A1) {
return A1(a: "hello") as T;
} else if (T == A2) {
return A2(a: "world") as T;
} else {
throw Exception("Unsupported type");
}
}

void test() {
T bean = createInstance();
print(bean.runtimeType); // 输出实例的运行时类型
}
}

void main() {
B<A1>().test(); // 输出: A1
B<A2>().test(); // 输出: A2
}

End

依赖关系可视化

参考文档:

一、cocoapods依赖关系可视化

cocoapods管理的三方库之间有时候存在依赖关系,Podfile.lock藏满了各个版本库的版本号信息和彼此的依赖关系。

1、网页可视化导出:cocoapods-graph

终端安装插件:

1
pip install cocoapods-graph

使用方式:

进入到工程的根目录,执行:

1
2
3
cd ~/Project/CQComponent/UIKit-Overlay-iOS/TSOverlayDemo

cocoapods-graph -f Podfile.lock --html

生成 Podfile.lock.html 文件,

cocoapods-graph-01

其内容展示如下:

cocoapods-graph-02

2、图片可视化导出:cocoapods-dependencies

终端安装插件:

1
2
gem install cocoapods-dependencies
brew install graphviz

使用方式:

进入到工程的根目录,执行:

1
pod dependencies [PODSPEC] [--graphviz] [--image]
  • pod 是 CocoaPods 的命令行工具。
  • dependencies 是 CocoaPods 中的一个命令,用于显示特定 pod 或项目中所有 pod 的依赖关系。
  • [PODSPEC] 是一个可选参数,用于指定特定 pod 的名称。如果你提供了 pod 的名称,它将显示该 pod 的依赖关系。如果你没有提供 pod 的名称,它将显示项目中所有 pod 的依赖关系。
  • --graphviz 是一个可选标志,它会生成一个 Graphviz DOT 文件(一般为Podfile.gv ),表示 pod 的依赖关系图。这个文件可以用于可视化依赖关系。
  • --image 是一个可选标志,它会从 Graphviz DOT 文件生成一个图像文件(一般为Podfile.png )。这样你就可以以图像格式查看依赖关系图。

示例1:不提供 [PODSPEC] 将显示项目中所有 pod 的依赖关系。

1
2
3
4
5
6
cd ~/Project/CQComponent/UIKit-Overlay-iOS/TSOverlayDemo

pod install

# 不提供 [PODSPEC] 将显示项目中所有 pod 的依赖关系。
pod dependencies --graphviz --image

生成 Podfile.gvPodfile.png文件如下:

cocoapods-dependencies-01-html

其中 Podfile.gv 可用 OmniGraffle应用程序 打开

gv的打开方式

打开方式说明如下:

序号 打开方式 打开说明 示例
1 Hieracchical 层次结构
2 Force-Directed 强制导向
3 Circular 圆形的
4 Radial 径向的

其各自详细效果图,详见 依赖关系可视化-TSOverlayDemo.graffle

示例2:提供 [PODSPEC] 将显示该 pod 的依赖关系

命令:pod dependencies AFNetworking --graphviz --image

这个命令将显示 AFNetworking 这个 pod 的依赖关系,并生成一个 Graphviz DOT 文件和相应的图像文件,用于可视化查看 AFNetworking 的依赖关系。你可以在生成的图像文件中查看依赖关系图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1、安装
gem install reversepoddependency

# 2、查找所安装库里的命令路径
gem which reversepoddependency
# gem which:这是一个用于定位 Ruby gem 安装路径的命令。当你执行 gem which GEM_NAME 时,它会返回指定 gem 的安装路径。
# which:这是一个用于查找可执行文件的命令。
# 得到的结果如下:
# /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/lib/reversepoddependency.rb
# 所以该包的 specbackwarddependency 应在如下目录:
# /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/
# 经查找 specbackwarddependency 的其实际地址为如下:
# /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency

# 3、使用所安装库里的命令
/usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency ~/.cocoapods/repos/trunk AFNetworking

在运行 gem install reversepoddependency 命令后,安装的 reversepoddependency gem 应该会被存储在你使用的 Ruby 安装目录下的 Gems 文件夹中。

问题:执行 /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency ~/.cocoapods/repos/trunk AFNetworking 时候出现如下错误:

specbackwarddependency_01

我们通过进入指定文件/usr/local/lib/ruby/gems/3.2.0/gems/cocoapods-core-1.12.1/lib/cocoapods-core/source.rb 查看其210行,并添加以下代码,即可过滤掉该库:

1
2
3
4
if name == "TUICalling" || name == "TUIChat" || name == "VK-ios-sdk" 
name = "AFNetworking"
version = "3.2.1"
end

specbackwarddependency_02

不然你得通过以下命令找到该所在库,然后再一个个删掉,这种方式会造成后续使用问题。所以我们选择修改判断代码

1
2
3
4
cd ~/.cocoapods/repos/trunk/Specs

# 查找指定库
find ~/.cocoapods/repos/trunk/Specs -name 'TUICalling'

添加代码后,重新执行

1
/usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency ~/.cocoapods/repos/trunk AFNetworking

得到的结果如下:

specbackwarddependency_03

End

项目目录结构规范实践

步骤

1、项目目录结构分类与创建

2、

一、项目目录结构分类与创建

1、utils
2、models
3、commons

1、独立类(utils\models\common)的文件归类、迁移

1.1、库创建(0.5h)

1.2、文件归类与迁移(4h)

1、util

2、models

用户

序号 类型
1 用户
2 内容
3

1.3、内部的依赖修复与优化

内部引用优化为使用相对路径或新的绝对路径。

1
import './a.dart';

1.4、外部的使用优化

优化为

Step1:内部 export 各种文件

1
2
export './src/a.dart';
export './src/b.dart';

Step2:外部 import 基础类库的总文件

1
2
3
import 'package:app_utils/app_utils.dart';
import 'package:app_models/app_models.dart';
import 'package:app_common/app_common.dart';

2、通用组件类(commons)文件归类、迁移

1、内含适配组件 adapt

每个视图都需要依赖

2、基础用户头像、商品视图

依赖:基础的像素适配

入参:关键性参数及自定义的点击回调

附:升级版通用功能组件类common_ui(如用户头像、商品视图等),见下文。

3、基础路由(route)

4、升级版通用功能组件类common_ui(如用户头像、商品视图等)

以用户头像举例:

①需要支持传入baseUserModel的时候,显示头像信息

②需要支持传入baseUserModel的时候,点击跳转到对应用户详情页

所以需要依赖的有:

序号 需求 需要依赖 其他
1 需要支持传入baseUserModel的时候,显示头像信息 models
2 需要支持传入baseUserModel的时候,点击跳转到对应用户详情页 route

商品视图:

基础类空安全升级