WebView深度學(xué)習(xí)(二)之全面總結(jié)WebView遇到的坑及優(yōu)化

上篇文章講到了WebView的基本使用以及Android和js的交互.

這篇文章講一下WebView遇到的那些坑,帶領(lǐng)各位爬坑。這里如果有你沒遇到的問題,歡迎留言告訴我,我盡我所能幫你解決。感謝大家支持。


? 三、WebView的那些坑

(1) 為什么Webview打開一個(gè)頁面,播放一段音樂,退出Activity時(shí)音樂還在后臺(tái)播放?

◆◆ 解決方案 1:

//銷毀Webview
@Override
protected void onDestroy() {
    if (mWebview != null) {
        mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
        mWebview.clearHistory();
        ((ViewGroup) mWebview.getParent()).removeView(mWebview);
        mWebview.destroy();
        mWebview = null;
    }
    super.onDestroy();
}

還有別問我為什么要移除,等你Error: WebView.destroy() called while still attached!之后你就知道了。

◆◆ 解決方案 2:

@Override
protected void onPause() {
   h5_webview.onPause();
   h5_webview.pauseTimers();
   super.onPause();
}
@Override
protected void onResume() {
   h5_webview.onResume();
   h5_webview.resumeTimers();
   super.onResume();
}

Webview的onPause()方法官網(wǎng)是這么解釋的:

   Does a best-effort attempt to pause any processing that can be paused safely, such as animations
and geolocation. Note that this call does not pause JavaScript. To pause JavaScript globally, use 
pauseTimers(). To resume WebView, call onResume().  
【翻譯:】通知內(nèi)核嘗試停止所有處理,如動(dòng)畫和地理位置,但是不能停止Js,如果想全局停止Js,
可以調(diào)用pauseTimers()全局停止Js,調(diào)用onResume()恢復(fù)。

(2) 怎么用網(wǎng)頁的標(biāo)題來設(shè)置自己的標(biāo)題欄?

◆◆ 解決方案:

WebChromeClient mWebChromeClient = new WebChromeClient() {    
    @Override    
    public void onReceivedTitle(WebView view, String title) {    
        super.onReceivedTitle(view, title);    
        txtTitle.setText(title);    
    }    
};  
mWedView.setWebChromeClient(mWebChromeClient());

★★ 注意事項(xiàng):

●   1.可能當(dāng)前頁面沒有標(biāo)題,獲取到的是null,那么你可以在跳轉(zhuǎn)到該Activity的時(shí)候自己帶一個(gè)標(biāo)題,或者有一個(gè)默認(rèn)標(biāo)題。
●   2.在一些機(jī)型上面,Webview.goBack()后,這個(gè)方法不一定會(huì)調(diào)用,所以標(biāo)題還是之前頁面的標(biāo)題。那么
你就需要用一個(gè)ArrayList來保持加載過的url,一個(gè)HashMap保存url及對(duì)應(yīng)的title.然后就是用WebView.canGoBack()來做判斷處理了。

(3) 為什么打包之后JS調(diào)用失?。ɑ蛘遅ebView與JavaScript相互調(diào)用時(shí),如果是debug沒有配置混淆時(shí),調(diào)用時(shí)沒問題的,但是當(dāng)設(shè)置混淆后發(fā)現(xiàn)無法正常調(diào)用了)?

◆◆ 解決方案:在proguard-rules.pro中添加混淆。

-keepattributes *Annotation*  
-keepattributes *JavascriptInterface*
-keep public class org.mq.study.webview.DemoJavaScriptInterface{
   public <methods>;
}
#假如是內(nèi)部類,混淆如下:
-keepattributes *JavascriptInterface*
-keep public class org.mq.study.webview.webview.DemoJavaScriptInterface$InnerClass{
    public <methods>;
}

其中org.mq.study.webview.DemoJavaScriptInterface 是不需要混淆的類名

(4) 5.0 以后的WebView加載的鏈接為Https開頭,但是鏈接里面的內(nèi)容,比如圖片為Http鏈接,這時(shí)候,圖片就會(huì)加載不出來,怎么解決?

★★ 原因分析:原因是Android 5.0上Webview默認(rèn)不允許加載Http與Https混合內(nèi)容:

◆◆ 解決方案:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 
    //兩者都可以
    webSetting.setMixedContentMode(webSetting.getMixedContentMode());
    //mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}

★★ 參數(shù)說明:

●   MIXED_CONTENT_ALWAYS_ALLOW 允許從任何來源加載內(nèi)容,即使起源是不安全的;
●   MIXED_CONTENT_NEVER_ALLOW 不允許Https加載Http的內(nèi)容,即不允許從安全的起源去加載一個(gè)不安全的
    資源;
●   MIXED_CONTENT_COMPLTIBILITY_MODE 當(dāng)涉及到混合式內(nèi)容時(shí),WebView會(huì)嘗試去兼容最新Web瀏覽器的
    風(fēng)格;

另外:在認(rèn)證證書不被Android所接受的情況下,我們可以通過設(shè)置重寫WebViewClient的onReceivedSslError方法在其中設(shè)置接受所有網(wǎng)站的證書來解決,具體代碼如下:

webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onReceivedSslError(WebView view,
                SslErrorHandler handler, SslError error) {
            //super.onReceivedSslError(view, handler, error);注意一定要去除這行代碼,否則設(shè)置無效。
            // handler.cancel();// Android默認(rèn)的處理方式
            handler.proceed();// 接受所有網(wǎng)站的證書
            // handleMessage(Message msg);// 進(jìn)行其他處理
        }
});

(5) WebView調(diào)用手機(jī)系統(tǒng)相冊(cè)來上傳圖片,開發(fā)過程中發(fā)現(xiàn)在很多機(jī)器上無法正常喚起系統(tǒng)相冊(cè)來選擇圖片。怎么解決?

★★ 原因分析:因?yàn)镚oogle攻城獅們對(duì)setWebChromeClient的回調(diào)方法openFileChooser做了多次修改,5.0以下openFileChooser有幾種重載方法,在5.0以上將回調(diào)方法該為了onShowFileChooser。

◆◆ 解決方案:為了兼容各個(gè)版本,我們需要對(duì)openFileChooser()進(jìn)行重載,同時(shí)針對(duì)5.0及以上重寫onShowFileChooser()方法:

上一段示例代碼,給大家看看:

public class MainActivity extends AppCompatActivity {

private ValueCallback<Uri> uploadMessage;
private ValueCallback<Uri[]> uploadMessageAboveL;
private final static int FILE_CHOOSER_RESULT_CODE = 10000;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    WebView webview = (WebView) findViewById(R.id.web_view);
    assert webview != null;
    WebSettings settings = webview.getSettings();
    settings.setUseWideViewPort(true);
    settings.setLoadWithOverviewMode(true);
    settings.setJavaScriptEnabled(true);
    webview.setWebChromeClient(new WebChromeClient() {

        //  android 3.0以下:用的這個(gè)方法
        public void openFileChooser(ValueCallback<Uri> valueCallback) {
            uploadMessage = valueCallback;
            openImageChooserActivity();
        }

        // android 3.0以上,android4.0以下:用的這個(gè)方法
        public void openFileChooser(ValueCallback valueCallback, String acceptType) {
            uploadMessage = valueCallback;
            openImageChooserActivity();
        }

        //android 4.0 - android 4.3  安卓4.4.4也用的這個(gè)方法
        public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, 
                        String capture) {
            uploadMessage = valueCallback;
            openImageChooserActivity();
        }

        //android4.4 無方法。。。

        // Android 5.0及以上用的這個(gè)方法
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> 
                 filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
            uploadMessageAboveL = filePathCallback;
            openImageChooserActivity();
            return true;
        }
    });
    String targetUrl = "file:///android_asset/up.html";
    webview.loadUrl(targetUrl);
}

private void openImageChooserActivity() {
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    i.addCategory(Intent.CATEGORY_OPENABLE);
    i.setType("image/*");
    startActivityForResult(Intent.createChooser(i, "Image Chooser"),
                  FILE_CHOOSER_RESULT_CODE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == FILE_CHOOSER_RESULT_CODE) {
        if (null == uploadMessage && null == uploadMessageAboveL) return;
        Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
        if (uploadMessageAboveL != null) {
            onActivityResultAboveL(requestCode, resultCode, data);
        } else if (uploadMessage != null) {
            uploadMessage.onReceiveValue(result);
            uploadMessage = null;
        }
    }
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
    if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
        return;
    Uri[] results = null;
    if (resultCode == Activity.RESULT_OK) {
        if (intent != null) {
            String dataString = intent.getDataString();
            ClipData clipData = intent.getClipData();
            if (clipData != null) {
                results = new Uri[clipData.getItemCount()];
                for (int i = 0; i < clipData.getItemCount(); i++) {
                    ClipData.Item item = clipData.getItemAt(i);
                    results[i] = item.getUri();
                }
            }
            if (dataString != null)
                results = new Uri[]{Uri.parse(dataString)};
        }
    }
    uploadMessageAboveL.onReceiveValue(results);
    uploadMessageAboveL = null;
}

}


重點(diǎn)坑:針對(duì)Android4.4,系統(tǒng)把openFileChooser方法去掉了,怎么解決?

詳情請(qǐng)見 博客 http://blog.csdn.net/xiexie758/article/details/52446937 我這里就不多說了。

(6) WebView調(diào)用手機(jī)系統(tǒng)相冊(cè)來上傳圖片,處理好第六點(diǎn)說的方法,我們打好release包測(cè)試的時(shí)候卻又發(fā)現(xiàn)還是沒法選擇圖片了。怎么解決?

★★ 原因分析:無奈去翻WebChromeClient的源碼,發(fā)現(xiàn)openFileChooser()是系統(tǒng)API,我們的release包是開啟了混淆的,所以在打包的時(shí)候混淆了openFileChooser(),這就導(dǎo)致無法回調(diào)openFileChooser()了。

◆◆ 解決方案也很簡(jiǎn)單,直接不混淆openFileChooser()就好了。

-keepclassmembers class * extends android.webkit.WebChromeClient{
   public void openFileChooser(...);
}

(7)怎么在 WebView 中長(zhǎng)按保存圖片?

1. 給 WebView添加監(jiān)聽

mWebview.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
    }
});

2. 獲取點(diǎn)擊的圖片地址

先獲取類型,根據(jù)相應(yīng)的類型來處理對(duì)應(yīng)的數(shù)據(jù)。

//首先判斷點(diǎn)擊的類型
WebView.HitTestResult result = ((WebView) v).getHitTestResult();
int type = result.getType();

//獲取具體信息,圖片這里就是圖片地址
String imgurl = result.getExtra();

type有這幾種類型:

  • WebView.HitTestResult.UNKNOWN_TYPE 未知類型
  • WebView.HitTestResult.PHONE_TYPE 電話類型
  • WebView.HitTestResult.EMAIL_TYPE 電子郵件類型
  • WebView.HitTestResult.GEO_TYPE 地圖類型
  • WebView.HitTestResult.SRC_ANCHOR_TYPE 超鏈接類型
  • WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE 帶有鏈接的圖片類型
  • WebView.HitTestResult.IMAGE_TYPE 單純的圖片類型
  • WebView.HitTestResult.EDIT_TEXT_TYPE 選中的文字類型

3. 操作圖片

你可以彈出保存圖片,或者點(diǎn)擊之后跳轉(zhuǎn)到顯示圖片的頁面。

最后整理一下代碼:

mWebView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        WebView.HitTestResult result = ((WebView)v).getHitTestResult();
        if (null == result)
            return false;
        int type = result.getType();
        if (type == WebView.HitTestResult.UNKNOWN_TYPE)
            return false;

        // 這里可以攔截很多類型,我們只處理圖片類型就可以了
        switch (type) {
            case WebView.HitTestResult.PHONE_TYPE: // 處理撥號(hào)
                break;
            case WebView.HitTestResult.EMAIL_TYPE: // 處理Email
                break;
            case WebView.HitTestResult.GEO_TYPE: // 地圖類型
                break;
            case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超鏈接
                break;
            case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
                break;
            case WebView.HitTestResult.IMAGE_TYPE: // 處理長(zhǎng)按圖片的菜單項(xiàng)
                // 獲取圖片的路徑
                String saveImgUrl = result.getExtra();

                // 跳轉(zhuǎn)到圖片詳情頁,顯示圖片
                Intent i = new Intent(MainActivity.this, ImageActivity.class);
                i.putExtra("imgUrl", saveImgUrl);
                startActivity(i);
                break;
            default:
                break;
        }
    }
});

(8) WebView 開啟硬件加速導(dǎo)致的問題?

WebView有很多問題,比如:不能打開pdf,播放視屏也只能打開硬件加速才能支持,在某些機(jī)型上會(huì)崩潰。
下面看一下硬件加速, 硬件加速 分為四個(gè)級(jí)別:

  • Application級(jí)別
     <application android:hardwareAccelerated="true"...>
  • Activity級(jí)別
     <activity android:hardwareAccelerated="true"...>
  • window級(jí)別(目前為止,Android還不支持在Window級(jí)別關(guān)閉硬件加速。)
    getWindow().setFlags(
         WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
         WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  • View級(jí)別
     view.setLayerType(View.LAYER_TYPE_HARDWARE, null);

WebView開啟硬件加速導(dǎo)致屏幕花屏問題的解決:

★★ 原因分析:
4.0以上的系統(tǒng)我們開啟硬件加速后,WebView渲染頁面更加快速,拖動(dòng)也更加順滑。但有個(gè)副作用就是,當(dāng)WebView視圖被整體遮住一塊,然后突然恢復(fù)時(shí)(比如使用SlideMenu將WebView從側(cè)邊滑出來時(shí)),這個(gè)過渡期會(huì)出現(xiàn)白塊同時(shí)界面閃爍。

◆◆ 解決方案:
在過渡期前將WebView的硬件加速臨時(shí)關(guān)閉,過渡期后再開啟,代碼如下:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

Android 4.0+ 版本中的EditText字符重疊問題:
做的軟件,在一些機(jī)器上,打字的時(shí)候,EditText中的內(nèi)容會(huì)出現(xiàn)重疊,而大部分機(jī)器沒有,所以感覺不是代碼的問題,一直沒有頭緒。

出現(xiàn)原因:JellyBean的硬件加速bug,在此我們關(guān)掉硬件加速即可。
解決方案:在EditText中加入一句:

android:layerType=”software”  

圖片無法顯示:
做的程序里有的時(shí)候會(huì)需要加載大圖,但是硬件加速中 OpenGL對(duì)于內(nèi)存是有限制的。如果遇到了這個(gè)限制,LogCat只會(huì)報(bào)一個(gè)Warning: Bitmap too large to be uploaded into a texture (587x7696, max=2048x2048)


這時(shí)我們就需要把硬件加速關(guān)閉了。
但開始我是這樣處理的,我關(guān)閉了整個(gè)應(yīng)用的硬件加速:

<application  
    android:allowBackup="true"  
    android:icon="@drawable/ic_launcher"  
    android:hardwareAccelerated="false"  
    android:label="@string/app_name"  
    android:theme="@style/AppTheme" >  

隨后我就發(fā)現(xiàn),雖然圖片可以顯示了,但是ListView和WebView等控件顯得特別的卡,這說明硬件加速對(duì)于程序的性能提升是很明顯的。所以我就改為對(duì)于Activity的關(guān)閉。

<activity  
    android:name="icyfox.webviewimagezoomertest.MainActivity"  
    android:label="@string/app_name"  
    android:hardwareAccelerated="false"  

(9) ViewPager里非首屏WebView點(diǎn)擊事件不響應(yīng)是什么原因?

??如果你的多個(gè)WebView是放在ViewPager里一個(gè)個(gè)加載出來的,那么就會(huì)遇到這樣的問題。ViewPager首屏WebView的創(chuàng)建是在前臺(tái),點(diǎn)擊時(shí)沒有問題;而其他非首屏的WebView是在后臺(tái)創(chuàng)建,滑動(dòng)到它后點(diǎn)擊頁面會(huì)出現(xiàn)如下錯(cuò)誤日志:

20955-20968/xx.xxx.xxx E/webcoreglue﹕ Should not happen: no rect-based-test nodes found

◆◆ 解決方案:
這個(gè)問題的辦法是繼承WebView類,在子類覆蓋onTouchEvent方法,填入如下代碼:

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
    }
    return super.onTouchEvent(ev);
}

(10) WebView白屏是什么原因?

◆◆ 解決方案:
WebView設(shè)置setLayerType(View.LAYER_TYPE_SOFTWARE,null); 示例代碼如此下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

◆◆ 相關(guān)源碼分析:
WebView繼承View,View中有三種layer type分別為LAYER_TYPE_NONE,LAYER_TYPE_SOFTWARE,LAYER_TYPE_HARDWARE。

  • 1.LAYER_TYPE_NONE:表明視圖沒有多余渲染層。
  • 2.LAYER_TYPE_SOFTWARE:表明視圖有一個(gè)軟件渲染層。無論是否開啟硬件加速,都會(huì)有一張
    Bitmap(software layer),并在上面對(duì) WebView 進(jìn)行渲染。
    好處:在進(jìn)行動(dòng)畫,使用software可以只畫一次ViewTree,很省。
    不適合使用場(chǎng)景:View樹經(jīng)常更新時(shí)不要用。尤其是在硬件加速打開時(shí),每次更新消耗的時(shí)間更多。因?yàn)殇秩就赀@張Bitmap后還需要再把這張Bitmap渲染到hardware layer上面去。
  1. LAYER_TYPE_HARDWARE:
    表明視圖有一個(gè)硬件渲染層。硬件加速關(guān)閉時(shí),作用同software。硬件加速打開時(shí)會(huì)在FBO(Framebuffer Object)上做渲染,在進(jìn)行動(dòng)畫時(shí),View樹也只需要畫一次。

◆◆ LAYER_TYPE_SOFTWARE 和 LAYER_TYPE_HARDWARE的區(qū)別:

1.前者是渲染到Bitmap,后者是渲染到FB上。
2.hardware可能會(huì)有一些操作不支持(出現(xiàn)白屏)。

◆◆ LAYER_TYPE_SOFTWARE 和 LAYER_TYPE_HARDWARE的相同:

都是開了一個(gè)buffer,把View畫到這個(gè)buffer上面去。

(11)給GLSurfaceView設(shè)置為software或者h(yuǎn)ardware后,發(fā)現(xiàn)沒有畫面了是什么原因?

◆◆ 問題分析:GLSurfaceView和WebView默認(rèn)LayerType都是NONE。

◆◆ 解決方案:給GLSurfaceView的LayerType設(shè)置為L(zhǎng)AYER_TYPE_NONE就可以了。

(12) WebView的緩存機(jī)制,比如token cookie session之類的在下一篇會(huì)講到,希望大家去看看

? 四、安卓8.0關(guān)于WebView的新特性

WebView新增了一些非常有用的API,可以使用和chrome瀏覽器類似的API來實(shí)現(xiàn)對(duì)惡意網(wǎng)站的檢測(cè)來保護(hù)web瀏覽的安全性,為此需要在manifest中添加如下meta-data標(biāo)簽:

<manifest>
<meta-data
    android:name="android.webkit.WebView.EnableSafeBrowing"
    android:value="true" />
<!-- ... -->
</manifest>

WebView還增加了關(guān)于多進(jìn)程的API,可以使用多進(jìn)程來增強(qiáng)安全性和健壯性,如果render進(jìn)程崩潰了,你還可以使用Termination Handler API來檢測(cè)到崩潰并做出相應(yīng)處理。


? 五、關(guān)于WebView的一點(diǎn)小優(yōu)化:

(1)給WebView加一個(gè)加載進(jìn)度條

??用Webview加載一個(gè)網(wǎng)頁時(shí),如果加載時(shí)間長(zhǎng),界面會(huì)一直空白,體驗(yàn)不太好,所以加個(gè)進(jìn)度條更好看一下,主流APP也都有進(jìn)度條效果,大概思路我來說一下:
??首先自定義一個(gè)HorizontalProgressView繼承View,然后自定義一個(gè)MyWebView繼承WebView,然后初始化的時(shí)候通過addView方法把前面自定義HorizontalProgressView,然后在MyWebView里面寫一個(gè)內(nèi)部類繼承WebChromeClient,大致代碼如下:

private class MyWebCromeClient extends WebChromeClient {
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        if (newProgress == 100) {
            //加載完畢進(jìn)度條消失
            progressView.setVisibility(View.GONE);
        } else {
            //更新進(jìn)度
            progressView.setProgress(newProgress);
        }
        super.onProgressChanged(view, newProgress);
    }
}

主要是通過MyWebCromeClient 的onProgressChanged方法里面的進(jìn)度值調(diào)用
progressView.setProgress()方法去更新進(jìn)度條,當(dāng)加載100%的時(shí)候讓進(jìn)度條消失。
具體實(shí)現(xiàn)你們自己去處理吧。

(2)加快HTML網(wǎng)頁加載完成的速度,等頁面finish再加載圖片

??默認(rèn)情況html代碼下載到WebView后,webkit開始解析網(wǎng)頁各個(gè)節(jié)點(diǎn),發(fā)現(xiàn)有外部樣式文件或者外部腳本文件時(shí),會(huì)異步發(fā)起網(wǎng)絡(luò)請(qǐng)求下載文件,但如果在這之前也有解析到image節(jié)點(diǎn),那勢(shì)必也會(huì)發(fā)起網(wǎng)絡(luò)請(qǐng)求下載相應(yīng)的圖片。在網(wǎng)絡(luò)情況較差的情況下,過多的網(wǎng)絡(luò)請(qǐng)求就會(huì)造成帶寬緊張,影響到css或js文件加載完成的時(shí)間,造成頁面空白loading過久。解決的方法就是告訴WebView先不要自動(dòng)加載圖片,等頁面finish后再發(fā)起圖片加載。

◆◆ 解決辦法:

在WebView初始化時(shí)設(shè)置如下代碼:

public void int () {
    if(Build.VERSION.SDK_INT >= 19) {
        webView.getSettings().setLoadsImagesAutomatically(true);
    } else {
        webView.getSettings().setLoadsImagesAutomatically(false);
    }
}

同時(shí)在WebView的WebViewClient實(shí)例中的onPageFinished()方法添加如下代碼:

@Override
public void onPageFinished(WebView view, String url) {
    if(!webView.getSettings().getLoadsImagesAutomatically()) {
        webView.getSettings().setLoadsImagesAutomatically(true);
    }
}

(3)自定義WebView頁面加載出錯(cuò)界面

??當(dāng)WebView加載頁面出錯(cuò)時(shí)(一般為404 NOT FOUND),安卓WebView會(huì)默認(rèn)顯示一個(gè)賣萌的出錯(cuò)界面。但我們?cè)趺茨茏層脩舭l(fā)現(xiàn)原來我使用的是網(wǎng)頁應(yīng)用呢,我們期望的是用戶在網(wǎng)頁上得到是如原生般應(yīng)用的體驗(yàn),那就先要從干掉這個(gè)默認(rèn)出錯(cuò)頁面開始。當(dāng)WebView加載出錯(cuò)時(shí),我們會(huì)在WebViewClient實(shí)例中的onReceivedError()方法接收到錯(cuò)誤,我們就在這里做些手腳:

@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清除掉默認(rèn)錯(cuò)誤頁內(nèi)容,再讓我們自定義的View得到顯示(mErrorFrame為蒙在WebView之上的一個(gè)LinearLayout布局,默認(rèn)為View.GONE)。

(4) 怎么知道WebView是否已經(jīng)滾動(dòng)到頁面底端?

◆◆ 解決方案:

  • 方案1,使用原生WebView的api可以獲取到:
    if (mWebView.getContentHeight() * mWebView.getScale()  == (mWebView.getHeight() + 
    mWebView.getScrollY())) {
        //說明已經(jīng)到底了
    }
  • 方案2,繼承WebView,重寫onScrollChanged方法:
    ??我們?cè)谧錾侠虞d下一頁這樣的功能時(shí),也需要知道當(dāng)前頁面滾動(dòng)條所處的狀態(tài),如果快到底部,則要發(fā)起網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)更新網(wǎng)頁。同樣繼承WebView類,在子類覆蓋onScrollChanged方法。
    ??以下代碼中mCurrContentHeight用于記錄上次觸發(fā)時(shí)的網(wǎng)頁高度,用來防止在網(wǎng)頁總高度未發(fā)生變化而目標(biāo)區(qū)域發(fā)生連續(xù)滾動(dòng)時(shí)會(huì)多次觸發(fā)TODO,mThreshold是一個(gè)閾值,當(dāng)頁面底部距離滾動(dòng)條底部的高度差<=這個(gè)值時(shí)會(huì)觸發(fā)TODO里面的代碼。

具體如下:

@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();
        // 當(dāng)前內(nèi)容高度下從未觸發(fā)過, 瀏覽器存在滾動(dòng)條且滑動(dòng)到將抵底部位置
        if (mCurrContentHeight != contentHeight && newY > 0 && contentHeight <= newY + getHeight() + mThreshold) {
            // TODO Something...
            mCurrContentHeight = contentHeight;
        }
    }
}

★★ 相關(guān)API介紹:

●   getContentHeight() @return the height of the HTML content
●   getScale() @return the current scale
●   getHeight() @return The height of your view
●   getScrollY() @return The top edge of the displayed part of your view, in pixels.

(5) 怎么知道WebView是否存在滾動(dòng)條?

??當(dāng)我們做類似上拉加載下一頁這樣的功能的時(shí)候,頁面初始的時(shí)候需要知道當(dāng)前WebView是否存在縱向滾動(dòng)條,如果有則不加載下一頁,如果沒有則加載下一頁直到其出現(xiàn)縱向滾動(dòng)條。
??首先繼承WebView類,在子類添加下面的代碼:

public boolean existVerticalScrollbar () {
    return computeVerticalScrollRange() > computeVerticalScrollExtent();
}

??computeVerticalScrollRange得到的是可滑動(dòng)的最大高度,computeVerticalScrollExtent得到的是滾動(dòng)把手自身的高,當(dāng)不存在滾動(dòng)條時(shí),兩者的值是相等的。當(dāng)有滾動(dòng)條時(shí)前者一定是大于后者的。


其實(shí)也有一些替代WebView的庫,比如騰訊的TBS 騰訊瀏覽服務(wù), 比如WebViewJavascriptBridge等方式,有興趣的可以去了解一下,個(gè)人建議還是用原生的WebView比較好,不能動(dòng)不動(dòng)就用框架,框架也不一定適用于自己項(xiàng)目,所以我們可以看看這些框架它的使用原理,吸收框架的精華,自己去靈活封裝一下用在自己項(xiàng)目中。


下一篇我們講解 WebView的內(nèi)存泄漏、漏洞以及緩存機(jī)制原理和解決方案


參考博文:
http://blog.csdn.net/carson_ho/article/details/64904691
http://bbs.csdn.net/topics/390905615
http://blog.csdn.net/cyuyanshujujiegou/article/details/52267817

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容