介绍

在调试 iOS 程序时发现某些情况下无法进入游戏,报错如下:

1
2
NSURLConnection finished with error - code -1001
NSURLConnection finished with error - code -1002

环境

  • Unity 5.6.6f2
  • Xcode 11

查找

刚开始的时候以为是服务器未启动或 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 库获取文件偏移后直接读取
  • 其他平台
    • 可以使用文件 API 直接读取