【问题标题】:Accelerate loading of WKWebView加速 WKWebView 的加载
【发布时间】:2015-04-18 23:16:34
【问题描述】:

正如here 解释的那样,WKWebView 有一个错误,即捆绑本地网页的应用程序必须将捆绑包复制到tmp 目录中。我将捆绑包复制到tmp 的代码是:

// Clear tmp directory
NSArray* temporaryDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];
for (NSString *file in temporaryDirectory) {
    [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];
}

NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"build"];
NSString *temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"build"];
NSError *error = nil;

// Copy directory
if(![fileManager copyItemAtPath:sourcePath toPath:temporaryPath error:&error]) {
    [self logError:@"Could not copy directory" :error];
}

我已经为这个特定的代码片段计时,它占用了我应用程序启动时间的 50%! (总共约 1 秒中的约 0.5 秒。)

有没有一种方法可以在 iOS 8 下加速(或完全避免)这个特定的代码片段?线程有帮助吗?

【问题讨论】:

  • 您是否尝试过将该代码放入后台队列?现在它看起来正在主线程上执行,这肯定会减慢启动速度。共享的NSFileManager 是线程安全的,所以你不应该在那里遇到任何问题。
  • 这些文件的复制在我的应用程序的关键路径中。我的应用程序实际上是一个WKWebView,需要尽快加载。
  • 您要复制的捆绑包有多大?
  • 您可以将图像处理为占用大部分空间的图像,并使其更小。但我会说 0.5 秒还不错,如果你将其降低到 0.2 或 0.1 秒,我认为不会很明显

标签: ios performance ios8 startup wkwebview


【解决方案1】:

鉴于您的资产足够大,需要 0.5 秒才能被复制,我的建议是采用稍微不同的方法来加快(明显)启动时间:

  1. 使用占位符 divs 创建一个普通的旧 HTML 页面以加载资源(如果您愿意,可以在 base64 中包含小型资源(例如加载指示器图像))
  2. 加载 HTML 后(可能在 webView:didFinishNavigation:),开始复制到构建文件夹
  3. 复制完成后,使用 javascript(evaluateJavaScript:completionHandler:)使用您的资产填充占位符 div

虽然我意识到这并不能直接解决问题,但我认为在这种情况下最好稍微改变一下方法。

【讨论】:

    【解决方案2】:

    注意:此技术适用于 UIKit 的 UIWebView 和 AppKit 的 WebView,但不适用于新的 WKWebView,它似乎忽略了 URL 加载系统。见 cmets。

    使用NSURLProtocol 处理本地文件读取,就好像它们是远程请求一样。有关完整示例,请参阅 PandoraBoy 中名为 ResourceURLProtocol 的示例。我将在这里介绍一个稍微简化的版本。我们将阅读http://.RESOURCE./path/to/file,就好像它是<resources>/path/to/file

    每个NSURLProtocol 都会被询问是否可以处理系统中出现的每个请求。它需要在+canInitWithRequest: 中回答是否可以。我们会说如果主机是.RESOURCE.,那么我们可以处理它。 .RESOURCE. 是非法的 DNS 名称,因此它不能与任何真实主机冲突(但它是用于 URL 目的的合法主机名)。

    NSString *ResourceHost = @".RESOURCE.";
    
    + (BOOL)canInitWithRequest:(NSURLRequest *)request {
        return ( [[[request URL] scheme] isEqualToString:@"http"] &&
                 [[[request URL] host] isEqualToString:ResourceHost] );
    }
    

    那么我们需要几个记账方法。这里没什么可看的。

    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
        return request;
    }
    
    -(void)stopLoading {
        return;
    }
    

    现在我们来看看它的实质。 startLoading 是您要对请求执行任何操作的地方。

    -(void)startLoading {
        NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];
        NSString *notifierPath = [[thisBundle resourcePath] stringByAppendingPathComponent:[[[self request] URL] path]];
        NSError *err;
        NSData *data = [NSData dataWithContentsOfFile:notifierPath
                                              options:NSUncachedRead // Assuming you only need to read this once
                                                error:&err];
        if( data != nil ) {
            // Assuming you're only reading HTML. 
            // If you need other things, you'll need to work out the correct MIME type
            NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[[self request] URL]
                                                                MIMEType:@"text/html"
                                                   expectedContentLength:[data length]
                                                        textEncodingName:nil];
    
            // And we just pass it to the caller
            [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
            [[self client] URLProtocol:self didLoadData:data];
            [[self client] URLProtocolDidFinishLoading:self];
        } else {
            NSLog(@"BUG:Unable to load resource:%@:%@", notifierPath, [err description]);
            [[self client] URLProtocol:self didFailWithError:err];
        }
    }
    

    我还发现一些 ResourceURL 包装器很有帮助:

    @implementation ResourceURL
    + (ResourceURL*) resourceURLWithPath:(NSString *)path {
        return [[[NSURL alloc] initWithScheme:@"http"
                                         host:ResourceHost
                                         path:path] autorelease];
    }    
    @end
    

    要使用它,您只需要先注册您的协议处理程序:

    [NSURLProtocol registerClass:[ResourceURLProtocol class]];
    

    然后你可以创建一个“资源 URL”并加载它:

    ResourceURL *resource = [ResourceURL resourceURLWithPath:...];
    [webView loadRequest:[NSURLRequest requestWithURL:resource]];
    

    有关NSURLProtocol 的更多详细信息以及更复杂的缓存示例,请参阅Drop-in Offline Caching for UIWebView (and NSURLProtocol)

    PandoraBoy 充满了NSURLProtocol 示例(查找名称中包含Protocol 的所有类)。您可以通过这种方式劫持、监视、重定向或操纵通过 URL 加载系统的任何内容。

    【讨论】:

    • 我可以让canInitWithRequest 返回YES,但永远不会调用startLoading。有什么想法吗?
    • 这是加载系统的重大变化。不;我不知道。
    【解决方案3】:

    尝试XWebView 而不复制文件。它为您提供了一个小型 http 服务器。您只需致电[webview loadFileURL:allowingReadAccessToURL:]

    【讨论】:

      【解决方案4】:

      将此与其余 0.5 秒的启动时间并行肯定会有所帮助。 (这只有在你在启动的 0.5 秒内所做的任何事情都不依赖于正在加载的 WebView 或正在读取的文件时才有可能)

      您将获得的最佳优化是减小“build”目录中文件的大小。试试这些 -

      1. 如果有的话,请缩小您的页面和 javascript 文件。 https://developers.google.com/speed/docs/insights/MinifyResources
      2. 如果目录很大,请考虑将其压缩,将 zip 添加到应用程序,将 zip 复制到“tmp”并在“tmp”中解压缩

      【讨论】:

        猜你喜欢
        • 2019-12-27
        • 1970-01-01
        • 1970-01-01
        • 2016-09-07
        • 2016-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多