埋点数据常见问题

埋点数据常见问题

一、页面的停留时长

页面的停留时长不应该只以系统didAppear和disAppear为判断依据。

还要扣除页面进入后台的时间。

解决:

进入后台的时候,触发消失的埋点;进入前台的时候触发显示的埋点。

相当于,如果用户 进入页面 – 退到后台 – 返回前台 – 离开页面

会等价于:显示 – 消失 – 显示 – 消失 。两次记录。而不是只有 进入页面– 离开页面的一次记录。

二、未显示的tab页面,也有使用时长

三、启动app的人数

序号 方案 结果
1 通过appStart计算启动app的人数 不准确❌
2 通过登录后必进入的接口请求 准确✅
3

方案1:通过appStart计算启动app的人数,结果不准确

原因说明:

appStart 用户为冷启动(杀掉再进)
当用户未登录:有appStart,但没userId。
进行登录后,有首页的访问记录,但是appStart已上报过。

所以,首页的访问记录 >= appStart(99.99999%是大于)

其他:
userId登录时候,未登录无;deviceId:登录和未登录都有

常见问题

1、数据筛选不准确

可能原因:

1、未区分版本

可能旧版本有问题,新版本上修复后,筛选的数据要以新版本为主。

举例:

2、会区分时间段

比如:计算页面的在某一天的时候时长。

实际举例:用户从昨天22:59开始进入app或者一直在app内的某个页面,直到第二天的23:59才退出。

则app获取到的数据是,用户使用了25个小时的应用。

如果计算当天,实际上,只用了23小时59分钟。

为方便后台计算该次记录的初始显示时间,客户端在消失的埋点里添加显示的时间。

WebView优化

[toc]

脑图与概览

点击查看 webView.xmind

参考文章:

概念

1、当App首次打开时,默认是并不初始化浏览器内核的;

只有当创建WebView实例的时候,才会创建WebView的基础框架。

所以与浏览器不同,App中打开WebView的第一步并不是建立连接,而是启动浏览器内核

先上结论

阶段 优化方案
(初次/重复)打开时,加载慢 webview提前(尽可能多的)初始化+全局化
加载中,加载慢 拦截加载自定义图片(webview_flutter暂不支持)

一、(初次/重复)打开时,加载慢:webview提前(尽可能多的)初始化+全局化

背景:有一款基于 cocos2d 的app游戏,其每次启动都需要加载游戏引擎,而且引擎本身又比较消耗时间。

请问1:如何提高初次打开的加载速度?

请问2:如何做到频繁退出进入游戏,不用都走一遍重新加载的流程呢?

序号 问题 解决
1 初次打开,加载慢 1、webview的在冷启动过后不久的提前初始化
2、初始化的时候多多进行可以初始化的部分,如尝试游戏引擎的加载。
2 重复打开,加载慢 webview设为全局变量,重复使用

优化前后对比小结:

方法 存在的问题 VS 改进的好处
未优化 每次进入游戏都开启页面,退出游戏都销毁页面。 使用上述方法,每次退出再进入该页面都会因为需要重新加载引擎,而导致游戏主页内容的显示很慢,从而严重影响app的使用体验。
优化后 1、游戏使用一个全局GameWebView(其他WebView不用全局)
2、游戏显示使用将webView添加到vc或者window上。
全局GameWebView不会被销毁,能够重复使用,提升加载速度。
①加载过的游戏,下次再进入不用重新重新加载引擎,有效的跳过loading的过程,极大的改善了用户体验。

附1:游戏引擎能否直接在原生上提前加载?

答:常见做法:构建出含Cocos的原生工程,然后将自己的功能添加上去。见 Cocos Creator 上原生平台二次开发指南

存在问题:不能自己在原生上添加游戏引擎。 Cocos Creator 引擎定制工作流程 也只是将其自己编译器创建的项目的引擎定制。

最终结论:游戏引擎没办法在原生上提前自己加载。

背景:如何将 Cocos Creator 构建出来的工程用正确的姿势接入Flutter 工程

附2:一个视图实例被添加后,再被添加到其他地方后的效果

此需求背景:《页面跳转相关(路由)》中【四、游戏与app跳转交互优化调研】)

测试过程见附录 附1:测试一个视图实例被添加后,再被添加到其他地方后的效果

被使用的方式 使用的结果
1 一个视图实例添加到一个vc上的两个container上 视图只显示在后面设置的位置
2 一个视图实例添加到不同viewController 视图只显示在后面设置的位置。则代表回来的时候需要重新更新视图位置。

二、加载中,加载慢

方案1: Scheme 拦截 WKURLSchemeHandler

解决:加载自定义图片(Flutter现有的webview_flutter暂时不支持,如要需要自己修改)

参考文档:

h5中

1
<h3 id="photo"> 自定义图片:canineschool-avatar://photo  </h3>

app中

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
let configuration = WKWebViewConfiguration()
configuration.setURLSchemeHandler(MyCustomSchemeHandler(), forURLScheme: "myScheme")
let webView = WKWebView(frame: .zero, configuration: configuration)
// 当WKWebView在加载一个使用"myScheme://"的资源时,就会调用我们的 MyCustomSchemeHandler 来获取数据。


class MyCustomSchemeHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
var data: Data?
var mimeType = "application/octet-stream" // 默认MIME类型

let url = urlSchemeTask.request.url!
// 判断URL的文件扩展名,如果是图片才进行特殊处理,即将webView的图片数据改为app的图片数据
if (["png", "jpg", "jpeg"].contains(url.pathExtension.lowercased()) {
data = SDWebImageManager.shared.imageData(forKey: url.absoluteString, options: [])
mimeType = url.pathExtension.lowercased() == "png" ? "image/png" : "image/jpeg"
} else { // 对于非图片类型,使用默认响应
}

// 创建URL响应,向WKWebView提供响应和数据
let response = URLResponse(url: url, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil)
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data: validData)
urlSchemeTask.didFinish()
}

func webView(_ webView: WKWebView, stop urlSchemTask: WKURLSchemeTask) {

}
}

WKURLSchemeHandler 拦截https时引起网络请求也被拦截了,如何重定向

方案2: 下载并解压zip,app加载本地静态文件

在WebView中加载本地资源:使用file协议在WebView中加载沙盒目录中的资源。

跨域问题:

方法1:在后端设置CORS(跨源资源共享)策略,允许来自特定来源的请求。

方法2:资源通过js来app中获取再回到web中显示。

方案3:在app中内置一个 WebServer

其他离线方案:

iOS离线静态资源包技术方案分析

Flutter下实现WebView拦截加载离线资源

三、打开后,白屏

1、白屏的原因及原理

  • 深入理解WKWebView白屏

    iOS设备上造成白屏的真正原因只有两个:内存使用过度、进程通信机制出现错误。

2、白屏的解决

webview_blank_flow

理想情况(不一定白屏都会调用到):

1
2
3
4
//进程终止(内存消耗过大导致白屏)
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
[webView reload];
}

其他处理方式:

白屏的像素判断法:iOS WKWebView白屏的像素检测方法iOS WKWebview 白屏的像素检测实现

webview截图 ==> 缩放图片,为了减少待会遍历的像素点 ==> 遍历像素点,判断白色像素占比超过95%则认定为白屏

1
2
3
4
5
totalCount++;
if (red == 255 && green == 255 && blue == 255) {
whiteCount++;
}
// 占比 proportion = (float)whiteCount/totalCount; 如果 占比 >0.95 则认为白屏

白屏的 webView的子视图WKCompositingView 不存在的判断法:白屏时 WKCompositingView 为空

附:WKCompositingView 也是我们在做 WebView同层渲染 的时候经常会遇到的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 判断是否白屏
- (BOOL)isBlankView:(UIView*)view { // YES:blank
Class wkCompositingView = NSClassFromString(@"WKCompositingView");
if ([view isKindOfClass:[wkCompositingView class]]) {
return NO;
}
for(UIView*subView inview.subviews) {
if (![self isBlankView:subView]) {
return NO;
}
}
return YES;
}

其他解决方案:

白屏判断点 参考文章
webView的 urltitledocument.body.innerHTML 为空 WKWebView 白屏处理

四、使用中的交互

1、常规

WKWebView 注入js代码: 001-UIKit-CQDemo-Flutter 中的 flutter_webview_kit

2、全局webView下如何正确返回前一页

见 《webview的一生.graffle》中的【全局webView下如何正确返回前一页】版面

五、WebView拦截请求的问题

阿里云官方文档: WKWebView使用私有API进行注册拦截请求

1、WKURLSchemeHandler 拦截https时引起网络请求也被拦截了,如何重定向

当使用WKURLSchemeHandler拦截https的图片时候,其自然的也会把网络请求也拦截了,需要将那些网络请求重定向。

为了区分出不同的https,可以通过获取url地址的扩展名,如 jpg、png、txt、MP4等。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
// 调用 _didPerformRedirection:newRequest: 执行重定向
// 拆成 _didPerf ormRe dire ctio n:n ewRe que st: 后倒序得到如下
NSArray *privateSelStrArr = @[@"st:", @"que", @"ewRe", @"n:n", @"ctio", @"dire", @"ormRe", @"_didPerf"];
NSString *selName = [[[privateSelStrArr reverseObjectEnumerator] allObjects] componentsJoinedByString:@""];
SEL sel = NSSelectorFromString(selName);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.schemeTask performSelector:sel withObject:response withObject:request];
#pragma clang diagnostic pop
completionHandler(request);
}

六、WebView同层渲染

1、同层渲染需求背景:

Webview 资源离线化之后不需要从后端拉取资源,省去了资源加载耗时减少等待时间,用户体验得到了明显提升,但受限于 Webview 的天生限制在某些场景下仍然能感受到与原生明显的体验落差。比如输入框弹出键盘时的页面卡顿、图片较多的页面内存占用过从后台回来前台时页面被重新加载、WebP 图片支持碎片化、图片不能跨页共享内存、视频播放组件体验不佳等问题。在 Webview 本身受限的情况下,如何在体验上向原生靠近是我们不得不面对的问题。如果能将影响用户体验的关键可交互组件用原生组件进行替换可以从根本上解决体验不如原生的问题。这部分内容我称为: WebView原生化

Webview 原生化是指把 Webview 内部分占位 DOM 元素用原生组件进行替换,原生组件与原始 Webview 混合便得到了用户看到的最终界面。

组件替换有两种方式,
蒙层方案:在 Webview 的直接上层添加原生视图蒙层,把原生组件添加到蒙层上占位 DOM 对应的位置。

缺点:当一个原生组件被添另到 Webview 上时它永远处顶层,不会被 Webview 内的弹窗覆盖,在滚动时原生组件会盖住原本应被显示的区域。

同层渲染方案:另一种是将原生组件添加到 Webiview 渲染时与占位 DOM 对应的合成层(独立出来的原生视图)上,下面会对比两种方案的差异。

以上引用内容摘自:微信小程序『同层渲染』技术是怎么回事? 强烈建议点开原文再看一遍。

2、同层渲染实现示例

关键问题:如何映射 DOM 到原生视图?如何在原生视图树中查找 「DOM」

「同层渲染」则是将原生组件直接渲染到 WebView 层级上。

2.1、入门简单实现

实现步骤:

1、创建一个可以生成WKChildScrollView原生组件的 DOM 节点

2、传递给客户端查找到该 DOM 节点对应的 WKChildScrollView 原生组件的必要信息

3、客户端根据传来的信息找到对应的原生组件,并将原生组件挂载到该 WKChildScrollView 节点上

2.2、微信小程序实现

Skyline 渲染引擎 /概览 /介绍

( 微信 Skyline 渲染引擎类似 Flutterr 的 SkyEngine)

微信小程序 原生组件说明

image-20240903234945043

image-20240903234704466

3、其他参考文档

【必读】前端需要懂的 APP 容器原理

容器 URL 统一化建设(一个 URL 地址可按需动态配置渲染成 H5、Native 等页面),小程序容器建设,Flutter 容器建设,容器的快照缓存、预渲染、混合渲染等优化与能力相关建设

其他

优化待处理的文章

APP中WebView性能优化

iOS WebView/H5 调试新姿势

iOS WebView/H5 调试新姿势

附1:测试一个视图实例被添加后,再被添加到其他地方后的效果

测试代码:详见 AppCommonJSCollect 中的 SharedNormalView 、 SharedWebView

1.1、测试一个视图实例添加到一个vc上的两个container上

ViewAddInOneViewController

1.2、测试一个视图实例添加到不同viewController

阶段1:进入新页面的时候,使用之前共享的视图

阶段1结果:虽然第二页复用了第一页中的视图,但是从第二页回来的时候,第一页的视图不见了。

ViewAddInDiffViewController1

阶段2:在阶段1的基础上,补充回来的时候(viewWillAppear),需要重新更新视图的布局

阶段2结果:视图显示正确了

ViewAddInDiffViewController2

End

目录

外部与app交互脑图

本地代码测试:

将文件放置到 /Library/WebServer/Documents/ 下,比如将 dvlp_h5_open_app_app_route_url_demo.html

放置:/Library/WebServer/Documents/h5_native_interacte/h5_open_app/dvlp_h5_open_app_app_route_url_demo.html

1、基本访问测试

访问:http://localhost/h5_native_interacte/h5_open_app/dvlp_h5_open_app_app_route_url_demo.html

2、参数携带测试

访问:http://localhost/h5_native_interacte/h5_open_app/dvlp_h5_open_app_app_route_url_demo.html?fileUrl=dvlp_h5_open_app_browser_url_demo.json

3、更多参数使用测试可参考 h5_open_apph5js

小程序容器技术

[toc]

小程序容器技术

React Native 的 Text 组件转换为 iOS 的原生 UILabel ,是怎么实现这一过程的?

小程序容器技术对比分析

1
2
3
<view class="container">
<text class="title">Hello, {{name}}!</text>
</view>
1
2
3
4
5
6
7
8
9
10
11
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

.title {
font-size: 24px;
color: #333;
}
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
import UIKit

class MiniProgramContainerViewController: UIViewController {

var nameLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white

// 创建一个简单的视图来显示文本
nameLabel = UILabel()
nameLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(nameLabel)

// 设置文本标签的位置
NSLayoutConstraint.activate([
nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
nameLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

// 模拟从WXML解析来的数据
let data = ["name": "World"]

// 模拟WXML模板解析
let template = """
<view class="container">
<text class="title">Hello, {{name}}!</text>
</view>
"""

// 简单的模板字符串替换
在实际的小程序容器中,你需要一个完整的模板引擎来解析WXML,并将其转换为iOS的UI组件。
你还需要一个样式引擎来解析WXSS,并应用样式到对应的UI组件上。
let resultString = template.replacingOccurrences(of: "{{name}}", with: data["name"] ?? "Visitor")

// 将解析后的字符串设置为标签的文本
nameLabel.text = resultString

// 应用WXSS样式(这里只是简单地设置样式,实际上需要更复杂的样式解析和应用逻辑)
nameLabel.font = UIFont.systemFont(ofSize: 24)
nameLabel.textColor = UIColor.black
}
}

在实际的小程序容器中,你需要一个完整的模板引擎来解析WXML,并将其转换为iOS的UI组件。你还需要一个样式引擎来解析WXSS,并应用样式到对应的UI组件上。

滴滴开源小程序框架Mpx

End

第4节:详解Exception异常处理

[toc]

详解Exception异常处理

好文分享:

一、异常的捕获

同步异常通过 try-catch 机制捕获

1
2
3
4
5
6
7
8
// 使用 try-catch 捕获同步异常
xxx() {
try {
throw StateError('This is a Dart exception.');
} catch (e) {
print(e);
}
}

异步异常采用 Future 提供的 catchError 语句捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用 catchError 捕获异步异常
Future<Map<String, dynamic>> simulate_get(String url) async {
Future.delayed(Duration(seconds: 1)).then((value) {
throw StateError('This is a Dart exception in Future.');
return {
'statusCode': 200,
'msg': '"${url}执行成功"',
};
}).catchError((onError) {
print(onError);
//rethrow; // 这里不能rethrow. 因为A rethrow must be inside of a catch clause.
});
}

二、异常的上抛

异步异常的上抛

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
 // 使用上文中自己建的异步方法 await simulate_get(url) 来模拟
Future<dynamic> requestBaidu1() async {
var url = "https://www.baidu.com/";
try {
dynamic response = await simulate_get(url);

if (response['statusCode'] == 200) {
return response.data;
} else {
throw Exception('后端接口出现异常');
}
} catch (e) {
print('网络错误:======>url:$url \nbody:${e.toString()}');
// 这里只有执行了rethrow 或 throw,外层才能继续 .catchError((onError) {}
rethrow;
// throw Exception('网络错误:======>url:$url \nbody:${e.toString()}');
}
}



// 使用网络请求库 dio 中的异步方法 await dio.get(url) 来模拟
Future<dynamic> requestBaidu2() async {
var url = "https://www.baidu.com/";
try {
Dio dio = new Dio();
Response response = await dio.get(url);

if (response.statusCode == 200) {
return response.data;
} else {
throw Exception('后端接口出现异常');
}
} catch (e) {
// throw Exception('网络错误:======>url:$url \nbody:${e.toString()}');
print('网络错误:======>url:$url \nbody:${e.toString()}');
rethrow;
}
}

进行如上的上抛操作后,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// xxxPage.dart	
getData() {
Api.requestBaidu2().then((dynamic data) {
if (data.isSuccess) {
Toast.show("请求成功,有数据 isSuccess = true", context);
updateWidgetType(WidgetType.SuccessWithData);
} else {
Toast.show("服务器失败 isSuccess = false", context);
}
}).catchError((onError) {
print('网络问题');
Toast.show("catchError网络问题111", context);
updateWidgetType(WidgetType.ErrorNetwork);
});
}

判断网络异常

1
2
3
4
5
6
7
checkNetwork() async {
try {
final result = await InternetAddress.lookup('baidu.com'); //尝试连接baidu
} on SocketException catch (_) {
Toast.show("catchError网络问题222333", context);
}
}

Git代码同步

[toc]

Git代码同步

1、拉取最新代码、提交

1
2
3
4
5
cd CQApp-api-mongodb/
git pull origin master
git add .
git commit -m "add api cardlist"
git push origin master

image-20201121123010573

image-20201121123124236

上传成功,我们通过网站或者sourcetree等查看效果

image-20201121124131447

1、问题1:未配置用户名和邮箱

image-20201121123308986

解决配置用户名和邮箱

1
2
3
4
5
6
7
8
# 1、全局配置:使用 --global 修饰后设置的全局的用户
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"


# 2、如果只想设置单个项目的用户:可cd到项目根目录下,执行如下命令:
$ git config user.name "Your Name"
$ git config user.email "email@example.com"

使用命令:git config –list 可查看当前用户信息以及其他的一些信息

image-20201121123421969

2、拉取提交省去密码输入的技巧–ssh

1
2
3
4
5
ssh-keygen -t rsa -C "ecs_aliyun"

cd /root/.ssh

cat id_rsa.pub

image-20201121114358468

到 github/gitee/gitlab 上添加对应的ssh

image-20201121114618923

四、提交云服务器代码

  • 1、从服务器重新拉代码,将本地代码更新为服务器上的最新代码
1
git pull

image-20220112144529354

  • 2、查看本地代码状态,是否有待提交的代码
1
git status
  • 3、将本地代码全部提交
1
git  add .
  • 4、commit提交并添加注释
1
git commit -m "我是提交的注释"

如果提交过程中出现如下问题:

image-20220112145348308

请先根据提示,设置全局邮件和用户名,以让其他开发者可以看到是谁提交。配置前和配置后,可使用

1
2
3
4
5
6
7
# 查看
git config -l

# 设置提交者的邮箱
git config --global user.email "dvlproad@163.com"
# 设置提交者的用户名
git config --global user.email "dvlproad"

image-20220112145514520

设置完后,重新执行

1
git commit -m "我是提交的注释"

即可。接下来就只剩最后一步,就可正式提交完成了。

  • 5、将本地的 master 分支推送到 origin 主机的 master 分支。
1
$ git push origin master

更多 git push用法,查看官网https://www.runoob.com/git/git-push.html

image-20220112163849708

结果:

image-20220112172647153

End

iOS功能_通用链接

[toc]

Universal Links(通用链接)

相关文档:

Universal Links(通用链接),是iOS9推出的一项新功能。如果你的应用(app)支持通用链接(Universal Link)之后,iOS用户能够在点击网页的链接的时候无缝的重定向到已经安装的app,不需要额外的任何操作。如果没有安装对应的app,那么点击链接会使用Safari打开网页。

附:在iOS9以前,对于各种浏览器,Safari中唤醒app的需求,我们通常是只能使用scheme的方式。现在iOS9之后,我们多了一种方式。两种方式的区别是scheme能够支持我们跳转app的时候加入参数。

二、Universal Links的表现及测试

假设对于 com.dvlproad.Beyond 这个app,

dvlproad.com/app 是通用链接, dvlproad.com/mobile 不是通用链接。

1、在浏览器Safari地址栏中的表现

则我们先分别将这些链接粘贴到浏览器Safari的地址栏中,

1、浏览器地址栏为非通用链接时,没什么特别。
2、而浏览器地址栏为通用链接的时候,
①如果我们有安装`com.dvlproad.Beyond这个app,则会发现该页面下滑后,在顶部会多了个在应用中打开的操作,形如:

UniversalLinks_show1

②如果我们没有安装com.dvlproad.Beyond这个app,则没有多这栏打开。即使你的通用链接都配置正确了。

2、在非浏览器,如备用录app里的表现

UniversalLinks_show2

我们会发现点击

1、备忘录中的链接为非通用链接时,点击链接没什么特别。
2、而备忘录中的链接为通用链接的时候,点击链接会直接跳转到我们的com.dvlproad.Beyond这个app上。

三、Universal Links的实现

要让一个链接成为通用链接。需要

1、开启配置 Associated Domains

只有以该域名开头的链接,才可能是通用链接。

2、制作上传 apple-app-site-association

1、Associated Domains

1、进入苹果Apple Developer -> Member Center -> Certificates, Identifiers & Profiles – >Identifiers - >App IDs–>Edit 然后开启打钩 Associated Domains 后保存。

img

如果你使用Xcode的自动管理证书,可直接跳到第二步,即Xcode会在你打开Associated Domains时,自动帮你处理证书配置问题以及appIDs打开Associated Domains。

2、在项目中添加Associated Domains

AssociatedDomains_Set1/AssociatedDomains_Set1.png)

点击Associated Domains的+号填入applinks:后面是你的域名。

AssociatedDomains_Set2/AssociatedDomains_Set2.png)

及添加完后,只有以该域名dvlproad.com开头的链接,才可能是通用链接。

附:有时候,上面的设置也会改使用二级域名,如applinks:app.dvlproad.com。

2、apple-app-site-association

App在安装的时候,iOS系统会去根据该app配置里填的那些Associated Domains下,去这些域的根目录下,取apple-app-site-association文件。

我们创建apple-app-site-association文件,并上传也是为了让iOS系统通过Associated Domainsapple-app-site-association这个文件,一起知道你的哪些是链接是Universal Links,哪些不是Universal Links。

2.1、制作apple-app-site-association 文件

创建一个命名为apple-app-site-association文件,文件名必须为apple-app-site-association!!!(注意这个文件必须没有后缀名

apple-app-site-association内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"applinks": {
"apps": [],
"details": [
{
"appID": "myTeamId.com.dvlproad.Beyond1",
"paths": [ "*" ]
},
{
"appID": "myTeamId.com.dvlproad.Beyond2",
"paths": [ "app" ]
}
]
}
}

appID 是由你的Team ID+BundleID组成,如果你不知道你的Team ID是什么?进入苹果Apple Developer -> Member Center ->Membership->Team ID

paths 是一个字符串的数组,这里是你的应用支持的通用链接与不支持的链接,例如你使用的是/*,代表着只要是Associated Domains域名下的所有的链接都支持。如果你不支持某一个链接,在这个链接前面加NOT空格,举个例子

“paths”:[ “NOT /home/“, “/“ ]

如果这样写就是除了不支持/home下所有的链接,其他链接都支持。

2.2、上传apple-app-site-association 文件

把你刚才制作好的apple-app-site-association文件,放到1中Associated Domains所填写的域名dvlproad.com的根目录下或者.well-known下(要怕除问题,两个地方都放也行。)。

2.3、检测上传的apple-app-site-association 文件是否成功

①、通过浏览器访问https://xxx.com/apple-app-site-association地址,看文件是否上传成功。

②、苹果有一个检测的接口 苹果测试入口 ,将你的域名放入测试口,点击TestURL即可。

2.4、上传apple-app-site-association 文件到dvlproad.github.io的例子

为方便测试,我们域名使用dvlproad.github.io这个。

1、apple-app-site-association必须放置在域名的根目录或者 .well-known 目录下,

2、在本地测试跟路径是否有效。

方法:将apple-app-site-association放置好后,我们执行hexo s,则我们就能成功访问http://localhost:4000,进而http://localhost:4000/apple-app-site-association

UniversalLinks_test1/UniversalLinks_test1.png)

3、本地可以后,就部署到服务器

测试部署成功与否,通过浏览器访问https://dvlproad.github.io/apple-app-site-association

1、优先本地部署测试,即 hexo s 后,通过浏览器访问:
http://localhost:4000/apple-app-site-association 如果可以下载则文件位置放置在根目录正确。
2、本地测试通过后,进行远程部署 hexo g 和 hexo d 测试,及通过浏览器访问:
https://dvlproad.github.io/apple-app-site-association
如果可以下载代表在1的基础上,远程部署也成功了。
如果无法访问/下载,而又确认部署完成了,那一般是部署了但还没同步到域名地址上的问题。建议等等或者再部署一下看看。

3、写一个通用链接并测试

为进一步了解知道哪些链接是通用链接,哪些不是。我们以上述例子进行说明。

则对于com.dvlproad.Beyond1这个app,由于paths是[ "*" ]

即只要是dvlproad.com开头的链接都是通用链接。

而对于com.dvlproad.Beyond2这个app,由于paths是[ "app" ]

所以,只有以dvlproad.com/app开头的链接才是通用链接。即形如 dvlproad.com/app/111、dvlproad.com/app/abc 等都是通用链接。

通用链接的测试方法,请按上面已经写玩的Universal Links的表现及测试进行测试,不再累述。

四、Universal Links的使用

在AppDelegate添加下面的方法处理Universal Links

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//TODO:后台进入前台/通用链接
#pragma mark - Universal Link
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler {

NSLog(@"userActivity : %@",userActivity.webpageURL.description);
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSURL *webUrl = userActivity.webpageURL;
NSArray *universalLinks = LUCKIN_UNIVERSALLINKS;
BOOL isUniversalLinks = NO;
for (NSString *universalLink in universalLinks) {
isUniversalLinks = [webUrl.host isEqualToString:universalLink];
if (isUniversalLinks) {
break;
}
}
if (isUniversalLinks) {
//TODO:判断域名是自己的网站,进行我们需要的处理
} else {
[[UIApplication sharedApplication] openURL:webUrl];
}
}
return YES;
}

当用Universal Links启动APP时就会调用上面的方法。

把我们设置好通用链接的URL放到备忘录中,长按URL会多出一条在“××××”中打开,当你点击时就会打开你的APP,并且在上面的方法中将URL传给你处理。

微信SDK的最新版本openSDK1.8.6 开始,需要进行Universal Links配置。

1、分析微信配Universal Links的原因

回顾iOS9之前,假设一个页面在微信app内部打开,其要跳转到app,一般需要先跳转到浏览器,如Safari中,然后才能再进行scheme跳转到我们的应用。

UniversalLinks_wechat1

而有了Universal Links后,现在你用微信打开的页面,跳转的时候,即可直接跳转到app中,不用再经过浏览器。

2、微信配Universal Links的操作

2.1、基本分享操作的支持

微信分享配置Universal Links的有两个地方。

1、微信开放平台 https://open.weixin.qq.com 上对应app的Universal Links配置

UniversalLinks_wechat_Set1/UniversalLinks_wechat_Set1.png)

2、微信SDK使用时候的Universal Links配置。

如果上述两个值设置不一样,会导致在app中点击分享的时候,没法正常调起分享操作。错误信息如下:

UniversalLinks_wechat_error1

我们知道Universal Links是用来做通用链接的。但要完成这一步的操作不需要我们去开Associated Domains和上传apple-app-site-association才能完成基本的分享操作了。

2.2、分享完微信能够正常返回

iOS 9系统策略更新,限制了http协议的访问,需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。

1
2
3
4
5
6
7
8
9
10
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>weixinULAPI</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

2.3、可以在微信内部直接跳转到app

实现Universal Links的实现。即

1、开启配置 Associated Domains

2、制作上传 apple-app-site-association

上面已说明,请查看上面内容。

End