Android WebView開發問題及優化彙總
我們在native與網頁相結合開發的過程中,難免會遇到關於WebView一些共通的問題。就我目前開發過程中遇到的問題以及最後得到的優化方案都將在這裡列舉出來。有些是老生常談,有些則是個人摸索得出解決方法。下面就是整理得到的些乾貨。
1.加快HTML網頁裝載完成的速度
預設情況html程式碼下載到WebView後,webkit開始解析網頁各個節點,發現有外部樣式檔案或者外部指令碼檔案時,會非同步發起網路請求下載檔案,但如果在這之前也有解析到image節點,那勢必也會發起網路請求下載相應的圖片。在網路情況較差的情況下,過多的網路請求就會造成頻寬緊張,影響到css或js檔案載入完成的時間,造成頁面空白loading過久。解決的方法就是告訴WebView先不要自動載入圖片,等頁面finish後再發起圖片載入。
故在WebView初始化時設定如下程式碼:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">int</span> () { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span>(Build.VERSION.SDK_INT >= <span class="number" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">19</span>) { webView.getSettings().setLoadsImagesAutomatically(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>); } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">else</span> { webView.getSettings().setLoadsImagesAutomatically(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">false</span>); } } </pre></td> </tr></tbody></table></figure>同時在WebView的WebViewClient例項中的onPageFinished()方法新增如下程式碼:
從上面的程式碼,可以看出我們對系統API在19以上的版本作了相容。因為4.4以上系統在onPageFinished時再恢復圖片載入時,如果存在多張圖片引用的是相同的src時,會只有一個image標籤得到載入,因而對於這樣的系統我們就先直接載入。
2.自定義出錯介面
當WebView載入頁面出錯時(一般為404 NOT FOUND),安卓WebView會預設顯示一個賣萌的出錯介面。但我們怎麼能讓使用者發現原來我使用的是網頁應用呢,我們期望的是使用者在網頁上得到是如原生般應用的體驗,那就先要從幹掉這個默認出錯頁面開始。當WebView加載出錯時,我們會在WebViewClient例項中的onReceivedError()方法接收到錯誤,我們就在這裡做些手腳:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onReceivedError</span> (WebView view, <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">int</span> errorCode, String description, String failingUrl) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">super</span>.onReceivedError(view, errorCode, description, failingUrl); loadDataWithBaseURL(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">""</span>, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"text/html"</span>, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"utf-8"</span>, <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>); mErrorFrame.setVisibility(View.VISIBLE); } </pre></td> </tr></tbody></table></figure>從上面可以看出,我們先使用loadDataWithBaseURL清除掉預設錯誤頁內容,再讓我們自定義的View得到顯示(mErrorFrame為蒙在WebView之上的一個LinearLayout佈局,預設為View.GONE)。
3.是否存在滾動條
當我們做類似上拉載入下一頁這樣的功能的時候,頁面初始的時候需要知道當前WebView是否存在縱向滾動條,如果有則不載入下一頁,如果沒有則載入下一頁直到其出現縱向滾動條。首先繼承WebView類,在子類新增下面的程式碼:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">existVerticalScrollbar</span> () { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> computeVerticalScrollRange() > computeVerticalScrollExtent(); } </pre></td> </tr></tbody></table></figure>computeVerticalScrollRange得到的是可滑動的最大高度,computeVerticalScrollExtent得到的是滾動把手自身的高,當不存在滾動條時,兩者的值是相等的。當有滾動條時前者一定是大於後者的。
4.是否已滾動到頁面底部
同樣我們在做上拉載入下一頁這樣的功能時,也需要知道當前頁面滾動條所處的狀態,如果快到底部,則要發起網路請求資料更新網頁。同樣繼承WebView類,在子類覆蓋onScrollChanged方法,具體如下:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 8 9 10 11 12 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">protected</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onScrollChanged</span>(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">int</span> newX, <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">int</span> newY, <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">int</span> oldX, <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">int</span> oldY) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">super</span>.onScrollChanged(newX, newY, oldX, oldY); <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (newY != oldY) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">float</span> contentHeight = getContentHeight() * getScale(); <span class="comment" style="margin: 0px; padding: 0px; color: rgb(147, 161, 161); font-style: italic;">// 當前內容高度下從未觸發過, 瀏覽器存在滾動條且滑動到將抵底部位置</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (mCurrContentHeight != contentHeight && newY > <span class="number" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">0</span> && contentHeight <= newY + getHeight() + mThreshold) { <span class="comment" style="margin: 0px; padding: 0px; color: rgb(147, 161, 161); font-style: italic;">// TODO Something...</span> mCurrContentHeight = contentHeight; } } } </pre></td> </tr></tbody></table></figure>上面mCurrContentHeight用於記錄上次觸發時的網頁高度,用來防止在網頁總高度未發生變化而目標區域發生連續滾動時會多次觸發TODO,mThreshold是一個閾值,當頁面底部距離滾動條底部的高度差<=這個值時會觸發TODO。
5.遠端網頁需訪問本地資源
當我們在WebView中加載出從web伺服器上拿取的內容時,是無法訪問本地資源的,如assets目錄下的圖片資源,因為這樣的行為屬於跨域行為(Cross-Domain),而WebView是禁止的。解決這個問題的方案是把html內容先下載到本地,然後使用loadDataWithBaseURL載入html。這樣就可以在html中使用file:///android_asset/xxx.png的連結來引用包裡面assets下的資源了。示例如下:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">private</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">loadWithAccessLocal</span>(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">final</span> String htmlUrl) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">new</span> Thread(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">new</span> Runnable() { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">run</span>() { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">try</span> { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">final</span> String htmlStr = NetService.fetchHtml(htmlUrl); <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (htmlStr != <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>) { TaskExecutor.runTaskOnUiThread(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">new</span> Runnable() { <span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">run</span>() { loadDataWithBaseURL(htmlUrl, htmlStr, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"text/html"</span>, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"UTF-8"</span>, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">""</span>); } }); <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span>; } } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">catch</span> (Exception e) { Log.e(<span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"Exception:"</span> + e.getMessage()); } TaskExecutor.runTaskOnUiThread(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">new</span> Runnable() { <span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">run</span>() { onPageLoadedError(-<span class="number" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">1</span>, <span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"fetch html failed"</span>); } }); } }).start(); } </pre></td> </tr></tbody></table></figure>上面有幾點需要注意:
- 從網路上下載html的過程應放在工作執行緒中
- html下載成功後渲染出html的步驟應放在UI主執行緒,不然WebView會報錯
- html下載失敗則可以使用我們前面講述的方法來顯示自定義錯誤介面
6.ViewPager裡非首屏WebView點選事件不響應
如果你的多個WebView是放在ViewPager裡一個個加載出來的,那麼就會遇到這樣的問題。ViewPager首屏WebView的建立是在前臺,點選時沒有問題;而其他非首屏的WebView是在後臺建立,滑動到它後點擊頁面會出現如下錯誤日誌:
20955-20968/xx.xxx.xxx E/webcoreglue﹕ Should not happen: no rect-based-test nodes found
解決這個問題的辦法是繼承WebView類,在子類覆蓋onTouchEvent方法,填入如下程式碼:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onTouchEvent</span>(MotionEvent ev) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (ev.getAction() == MotionEvent.ACTION_DOWN) { onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY()); } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">super</span>.onTouchEvent(ev); } </pre></td> </tr></tbody></table></figure>7.WebView硬體加速導致頁面渲染閃爍
4.0以上的系統我們開啟硬體加速後,WebView渲染頁面更加快速,拖動也更加順滑。但有個副作用就是,當WebView檢視被整體遮住一塊,然後突然恢復時(比如使用SlideMenu將WebView從側邊滑出來時),這個過渡期會出現白塊同時介面閃爍。解決這個問題的方法是在過渡期前將WebView的硬體加速臨時關閉,過渡期後再開啟,程式碼如下:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { webview.setLayerType(View.LAYER_TYPE_SOFTWARE, <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>); } </pre></td> </tr></tbody></table></figure>8.避免addJavaScriptInterface帶來的安全問題
使用開源專案Safe Java-JS WebView Bridge可以很好替代addJavaScriptInterface方法,同時增加了非同步回撥等支援,並且不存在了安全風險。
9.WebView與上層父元素的TouchMove事件衝突
在開發過程中你可能會遇到這樣一種情況。端裡面使用ViewPager嵌套了多個WebView頁面,同時某一個WebView中的頁面元素需要響應TouchMove事件。正如下圖所示的情景:
這時你就會發現上層(ViewPager)阻斷了下層(WebView)接收TouchMove事件,即使你的WebView在TouchDown時返回true也無效,因為上層直接使用了onInterceptTouchEvent對後續的TouchMove進行了攔截。針對這個問題的解決,簡單做法是在重寫WebView onTouchEvent方法,如下:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onTouchEvent</span>(MotionEvent ev) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> ret = <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">super</span>.onTouchEvent(ev); <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (mPreventParentTouch) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">switch</span> (ev.getAction()) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">case</span> MotionEvent.ACTION_MOVE: requestDisallowInterceptTouchEvent(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>); ret = <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">break</span>; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">case</span> MotionEvent.ACTION_UP: <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">case</span> MotionEvent.ACTION_CANCEL: requestDisallowInterceptTouchEvent(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">false</span>); mPreventParentTouch = <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">false</span>; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">break</span>; } } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> ret; } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">preventParentTouchEvent</span> () { mPreventParentTouch = <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>; } </pre></td> </tr></tbody></table></figure>程式碼控制的關鍵在於mPreventParentTouch這個變數,mPreventParentTouch預設為false,當用戶touchdown頁面元素時通知該WebView將mPreventParentTouch設定為true。示意程式碼如下:
<figure class="highlight JAVASCRIPT" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 8 9 10 11 12 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><script type=<span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"text/javascript"</span>> document.getElementById(<span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"targetEle"</span>).addEventListener(<span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"touchstart"</span>, <span class="function" style="margin: 0px; padding: 0px;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">function</span><span class="params" style="margin: 0px; padding: 0px;">(ev)</span> {</span> HostApp.preventParentTouchEvent(); <span class="comment" style="margin: 0px; padding: 0px; color: rgb(147, 161, 161); font-style: italic;">// 通知WebView阻止祖先對其Touch事件的攔截</span> } ); document.getElementById(<span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"targetEle"</span>).addEventListener(<span class="string" style="margin: 0px; padding: 0px; color: rgb(42, 161, 152);">"touchmove"</span>, <span class="function" style="margin: 0px; padding: 0px;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">function</span><span class="params" style="margin: 0px; padding: 0px;">(ev)</span> {</span> <span class="comment" style="margin: 0px; padding: 0px; color: rgb(147, 161, 161); font-style: italic;">// todo something on this page</span> } ); <span class="xml" style="margin: 0px; padding: 0px;"></<span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">script</span>></span> </pre></td> </tr></tbody></table></figure>關於web頁面如何通知WebView(即呼叫Java方法)請參看第8條。
剛提到了上面是一種簡單的做法,並不能很好的解決手指滑動過快帶來的誤操作問題,即當用戶快速地滑動時,還是有一定機率會出現ViewPager攔截TouchMove事件而發生了Tab切換而非頁面元素做出了響應。要完美解決此問題,就要用到稍微複雜一點的方法(僅是整體訊息傳遞流程複雜一點)。
首先假設在ViewPager之上還有一個父元素叫做ParentViewOnViewPager,當我們接收到頁面preventParentTouchEvent通知時就先於ViewPager而進行攔截。如下:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><figcaption style="margin: 0px 0px 5px; padding: 0px; color: rgb(153, 153, 153); text-shadow: rgb(255, 255, 255) 0px 0px 1px;"><span style="margin: 0px; padding: 0px;">ParentViewOnViewPager.java</span></figcaption><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="class" style="margin: 0px; padding: 0px;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">class</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(181, 137, 0);">ParentViewOnViewPager</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">extends</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(181, 137, 0);">FrameLayout</span> {</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">private</span> MineWebView mDispatchWebView; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">preventParentTouchEvent</span> (WebView view) { mDispatchWebView = (MineWebView)view; } <span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onInterceptTouchEvent</span>(MotionEvent ev) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (ev.getAction() == MotionEvent.ACTION_MOVE && mDispatchWebView != <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>) { mDispatchWebView.ignoreTouchCancel(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>); <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>; } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">false</span>; } <span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onTouchEvent</span>(MotionEvent ev) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">if</span> (mDispatchWebView != <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>){ <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">switch</span> (ev.getAction()) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">case</span> MotionEvent.ACTION_MOVE: mDispatchWebView.onTouchEvent(ev); <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">break</span>; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">default</span>: mDispatchWebView.ignoreTouchCancel(<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">false</span>); mDispatchWebView.onTouchEvent(ev); mDispatchWebView = <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">null</span>; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">break</span>; } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">true</span>; } <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">super</span>.onTouchEvent(ev); } } </pre></td> </tr></tbody></table></figure>即當ParentViewOnViewPager接收到通知時,發起TouchEvent攔截,將攔截到的Touch事件轉嫁到裝載頁面的mDispatchWebView進行事件派發。這樣就直接跳過了ViewPager這一層。這裡需要注意的是當ParentViewOnViewPager發起攔截時,WebView會接收到一個TouchCancel事件,WebView應該忽略這個事件,以避免頁面接收到這個事件而打斷整個處理流程。如下程式碼所示:
<figure class="highlight JAVA" style="margin: 15px 0px 0px; padding: 7px 15px; border: 1px solid rgb(153, 153, 153); line-height: 1.6; overflow: auto; position: relative; font-size: 0.9em; color: rgb(68, 68, 68); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; text-align: justify; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><figcaption style="margin: 0px 0px 5px; padding: 0px; color: rgb(153, 153, 153); text-shadow: rgb(255, 255, 255) 0px 0px 1px;"><span style="margin: 0px; padding: 0px;">MineWebView.java</span></figcaption><table style="margin: 0px; padding: 0px; border-spacing: 0px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"> <td class="gutter" style="margin: 0px; padding: 0px 15px 0px 0px; color: rgb(153, 153, 153); border-right-width: 1px; border-right-style: solid; border-right-color: rgb(153, 153, 153); text-align: right;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;">1 2 3 4 5 6 7 8 9 10 11 12 </pre></td> <td class="code" style="margin: 0px; padding: 0px 0px 0px 15px; color: rgb(102, 102, 102);"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; border: none; background-color: rgb(238, 238, 238); background-position: initial initial; background-repeat: initial initial;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="class" style="margin: 0px; padding: 0px;"><span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">class</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(181, 137, 0);">MineWebView</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">extends</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(181, 137, 0);">WebView</span> {</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> mIgnoreTouchCancel; <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">void</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">ignoreTouchCancel</span> (<span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> val) { mIgnoreTouchCancel = val; } <span class="annotation" style="margin: 0px; padding: 0px;">@Override</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">public</span> <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">boolean</span> <span class="title" style="margin: 0px; padding: 0px; color: rgb(38, 139, 210);">onTouchEvent</span>(MotionEvent ev) { <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">return</span> ev.getAction() == MotionEvent.ACTION_CANCEL && mIgnoreTouchCancel || <span class="keyword" style="margin: 0px; padding: 0px; color: rgb(133, 153, 0);">super</span>.onTouchEvent(ev); } } </pre></td> </tr></tbody></table></figure>另外針對這種解決方案,頁面端的JS指令碼不用做任何變動。
謝謝分享