技术选型流程

[toc]

技术选型流程

一、选型流程

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

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

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

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

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

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

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

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

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

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

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

End

UI组件选择历程

[toc]

设备相关

一、分辨率与尺寸

最新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;
}

End

视频

[toc]

前言

一、视频上传

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

二、常见视频播放

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

2、m3u8文件认识

现在比较常见的视频流媒体,大部分都是 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文件示例

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动效实现方案

四、视频预加载

五、视频帧

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

六、视频播放暗掉

核心:

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

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

视频解码

音频降噪

枚举值名称 枚举值 枚举类型描述
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

页面加载体验优化

[toc]

页面加载体验优化

一、页面初始加载优化

1、数据携带/数据参照

通过前一个页面的已获得数据,对所进入的新页面中的数据进行预填充。

eg1:商品列表 —> 商品详情:使用数据携带

eg2:愿望星count个数:通过count的0与非0,知晓所进入的页面初始状态更有可能是哪种状态

1、如果所进入的页面没有缓存数据,则携带的数据在进入的时候直接使用,后台接口返回实时数据后,再更新

2、如果所进入的页面有缓存数据,则携带的数据只能给缓存数据,而不能是后台接口返回的实时数据

2、管理的数据的缓存

2.1、普通数据的缓存

通过缓存框架,将数据缓存起来(key需携带uid),下次界面展示时候,优先从缓存中获取。

附:缓存框架详见:flutter_cache_kit使用文档

2.2、管理数据的缓存

建立Service层,管理用户所有数据的变动。

好处:与普通数据的缓存相比,能够在将来增加数据变动时候,通过本地通知系统,告知相关页面更新相应数据,而不用等到重新下载后才能显示已知道会更新的数据。

使用要点:

修改的时候,同步数据;下次界面需要数据,优先从用户管理服务中获取初始数据

eg1:用户愿望单收藏、商品收藏、品牌收藏、足迹数据

eg2:用户会员中心数据

3、默认数据

如果没有从前一页携带数据过来,则使用与产品约定的默认数据来加载。

1
2
3
if (携带数据) return 携带的.orderCount; 
if (管理数据) return servier.orderCount;
if (默认数据) return default_orderCount;

4、预览页:美团/饿了么的骨架屏灰底效果

对携带的数据与默认数据中相同的对象,使用携带的数据替换掉,一起整合成初始默认数据(携带的数据优先级高)。

5、添加请求加载圈

6、网络接口的缓存数据

页面处理优化及处理过程中的内容(图片)加载优化如下图所示:

图片来源可查看 《app启动与页面加载.graffle》中的【二、页面加载】版面

image-20240725001944956

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

二、图片优化

图片优化,请查看我的另一篇文章:图片框架.md

图片高磁盘占用,请查看我的另一篇文章:《高磁盘占用的排查与优化.md

三、网络优化

api数据缓存,请查看我的另一篇文章:《网络框架.md

弱网优化,请查看我的另一篇文章:《弱网优化空间探索.md

四、按钮点击区域优化

eg:返回键的点击

防抖(立即执行):

五、样式

loading加载动画按需添加

返回按钮统一

toast提示统一

底部弹窗统一

六、底层异常处理

1、图片的404错误

错误示例:

https://img.alicdn.com/bao/uploaded/i1/1799996758/TB2YYnmoFXXXXatXFXXXXXXXXXX_!!1799996758.jpg

错误提示:

flutter: CacheManager: Failed to download file from https://img.alicdn.com/bao/uploaded/i3/791232018/TB2_zAjsVXXXXaNXXXXXXXXXXXX_!!791232018.jpg with error:

image-20220119162152615

修改方案:

Flutter cached_network_image图片缓存异常/加载失败优化

七、代码优化

屏幕适配写法

iOS 等比例UI适配方案

八、其他预加载优化

1、列表预加载

1.1、轮播图列表预加载

预加载下一张(不是全部)

2、视频预加载

3、全局WebView

End

代码编写技巧

[toc]

代码编写技巧

1
2
3
4
5
self.selectedBackgroundView = ({
UIView *view = [UIView new];
view.backgroundColor = [UIColor colorWithRed:244/255.0 green:244/255.0 blue:244/255.0 alpha:1.0]; //#f4f4f4
view;
});

枚举转 int AvatarStatus

协变和逆变 ?= 策略

在Dart1.x的版本种是既支持协变又支持逆变,但是在Dart2.x版本开始仅支持协变。所以后面我们就不再讨论Dart的逆变。

1
2
3
4
5
6
7
8
9
class PrintMsg<T> {

T _msg;

set msg(T msg) { this._msg = msg; }

void printMsg() { print(_msg); }

}

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
}