Android WebView の開発 (3): WebView のパフォーマンスの最適化

1. Android WebView の開発 (1): 基本的なアプリケーション
2. Android WebView の開発 (2): WebView とネイティブの相互作用
3. Android WebView の開発 (3): WebView のパフォーマンスの最適化
4. Android WebView の開発 (4): WebView の独立したプロセス ソリューション
5 、Android WebView の開発 (5): カスタム WebView ツールバー


添付の GitHub ソース コード: WebViewExplore


WebView パフォーマンス最適化ソリューション:

1. WebView 事前初期化:

WebView のパフォーマンスの損失を減らすために、適切なタイミングで事前に WebView を作成し、キャッシュ プールに格納することができます.ページがコンテンツを表示する必要がある場合、作成された WebView をキャッシュ プールから直接取得できます.パフォーマンス データ、WebView の事前作成で削減できる最初の画面のレンダリング時間は 200 ミリ秒以上です。

画像

例としてニュース ランディング ページを取り上げます.ユーザーがニュース リスト ページに入ると、最初の WebView が作成されます.ユーザーがニュース ランディング ページに入ると、H5 ページをレンダリングするためにキャッシュ プールから取得されます.ページの読み込み速度に影響を与えないように, 同時に, 次回もランディング ページ キャッシュ プールに利用可能な WebView コンポーネントが残っていることを確認します. ページが読み込まれるたびに、WebView を事前に作成するロジックをトリガーします ( pageFinish) またはランディング ページから戻る。

WebView の初期化はコンテキストにバインドする必要があるため、事前に作成されたロジックを実装する場合は、コンテキストの一貫性を確保する必要があります. 従来のアプローチとして、フラグメントを使用して、 H5 ページ、コンテキストが外部アクティビティ インスタンスを使用できるようにしますが、フラグメントの切り替え自体のスムーズさに問題があり、WebView の事前作成に適用できるシナリオが制限されます。この目的のために、より完全な代替手段、つまりMutableContextWrapper [ baseContext の外部変更を可能にする新しいコンテキスト ラッパー クラス、および ContextWrapper によって呼び出されるすべてのメソッドが実行のために baseContext にプロキシされる]を見つけました。

WebView を事前に作成するコードは次のとおりです。


/**
     * 创建WebView实例
     * 用了applicationContext
     */
    @DebugTrace
    public void prepareNewWebView() {
        if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) {
            mCachedWebViewStack.push(new WebView(new MutableContextWrapper(getAppContext())));
        }
    }
    /**
     * 从缓存池中获取合适的WebView
     * 
     * @param context activity context
     * @return WebView
     */
    private WebView acquireWebViewInternal(Context context) {
        // 为空,直接返回新实例
        if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
            return new WebView(context);
        }
        WebView webView = mCachedWebViewStack.pop();
        // webView不为空,则开始使用预创建的WebView,并且替换Context
        MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
        contextWrapper.setBaseContext(context);
        return webView;
    }

2. 関連するネイティブ コンポーネントの遅延読み込み:

WebView が初期化された後、フレームワークの onCreate または OnResume が終了するのを待たずに、すぐに loadUrl を実行できます。さらに、WebView の最初の完了からページのホームページの描画が完了するまでの間、UI スレッドでの他の操作は最小限に抑える必要があります。UI スレッドがビジー状態になると、WebView.loadUrl の速度が低下します。

ニュース ランディング ページのシナリオに固有のものですが、ランディング ページはWebView + ネイティブ コメント コンポーネントの 2 つの部分で構成されているため、通常のプロセスでは、WebView の初期化が完了した後に、コメント コンポーネントの初期化とコメント データの取得が開始されます。この時点では、コメントの初期化はまだ onCreate の UI メッセージ処理にあるため、メイン ドキュメントをロードするカーネルのロジックが大幅に遅延します。ユーザーがランディング・ページに入ったときにコメント・コンポーネントがユーザーに表示されないことを考慮して、コメント・コンポーネントの初期化は、ページの pageFinishまたは最初の画面のレンダリングが完了するまで遅延されます。

3. オフライン キャッシュ:

WebSettings settings = webView.getSettings();
settings.setAppCacheEnabled(true);
settings.setDatabaseEnabled(true);
settings.setDomStorageEnabled(true);//开启DOM缓存,关闭的话H5自身的一些操作是无效的
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setJavaScriptEnabled(true);

ここでは、setCacheMode メソッドを介して WebView のキャッシュ ポリシーを設定します。WebSettings.LOAD_DEFAULT は、デフォルトのキャッシュ ポリシーです。キャッシュが利用可能で有効期限が切れていない場合はキャッシュをロードし、それ以外の場合は、ネットワーク経由でリソースを取得します。このように、ページのネットワーク リクエストの数を減らすことができるので、どうすればページをオフラインで開くことができますか? ここでは、ページの読み込み時にネットワークの状態を判断することで、ネットワークなしで webview のキャッシュ戦略を変更できます。

ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if(info.isAvailable())
{
    settings.setCacheMode(WebSettings.LOAD_DEFAULT);
}else 
{
    settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);//不使用网络,只加载缓存
}

このようにして、ハイブリッドアプリケーションにネットワークなしで機能の一部を使用させることができるので、何も表示されません. もちろん、キャッシュを改善すれば、ネットワークが良好な場合、たとえば WIFI 状態で, バックグラウンドでいくつかのウェブページを読み込んでキャッシュする. これを行うと, ネットワークなしで最初にいくつかのページを開いたときでも、ページを表示することができます.
もちろん、リソースをキャッシュした後に問題が発生します。つまり、リソースを時間内に更新できず、WebSettings.LOAD_DEFAULT 内のページにキャッシュされたバージョンはあまり効果的ではないようですので、キャッシュ バージョンを実行する必要があるかもしれません。自分でコントロールこのキャッシュ バージョン コントロールは、APP バージョン アップデートに配置できます。

if (upgrade.cacheControl > cacheControl)
{
    webView.clearCache(true);//删除DOM缓存
    VersionUtils.clearCache(mContext.getCacheDir());//删除APP缓存
    try
    {
        mContext.deleteDatabase("webview.db");//删除数据库缓存
        mContext.deleteDatabase("webviewCache.db");
    }
    catch (Exception e)
    {
    }
}

4. リソースの事前 (非同期) ロード:

1. shouldInterceptRequest (pre)による非同期ロード:

ページには多くのリソース、画像、CSS、および js が含まれている場合があり、JQuery のような巨大なものも参照されます. ロードからページのレンダリングまでに長い時間がかかります. 解決策は、特定の戦略とタイミングに従うことです. リクエスト部分事前に CDN からローカルにランディング ページの html キャッシュを取得するか、html、css、js、画像などのリソースをクライアントでローカルに事前設定し、フロントエンドのバージョン管理と増分更新戦略についてサーバーと交渉することができます。 、そのため、Webview にアクセスしたら、最初にローカル キャッシュ ページ リソースをすばやく読み込み、読み込みが完了したら WebView に戻ることができます。あとは、更新が必要な増分リソースをプルするだけです。これにより、読み込み速度が向上し、サーバーの負荷が軽減されます。[子スレッドで実行] WebClient クラスの shouldInterceptRequest メソッドを書き換え 、このクラスを WebView に設定します。

        /**
         * 【实现预加载】
         * 有时候一个页面资源比较多,图片,CSS,js比较多,还引用了JQuery这种庞然巨兽,
         * 从加载到页面渲染完成需要比较长的时间,有一个解决方案是将这些资源打包进APK里面,
         * 然后当页面加载这些资源的时候让它从本地获取,这样可以提升加载速度也能减少服务器压力。
         */
        @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            if (request == null) {
                return null;
            }
            String url = request.getUrl().toString();
            Log.d(TAG, "shouldInterceptRequest---> " + url);
            return getWebResourceResponse(url);
        }

        protected WebResourceResponse getWebResourceResponse(String url) {
            //此处[tag]等需要跟服务端协商好,再处理
            if (url.contains("[tag]")) {
                try {
                    String localPath = url.replaceFirst("^http.*[tag]\\]", "");
                    InputStream is = getContext().getAssets().open(localPath);
                    Log.d(TAG, "shouldInterceptRequest: localPath " + localPath);
                    String mimeType = "text/javascript";
                    if (localPath.endsWith("css")) {
                        mimeType = "text/css";
                    }
                    return new WebResourceResponse(mimeType, "UTF-8", is);
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            } else {
                return null;
            }
        }

一般的に使用される画像の読み込みを例にとると、このソリューションは、画像のレンダリング速度を満たしながら、クライアントとフロントエンドのコードを分離します. クライアントはサーバーとして機能し、画像をリクエストしてキャッシュし、フロントエンドとクライアントが確実に共有できるようにします.画像キャッシュ。

2. H5 ページ プルの最適化:

プルする必要があるこれらの増分リソースについては、webpack+gzip データ圧縮と CDN アクセラレーションを実行して、プル速度を上げることができます。また、ネットワーク接続を確立するときに、フロント エンドによって要求されたドメイン名をクライアント API インターフェイスのドメイン名と一致させることで、DNS 解決時間を短縮できます
最後に、H5 ページでは、画像リソースのプルが最も時間がかかります. より良い解決策は、画像以外のコンテンツを最初に読み込んで表示し、これらの画像の読み込みを遅らせて、ユーザー エクスペリエンスを向上させることです. WebView には、画像の読み込みをブロックするために使用される setBlockNetworkImage(boolean) メソッドがあります。このメソッドを使用して、画像の遅延読み込みを実装できます。onPageStarted のときに画像の読み込みをブロックし、onPageFinished のときに画像の読み込みを有効にします。

5. メモリ リーク:

まず、WebView のメモリ リークの問題については、AS Profiler を使用して以下を検出できます。

Android はどのようにプロファイラーを使用してメモリ リークをチェックしていますか [ここで注意すべきことは、ガベージ コレクション ボタンをクリックするときに、ガベージ コレクション ボタンをクリックするときにさらに数回クリックする必要があることです。そうしないと、実行できなくなる可能性があります。通常のガベージコレクション]。

また、これまで何度も WebView のメモリ リークを検出しようと試みてきましたが、「残念ながら」メモリ リークは発生しないことがわかりました。複数の電話を試しましたが、何も起こりませんでした。あとで、関連記事を調べてみたら [ Android-WebView でまだメモリリークが起きているのか? ]、WebView のメモリ リークは、実際には初期のカーネル バージョンの単なる脆弱性であり、既に修正されていることを知りました。したがって、5.0 以降を含むほとんどのデバイスではいわゆるメモリ リークは発生せず、低バージョンの Android システム (Android 5 以降) の Chromium カーネルは一般的に古すぎないため、メモリ リークの可能性は比較的小さいはずです。モデルのこの小さな部分との互換性が必要な場合は、次の 2 つの方法を使用できます。

1. xml レイアウト ファイルで webview コントロールを直接ネストすることは避けますが、addview メソッドを使用して新しい webview を作成し、それをレイアウトにロードします。コンテキスト変数は applicationContext を使用します。

webView = new WebView(getApplicationContext());
webView.getSettings().setJavaScriptEnabled(true);
framelayout.addView(webView);
webView.loadUrl(url);

2. アクティビティのライフ サイクルが終了するときにリソースを破棄/解放します。

webview によるメモリ リークは、主に org.chromium.android_webview.AwContents クラスにコンポーネント コールバックを登録することによって発生しますが、正常に登録を解除することはできません。


org.chromium.android_webview.AwContents クラスには、onAttachedToWindow と onDetachedFromWindow の 2 つのメソッドがあります。システムは、onDetachedFromWindow() メソッドの最初の行で、アタッチおよびデタッチ時にコンポーネント コールバックを登録および登録解除します。

if (isDestroyed()) return;, 

isDestroyed() が true を返した場合、後続のロジックは正常に実行できないため、登録解除操作は実行されません; アクティビティが終了すると、アクティブに WebView.destroy() メソッドが呼び出され、isDestroyed() が true を返します。 destroy() の実行時間は onDetachedFromWindow より前なので、unregister() は正常に実行できません。
次に、解決策は次のとおりです。onDetachedFromWindow を最初に実行し、destroy() を積極的に呼び出す前に、その親から webview を削除します。

ViewParent parent = mWebView.getParent();
if (parent != null) {
    ((ViewGroup) parent).removeView(mWebView);
}

mWebView.destroy();

完全なアクティビティの onDestroy() メソッド: など:

    @Override
    protected void onDestroy() {
        if (mWebView != null) {
            mWebView.destroy();

            ViewParent parent = mWebView.getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(mWebView);
            }
            mWebView.stopLoading();
            // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
            mWebView.getSettings().setJavaScriptEnabled(false);
            mWebView.clearHistory();
            mWebView.clearView();
            mWebView.removeAllViews();
            mWebView.destroy();
        }
        super.onDestroy();
    }

3. WebView の独立したプロセス ソリューション:

WebView のメモリ リークと OOM を解決するもう 1 つの解決策は、独立したプロセスを開くことで実現することです。

WebView 独立プロセス ソリューション

参考:

WebView のメモリ リーク - 解決策の概要 - ショート ブック

Toutiao の品質最適化 - グラフィック詳細ページを数秒で開く方法

WebView のパフォーマンス、エクスペリエンスの分析と最適化 - Meituan テクニカル チーム

Baidu APP-Android H5 ファースト スクリーン最適化の実践

おすすめ

転載: blog.csdn.net/u012440207/article/details/121781463
おすすめ