介绍
在调试 iOS 程序时发现某些情况下无法进入游戏,报错如下:
1
2
|
NSURLConnection finished with error - code -1001
NSURLConnection finished with error - code -1002
|
环境
查找
刚开始的时候以为是服务器未启动或 URL 配置错误,导致出现此网络错误。使用 Charles 抓包后并未抓取到任何请求。
查看日志后发现是 StreamingAssets
下文件无法读取到导致报错。
由于 Android 平台下 StreamingAssets
中的文件是压缩状态,因此项目使用 WWW 加载 StreamingAssets
中的文件,而且为了保证不同平台一致,所有平台都使用了这种方法加载。
调试
项目使用 IL2CPP 打包,因此可以方便地使用 Xcode 断点调试,由于错误是由 NSURLConnection 产生,全局文本搜索后发现只在 WWWConnection.m
中有调用。
简单分析后发现有三处可以断点的地方,经过断点测试后确定是启动下载、线程中开始下载、下载完成报错回调:
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
45
46
|
- (void)startConnection
{
if (!_connectionCancelled)
[self.connection start];
_connectionStarted = YES;
}
// NSURLConnection Delegate Methods
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response;
{
if (response && self.manuallyHandleRedirect)
{
// notify TransportiPhone of the redirect and signal to process the next response.
if ([response isKindOfClass: [NSHTTPURLResponse class]])
{
NSHTTPURLResponse *httpresponse = (NSHTTPURLResponse*)response;
NSMutableDictionary *headers = [httpresponse.allHeaderFields mutableCopy];
// grab the correct URL from the request that would have
// automatically been called through NSURLConnection.
// The reason we do this is that WebRequestProto's state needs to
// get updated internally, so we intercept redirects, cancel the current
// NSURLConnection, notify WebRequestProto and let it construct a new
// request from the updated URL
[headers setObject: [request.URL absoluteString] forKey: @"Location"];
httpresponse = [[NSHTTPURLResponse alloc] initWithURL: response.URL statusCode: httpresponse.statusCode HTTPVersion: nil headerFields: headers];
[self handleResponse: httpresponse];
}
else
{
[self handleResponse: response];
}
SignalConnection(self);
[self cancelConnection];
return nil;
}
return request;
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
UnityReportWWWStatusError(self.udata, (int)[error code], [[error localizedDescription] UTF8String]);
UnityReportWWWFinishedLoadingData(self.udata);
SignalConnection(self);
}
|
分析
在 startConnection()
中断点可以看到使用的 URL,而在另外两处断点看到的 URL 是空。
file:///var/containers/Bundle/Application/A0DB45C7-2E0A-464E-89CF-B535D5077FF8/游戏 123.app/Data/Raw/Config.yml
可以看到路径中包含了产品中文名,猜测是这里导致出问题,WWW 使用的 URL 必须经过 Escape 之后才能使用。
尝试使用 WWW.EscapeURL
转义后再调用 WWW,结果依然是同样报错。
因此尝试测试,结果如下:
1
2
3
4
5
|
Debug.Log(System.Uri.EscapeUriString("file:///var/containers/Bundle/Application/A0DB45C7-2E0A-464E-89CF-B535D5077FF8/游戏 123.app/Data/Raw/Config.yml"));
Debug.Log(WWW.EscapeURL("file:///var/containers/Bundle/Application/A0DB45C7-2E0A-464E-89CF-B535D5077FF8/游戏 123.app/Data/Raw/Config.yml"));
file:///var/containers/Bundle/Application/A0DB45C7-2E0A-464E-89CF-B535D5077FF8/%e6%b8%b8%e6%88%8f%20123.app/Data/Raw/Config.yml
file%3a%2f%2f%2fvar%2fcontainers%2fBundle%2fApplication%2fA0DB45C7-2E0A-464E-89CF-B535D5077FF8%2f%e6%b8%b8%e6%88%8f+123.app%2fData%2fRaw%2fConfig.yml
|
可以看到 WWW.EscapeURL
错误地将 /
与空格转换了。
最后使用 Uri.EscapeUriString
成功解决问题。
正确做法
- Android 平台
- 使用 WWW 加载
- 使用 zip 库解压读取文件
- 或文件在打包时设置为 store 不压缩,然后使用 zip 库获取文件偏移后直接读取
- 其他平台