• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 你真的了解WebView么

    標簽: H5  Android  WebView  iOS  JSBridge  Schema URL  

    導讀

    本文作者:陸云海(微信公眾號: 大轉轉FE)

    發布時間:2019-03-08

    原文地址:https://mp.weixin.qq.com/s/x6njiJWqN-4lOJy158_UYA

    因為我是做 Hybrid APP 開發的,所以經常與 WebView 打交道。這篇文章寫得非常好,很全面。其實已經在我的收藏夾里面待了很久了,今天特意轉發到自己的博客,分享給大家。


    WebView 是我們前端開發從PC端演進到移動端的一個重要載體,現在大家每天使用的APP,WebView 都發揮著它的重要性。接下來讓我們從 WebView 看世界。

    1. 適用場景

    提到應用場景,大家最直觀的能想到一些 APP 內嵌的頁面,為我們提供各種各樣的交互,就像下面圖片里的這樣:

    APP交互

    其實 WebView 的應用場景遠遠不止這些,其實在一些PC的軟件里,和我們交互的也是我們的html頁面,只是穿著 WebView 的衣服,衣服太美而我們沒有發現他們的真諦。

    另外,還有一些網絡機頂盒里的交互,也是 WebView 在和我們打交道,比如一些早期的IPTV里的EPG都是運行在 WebView 里的,它們基于 webkit 內核,盡管我們使用的交互方式是遙控器。

    當然,今天我們會從 native 的角度切入,帶大家認識真正的 WebView

    2. 與 APP Native 的交互

    說了這么多,其實目前使用頻率最多的,還是客戶端內嵌的 WebView,小到我們地鐵里用手機看的一篇公眾號文章,大到我們使用 APP 中的一些重要交互流程,其實都是 WebView 打開m頁去承接的。那么,到底m頁怎么和 native 去交互的呢?

    目前 JavaScript 和客戶端(后面統稱 native)交互的常見方式有兩種,一種是通過 JSBridge 的方式,另一種是通過 Schema URL 的方式。

    2.1 JSBridge

    首先,我們來說說 JSBridge。體現的形式其實就是,當我們在 native 內打開m頁,native 會在全局的 window 下,為我們注入一個 JSBridge。這個 JSBridge 里面會包含我們與 native 交互的各種方法:判斷第三方 APP 是否安裝、獲取網絡信息等等功能。

    舉個例子:

    /**
     * 作用域下的JSBridge和實例化后的getNetInfomation均根據實際約定情況而定,這里只是用來舉例說明
     */
    const bridge = window.JSBridge
    console.log(bridge.getNetInfomation())
    
    • iOS

    在 iOS 中,主要使用 WebViewJavascriptBridge 來注冊,可以參考Github WebViewJavascriptBridge

    jsBridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    
    ...
    
    [jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback
     responseCallback) {
        // to do
    }];
    
    • Android

    在Android中,需要通過 addJavascriptInterface 來注冊

    class JSBridge {
        @JavascriptInterface // 注意這里的注解。出于安全的考慮,4.2 之后強制要求,不然無法從 Javascript 中發起調用
        public void getNetInfomation() {
            // to do
        };
    }
    
    webView.addJavascriptInterface(new JSBridge(), "JSBridge");
    

    2.2 Schema URL

    如果說 JSBridge 的方式是只能在 native 內部交互,那么 Schame URL 的不僅可以在 native 內交互,也是可以跨APP來交互的。Schame URL 也是目前我們轉轉使用的主要方式,它類似一個偽協議的鏈接(也可以叫做統跳協議),比如:

    schema://path?param=abc
    

    在 WebView 里,當m頁發起 Schame URL 請求時,native 端會去進行捕獲。這里可以順帶給大家普及一下 iOS 和 Android 的知識,具體如下:

    • iOS

    UIWebView 為例,在 iOS 中,UIWebView 內發起網絡請求時,可以通過 delegate 在 native 層來攔截,然后將捕獲的 Schema URL 進行觸發對應的功能或業務邏輯,可以用shouldStartLoadWithRequest。代碼如下:

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
        //獲取scheme url后自行進行處理
        NSURL *url = [request URL];
        NSString *requestString = [[request URL] absoluteString];
        return YES;
    }
    
    • Android

    在 Android 中,可以使用 shouldoverrideurlloading 來捕獲 Schema URL。代碼如下:

    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //讀取到url后自行進行分析處理
        //這里注意:如果返回false,則WebView處理鏈接url,如果返回true,代表WebView根據程序來執行url
        return true;
    }
    

    上面分別是 iOS 和 Android 簡單的 Schema URL 捕獲代碼,可以在函數中根據自己的需求,執行對應的業務邏輯,來達到想要的功能。

    當然,剛才我們提到通過 Schema URL 的方式可以進行跨端交互,那具體如何操作呢?

    其實對于 JavaScript,在 WebView 里基本是一樣的,也是發起一個 Schema URL 的請求,只不過在 native 側會有些許變化。

    首先,給大家普及一個小知識,就是在 natvie 中(包括 iOS 和 Android),會通過 Schema URL 找到相匹配的App。其中 iOS 不可以重復,就像 appId 一樣;Android 可以重復,遇到重復情況時,會彈窗讓用戶選擇其中之一。

    那么,有了這個知識點做鋪墊,就可以理解,當我們在其他APP中,像這個 Schema URL 發起請求時,系統底層(iOS & Android)會通過 Schema URL 去找到所匹配的APP,然后將此APP拉起。拉起APP后,對應處理如下:

    • iOS

    在 iOS端內,會將 Schema URL 作為參數傳入一個提前定義好的回調函數內,然后執行該回調函數。此回調函數,可以通過得到的 Schema URL 去進行解析,然后定向到APP內的固定的某個頁面。

    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
        // 參數 url 即為獲取的 Schema URL
        // to do
    }
    
    • Android

    在 Android 端內,會稍微麻煩一些,在外部的m頁,會發起一個 Schema URL 的偽協議鏈接,系統會去根據這個 Schema URL 去檢索,需要被拉起的APP需要有一個配置文件,大致如下:

    <activity
        android:name=".activity.StartActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="zhuanzhuan"/>
        </intent-filter>
    </activity>
    

    以上面的代碼為例,在上面配置中 scheme 為 zhuanzhuan,只要是 “zhuanzhuan://” 開頭的 Schema URL 都會調起配置該 schema 的Activity(類似上面代碼的 StartActivity),此 Activity會對這個 Schema URL 做處理,例如:

    public class StartActivity extends TempBaseActivity {
        Intent intent;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            intent = getIntent();
            Uri uri = intent.getData();
        }
    }
    

    例如上面的代碼,可以在此 Activity 中,通過 intent 中的 getData 方法,獲取到傳入的Schema URL 的相關信息,如下圖:

    在這里插入圖片描述

    這也是我們在第三方APP內,可以調起我們的APP的原理。當然現在市場上一些APP,為了怕有流量流失,會對 Schema URL 進行限制,只有plist白名單里的 Schema URL 才能對應拉起,否則會被直接過濾掉。比如我們的wx爸爸,開通白名單后,才可以使用更多的 jsApiList,通過 Schema URL 的拉起就是其中之一,在此不做贅述…… :)

    3. WebView的進化

    對于 WebView,要說進化、或者蛻變,讓我第一想到的就是 iOS 的 WKWebView 了,每一個事物存在都有它的必然,讓我們一起看看這個 super 版的 WebView。

    3.1 WKWebView 的出現

    目前混合開發已然成為了主流,為了提高體驗,WKWebView 在 iOS8 發布時,也隨之一起誕生。在這之前 iOS端一直使用的是 UIWebView

    從性能方面來說,WKWebView 會比 UIWebView 高很多,可以算是一次飛躍。它采用了跨進程的方案,用 Nitro JS 解析器,高達 60fps 的刷新率。同時,提供了很好的H5頁面支持,類比 UIWebView 還多提供了一個加載進度的屬性。目前一些一線互聯網APP已經在 iOS端切換到了 WKWebView,所以感覺我們無法拒絕。

    整個 WKWebView 的初始化也很簡單,基本和 UIWebView 的很像。

    WKWebView *webView = [[WKWebView alloc] init];
    NSURL *url = [NSURL URLWithString:@"https://m.zhuanzhuan.com"];
    [webView loadRequest:[NSURLRequest requestWithURL:url]];
    

    3.2 WKWebView 與 UIWebView 的對比

    上面有提到性能的提升,為什么 APP 接入 WKWebView 之后,相對比 UIWebView 內存占用小那么多,主要是因為網頁的載入和渲染這些耗內存和性能的過程都是由 WKWebView 進程去實現的,WKWebView 是獨立于APP的進程。如下圖:

    在這里插入圖片描述

    這樣,互相進程獨立相當于把整個APP的進程對內存的占用量減少,APP進程會更為穩定。況且,即使頁面進程崩潰,體現出來的就是頁面白屏或載入失敗,不會影響到整個APP進程的崩潰。

    除了上面說的性能以外,WKWebView 會比 UIWebView 多了一個詢問過程。在服務器完成響應之后,會詢問獲取內容是否載入到容器內,在控制上會比 UIWebView 更細粒度一點,也可以在一些通信上更好的和m頁進行交互。大概流程如下圖:

    在這里插入圖片描述

    WKWebView 的代理協議為 WKNavigationDelegate,對比 UIWebDelegate 首先跳轉詢問,就是載入 URL之前的一次調用,詢問開發者是否下載并載入當前 URL,UIWebView 只有一次詢問,就是請求之前的詢問,而 WKWebView 在 URL 下載完畢之后還會發一次詢問,讓開發者根據服務器返回的 Web 內容再次做一次確定。

    3.3 任重而道遠

    前面說到 WKWebView 這么贊,其實開發中也有一些痛點。不同于 UIWebViewWKWebView 很多交互都是異步的,所以在很大程度上,在和m頁通信的時候,提高了開發成本。

    3.3.1 cookie

    首先就是 cookie 問題,這個目前我認為也是 WKWebView 在業界的一個坑。之前出現過一個問題,就是在 iOS 登陸完成后,馬上進入m頁,會有登錄態的 cookie 獲取不到的問題。這個問題在 UIWebView 中是不存在的。

    經過調研發現,主要問題是 UIWebView 對 cookie 是通過 NSHTTPCookieStorage 來統一處理的,服務端響應時寫入,然后在下次請求時,在請求頭里會帶上相應的 cookie,來做到m頁和 native 共享 cookie 的值。

    但是在 WKWebView 中,則不然。它雖然也會對 NSHTTPCookieStorage 來寫入cookie,但卻不是實時存儲的。而且從實際的測試中發現,不同的 iOS 版本,延遲的時間還不一樣,無意對m頁的開發者是一種挑戰。同樣,發起請求時,也不是實時讀取,無法做到和 native 同步,導致頁面邏輯出錯。

    針對這個問題,目前我們轉轉的解決方法是需要客戶端手動干預一下 cookie 的存儲。將服務響應的 cookie,持久化到本地,在下次 WebView 啟動時,讀取本地的 cookie 值,手動再去通過 native 往 WebView 寫入。大致流程如下圖:

    在這里插入圖片描述

    當然這也不是很完美的解決方案,因為偶爾還有spa的頁面路由切換的時候丟失 cookie 的問題。cookie 的問題還需要我們和客戶端的同學繼續去探索解決。在這里,如果大家有什么好的建議和處理方法歡迎留言,大家一起學習進步。

    3.3.2 緩存

    除了 cookie 以外,WKWebView 的緩存問題,最近我們也在關注。由于 WKWebView 內部默認使用一套緩存機制,開發者可以操作的權限會有限制,特別是 iOS 8 版本,也許是當時剛誕生 WKWebView 的緣故,還很不完善,根本沒法操作(當然相 iOS 8 很快會退出歷史舞臺)。對于一些m頁的靜態資源,偶爾會出現緩存不更新的情況,著實讓人頭疼。

    但在 iOS 9 之后,系統提供了緩存管理的接口 WKWebsiteDataStore

    // RemoveCache
    NSSet *websiteTypes = [NSSet setWithArray:@[
                                                WKWebsiteDataTypeDiskCache,
                                                WKWebsiteDataTypeMemoryCache]];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:0];
    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteTypes
                                               modifiedSince:date
                                           completionHandler:^{
    }];
    

    至于 iOS 8,就只能通過刪除文件來解決了,一般 WKWebView 的緩存數據會存儲在這個目錄里:

    ~/Library/Caches/BundleID/WebKit/
    

    可通過刪除該目錄來實現清理緩存。

    另外,以上我們說的痛點以外,還有 WebView 的通病,就是我們每次首次打開m頁時,都要有 WebView 初始化的過程,那么如何減少初始化 WebView 的時間,也是我們可以提高頁面打開速度的一個重要環節。

    當然,為了提高頁面的打開速度,咱們m頁也可以跟 native 去結合,做一些離線方案,目前轉轉內部也有一些離線頁面的項目有上線,今天就不在此展開。

    4. Android 的 WebView

    這一節是我 Fantasy 補充的

    Android 的 WebView 沒有像 iOS 那樣,分了兩個版本,雖然 Android 不斷地在升級更新,但是 WebView 依舊還是那個 WebView,比起 iOS 的 WKWebView,那可是垃圾得狠,哈哈哈。想了解更多詳細內容,可以看看這篇文章 《如何設計一個優雅健壯的Android WebView?(上)》

    5. 總結

    講到這里,我們也進入尾聲了,也許不久的將來各種新興的技術會掩蓋一些 WebView 的光環,像 react-native、小程序、安卓的輕應用開發等等,但是不可否認的是,WebView 不會輕易退出歷史舞臺,我們會把交互做的更好,我們也有情懷。哪有什么歲月靜好,只不過有人負重前行……


    如果想進一步交流和學習的同學,可以加一下QQ群哦!

    Android開發者群號:371529514

    版權聲明:本文為Fantasy_Lin_原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/Fantasy_Lin_/article/details/107288401

    智能推薦

    CSS權重,你真的懂了么

    1.什么是CSS權重? css權重決定了哪個css規則會起重要,當多個規則被應用于同一個元素時,權重高的將會起作用(權重高代表優先級高)。在日常開發過程中,大家多少都遇到過這樣的問題,寫不同的css樣式文件,結果產生相互覆蓋的問題。本文將詳細講述一下css權重規則,便于大家避坑。 2.CSS權重規則(級別由高到低) (1) !important 聲明級別最高,但同時也會被important級別覆蓋...

    你真的會寫 Markdown 么?

    1 文章簡介 本文使用 Markdown 進行排版,詳盡介紹了相關書寫規范,排版工具為 mdnice 需要注意的是,有部分編輯器自定義語法,并不完全通用所有平臺 2 通用語法 2.1 標題 在文字寫書寫不同數量的#可以完成不同的標題,如下: 一級標題 二級標題 三級標題 2.2 無序列表 無序列表的使用,在符號-后加空格使用。如下: 無序列表 1 無序列表 2 無序列表 3 如果要控制列表的層級,...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    猜你喜歡

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    精品国产乱码久久久久久蜜桃不卡