Android混合編程:WebView實踐

關於做者javascript

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。php

文章目錄css

  • 一 基本用法
  • 二 代碼交互
  • 三 性能優化

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

一 基本用法

WebView也是Android View的一種, 咱們一般用它來在應用內部展現網頁, 和以往同樣, 咱們先來簡單看一下它的基本用法。java

添加網絡權限android

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

在佈局中添加WebViewgit

<?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的其餘特性。github

WebView基本組件

瞭解了基本用法, 咱們對WebView就有了大體的印象, 下面咱們來看看構建Web應用的三個重要組件。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);
    }
}複製代碼
相關文章
相關標籤/搜索