優(yōu)化 WebView 的加載速度實(shí)例

問(wèn)題

WebView 在 App 中承載著網(wǎng)頁(yè)加載的功能,所以對(duì)于一些內(nèi)容的展示占據(jù)著很重要的地位,在進(jìn)行加載網(wǎng)頁(yè)的時(shí)候如果直接進(jìn)行內(nèi)容的加載,會(huì)發(fā)現(xiàn)網(wǎng)頁(yè)加載速度有點(diǎn)讓人不是很滿(mǎn)意,尤其是一些內(nèi)容較為豐富的頁(yè)面,加載速度就變得讓人著急了。

優(yōu)化方案

對(duì)UIWebView稍有了解的人都會(huì)知道,它的加載機(jī)制如下:


web 加載過(guò)程

由于在初始化以及展現(xiàn)的過(guò)程不是我們所能控制的,優(yōu)化的地方也就集中在了白屏與 loading 的過(guò)程中了。我們知道 webView 在進(jìn)行 request 的時(shí)候是去加載一個(gè) URL 鏈接,通過(guò)鏈接進(jìn)行頁(yè)面的下載,頁(yè)面加載的同時(shí)去加載一些樣式表以及 js 相關(guān)的腳本,最后渲染界面進(jìn)而進(jìn)行網(wǎng)頁(yè)的展示。

考慮到中間的白屏階段主要集中在頁(yè)面的鏈接、以及一些相關(guān)樣式的加載中,所以我們可以在這塊想辦法進(jìn)行優(yōu)化。一般一個(gè)頁(yè)面的樣式、js 腳本的內(nèi)容都是固定的。每次去瀏覽網(wǎng)頁(yè)內(nèi)容的時(shí)候,實(shí)際上是網(wǎng)頁(yè)的正文內(nèi)容的變化,這些樣式以及 js 腳本不會(huì)隨之改變,所以鑒于此,就有了一種方案:

可以考慮去把某個(gè)頁(yè)面的相關(guān)的 css 樣式以及js腳本在加載該頁(yè)面前緩存到本地,在加載的時(shí)候直接去加載緩存,而網(wǎng)頁(yè)的正文內(nèi)容進(jìn)行單獨(dú)的網(wǎng)絡(luò)請(qǐng)求進(jìn)而達(dá)到加載速度上的優(yōu)化

而這個(gè)思路的簡(jiǎn)言之就是通過(guò)本地模板緩存機(jī)制進(jìn)行加載速度優(yōu)化,省去了網(wǎng)絡(luò)獲取這些固定文件的時(shí)間。

實(shí)現(xiàn)

整個(gè)原理的實(shí)現(xiàn)流程可以通過(guò)下面的過(guò)程進(jìn)行展示

緩存與加載過(guò)程

因?yàn)榧虞d本地的模板只是將網(wǎng)頁(yè)的樣式以及相關(guān)的 js 腳本加載上了,正文內(nèi)容還需要單獨(dú)去請(qǐng)求,這里有兩種方案去實(shí)現(xiàn)網(wǎng)頁(yè)正文的請(qǐng)求:

方案一:通過(guò)原生接口去實(shí)現(xiàn)正文的內(nèi)容的請(qǐng)求,這里需要 js 與本地 native 的相關(guān)調(diào)用,需要與后臺(tái)配合完成(推薦方案)
方案二:通過(guò)js 腳本直接去請(qǐng)求正文內(nèi)容,不需要 navtive 去請(qǐng)求數(shù)據(jù),native 只需要加載頁(yè)面的 html 既可

因?yàn)樵涌谡?qǐng)求速度要比 js 腳本去線(xiàn)上那數(shù)據(jù)要快,所以可以通過(guò) js 與本地代碼相互調(diào)用的方式去獲取網(wǎng)頁(yè)正文內(nèi)容。

編碼

為了最大化提升網(wǎng)頁(yè)的加載速度,這里我選擇了方案一來(lái)進(jìn)行優(yōu)化。
由于內(nèi)容的獲取放在了 App 中進(jìn)行,所以為了保證在 js 調(diào)用本地內(nèi)容請(qǐng)求的方法之前,我們需要將js 與本地的交互的對(duì)象注入到 js 中,以便加載的時(shí)候能夠調(diào)起本地的內(nèi)容請(qǐng)求方法。

  • 創(chuàng)建 js 與App 本地交互的對(duì)象
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol JSExportDelegate<JSExport>

- (void)requestData;

@end

@interface LCJSExportApi : NSObject<JSExportDelegate>

@property(nonatomic,weak) JSContext *context;

- (void)requestData;

@end

#import "LCJSExportApi.h"

@implementation LCJSExportApi

- (void)requestData{
    
    NSLog(@"網(wǎng)絡(luò)請(qǐng)求");
    
    //保存當(dāng)前的線(xiàn)程
    NSThread *currentThread = [NSThread currentThread];
    //模擬網(wǎng)絡(luò)請(qǐng)求
    dispatch_async(dispatch_get_main_queue(), ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
           
            NSString *path = [[NSBundle mainBundle] pathForResource:@"response" ofType:@"json"];
            NSData *data = [NSData dataWithContentsOfFile:path];
            
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            NSDictionary *dataDict = dict[@"data"];
            NSString *title = dataDict[@"title"];
            NSString *content = dataDict[@"content"];
            
            //由于網(wǎng)絡(luò)請(qǐng)求是異步請(qǐng)求,在獲取到數(shù)據(jù)之后放在之前的線(xiàn)程中進(jìn)行數(shù)據(jù)的回傳
            [self performSelector:@selector(transToRespnose:) onThread:currentThread withObject:@[title,content] waitUntilDone:NO];
            
        });
       
    });

}

- (void)transToRespnose:(NSArray *)array{
    [self.context[@"returnData"] callWithArguments:array];
};

@end
  • 這里需要說(shuō)明一下,在創(chuàng)建交互對(duì)象的時(shí)候我們需要同時(shí)去寫(xiě)一個(gè)管理 js 與本地對(duì)象交互的協(xié)議,這個(gè)協(xié)議繼承自 JSExport,對(duì)于 js 中需要調(diào)用的方法需要在此協(xié)議中進(jìn)行注冊(cè),這樣才能保證 js方法到本地的映射。
  • 屬性context 是用來(lái)保存當(dāng)前 webView 的上下文的,為了方便在網(wǎng)絡(luò)請(qǐng)求之后回傳數(shù)據(jù),這里需要通過(guò)上下文 context 去調(diào)用 js 中的方法進(jìn)而完成數(shù)據(jù)的回傳
  • 這里模仿了網(wǎng)絡(luò)的異步請(qǐng)求,因?yàn)槭钱惒骄€(xiàn)程操作,所以在最后獲取完數(shù)據(jù)之后要返回到當(dāng)前的線(xiàn)程中去執(zhí)行js 數(shù)據(jù)的回傳操作,否則會(huì)造成界面線(xiàn)程的卡死,所以在進(jìn)行網(wǎng)絡(luò)請(qǐng)求之前保存當(dāng)前的線(xiàn)程,然后在當(dāng)前線(xiàn)程上去回傳數(shù)據(jù)(坑點(diǎn))

其實(shí) js 的注入分兩種,另一種是直接將本地的代碼注入到 js 中,如下:

self.jsContext[@"fastConnect.request"] = ^(){
        NSLog(@"網(wǎng)絡(luò)請(qǐng)求");
    };

這種方式是通過(guò)找到 js 中的 fastConnect.request 方法,然后通過(guò) block來(lái)響應(yīng)對(duì)應(yīng)的調(diào)用,前提是這些方法的映射是在當(dāng)前網(wǎng)頁(yè)已存在 context的基礎(chǔ)上。如果當(dāng)前網(wǎng)頁(yè)沒(méi)有 context,那么這些映射是無(wú)效的。而網(wǎng)絡(luò)的請(qǐng)求的注入需要加在 js 的加載之前,否則在加載 js 的時(shí)候會(huì)因?yàn)闆](méi)有注入方法而導(dǎo)致方法調(diào)用失敗進(jìn)而出現(xiàn)問(wèn)題。所以這里采用了注入對(duì)象的方法,在加載之前就已經(jīng)將相關(guān)的代碼注入到j(luò)s 中,從而達(dá)到調(diào)用本地內(nèi)容請(qǐng)求的目的。

  • 創(chuàng)建 webView 注入 js 交互對(duì)象

@interface LCCacheTempateVC ()

@property (nonatomic,strong) UIWebView *webView;

@property (nonatomic,strong) JSContext *jsContext;

@end

@implementation LCCacheTempateVC

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self creatView];
}

- (void)creatView{
    self.view.backgroundColor = [UIColor whiteColor];
    
    _webView = [UIWebView new];
    _webView.frame = self.view.bounds;
    [self fillJsExportMethod];
   
    [self.view addSubview:self.webView];
   
    NSString *path = [[NSBundle mainBundle] pathForResource:@"news" ofType:@"html"];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];
    [self.webView loadRequest:request];

}

- (void)fillJsExportMethod{
    
    //獲取該UIWebview的javascript上下文
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    LCJSExportApi *JsObjct = [LCJSExportApi new];
    JsObjct.context = self.jsContext;
    
    [self.jsContext setObject:JsObjct forKeyedSubscript:@"fastConnect"];
   
}

為了避免 jsContext 強(qiáng)引用導(dǎo)致引用問(wèn)題的發(fā)生,注意在管理JS對(duì)象中將其屬性設(shè)置為 weak。

經(jīng)測(cè)試,如果將網(wǎng)頁(yè)的基本構(gòu)架(模板)緩存到本地,再去加載復(fù)雜網(wǎng)頁(yè)的時(shí)候,有著明顯的速度提升,為此,設(shè)計(jì)一個(gè)適用于自己項(xiàng)目的模板的通用模塊是提升用戶(hù)瀏覽網(wǎng)頁(yè)體驗(yàn)的絕佳選擇,所以,了解一下?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容