sponsored links

WKWebView实现网页静态资源优先从本地加载

  前言:最近微信的小游戏跳一跳特别的火,顺便也让h5小游戏更加的火热。另外微信小程序,以及支付宝的小程序都是用H5写的。无论是小游戏还是小程序,这些都需要加载更多的资源文件,处理更多的业务。这些都对网页加载的速度提出了较高的要求。UIWebView由于占用内存大,释放不掉一直备受诟病。而且目前是大多数的app支持的最低版本都是从iOS 8开始的。我这里主要针对WKWebView来说一下。

资源包压缩下载VS静态资源文件下载

  根据不同的业务需求,不同的app对于资源文件的处理情形是不同的。以12306app为例。选择了下载资源压缩到沙盒的策略,列车班次发生调整时,调用接口,强制下载资源压缩包到本地。注释:但是WKWebView加载本地资源文件,有些麻烦,后续会是专门深入研究下。由于强制下载资源包的形式用户体验不是特别好,很多小游戏,以及小程序为了更好的用户体验通常选择隐性下载静态资源文件的形式,加载时优先使用本地已下载的资源文件进行加载,不仅可以提高加载速度,而且还可以为用户节省流量。

网络请求的拦截

  NSURLProtocol相信很多小伙伴都挺听说并使用过。记得很早一段时间,大家对于WKWebView使用NSURLProtocol进行网络请求进行拦截没有很好的办法,还好不知道哪位大神最终找到了解决的办法,在此万分感谢。代码入如下:

//2.注册
    [NSURLProtocol registerClass:[NSURLProtocolCustom class]];
    //3.实现拦截功能,这个是核心
    Class cls = NSClassFromString(@"WKBrowsingContextController");
    SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
    if ([(id)cls respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [(id)cls performSelector:sel withObject:@"http"];
        [(id)cls performSelector:sel withObject:@"https"];
#pragma clang diagnostic pop
    }

加载时优先加载本地资源文件

  对WKWebView发出的网络请求进行拦截后,我们需要对资源文件的进行判断本,判断本地是否有对应的资源文件,如果有的话优先加载本地的资源文件。对于资源文件的匹配,我这里将网络请求中资源文件的url进行MD5序列化后,作为资源文件的名字。代码如下:

//
//  NSURLProtocolCustom.m
//  WKWebViewDemo1
//
//  Created by JackLee on 2018/2/27.
//  Copyright  2018年 JackLee. All rights reserved.
//

#import "NSURLProtocolCustom.h"
#import "NSString+MD5.h"
#import <JKSandBoxManager/JKSandBoxManager.h>
#import <AFNetworking/AFNetworking.h>

@interface NSURLProtocolCustom ()

@property (nonatomic, strong) AFURLSessionManager *manager;

@end

static NSString* const FilteredKey = @"FilteredKey";

@implementation NSURLProtocolCustom
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    NSString *extension = request.URL.pathExtension;
    BOOL isSource = [[self resourceTypes] indexOfObjectPassingTest:^BOOL(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        return [extension compare:obj options:NSCaseInsensitiveSearch] == NSOrderedSame;
    }] != NSNotFound;
    return [NSURLProtocol propertyForKey:FilteredKey inRequest:request] == nil && isSource;
}

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

- (void)startLoading
{
    NSMutableURLRequest *mutableReqeust = [super.request mutableCopy];
    //标记该请求已经处理
    [NSURLProtocol setProperty:@YES forKey:FilteredKey inRequest:mutableReqeust];
    NSString *fileName = [NSString stringWithFormat:@"%@.%@",[super.request.URL.absoluteString MD5Hash],super.request.URL.pathExtension];

    NSString *gameId = [[NSUserDefaults standardUserDefaults] stringForKey:@"GameId"];
    NSString *nameSpace = [NSString stringWithFormat:@"GameId%@",gameId];
    NSString *fileDir =[JKSandBoxManager createCacheFilePathWithFolderName:nameSpace];
    NSString *filePath =[fileDir stringByAppendingString:[NSString stringWithFormat:@"/%@",fileName]];
    NSLog(@"targetpath %@",filePath);

    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { //文件不存在,去下载
        [self downloadResourcesWithRequest:[mutableReqeust copy]];
        return;
    }
    //加载本地资源
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    [self sendResponseWithData:data mimeType:[self getMimeTypeWithFilePath:filePath]];
}

- (void)stopLoading
{

}

- (void)sendResponseWithData:(NSData *)data mimeType:(nullable NSString *)mimeType
{
    // 这里需要用到MIMEType
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:super.request.URL
                                                        MIMEType:mimeType
                                           expectedContentLength:-1
                                                textEncodingName:nil];

    //硬编码 开始嵌入本地资源到web中
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [[self client] URLProtocol:self didLoadData:data];
    [[self client] URLProtocolDidFinishLoading:self];
}

/**
 * manager的懒加载
 */
- (AFURLSessionManager *)manager {
    if (!_manager) {
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        // 1. 创建会话管理者
        _manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    }
    return _manager;
}

////下载资源文件
- (void)downloadResourcesWithRequest:(NSURLRequest *)request{

    NSString *fileName = [NSString stringWithFormat:@"%@.%@",[super.request.URL.absoluteString MD5Hash],super.request.URL.pathExtension];

    NSString *gameId = [[NSUserDefaults standardUserDefaults] stringForKey:@"GameId"];
    NSString *nameSpace = [NSString stringWithFormat:@"GameId%@",gameId];
    NSString *fileDir =[JKSandBoxManager createCacheFilePathWithFolderName:nameSpace];
    NSString *targetFilePath =[fileDir stringByAppendingString:[NSString stringWithFormat:@"/%@",fileName]];

    NSURLSessionDownloadTask *downloadTask = [self.manager downloadTaskWithRequest:request progress:^(NSProgress *downloadProgress) {
        // 下载进度

    } destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
       NSURL *path =  [NSURL fileURLWithPath:JKSandBoxPathTemp];
        return [path URLByAppendingPathComponent:[NSString stringWithFormat:@"%@",fileName]];

    } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
        [JKSandBoxManager moveFileFrom:filePath.path to:targetFilePath];
        NSLog(@"targetpath %@",targetFilePath);
        NSData *data = [NSData dataWithContentsOfFile:targetFilePath];
        [self sendResponseWithData:data mimeType:[self getMimeTypeWithFilePath:targetFilePath]];
    }];

    // 4. 开启下载任务
    [downloadTask resume];

}

- (NSString *)getMimeTypeWithFilePath:(NSString *)filePath{
    CFStringRef pathExtension = (__bridge_retained CFStringRef)[filePath pathExtension];
    CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, NULL);
    CFRelease(pathExtension);

    //The UTI can be converted to a mime type:
    NSString *mimeType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);
    if (type != NULL)
        CFRelease(type);

    return mimeType;
}

+ (NSArray *)resourceTypes{
    return @[@"png", @"jpeg", @"gif", @"jpg",@"jpg",@"json", @"js", @"css",@"mp3",@"fnt"];
}

@end

其中,这里对资源文件的下载没有使用NSURLConnection,主要是NSURLConnection在iOS 9 以后就被废弃掉了。我这里用了AFnetworking进行处理。

处理资源文件失效

  对着小程序或者小游戏的更新。某些资源文件会失效,如果不及时清除的话,就会非常的占用资源。针对这种情况,我们可以让用户主动删除相关的资源文件,也可以给资源文件设置有效期,进行自动的删除操作。
demo如下:demo

更多优质文章,可以微信扫码关注:
WKWebView实现网页静态资源优先从本地加载

Tags: