WebView 性能和用戶體驗(yàn)優(yōu)化

回顧系統(tǒng) WebView 進(jìn)化史

  • 從Android4.4系統(tǒng)開始,Chromium內(nèi)核取代了Webkit內(nèi)核。
  • 從Android5.0系統(tǒng)開始,WebView移植成了一個(gè)獨(dú)立的apk,可以不依賴系統(tǒng)而獨(dú)立存在和更新。
  • 從Android7.0 系統(tǒng)開始,如果用戶手機(jī)里安裝了 Chrome , 系統(tǒng)優(yōu)先選擇 Chrome 為應(yīng)用提供 WebView 渲染。
  • 從Android8.0系統(tǒng)開始,默認(rèn)開啟WebView多進(jìn)程模式,即WebView運(yùn)行在獨(dú)立的沙盒進(jìn)程中。

隨著技術(shù)的發(fā)展 , Google 推出了 PWA Web 形態(tài)App ,微信推出小程序 ,F(xiàn)acebook 推出 React , 前端變得越來越廣泛(復(fù)雜的前端環(huán)境) , 所以移動(dòng)端的 Web 性能變得越來越重要 , 雖然隨著 Google 不斷的對(duì) WebView 內(nèi)核升級(jí) , 性能也跟上了腳步 ,但是在移動(dòng)端還是有很多方面值得我們?nèi)?yōu)化 。

內(nèi)核初始化

第一次打開 Web 頁面 , 使用 WebView 加載頁面的時(shí)候特別慢 ,第二次打開就能明顯的感覺到速度有提升 ,為什么 ? 是因?yàn)樵谀愕谝淮渭虞d頁面的時(shí)候 WebView 內(nèi)核并沒有初始化 , 所以在第一次加載頁面的時(shí)候需要耗時(shí)去初始化 WebView 內(nèi)核 。提前初始化 WebView 內(nèi)核 ,例如如下把它放到了 Application 里面去初始化 , 在頁面里可以直接使用該 WebView

public class App extends Application {

    private WebView mWebView ;
    @Override
    public void onCreate() {
        super.onCreate();
        mWebView = new WebView(new MutableContextWrapper(this));
        
    }

}

復(fù)用 WebView

復(fù)用思想在移動(dòng)端是一種很重要的思想 , 像 ListView ,RecyclerView 復(fù)用子View 一樣 , 大大提高了性能和節(jié)儉內(nèi)存 , 如果你大量使用 WebView 那么我建議你可以考慮一下復(fù)用 WebView , 如果你的應(yīng)用只是在某些頁面使用了 WebView 那么我建議你放棄復(fù)用 WebView , 因?yàn)閺?fù)用 WebView 并不會(huì)給你帶來多大的性能提升而且會(huì)帶來一些問題 ,而且在內(nèi)存吃緊移動(dòng)端 ,內(nèi)存顯得特別珍貴 , 下面給出一些測(cè)試代碼和數(shù)據(jù)。

驗(yàn)證復(fù)用 WebView 和提前初始化 WebView 必要性

private void testWebViewInitUsedTime(){
        long p = System.currentTimeMillis();
        WebView mWebView = new WebView(this);
        long n = System.currentTimeMillis();
        Log.i("Info", "testWebViewFirstInit use time:" + (n-p));
    }
 testWebViewInitUsedTime();
 testWebViewInitUsedTime();
//測(cè)試環(huán)境 Android 7.0  三星S7
testWebViewFirstInit use time:182
testWebViewFirstInit use time:4

上面是測(cè)試 WebView 初始耗時(shí)的一些代碼 , 可以看出第一次提前初始化還是很有必要的 , 第二初始化只耗時(shí) 4 毫秒 , 也就是說一般情況創(chuàng)建一個(gè) WebView 只需要4毫秒 ,如果單純幾個(gè)頁面是復(fù)用 WebView 這種優(yōu)化意義不大 , 因?yàn)樯晕⑻幚聿煌桩?dāng)就會(huì)出現(xiàn)泄漏 。

下面給出復(fù)用 WebView 的一些關(guān)鍵代碼

public class WebPools {


    private final Queue<WebView> mWebViews;

    private Object lock = new Object();
    private static WebPools mWebPools = null;

    private static final AtomicReference<WebPools> mAtomicReference = new AtomicReference<>();
    private static final String TAG=WebPools.class.getSimpleName();

    private WebPools() {
        mWebViews = new LinkedBlockingQueue<>();
    }


    public static WebPools getInstance() {

        for (; ; ) {
            if (mWebPools != null)
                return mWebPools;
            if (mAtomicReference.compareAndSet(null, new WebPools()))
                return mWebPools=mAtomicReference.get();

        }
    }


    public void recycle(WebView webView) {
        recycleInternal(webView);
    }



    public WebView acquireWebView(Activity activity) {
        return acquireWebViewInternal(activity);
    }

    private WebView acquireWebViewInternal(Activity activity) {

        WebView mWebView = mWebViews.poll();

        LogUtils.i(TAG,"acquireWebViewInternal  webview:"+mWebView);
        if (mWebView == null) {
            synchronized (lock) {
                return new WebView(new MutableContextWrapper(activity));
            }
        } else {
            MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
            mMutableContextWrapper.setBaseContext(activity);
            return mWebView;
        }
    }



    private void recycleInternal(WebView webView) {
        try {

            if (webView.getContext() instanceof MutableContextWrapper) {

                MutableContextWrapper mContext = (MutableContextWrapper) webView.getContext();
                mContext.setBaseContext(mContext.getApplicationContext());
                LogUtils.i(TAG,"enqueue  webview:"+webView);
                mWebViews.offer(webView);
            }
            if(webView.getContext() instanceof  Activity){
//            throw new RuntimeException("leaked");
                LogUtils.i(TAG,"Abandon this webview  , It will cause leak if enqueue !");
            }

        }catch (Exception e){
            e.printStackTrace();
        }


    }

}

注意在 WebView 進(jìn)入 WebPools 之前 , 需要重置 WebView ,包括清空注入 WebView 的注入對(duì)象 , 否則非常容易泄露。

WebView 獨(dú)立進(jìn)程 , 進(jìn)程預(yù)加載 。

因?yàn)?WebView 內(nèi)存泄露 , 以及多進(jìn)程內(nèi)存拓展 , 相信有一部分開發(fā)人員會(huì)把 WebView 放在一個(gè)獨(dú)立的進(jìn)程里面 , 那么第一次加載 WebView 頁面 ,加上系統(tǒng)需要時(shí)間 Fork 出新進(jìn)程 , 那么加載變得更慢了 , 因?yàn)檫M(jìn)程的創(chuàng)建也是一件耗時(shí)的事情 , 所謂的預(yù)加載進(jìn)程 , 就是提前把進(jìn)程創(chuàng)建出來 , 提升加載速度 ,大致的做法如下

        <service
            android:name=".PreWebService"
            android:process=":web"/>
        <activity
            android:name=".WebActivity"
            android:process=":web"
            />

其實(shí)不一定要 Service , 啟動(dòng)「web」 進(jìn)程 Broadcast 廣播也是可以的 , 提前在進(jìn)入 WebView 頁面之前 , 先啟動(dòng) PreWebService 把 「web」 進(jìn)程創(chuàng)建了 ,當(dāng)系統(tǒng)在啟動(dòng) WebActivity 的時(shí)候 , 系統(tǒng)發(fā)現(xiàn)了 「web」 進(jìn)程已經(jīng)創(chuàng)建存在了 , 系統(tǒng)就不需要耗費(fèi)時(shí)間 Fork 出新的「web」進(jìn)程了。

提前顯示進(jìn)度條

提前顯示進(jìn)度條不是提升性能 , 但是對(duì)用戶體驗(yàn)來說也是很重要的一點(diǎn) , WebView.loadUrl("url") 不會(huì)立馬就回調(diào) onPageStarted 或者 onProgressChanged 因?yàn)樵谶@一時(shí)間段 , WebView 有可能在初始化內(nèi)核 , 也有可能在與服務(wù)器建立連接 , 這個(gè)時(shí)間段容易出現(xiàn)白屏 , 白屏用戶體驗(yàn)是很糟糕的 , 所以我建議

private void go(String url) {
        this.mWebView.loadUrl(url); 
        this.mIndicator.show() //顯示進(jìn)度條
}

loadUrl 之后立馬就把進(jìn)度條顯示出來 , 給用戶一個(gè)明顯視覺 。

開啟軟硬件加速

開啟軟硬件加速這個(gè)性能提升還是很明顯的,但是會(huì)耗費(fèi)更大的內(nèi)存 。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

總結(jié)

上面提出這些性能優(yōu)化都不是那么完美無缺的,基本都會(huì)帶來一部分系統(tǒng)資源的消耗 , 比如在 Application 里面提前初始化WebView , 雖然提升了 WebView 頁面的啟動(dòng)速度, 但是缺拖慢了 App 的冷啟動(dòng)速度 ,獨(dú)立進(jìn)程和開啟軟硬件加速也都會(huì)帶來內(nèi)存更大的開銷 ,所以凡事都是存在利和弊,至于在項(xiàng)目中利與弊怎么權(quán)衡,都是需要根據(jù)用戶需求和各種因素來量度的。

最后

留下一個(gè)基于 WebView 的強(qiáng)大庫的傳送門 GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容