1. 程式人生 > >WKWebView實現網頁靜態資源優先從本地載入

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

更多優質文章,可以微信掃碼關注:
這裡寫圖片描述