Android WebView開発問題まとめ

10701 ワード

nativeとウェブページを組み合わせて開発する過程で、WebViewに関する共通の問題に直面するのは避けられない.私が現在開発している過程で出会った問題と最後に得た最適化案についてここで列挙します.ありふれた話もあれば、個人的に解決策を模索することもある.
1.HTMLのWebページのマウントを高速化
デフォルトhtmlコードがWebViewにダウンロードされると、webkitはWebページの各ノードの解析を開始し、外部スタイルファイルや外部スクリプトファイルがあることを発見すると、非同期でネットワーク要求ダウンロードファイルが開始されますが、その前にimageノードにも解析されている場合は、ネットワーク要求ダウンロード対応の画像も開始されるに違いありません.ネットワークの状況が悪い場合、ネットワーク要求が多すぎると帯域幅が緊張し、cssまたはjsファイルのロードが完了する時間に影響し、ページの空白loadingが長すぎる.解決策は、Web Viewに画像を自動的にロードしないで、ページfinishを待ってから画像ロードを開始するように伝えることです.
WebViewの初期化時に次のコードを設定します.
public void int () {
    if(Build.VERSION.SDK_INT >= 19) {
        webView.getSettings().setLoadsImagesAutomatically(true);
    } else {
        webView.getSettings().setLoadsImagesAutomatically(false);
    }
}

WebViewのWebViewClientインスタンスのonPageFinished()メソッドには、次のコードが追加されます.
@Override
public void onPageFinished(WebView view, String url) {
    if(!webView.getSettings().getLoadsImagesAutomatically()) {
        webView.getSettings().setLoadsImagesAutomatically(true);
    }
}

上記のコードから,システムAPIが19以上のバージョンで互換性があることが分かる.4.4以上のシステムがonPageFinished時にピクチャロードを再開する場合、同じsrcを参照するピクチャが複数ある場合、imageラベルが1つしかロードされないので、このようなシステムについては直接ロードします.
2.カスタムエラーインタフェース
WebViewがページをロード中にエラーが発生した場合(一般的に404 NOT FOUND)、アンドロイドWebViewはデフォルトで萌えを売るエラーインタフェースを表示します.しかし、私たちはどのようにユーザーに私が使っていたのがウェブアプリケーションであることを発見させることができますか?私たちが望んでいるのは、ユーザーがウェブ上でオリジナルのようにアプリケーションしている体験を得ることです.それでは、まずこのデフォルトのエラーページを殺すことから始めます.WebViewLoadエラーが発生した場合、WebViewClientインスタンスのonReceivedError()メソッドでエラーを受信します.
@Override
public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) {
    super.onReceivedError(view, errorCode, description, failingUrl);
    loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
    mErrorFrame.setVisibility(View.VISIBLE);
}

上記のように、loadDataWithBaseURLを使用してデフォルトのエラーページの内容を消去し、カスタマイズしたViewを表示させます(mErrorFrameはWebViewの上にあるLinearLayoutレイアウトで、デフォルトはView.GONEです).
3.スクロールバーがあるか
次のページをアップロードするような機能をする場合、ページの初期には、現在のWebViewに縦スクロールバーがあるかどうかを知る必要があります.ある場合は次のページをロードしません.ない場合は、縦スクロールバーが表示されるまで次のページをロードします.まずWebViewクラスを継承し、サブクラスに次のコードを追加します.
public boolean existVerticalScrollbar () {
    return computeVerticalScrollRange() > computeVerticalScrollExtent();
}

computeVerticalScrollRangeはスライド可能な最大高さであり、computeVerticalScrollExtentはスクロールハンドル自体の高さであり、スクロールバーが存在しない場合、両者の値は等しい.スクロールバーがある場合、前者は後者より大きいに違いありません.
4.ページ下部にスクロールしたか
同様に、次のページをアップロードするような機能を行う場合も、現在のページスクロールバーの状態を知る必要があります.最速部であれば、ネットワーク要求データのページ更新を開始します.同様にWebViewクラスを継承し、サブクラスでonScrollChangedメソッドを上書きします.具体的には、次のとおりです.
@Override
protected void onScrollChanged(int newX, int newY, int oldX, int oldY) {
    super.onScrollChanged(newX, newY, oldX, oldY);
    if (newY != oldY) {
        float contentHeight = getContentHeight() * getScale();
        //             ,                   
        if (mCurrContentHeight != contentHeight && newY > 0 && contentHeight <= newY + getHeight() + mThreshold) {
            // TODO Something...
            mCurrContentHeight = contentHeight;
        }
    }
}

上のmCurrContentHeightは、前回のトリガ時のページの高さを記録するために使用され、ページの総高さが変化せず、ターゲット領域が連続してスクロールされるとTODOが複数回トリガーされることを防止するために使用されます.mThresholdはしきい値であり、ページの下部がスクロールバーの下部からの高さの差<=この値であるとTODOがトリガーされます.
5.リモートWebページはローカルリソースにアクセスする必要がある
Webサーバから取得したコンテンツをWebViewにロードすると、assetsディレクトリの下のピクチャリソースなどのローカルリソースにアクセスできません.このような動作はドメイン間動作(Cross-Domain)に属し、WebViewは禁止されています.この問題を解決する方法は、htmlコンテンツをローカルにダウンロードし、loadDataWithBase URLを使用してhtmlをロードすることです.これでhtmlで使用できますfile:///android_asset/xxx.pngのリンクはパッケージの中のassetsの下のリソースを参照します.例は次のとおりです.
private void loadWithAccessLocal(final String htmlUrl) {
    new Thread(new Runnable() {
        public void run() {
            try {
                final String htmlStr = NetService.fetchHtml(htmlUrl);
                if (htmlStr != null) {
                    TaskExecutor.runTaskOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            loadDataWithBaseURL(htmlUrl, htmlStr, "text/html", "UTF-8", "");
                        }
                    });
                    return;
                }
            } catch (Exception e) {
                Log.e("Exception:" + e.getMessage());
            }
            TaskExecutor.runTaskOnUiThread(new Runnable() {
                @Override
                public void run() {
                    onPageLoadedError(-1, "fetch html failed");
                }
            });
        }
    }).start();
}

以下の点に注意してください.
htmlをネットワークからダウンロードするプロセスは、ワークスレッドのに配置する必要があります.
htmlのダウンロードに成功した後にhtmlをレンダリングするステップはUIのメインスレッドに置くべきで、さもなくばWebViewはを間違えます
htmlのダウンロードに失敗した場合、前述の方法を使用して、カスタムエラーインタフェースの完全なdemoプロジェクトコードを表示することができます.http://yunpan.cn/cgQPvJQxxkCBj(抽出コード:6712).
6.ViewPagerの最初の画面ではないWebViewクリックイベントは応答しません
複数のWebViewがViewPagerに1つずつロードされている場合は、このような問題に直面します.ViewPagerの最初の画面WebViewの作成はフロントで、クリックしても問題ありません.他の最初の画面ではないWebViewはバックグラウンドで作成され、スライドしてページをクリックすると次のエラーログが表示されます.
20955-20968/xx.xxx.xxx E/webcoreglue﹕ Should not happen: no rect-based-test nodes found
この問題を解決する方法はWebViewクラスを継承し、サブクラスでonTouchEventメソッドを上書きし、次のコードを入力します.
@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
    }
    return super.onTouchEvent(ev);
}

この方法の最初の提案はWebView in ViewPager not receive user inputsである.
7.WebViewハードウェアアクセラレータによるページレンダリングの点滅
4.0以上のシステムハードウェアアクセラレータをオンにすると、WebViewレンダリングページがより速くなり、ドラッグもよりスムーズになります.しかし、副作用は、WebViewビューが全体的にブロックされ、突然回復すると(例えば、SlideMenuを使用してWebViewをサイドからスライドさせると)、この遷移期間に白いブロックが同時にインタフェースが点滅することです.この問題を解決する方法は、移行期間前にWebViewのハードウェアを一時的に停止し、移行期間後に再開することです.コードは次のとおりです.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

8.addJavaScriptInterfaceによるセキュリティ問題の回避
オープンソースプロジェクトSafe Java-JS WebView Bridgeを使用すると、addJavaScriptInterfaceメソッドの代わりに、非同期コールバックなどのサポートが追加され、セキュリティ上のリスクはありません.
9.WebViewと上位の親要素のTouchMoveイベントの競合
開発の過程でこのような状況に遭遇する可能性があります.エンドでは、ViewPagerを使用して複数のWebViewページをネストし、あるWebViewのページ要素がTouchMoveイベントに応答する必要があります.ソリューションの詳細については、次の手順に従います.http://www.pedant.cn/2014/09/23/webview-touch-conflict
10.Cacheと履歴のクリーンアップ
開発の過程でこのような状況に遭遇する可能性があります.エンドでは、ViewPagerを使用して複数のWebViewページをネストし、あるWebViewのページ要素がTouchMoveイベントに応答する必要があります.ソリューションの詳細については、次の手順に従います.http://www.pedant.cn/2014/09/23/webview-touch-conflict
webView.clearCache(true); 
webView.clearHistory();

11.WebView Cookiesクリーンアップ
CookieSyncManager.createInstance(this); 
CookieSyncManager.getInstance().startSync(); 
CookieManager.getInstance().removeSessionCookie(); 

12.WebViewにおける非ハイパーリンク要求(Ajax要求など)の処理:
リクエストヘッダを追加する必要がある場合がありますが、ハイパーリンク以外のリクエストはshouldOverrindingでブロックしてwebViewを使用することはできません.loadUrl(String url,HashMap headers)メソッドによるリクエストヘッダの追加は、onWebViewResourceメソッドで対応するリクエストをブロックするなど、urlに特別なタグ/プロトコルを追加し、追加するリクエストヘッダをget形式でurlの末尾につなぐshouldInterceptRequest()メソッドでは、JSのロードなど、すべてのWebページのリソースリクエストをブロックすることができます.画像やAjaxリクエストなどEx:
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,String url) {
	//     ( Ajax)           ,    url  ,      imei    

	String ajaxUrl = url;
	//    :req=ajax
	if (url.contains("req=ajax")) {
	   ajaxUrl += "&imei=" + imei;
	}

	return super.shouldInterceptRequest(view, ajaxUrl);

}

13.WebViewページでオーディオが再生され、Activityを終了してもオーディオは再生されます
ActivityのonDestory()で呼び出す必要があります
webView.destroy();

ただし、ダイレクトコールでは次のエラーが発生する可能性があります.
10-10 15:01:11.402: E/ViewRootImpl(7502): sendUserActionEvent() mView == null
10-10 15:01:26.818: E/webview(7502): java.lang.Throwable: Error: WebView.destroy() called while still attached!
10-10 15:01:26.818: E/webview(7502): 	at android.webkit.WebViewClassic.destroy(WebViewClassic.java:4142)
10-10 15:01:26.818: E/webview(7502): 	at android.webkit.WebView.destroy(WebView.java:707)
10-10 15:01:26.818: E/webview(7502): 	at com.didi.taxi.ui.webview.OperatingWebViewActivity.onDestroy(OperatingWebViewActivity.java:236)
10-10 15:01:26.818: E/webview(7502): 	at android.app.Activity.performDestroy(Activity.java:5543)
10-10 15:01:26.818: E/webview(7502): 	at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1134)
10-10 15:01:26.818: E/webview(7502): 	at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3619)
10-10 15:01:26.818: E/webview(7502): 	at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3654)
10-10 15:01:26.818: E/webview(7502): 	at android.app.ActivityThread.access$1300(ActivityThread.java:159)
10-10 15:01:26.818: E/webview(7502): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1369)
10-10 15:01:26.818: E/webview(7502): 	at android.os.Handler.dispatchMessage(Handler.java:99)
10-10 15:01:26.818: E/webview(7502): 	at android.os.Looper.loop(Looper.java:137)
10-10 15:01:26.818: E/webview(7502): 	at android.app.ActivityThread.main(ActivityThread.java:5419)
10-10 15:01:26.818: E/webview(7502): 	at java.lang.reflect.Method.invokeNative(Native Method)
10-10 15:01:26.818: E/webview(7502): 	at java.lang.reflect.Method.invoke(Method.java:525)
10-10 15:01:26.818: E/webview(7502): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)
10-10 15:01:26.818: E/webview(7502): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
10-10 15:01:26.818: E/webview(7502): 	at dalvik.system.NativeStart.main(Native Method)

以上のように、webviewがdestoryを呼び出すと、webviewはActivityにバインドされます.これは、カスタムウェブビュー構築時にActivityのcontextオブジェクトが渡されたため、親コンテナからウェブビューを削除してから、ウェブビューを破棄する必要があります.
rootLayout.removeView(webView);
webView.destroy();

14.WebViewはカスタムメニューを長押しし、コピー共有機能を実現する
この機能はまず2つの側面から達成できます.
(1)jsで完了:
処理するselection.longTouch
ここでは、以下のようなオープンソースプロジェクトをお勧めします.
https://github.com/btate/BTAndroidWebViewSelection
(2)android層処理:
まずOnTouchListenerを使用して長押しでリスニングを実現し、WebViewのContext menuを実現し、最後にwebviewのemulateShiftHeld()を呼び出し、Androidの異なるバージョンに適した反射方式で呼び出すことが望ましい.