Android WebView開發常見的坑
現在的App基本上都會使用Native+H5的方式來開發的,例如網易新聞詳情頁面,微信公號詳情頁面都會使用WebView開發。這樣可以很容易實現圖文排版的需求,而且混合開發的好處也是顯而易見的。
AC在開發項目的時候也經常使用WebView這個控件,這個控件使用很方便,但卻也有諸多問題。以下是AC在開發過程中踩過的坑,希望對使用這個控件的小伙伴們有用。
1、WebView無法顯示html中的alert和confirm對話框
WebView要顯示html中的alert和confirm對話框,需要實現WebViewChromClient接口。WebView只是一個承載體,各種內容的渲染需要使用WebviewChromClient去實現,所以set一個默認的基類WebChromeClient就行
mWebView.setWebChromeClient(new WebChromeClient());
用於彈起alert等,如果要定制alert,confirm對話框就必需重寫onAlert和onConfirm方法
2、WebView中實現的JS方法無法調用
在實現WebView與JS交互的過程中,如果遇到點擊后JS方法無響應,應該注意以下問題:
(1)WebView.addJavascriptInterface(new AndroidClick(), "app");這個方法的別名android是否與JS中的對象名稱一致如
<div style="text-align:center;"><a onclick="window.app.onclick('www.115.com')" >內容已自動優化閱讀,點擊查看原文</a></div>
(2)如果是H5通過alert方法來提示對話框的信息的時候,WebView需要實現注冊這個回調函數
mWebView.setWebChromeClient(new CustomWebChromeClient());
並實現以下alert回調方法,並可以實現自定義的對話框樣式。
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
//return super.onJsAlert(view, url, message, result);
showConfirmDialog(view.getContext(), message, result, false);
return true;
}
類似的還有onJsConfirm方法等。
(3)如果發布的APP有進行混淆,那么AndroidClick這個JS 與 JAVA交互的類需要proguard.cfg文件忽略這個類的混淆,否則混淆后JS將執行不了。
-keepclassmembers class net.angrycode.js2java.AndroidClick{ *; }
(4)JS調用Native方法時,如果前端執行一些比較耗時的操作,前端代碼就有可能會跑在線程里,這時候如果JS方法調用Native方法做一些邏輯操作,調用就會有問題,雖然不會Crash但是會報錯。參考解決方法是(mJSInterface是通過addJavaInterface注入到WebView中的對象)
/**
* 提交創建申請單數據
*/
mJSInterface.setOnPutApplyListener(json -> {
mWebView.post(() -> {
//確保是在主線程中訪問Native相關控件
});
});
3、快速打開和關閉WebView頁面發生了控件空指針異常問題
這個問題可能有很多原因,但WebView加載過程中如果關閉了頁面控件被回收而加載線程還在繼續跑,那么數據返回時頁面就有可能發生空指針異常。這個時候可以在WebViewClient以及WebViewChrome接口中的onPageStart以及onPageFinish,onProgressChange這幾個回調方法中判斷當前頁面是否存在,若不存在則直接返回。
@Override
public void onPageFinished(WebView view, String url) {
if (getActivity() == null || getActivity().isFinishing()) {
return;
}
super.onPageFinished(view, url);
//...
}
4、WebView與JS交互引起的安全問題
4.2以下系統這里推薦一個開源項目https://github.com/pedant/safe-java-js-webview-bridge
當然WebView的安全不止這個,可以自行百度或者Google腦部相關姿勢。AC也會在之后的文章中專門整理這個問題。
同時此問題在官方4.2(API Level 17)以上手機已經得到修復,使用@JavascriptInterface 注解聲明addJavascriptInterface注入的方法。即只有使用@JavascriptInterface的方法才會被注入到WebView中。
5、WebView長按彈出ActionMode菜單樣式問題
三星手機WebView彈出的菜單樣式有可能會出現此問題,解決方法可以繼承WebView重寫startActionMode()方法,然后修改menu的菜單樣式。
@Override
public ActionMode startActionMode(ActionMode.Callback callback) {
return super.startActionMode(new CustomCallback(getContext(), callback));
}
public static class CustomCallback implements ActionMode.Callback {
private ActionMode.Callback callback;
private Context context;
public CustomCallback(Context context, ActionMode.Callback callback) {
this.callback = callback;
this.context = context;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return callback.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
MenuItem menuItem = menu.getItem(i);
final Drawable moreMenuDrawable = menuItem.getIcon();
if (moreMenuDrawable != null) {
menuItem.setIcon(DrawableUtil.getThemeDrawable(context, moreMenuDrawable));
}
}
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return callback.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
callback.onDestroyActionMode(mode);
context = null;
}
}
當然如果不需要長按事件,可以注冊WebView的長按事件
mWebView.setOnLongClickListener(v -> {
return true;
});
6、硬件加速問題
一般情況下,使用WebView開發都會使用硬件加速來提高WebView的渲染速度。可以在AndroidManifest.xml文件中設置
android:hardwareAccelerated="true"
也可以在頁面中使用
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
但是簡單的使用以上兩種方法,開啟硬件加速以及不開啟硬件加速在一些手機上都會出現這樣或者那樣的問題,例如,如果一直開啟了硬件加速,某些手機有可能會出現屏幕花屏的問題;還有WebView在不同廠商的手機中依然可能會出現Crash問題。而Crash的問題一般是報了WebView底層的錯誤。可以參考以下處理方式:
在onPageStart中開啟硬件加速,在onPageFinish中關閉硬件加速。
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (getActivity() == null || getActivity().isFinishing()) {
return;
}
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
if (getActivity() == null || getActivity().isFinishing()) {
return;
}
view.setLayerType(View.LAYER_TYPE_NONE, null);
super.onPageFinished(view, url);
}
7、使用獨立進程跑WebView
有一定使用WebView經驗的老司機可能都把項目中的WebView模塊抽取出來,並跑在獨立的進程中去。例如在manifest文件中使用屬性process指定獨立的進程。
<!-- Web 頁面 -->
<activity
android:name=".UI.CommonUI.Activity.WebBrowserActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:hardwareAccelerated="true"
android:label=""
android:process=":web"
android:screenOrientation="portrait" />
這樣做的是因為WebView在以前的版本的底層實現中會發生內存泄漏,導致頁面關閉但是依然沒有釋放內存,而在獨立進程中的WebView模塊就可以很好解決此問題,在關閉WebView的時候就關閉進程,這樣就可以釋放相關的內存了。
但是使用多進程架構,進程間數據共享就是一個問題了。例如進程A設置了cookie,同樣我也要在進程B共享這個cookie。目前AC認為可行的解決方案是使用ContentProvider來共享數據。此問題AC沒有寫相應的Demo,希望有老司機可以帶路。
8、WebView生命周期回調
WebView也有生命周期回調方法,這些方法需要在Activity或Fragment相應的生命方法中回調。主要是onResume(),onPasuse()和onDestory()(或者onDestoryView())這幾個方法的回調實現。
@Override
public void onResume() {
if (mWebView != null) {
mWebView.resumeTimers();
mWebView.onResume();
}
super.onResume();
}
@Override
public void onPause() {
if (mWebView != null) {
mWebView.pauseTimers();
mWebView.onPause();
}
super.onPause();
}
@Override
public void onDestroyView() {
if (mWebView != null) {
mWebView.stopLoading();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.removeAllViews();
mWebView.setWebChromeClient(null);
mWebView.setWebViewClient(null);
unregisterForContextMenu(mWebView);
mWebView.destroy();
}
super.onDestroyView();
}
這幾個方法的回調實現有利無弊,可以很好地避免翻車。例如WebView中播放聲音在頁面關閉之后還聲音的問題,WebView頁面跳轉其他頁面后返回顯示空白不刷新的問題等等。
以上便是AngryCode在使用WebView開發過程中踩過的坑,相應解決方案純粹是經驗參考,因為使用環境以及能力的局限,如果文章出現錯誤,歡迎老司機留言指出。