介绍
在调试 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 库获取文件偏移后直接读取
 
 
- 其他平台