WKWebView實現網頁靜態資源優先從本地載入
前言:最近微信的小遊戲跳一跳特別的火,順便也讓h5小遊戲更加的火熱。另外微信小程式,以及支付寶的小程式都是用H5寫的。無論是小遊戲還是小程式,這些都需要載入更多的資原始檔,處理更多的業務。這些都對網頁載入的速度提出了較高的要求。UIWebView由於佔用記憶體大,釋放不掉一直備受詬病。而且目前是大多數的app支援的最低版本都是從iOS 8開始的。我這裡主要針對WKWebView來說一下。
資源包壓縮下載VS靜態資原始檔下載
根據不同的業務需求,不同的app對於資原始檔的處理情形是不同的。以12306app為例。選擇了下載資源壓縮到沙盒的策略,列車班次發生調整時,呼叫介面,強制下載資源壓縮包到本地。註釋:
網路請求的攔截
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
更多優質文章,可以微信掃碼關注: