在native與網(wǎng)頁相結合開發(fā)的過程中,難免會遇到關于WebView一些共通的問題。就我目前開發(fā)過程中遇到的問題以及最后得到的優(yōu)化方案都將在這里列舉出來。有些是老生常談,有些則是個人摸索得出解決方法。
1.加快HTML網(wǎng)頁裝載完成的速度
默認情況html代碼下載到WebView后,webkit開始解析網(wǎng)頁各個節(jié)點,發(fā)現(xiàn)有外部樣式文件或者外部腳本文件時,會異步發(fā)起網(wǎng)絡請求下載文件,但如果在這之前也有解析到image節(jié)點,那勢必也會發(fā)起網(wǎng)絡請求下載相應的圖片。在網(wǎng)絡情況較差的情況下,過多的網(wǎng)絡請求就會造成帶寬緊張,影響到css或js文件加載完成的時間,造成頁面空白loading過久。解決的方法就是告訴WebView先不要自動加載圖片,等頁面finish后再發(fā)起圖片加載。
故在WebView初始化時設置如下代碼:
[java] ?view plaincopy
public ? void ? int ?()?{?? ????if (Build.VERSION.SDK_INT?>=? 19 )?{?? ????????webView.getSettings().setLoadsImagesAutomatically(true );?? ????}?else ?{?? ????????webView.getSettings().setLoadsImagesAutomatically(false );?? ????}?? }??
同時在WebView的WebViewClient實例中的onPageFinished()方法添加如下代碼:
[java] ?view plaincopy
@Override ?? public ? void ?onPageFinished(WebView?view,?String?url)?{?? ????if (!webView.getSettings().getLoadsImagesAutomatically())?{?? ????????webView.getSettings().setLoadsImagesAutomatically(true );?? ????}?? }??
從上面的代碼,可以看出我們對系統(tǒng)API在19以上的版本作了兼容。因為4.4以上系統(tǒng)在onPageFinished時再恢復圖片加載時,如果存在多張圖片引用的是相同的src時,會只有一個image標簽得到加載,因而對于這樣的系統(tǒng)我們就先直接加載。
2.自定義出錯界面
當WebView加載頁面出錯時(一般為404 NOT FOUND),安卓WebView會默認顯示一個賣萌的出錯界面。但我們怎么能讓用戶發(fā)現(xiàn)原來我使用的是網(wǎng)頁應用呢,我們期望的是用戶在網(wǎng)頁上得到是如原生般應用的體驗,那就先要從干掉這個默認出錯頁面開始。當WebView加載出錯時,我們會在WebViewClient實例中的onReceivedError()方法接收到錯誤,我們就在這里做些手腳:
[java] ?view plaincopy
@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是否存在縱向滾動條,如果有則不加載下一頁,如果沒有則加載下一頁直到其出現(xiàn)縱向滾動條。首先繼承WebView類,在子類添加下面的代碼:
[java] ?view plaincopy
public ? boolean ?existVerticalScrollbar?()?{?? ????return ?computeVerticalScrollRange()?>?computeVerticalScrollExtent();?? }??
computeVerticalScrollRange得到的是可滑動的最大高度,computeVerticalScrollExtent得到的是滾動把手自身的高,當不存在滾動條時,兩者的值是相等的。當有滾動條時前者一定是大于后者的。
4.是否已滾動到頁面底部
同樣我們在做上拉加載下一頁這樣的功能時,也需要知道當前頁面滾動條所處的狀態(tài),如果快到底部,則要發(fā)起網(wǎng)絡請求數(shù)據(jù)更新網(wǎng)頁。同樣繼承WebView類,在子類覆蓋onScrollChanged方法,具體如下:
[java] ?view plaincopy
@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)?{?? ?????????????? ????????????mCurrContentHeight?=?contentHeight;?? ????????}?? ????}?? }??
上面mCurrContentHeight用于記錄上次觸發(fā)時的網(wǎng)頁高度,用來防止在網(wǎng)頁總高度 未發(fā)生變化而目標區(qū)域發(fā)生連續(xù)滾動時會多次觸發(fā)TODO,mThreshold是一個閾值,當頁面底部 距離滾動條底部的高度差<=這個值時會觸發(fā)TODO。
5.遠程網(wǎng)頁需訪問本地資源
當我們在WebView中加載出從web服務器上拿取的內容時,是無法訪問本地資源的,如assets目錄下的圖片資源,因為這樣的行為屬于跨域行為(Cross-Domain),而WebView是禁止的。解決這個問題的方案是把html內容先下載到本地,然后使用loadDataWithBaseURL加載html。這樣就可以在html中使用?file:///android_asset/xxx.png ?的鏈接來引用包里面assets下的資源了。示例如下:
[java] ?view plaincopy
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();?? }??
上面有幾點需要注意:
從網(wǎng)絡上下載html的過程應放在工作線程 中 html下載成功后渲染出html的步驟應放在UI主線程 ,不然WebView會報錯 html下載失敗則可以使用我們前面講述的方法來顯示自定義錯誤界面 完整的demo項目代碼我已放到:http://yunpan.cn/cgQPvJQxxkCBj?(提取碼:6712)。
如果你的多個WebView是放在ViewPager里一個個加載出來的,那么就會遇到這樣的問題。ViewPager首屏WebView的創(chuàng)建是在前臺,點擊時沒有問題;而其他非首屏的WebView是在后臺創(chuàng)建,滑動到它后點擊頁面會出現(xiàn)如下錯誤日志:
20955-20968/xx.xxx.xxx E/webcoreglue﹕ Should not happen: no rect-based-test nodes found
解決這個問題的辦法是繼承WebView類,在子類覆蓋onTouchEvent方法,填入如下代碼:
[java] ?view plaincopy
@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以上的系統(tǒng)我們開啟硬件加速后,WebView渲染頁面更加快速,拖動也更加順滑。但有個副作用就是,當WebView視圖被整體遮住一塊,然后突然恢復時(比如使用SlideMenu將WebView從側邊滑出來時),這個過渡期會出現(xiàn)白塊同時界面閃爍。解決這個問題的方法是在過渡期前將WebView的硬件加速臨時關閉,過渡期后再開啟,代碼如下:
[java] ?view plaincopy
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事件沖突
在開發(fā)過程中你可能會遇到這樣一種情況。端里面使用ViewPager嵌套了多個WebView頁面,同時某一個WebView中的頁面元素需要響應TouchMove事件。詳細解決方案請移步:http://www.pedant.cn/2014/09/23/webview-touch-conflict
10.清理Cache和歷史記錄
在開發(fā)過程中你可能會遇到這樣一種情況。端里面使用ViewPager嵌套了多個WebView頁面,同時某一個WebView中的頁面元素需要響應TouchMove事件。詳細解決方案請移步:http://www.pedant.cn/2014/09/23/webview-touch-conflict
[java] ?view plaincopy
webView.clearCache( true );??? webView.clearHistory();??
11.WebView Cookies清理
[java] ?view plaincopy
CookieSyncManager.createInstance( this );??? CookieSyncManager.getInstance().startSync();??? CookieManager.getInstance().removeSessionCookie();??? 12.處理WebView中的非超鏈接請求(如Ajax請求):? 有時候需要加上請求頭,但是非超鏈接的請求,沒有辦法再shouldOverrinding中攔截并用webView.loadUrl(String url,HashMap headers)方法添加請求頭目前用了一個臨時的辦法解決: ? ? ? 首先需要在url中加特殊標記/協(xié)議, 如在onWebViewResource方法中攔截對應的請求,然后將要添加的請求頭,以get形式拼接到url末尾 在shouldInterceptRequest()方法中,可以攔截到所有的網(wǎng)頁中資源請求,比如加載JS,圖片以及Ajax請求等等 Ex:
[java] ?view plaincopy
@SuppressLint ( "NewApi" )?? @Override ?? public ?WebResourceResponse?shouldInterceptRequest(WebView?view,String?url)?{?? ?????? ?? ????String?ajaxUrl?=?url;?? ?????? ????if ?(url.contains( "req=ajax" ))?{?? ???????ajaxUrl?+=?"&imei=" ?+?imei;?? ????}?? ?? ????return ? super .shouldInterceptRequest(view,?ajaxUrl);?? ?? }?? 13.WebView頁面中播放了音頻,退出Activity后音頻仍然在播放 需要在Activity的onDestory()中調用
[java] ?view plaincopy
webView.destroy();?? 但是直接調用可能會引起如下錯誤:
[java] ?view plaincopy
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上。這是由于自定義webview構建時傳入了該Activity的context對象,因此需要先從父容器中移除webview,然后再銷毀webview:
[java] ?view plaincopy
rootLayout.removeView(webView);?? webView.destroy();??
14.WebView長按自定義菜單,實現(xiàn)復制分享相關功能
這個功能首先可以從兩方面完成:
? (1) 在js中完成:
? ? 處理Android . selection . longTouch
這里推薦一個開源項目進行參考,:
https://github.com/btate/BTAndroidWebViewSelection
?? ?(2) android層處理:
? ? ?首先使用OnTouchListener實現(xiàn)長按實現(xiàn)監(jiān)聽,然后實現(xiàn)WebView的Context menu,最后調用webview中的emulateShiftHeld(),為了適配安卓不同版本,最好使用反射方式調用。
總結
以上是生活随笔 為你收集整理的Android WebView开发问题汇总 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內容還不錯,歡迎將生活随笔 推薦給好友。