Android混合程式設計:WebView實踐

蘇策發表於2017-10-26

關於作者

郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。

文章目錄

  • 一 基本用法
  • 二 程式碼互動
  • 三 效能優化

第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄

一 基本用法

WebView也是Android View的一種, 我們通常用它來在應用內部展示網頁, 和以往一樣, 我們先來簡單看一下它的基本用法。

新增網路許可權

<uses-permission android:name="android.permission.INTERNET" />複製程式碼

在佈局中新增WebView

<?xml version="1.0" encoding="utf-8"?>
<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/webview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
/>複製程式碼

使用WebView載入網頁

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");複製程式碼

以上就是WebView的簡單用法, 相比大家已經十分熟悉, 下面我們就來逐一看看WebView的其他特性。

WebView基本元件

瞭解了基本用法, 我們對WebView就有了大致的印象, 下面我們來看看構建Web應用的三個重要元件。

WebSettings

WebSettings用來對WebView做各種設定, 你可以這樣獲取WebSettings:

WebSettings webSettings = mWebView .getSettings();複製程式碼

WebSettings的常見設定如下所示:

JS處理

  • setJavaScriptEnabled(true); //支援js
  • setPluginsEnabled(true); //支援外掛
  • setJavaScriptCanOpenWindowsAutomatically(true); //支援通過JS開啟新視窗

縮放處理

  • setUseWideViewPort(true); //將圖片調整到適合webview的大小
  • setLoadWithOverviewMode(true); // 縮放至螢幕的大小
  • setSupportZoom(true); //支援縮放,預設為true。是下面那個的前提。
  • setBuiltInZoomControls(true); //設定內建的縮放控制元件。 這個取決於setSupportZoom(), 若setSupportZoom(false),則該WebView不可縮放,這個不管設定什麼都不能縮放。
  • setDisplayZoomControls(false); //隱藏原生的縮放控制元件

內容佈局

  • setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); //支援內容重新佈局
  • supportMultipleWindows(); //多視窗

檔案快取

  • setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //關閉webview中快取
  • setAllowFileAccess(true); //設定可以訪問檔案

其他設定

  • setNeedInitialFocus(true); //當webview呼叫requestFocus時為webview設定節點
  • setLoadsImagesAutomatically(true); //支援自動載入圖片
  • setDefaultTextEncodingName("utf-8"); //設定編碼格式
  • setPluginState(PluginState.OFF); //設定是否支援flash外掛
  • setDefaultFontSize(20); //設定預設字型大小

WebViewClient

WebViewClient用來幫助WebView處理各種通知, 請求事件。我們通過繼承WebViewClient並過載它的方法可以實現不同功能的定製。具體如下所示:

  • shouldOverrideUrlLoading(WebView view, String url) //在網頁上的所有載入都經過這個方法,這個函式我們可以做很多操作。比如獲取url,檢視url.contains(“add”),進行新增操作

  • shouldOverrideKeyEvent(WebView view, KeyEvent event) //處理在瀏覽器中的按鍵事件。

  • onPageStarted(WebView view, String url, Bitmap favicon) //開始載入頁面時呼叫的,我們可以設定一個loading的頁面,告訴使用者程式在等待網路響應。

  • onPageFinished(WebView view, String url) //在頁面載入結束時呼叫, 我們可以關閉loading 條,切換程式動作。

  • onLoadResource(WebView view, String url) //在載入頁面資源時會呼叫,每一個資源(比如圖片)的載入都會呼叫一次。

  • onReceivedError(WebView view, int errorCode, String description, String failingUrl) //報告錯誤資訊

  • doUpdateVisitedHistory(WebView view, String url, boolean isReload) //更新歷史記錄

  • onFormResubmission(WebView view, Message dontResend, Message resend) //應用程式重新請求網頁資料

  • onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,String realm) //獲取返回資訊授權請求

  • onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) //讓webview處理https請求。

  • onScaleChanged(WebView view, float oldScale, float newScale) //WebView發生改變時呼叫

  • onUnhandledKeyEvent(WebView view, KeyEvent event) //Key事件未被載入時呼叫

WebChromeClient

WebChromeClient用來幫助WebView處理JS的對話方塊、網址圖示、網址標題和載入進度等。同樣地, 通過繼承WebChromeClient並過載它的方法也可以實現不同功能的定製, 如下所示:

  • public void onProgressChanged(WebView view, int newProgress); //獲得網頁的載入進度,顯示在右上角的TextView控制元件中

  • public void onReceivedTitle(WebView view, String title); //獲取Web頁中的title用來設定自己介面中的title, 當載入出錯的時候,比如無網路,這時onReceiveTitle中獲取的標題為"找不到該網頁",

  • public void onReceivedIcon(WebView view, Bitmap icon); //獲取Web頁中的icon

  • public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg);

  • public void onCloseWindow(WebView window);

  • public boolean onJsAlert(WebView view, String url, String message, JsResult result); //處理alert彈出框,html 彈框的一種方式

  • public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) //處理confirm彈出框

  • public boolean onJsConfirm(WebView view, String url, String message, JsResult result); //處理prompt彈出框

WebView生命週期

onResume()

WebView為活躍狀態時回撥,可以正常執行網頁的響應。

onPause()

WebView被切換到後臺時回撥, 頁面被失去焦點, 變成不可見狀態,onPause動作通知核心暫停所有的動作,比如DOM的解析、plugin的執行、JavaScript執行。

pauseTimers()

當應用程式被切換到後臺時回撥,該方法針對全應用程式的WebView,它會暫停所有webview的layout,parsing,javascripttimer。降低CPU功耗。

resumeTimers()

恢復pauseTimers時的動作。

destroy()

關閉了Activity時回撥, WebView呼叫destory時, WebView仍繫結在Activity上.這是由於自定義WebView構建時傳入了該Activity的context物件, 因此需要先從父
容器中移除WebView, 然後再銷燬webview。

mRootLayout.removeView(webView);  
mWebView.destroy();複製程式碼

WebView頁面導航

頁面跳轉

當我們在WebView點選連結時, 預設的WebView會直接跳轉到別的瀏覽器中, 如果想要實現在WebView內跳轉就需要設定WebViewClient, 下面我們先來
說說WebView、WebViewClient、WebChromeClient三者的區別。

  • WebView: 主要負責解析和渲染網頁
  • WebViewClient: 輔助WebView處理各種通知和請求事件
  • WebChromeClient: 輔助WebView處理JavaScript中的對話方塊, 網址圖示和標題等

如果我們想控制不同連結的跳轉方式, 我們需要繼承WebViewClient重寫shouldOverrideUrlLoading()方法

    static class CustomWebViewClient extends WebViewClient {

        private Context mContext;

        public CustomWebViewClient(Context context) {
            mContext = context;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (Uri.parse(url).getHost().equals("github.com/guoxiaoxing")) {
                //如果是自己站點的連結, 則用本地WebView跳轉
                return false;
            }
            //如果不是自己的站點則launch別的Activity來處理
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            mContext.startActivity(intent);
            return true;
        }
    }複製程式碼

關於shouldOverrideUrlLoading()方法的兩點說明:

1 方法返回值

返回true: Android 系統會處理URL, 一般是喚起系統瀏覽器。
返回false: 當前 WebView 處理URL。

由於預設放回false, 如果我們只想在WebView內處理連結跳轉只需要設定mWebView.setWebViewClient(new WebViewClient())即可

/** 
     * Give the host application a chance to take over the control when a new 
     * url is about to be loaded in the current WebView. If WebViewClient is not 
     * provided, by default WebView will ask Activity Manager to choose the 
     * proper handler for the url. If WebViewClient is provided, return true 
     * means the host application handles the url, while return false means the 
     * current WebView handles the url. 
     * This method is not called for requests using the POST "method". 
     * 
     * @param view The WebView that is initiating the callback. 
     * @param url The url to be loaded. 
     * @return True if the host application wants to leave the current WebView 
     *         and handle the url itself, otherwise return false. 
     */  
    public boolean shouldOverrideUrlLoading(WebView view, String url) {  
        return false;  
    }複製程式碼

2 方法deprecated問題

shouldOverrideUrlLoading()方法在API >= 24時被標記deprecated, 它的替代方法是

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
            view.loadUrl(request.toString());
            return true;
        }複製程式碼

但是public boolean shouldOverrideUrlLoading(WebView view, String url)支援更廣泛的API我們在使用的時候還是它,
關於這兩個方法的討論可以參見:

stackoverflow.com/questions/3…
stackoverflow.com/questions/2…

頁面回退

Android的返回鍵, 如果想要實現WebView內網頁的回退, 可以重寫onKeyEvent()方法。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check if the key event was the Back button and if there's history
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it wasn't the Back key or there's no web page history, bubble up to the default
    // system behavior (probably exit the activity)
    return super.onKeyDown(keyCode, event);
}複製程式碼

頁面滑動

關於頁面滑動, 我們在做下拉重新整理等功能時, 經常會去判斷WebView是否滾動到頂部或者滾動到底部。

我們先來看一看三個判斷高度的方法

getScrollY();複製程式碼

該方法返回的是當前可見區域的頂端距整個頁面頂端的距離,也就是當前內容滾動的距離.

getHeight();
getBottom();複製程式碼

該方法都返回當前WebView這個容器的高度

getContentHeight();複製程式碼

返回的是整個html的高度, 但並不等同於當前整個頁面的高度, 因為WebView有縮放功能, 所以當前整個頁面的高度實際上應該是原始html的高度
再乘上縮放比例. 因此, 判斷方法是:

if (webView.getContentHeight() * webView.getScale() == (webView.getHeight() + webView.getScrollY())) {
    //已經處於底端
}

if(webView.getScrollY() == 0){
    //處於頂端
}複製程式碼

以上這個方法也是我們常用的方法, 不過從API 17開始, mWebView.getScale()被標記為deprecated

This method was deprecated in API level 17. This method is prone to inaccuracy due to race conditions
between the web rendering and UI threads; prefer onScaleChanged(WebView,

因為scale的獲取可以用一下方式:

public class CustomWebView extends WebView {

public CustomWebView(Context context) {
    super(context);
    setWebViewClient(new WebViewClient() {
        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
            super.onScaleChanged(view, oldScale, newScale);
            mCurrentScale = newScale
        }
    });
}複製程式碼

關於mWebView.getScale()的討論可以參見:

developer.android.com/reference/a…

stackoverflow.com/questions/1…

WebView快取實現

在專案中如果使用到WebView控制元件, 當載入html頁面時, 會在/data/data/包名目錄下生成database與cache兩個資料夾。
請求的url記錄是儲存在WebViewCache.db, 而url的內容是儲存在WebViewCache資料夾下。

控制快取行為

WebSettings webSettings = mWebView.getSettings();
//優先使用快取
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); 
//只在快取中讀取
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
/不使用快取
WwebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);複製程式碼

清除快取

clearCache(true); //清除網頁訪問留下的快取,由於核心快取是全域性的因此這個方法不僅僅針對webview而是針對整個應用程式.
clearHistory (); //清除當前webview訪問的歷史記錄,只會webview訪問歷史記錄裡的所有記錄除了當前訪問記錄.
clearFormData () //這個api僅僅清除自動完成填充的表單資料,並不會清除WebView儲存到本地的資料。複製程式碼

WebView Cookies

新增Cookies

public void synCookies() {
    if (!CacheUtils.isLogin(this)) return;
    CookieSyncManager.createInstance(this);
    CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.setAcceptCookie(true);
    cookieManager.removeSessionCookie();//移除
    String cookies = PreferenceHelper.readString(this, AppConfig.COOKIE_KEY, AppConfig.COOKIE_KEY);
    KJLoger.debug(cookies);
    cookieManager.setCookie(url, cookies);
    CookieSyncManager.getInstance().sync();
}複製程式碼

清除Cookies

CookieManager.getInstance().removeSessionCookie();複製程式碼

WebView本地資源訪問

當我們在WebView中載入出從web伺服器上拿取的內容時,是無法訪問本地資源的,如assets目錄下的圖片資源,因為這樣的行為屬於跨域行為(Cross-Domain),而WebView是禁止
的。解決這個問題的方案是把html內容先下載到本地,然後使用loadDataWithBaseURL載入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下載失敗則可以使用我們前面講述的方法來顯示自定義錯誤介面

二 程式碼互動

Android原生方案

關於WebView中Java程式碼和JS程式碼的互動實現, Android給了一套原生的方案, 我們先來看看原生的用法。後面我們還會講到其他的開源方法。

JavaScript程式碼和Android程式碼是通過addJavascriptInterface()來建立連線的, 我們來看下具體的用法。

1 設定WebView支援JavaScript

webView.getSettings().setJavaScriptEnabled(true);複製程式碼

2 在Android工程裡定義一個介面

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}複製程式碼

注意: API >= 17時, 必須在被JavaScript呼叫的Android方法前新增@JavascriptInterface註解, 否則將無法識別。

3 在Android程式碼中將該介面新增到WebView

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");複製程式碼

這個"Android"就是我們為這個介面取的別名, 在JavaScript就可以通過Android.showToast(toast)這種方式來呼叫此方法。

4 在JavaScript中呼叫Android方法

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>複製程式碼

在JavaScript中我們不用再去例項化WebAppInterface介面, WebView會自動幫我們完成這一工作, 使它能夠為WebPage所用。

注意:

由於addJavascriptInterface()給予了JS程式碼控制應用的能力, 這是一項非常有用的特性, 但同時也帶來了安全上的隱患,

Using addJavascriptInterface() allows JavaScript to control your Android application. This can be a very useful feature or a dangerous
security issue. When the HTML in the WebView is untrustworthy (for example, part or all of the HTML is provided by an unknown person or
process), then an attacker can include HTML that executes your client-side code and possibly any code of the attacker's choosing. As such,
you should not use addJavascriptInterface() unless you wrote all of the HTML and JavaScript that appears in your WebView. You should also
not allow the user to navigate to other web pages that are not your own, within your WebView (instead, allow the user's default browser
application to open foreign links—by default, the user's web browser opens all URL links, so be careful only if you handle page navigation
as described in the following section).

下面正式引入我們在專案中常用的兩套開源的替代方案

jockeyjs開源方案

jockeyjs是一套IOS/Android雙平臺的Native和JS互動方法, 比較適合用在專案中。

Library to facilitate communication between iOS apps and JS apps running inside a UIWebView

jockeyjs對Native和JS的互動做了優美的封裝, 事件的傳送與接收都可以通過send()和on()來完成。我們先簡單的看一下Event的傳送與接收。

Sending events from app to JavaScript

// Send an event to JavaScript, passing a payload
jockey.send("event-name", webView, payload);

//With a callback to execute after all listeners have finished
jockey.send("event-name", webView, payload, new JockeyCallback() {
    @Override
    public void call() {
        //Your execution code
    }
});複製程式碼

Receiving events from app in JavaScript

// Listen for an event from iOS, but don't notify iOS we've completed processing
// until an asynchronous function has finished (in this case a timeout).
Jockey.on("event-name", function(payload, complete) {
  // Example of event'ed handler.
  setTimeout(function() {
    alert("Timeout over!");
    complete();
  }, 1000);
});複製程式碼

Sending events from JavaScript to app

// Send an event to iOS.
Jockey.send("event-name");

// Send an event to iOS, passing an optional payload.
Jockey.send("event-name", {
  key: "value"
});

// Send an event to iOS, pass an optional payload, and catch the callback when all the
// iOS listeners have finished processing.
Jockey.send("event-name", {
  key: "value"
}, function() {
  alert("iOS has finished processing!");
});複製程式碼

Receiving events from JavaScript in app

//Listen for an event from JavaScript and log a message when we have receied it.
jockey.on("event-name", new JockeyHandler() {
    @Override
    protected void doPerform(Map<Object, Object> payload) {
        Log.d("jockey", "Things are happening");
    }
});

//Listen for an event from JavaScript, but don't notify the JavaScript that the listener has completed
//until an asynchronous function has finished
//Note: Because this method is executed in the background, if you want the method to interact with the UI thread
//it will need to use something like a android.os.Handler to post to the UI thread.
jockey.on("event-name", new JockeyAsyncHandler() {
    @Override
    protected void doPerform(Map<Object, Object> payload) {
        //Do something asynchronously
        //No need to called completed(), Jockey will take care of that for you!
    }
});


//We can even chain together several handlers so that they get processed in sequence.
//Here we also see an example of the NativeOS interface which allows us to chain some common
//system handlers to simulate native UI interactions.
jockey.on("event-name", nativeOS(this)
            .toast("Event occurred!")
            .vibrate(100), //Don't forget to grant permission
            new JockeyHandler() {
                @Override
                protected void doPerform(Map<Object, Object> payload) {
                }
            }
);

//...More Handlers


//If you would like to stop listening for a specific event
jockey.off("event-name");

//If you would like to stop listening to ALL events
jockey.clear();複製程式碼

通過上面的程式碼, 我們對jockeyjs的使用有了大致的理解, 下面我們具體來看一下在專案中的使用。

1 依賴配置

下載程式碼: github.com/tcoulter/jo…, 將JockeyJS.Android匯入到工程中。

2 jockeyjs配置

jockeyjs有兩種使用方式

方式一:

只在一個Activity中使用jockey或者多Activity共享一個jockey例項

//Declare an instance of Jockey
Jockey jockey;

//The WebView that we will be using, assumed to be instantiated either through findViewById or some method of injection.
WebView webView;

WebViewClient myWebViewClient;

@Override
protected void onStart() {
    super.onStart();

    //Get the default JockeyImpl
    jockey = JockeyImpl.getDefault();

    //Configure your webView to be used with Jockey
    jockey.configure(webView);

    //Pass Jockey your custom WebViewClient
    //Notice we can do this even after our webView has been configured.
    jockey.setWebViewClient(myWebViewClient)

    //Set some event handlers
    setJockeyEvents();

    //Load your webPage
    webView.loadUrl("file:///your.url.com");
}複製程式碼

方式二:

另一種就是把jockey當成一種全域性的Service來用, 這種方式下我們可以在多個Activity之間甚至整個應用內共享handler. 當然我們同樣需要
把jockey的生命週期和應用的生命週期繫結在一起。

//First we declare the members involved in using Jockey

//A WebView to interact with
private WebView webView;

//Our instance of the Jockey interface
private Jockey jockey;

//A helper for binding services
private boolean _bound;

//A service connection for making use of the JockeyService
private ServiceConnection _connection = new ServiceConnection() {
    @Override
    public void onServiceDisconnected(ComponentName name) {
        _bound = false;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        JockeyBinder binder = (JockeyBinder) service;

        //Retrieves the instance of the JockeyService from the binder
        jockey = binder.getService();

        //This will setup the WebView to enable JavaScript execution and provide a custom JockeyWebViewClient
        jockey.configure(webView);

        //Make Jockey start listening for events
        setJockeyEvents();

        _bound = true;

        //Redirect the WebView to your webpage.
        webView.loadUrl("file:///android_assets/index.html");
    }

}

///....Other member variables....////


//Then we bind the JockeyService to our activity through a helper function in our onStart method
@Override
protected void onStart() {
    super.onStart();
    JockeyService.bind(this, _connection);
}

//In order to bind this with the Android lifecycle we need to make sure that the service also shuts down at the appropriate time.
@Override
protected void onStop() {
    super.onStop();
    if (_bound) {
        JockeyService.unbind(this, _connection);
    }
}複製程式碼

以上便是jockeyjs的大致用法.

三 效能優化

優化網頁載入速度

預設情況html程式碼下載到WebView後,webkit開始解析網頁各個節點,發現有外部樣式檔案或者外部指令碼檔案時,會非同步發起網路請求下載檔案,但如果
在這之前也有解析到image節點,那勢必也會發起網路請求下載相應的圖片。在網路情況較差的情況下,過多的網路請求就會造成頻寬緊張,影響到css或
js檔案載入完成的時間,造成頁面空白loading過久。解決的方法就是告訴WebView先不要自動載入圖片,等頁面finish後再發起圖片載入。

設定WebView, 先禁止載入圖片

WebSettings webSettings = mWebView.getSettings();

//圖片載入
if(Build.VERSION.SDK_INT >= 19){
    webSettings.setLoadsImagesAutomatically(true);
}else {
    webSettings.setLoadsImagesAutomatically(false);
}複製程式碼

覆寫WebViewClient的onPageFinished()方法, 頁面載入結束後再載入圖片

@Override
public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);
    if (!view.getSettings().getLoadsImagesAutomatically()) {
        view.getSettings().setLoadsImagesAutomatically(true);
    }
}複製程式碼

注意: 4.4以上系統在onPageFinished時再恢復圖片載入時,如果存在多張圖片引用的是相同的src時,會只有一個image標籤得到載入,因而對於這樣的系統我們就先直接載入。

硬體加速頁面閃爍問題

4.0以上的系統我們開啟硬體加速後,WebView渲染頁面更加快速,拖動也更加順滑。但有個副作用就是,當WebView檢視被整體遮住一塊,然後突然恢復時(比如使用SlideMenu將WebView從側邊
滑出來時),這個過渡期會出現白塊同時介面閃爍。解決這個問題的方法是在過渡期前將WebView的硬體加速臨時關閉,過渡期後再開啟,如下所示:

過度前關閉硬體加速

if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){
    mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}複製程式碼

過度前開啟硬體加速

if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){
    mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}複製程式碼

以上就是本篇文章的全部內容, 大致就說這麼多, 在實際的專案中我們通常會自己去封裝一個H5Activity用來統一顯示H5頁面, 下面就提供了完整的H5Activity,

封裝了WebView各種特性與jockeyjs程式碼互動。該H5Activity提供WebView常用設定、H5頁面解析、標題解析、進度條顯示、錯誤頁面展示、重新載入等功能。可以拿去稍作改造, 用於自己的專案中。

package com.guoxiaoxing.webview;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import com.jockeyjs.Jockey;
import com.jockeyjs.JockeyImpl;
public class H5Activity extends AppCompatActivity {

    public static final String H5_URL = "H5_URL";
    private static final String JOCKEY_EVENT_NAME = "JOCKEY_EVENT_NAME";
    private static final String TAG = H5Activity.class.getSimpleName();

    private Toolbar mToolbar;
    private ProgressBar mProgressBar;

    private Jockey mJockey;
    private WebView mWebView;
    private WebViewClient mWebViewClient;
    private WebChromeClient mWebChromeClient;

    private String mUrl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_h5);
        setupView();
        setupSettings();
    }

    @Override
    protected void onStart() {
        super.onStart();
        setupJockey();
        setupData();
    }

    private void setupView() {
        mToolbar = (Toolbar) findViewById(R.id.h5_toolbar);
        mProgressBar = (ProgressBar) findViewById(R.id.h5_progressbar);
        mWebView = (WebView) findViewById(R.id.h5_webview);
    }

    private void setupSettings() {

        mWebView.setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY);
        mWebView.setHorizontalScrollBarEnabled(false);
        mWebView.setOverScrollMode(WebView.OVER_SCROLL_NEVER);

        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setSupportZoom(true);
        mWebSettings.setLoadWithOverviewMode(true);
        mWebSettings.setUseWideViewPort(true);
        mWebSettings.setDefaultTextEncodingName("utf-8");
        mWebSettings.setLoadsImagesAutomatically(true);

        //JS
        mWebSettings.setJavaScriptEnabled(true);
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        mWebSettings.setAllowFileAccess(true);
        mWebSettings.setUseWideViewPort(true);
        mWebSettings.setDatabaseEnabled(true);
        mWebSettings.setLoadWithOverviewMode(true);
        mWebSettings.setDomStorageEnabled(true);


        //快取
        ConnectivityManager connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = connectivityManager.getActiveNetworkInfo();
        if (info != null && info.isConnected()) {
            String wvcc = info.getTypeName();
            Log.d(TAG, "current network: " + wvcc);
            mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        } else {
            Log.d(TAG, "No network is connected, use cache");
            mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
        }

        if (Build.VERSION.SDK_INT >= 16) {
            mWebSettings.setAllowFileAccessFromFileURLs(true);
            mWebSettings.setAllowUniversalAccessFromFileURLs(true);
        }

        if (Build.VERSION.SDK_INT >= 12) {
            mWebSettings.setAllowContentAccess(true);
        }

        setupWebViewClient();
        setupWebChromeClient();
    }

    private void setupJockey() {
        mJockey = JockeyImpl.getDefault();
        mJockey.configure(mWebView);
        mJockey.setWebViewClient(mWebViewClient);
        mJockey.setOnValidateListener(new Jockey.OnValidateListener() {
            @Override
            public boolean validate(String host) {
                return "yourdomain.com".equals(host);
            }
        });

        //TODO set your event handler
        mJockey.on(JOCKEY_EVENT_NAME, new EventHandler());
    }

    private void setupData() {
        mUrl = getIntent().getStringExtra(H5_URL);
        if (TextUtils.isEmpty(mUrl)) {
            //TODO show error page
        } else {
            mWebView.loadUrl(mUrl);
        }
    }

    private void setupWebViewClient() {
        mWebViewClient = new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                //TODO 處理URL, 例如對指定的URL做不同的處理等
                return false;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
            }

            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                super.onReceivedError(view, request, error);
            }
        };
        mWebView.setWebViewClient(mWebViewClient);
    }

    private void setupWebChromeClient() {
        mWebChromeClient = new WebChromeClient() {
            @Override
            public void onReceivedTitle(WebView view, String title) {
                super.onReceivedTitle(view, title);
                mToolbar.setTitle(title);

            }

            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
                mProgressBar.setProgress(newProgress);
                if (newProgress == 100) {
                    mProgressBar.setVisibility(View.GONE);
                } else {
                    mProgressBar.setVisibility(View.VISIBLE);
                }
            }

            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                return super.onJsAlert(view, url, message, result);
            }
        };
        mWebView.setWebChromeClient(mWebChromeClient);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}複製程式碼

相關文章