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

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

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

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

內(nèi)核初始化

第一次打開(kāi) Web 頁(yè)面 , 使用 WebView 加載頁(yè)面的時(shí)候特別慢 贞岭,第二次打開(kāi)就能明顯的感覺(jué)到速度有提升 八毯,為什么 ? 是因?yàn)樵谀愕谝淮渭虞d頁(yè)面的時(shí)候 WebView 內(nèi)核并沒(méi)有初始化 瞄桨, 所以在第一次加載頁(yè)面的時(shí)候需要耗時(shí)去初始化 WebView 內(nèi)核 话速。提前初始化 WebView 內(nèi)核 ,例如如下把它放到了 Application 里面去初始化 , 在頁(yè)面里可以直接使用該 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)用只是在某些頁(yè)面使用了 WebView 那么我建議你放棄復(fù)用 WebView , 因?yàn)閺?fù)用 WebView 并不會(huì)給你帶來(lái)多大的性能提升而且會(huì)帶來(lái)一些問(wèn)題 物赶,而且在內(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 毫秒 , 也就是說(shuō)一般情況創(chuàng)建一個(gè) WebView 只需要4毫秒 错维,如果單純幾個(gè)頁(yè)面是復(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)存拓展 捂襟, 相信有一部分開(kāi)發(fā)人員會(huì)把 WebView 放在一個(gè)獨(dú)立的進(jìn)程里面 咬腕, 那么第一次加載 WebView 頁(yè)面 ,加上系統(tǒng)需要時(shí)間 Fork 出新進(jìn)程 葬荷, 那么加載變得更慢了 涨共, 因?yàn)檫M(jìn)程的創(chuàng)建也是一件耗時(shí)的事情 , 所謂的預(yù)加載進(jìn)程 宠漩, 就是提前把進(jìn)程創(chuàng)建出來(lái) 煞赢, 提升加載速度 ,大致的做法如下

        <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 頁(yè)面之前 , 先啟動(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)來(lái)說(shuō)也是很重要的一點(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)度條顯示出來(lái) 呐能, 給用戶一個(gè)明顯視覺(jué) 膛虫。

開(kāi)啟軟硬件加速

開(kāi)啟軟硬件加速這個(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)化都不是那么完美無(wú)缺的裆针,基本都會(huì)帶來(lái)一部分系統(tǒng)資源的消耗 刨摩, 比如在 Application 里面提前初始化WebView 寺晌, 雖然提升了 WebView 頁(yè)面的啟動(dòng)速度, 但是缺拖慢了 App 的冷啟動(dòng)速度 澡刹,獨(dú)立進(jìn)程和開(kāi)啟軟硬件加速也都會(huì)帶來(lái)內(nèi)存更大的開(kāi)銷 呻征,所以凡事都是存在利和弊,至于在項(xiàng)目中利與弊怎么權(quán)衡罢浇,都是需要根據(jù)用戶需求和各種因素來(lái)量度的陆赋。

最后

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末己莺,一起剝皮案震驚了整個(gè)濱河市奏甫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凌受,老刑警劉巖阵子,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異胜蛉,居然都是意外死亡挠进,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)誊册,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)领突,“玉大人,你說(shuō)我怎么就攤上這事案怯【” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵嘲碱,是天一觀的道長(zhǎng)金砍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)麦锯,這世上最難降的妖魔是什么恕稠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮扶欣,結(jié)果婚禮上鹅巍,老公的妹妹穿的比我還像新娘。我一直安慰自己料祠,他們只是感情好骆捧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著髓绽,像睡著了一般凑懂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梧宫,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天接谨,我揣著相機(jī)與錄音摆碉,去河邊找鬼。 笑死脓豪,一個(gè)胖子當(dāng)著我的面吹牛巷帝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扫夜,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼楞泼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了笤闯?” 一聲冷哼從身側(cè)響起堕阔,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颗味,沒(méi)想到半個(gè)月后超陆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浦马,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年时呀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晶默。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谨娜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磺陡,到底是詐尸還是另有隱情趴梢,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布币他,位于F島的核電站坞靶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圆丹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一躯喇、第九天 我趴在偏房一處隱蔽的房頂上張望辫封。 院中可真熱鬧,春花似錦廉丽、人聲如沸倦微。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)欣福。三九已至,卻和暖如春焦履,著一層夾襖步出監(jiān)牢的瞬間拓劝,已是汗流浹背雏逾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郑临,地道東北人栖博。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像厢洞,于是被迫代替她去往敵國(guó)和親仇让。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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