Android WebView使用深刻淺出

目前不少android app都內置了能夠顯示web頁面的界面,會發現這個界面通常都是由一個叫作WebView的組件渲染出來的,學習該組件能夠爲你的app開發提高擴展性。javascript

先說下WebView的一些優勢:html

  • 能夠直接顯示和渲染web頁面,直接顯示網頁
  • webview能夠直接用html文件(網絡上或本地assets中)做佈局
  • 和JavaScript交互調用

 

1、基本使用java

首先layout中即爲一個基本的簡單控件:android

<WebView
        android:id="@+id/webView1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_marginTop="10dp" />

同時,由於要房訪問網絡,因此manifest中必需要加uses-permission:web

<uses-permission android:name="android.permission.INTERNET"/>

在activity中便可得到webview的引用,同時load一個網址:瀏覽器

webview = (WebView) findViewById(R.id.webView1);
webview.loadUrl("http://www.baidu.com/");
//webview.reload();// reload page

這個時候發現一個問題,啓動應用後,自動的打開了系統內置的瀏覽器,解決這個問題須要爲webview設置 WebViewClient,並重寫方法:服務器

webview.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

若本身定義了一個頁面加載進度的progressbar,須要展現給用戶的時候,能夠經過以下方式獲取webview內頁面的加載進度:cookie

webview.setWebChromeClient(new WebChromeClient(){
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                //get the newProgress and refresh progress bar
            }
        });

每一個頁面,都有一個標題,好比www.baidu.com這個頁面的title即「百度一下,你就知道」,那麼如何知道當前webview正在加載的頁面的title呢:網絡

webview.setWebChromeClient(new WebChromeClient(){
            @Override
            public void onReceivedTitle(WebView view, String title) {
                titleview.setText(title);//a textview
            }
        });

 

2、經過webview控件下載文件app

一般webview渲染的界面中含有能夠下載文件的連接,點擊該連接後,應該開始執行下載的操做並保存文件到本地中。webview來下載頁面中的文件一般有兩種方式:

1. 本身經過一個線程寫java io的代碼來下載和保存文件(可控性好)

2. 調用系統download的模塊(代碼簡單)

 

方法一:

首先要寫一個下載並保存文件的線程類

public class HttpThread extends Thread {


    private String mUrl;

    public HttpThread(String mUrl) {
        this.mUrl = mUrl;
    }
    
    @Override
    public void run() {
        URL url;
        try {
            url = new URL(mUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true);
            conn.setDoOutput(true);
            InputStream in = conn.getInputStream();
            
            File downloadFile;
            File sdFile;
            FileOutputStream out = null;
            if(Environment.getExternalStorageState().equals(Environment.MEDIA_UNMOUNTED)){
                downloadFile = Environment.getExternalStorageDirectory();
                sdFile = new File(downloadFile, "test.file");
                out = new FileOutputStream(sdFile);
            }
            
            //buffer 4k
            byte[] buffer = new byte[1024 * 4];
            int len = 0;
            while((len = in.read(buffer)) != -1){
                if(out != null)
                    out.write(buffer, 0, len);
            }
            
            //close resource
            if(out != null)
                out.close();
            
            if(in != null){
                in.close();
            }
            
            
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}


隨後要實現一個DownloadListener接口,這個接口實現方法OnDownloadStart(),當用戶點擊一個能夠下載的連接時,該回調方法被調用同時傳進來該連接的URL,隨後便可以對該URL塞入HttpThread進行下載操做:

//建立DownloadListener (webkit包)
class MyDownloadListenter implements DownloadListener{

        @Override
        public void onDownloadStart(String url, String userAgent,
                String contentDisposition, String mimetype, long contentLength) {
            System.out.println("url ==== >" + url);
            new HttpThread(url).start();
        }
        
    }

//給webview加入監聽
webview.setDownloadListener(new MyDownloadListenter());


方法二:

直接發送一個action_view的intent便可:

class MyDownloadListenter implements DownloadListener{

        @Override
        public void onDownloadStart(String url, String userAgent,
                String contentDisposition, String mimetype, long contentLength) {
            System.out.println("url ==== >" + url);
            //new HttpThread(url).start();
            
            Uri uri = Uri.parse(url);
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(intent);
        }
        
    }

 

3、錯誤處理

當咱們使用瀏覽器的時候,一般由於加載的頁面的服務器的各類緣由致使各類出錯的狀況,最日常的好比404錯誤,一般狀況下瀏覽器會提示一個錯誤提示頁面。事實上這個錯誤提示頁面是瀏覽器在加載了本地的一個頁面,用來提示用戶目前已經出錯了。

可是當咱們的app裏面使用webview控件的時候遇到了諸如404這類的錯誤的時候,若也顯示瀏覽器裏面的那種錯誤提示頁面就顯得很醜陋了,那麼這個時候咱們的app就須要加載一個本地的錯誤提示頁面,即webview如何加載一個本地的頁面。

1. 首先咱們須要些一個html文件,好比error_handle.html,這個文件裏面就是當出錯的時候須要展現給用戶看的一個錯誤提示頁面,儘可能作的精美一些。而後將該文件放置到代碼根目錄的assets文件夾下。

2. 隨後咱們須要複寫WebViewClient的onRecievedError方法,該方法傳回了錯誤碼,根據錯誤類型能夠進行不一樣的錯誤分類處理

webview.setWebViewClient(new WebViewClient(){
       
            @Override
            public void onReceivedError(WebView view, int errorCode,
                    String description, String failingUrl) {
                switch(errorCode)
                {
                case HttpStatus.SC_NOT_FOUND:
                    view.loadUrl("file:///android_assets/error_handle.html");
                    break;
                }
            }
        });

其實,當出錯的時候,咱們能夠選擇隱藏掉webview,而顯示native的錯誤處理控件,這個時候只須要在onReceivedError裏面顯示出錯誤處理的native控件同時隱藏掉webview便可。

 

4、webview同步cookies

cookies是服務器用來保存每一個客戶的經常使用信息的,下次客戶進入一個諸如登錄的頁面時服務器會檢測cookie信息,若是經過則直接進入登錄後的頁面。

在webview中,若是以前已經登錄過了,那麼下次再進入一樣的登錄界面時,若須要再次登錄的話,必定會很惱人,因此這裏提供一個webview同步cookies的方法。

 

1.首先,咱們假設某個網站的登錄界面須要提供兩個參數,一個是name,一個是pwd,那麼要是對這個頁面進行登錄,那麼必須給與這兩個信息。咱們假設服務器已經註冊了name爲jason,pwd爲123456這個帳號。

2.下面,寫一個Thread用來將name和pwd自動的登入,在服務器返回的response中得到cookie信息,稍後對這個cookie進行保存,這裏先給出這個Thread的代碼:

public class HttpCookie extends Thread {

    private Handler mHandler;

    public HttpCookie(Handler mHandler) {
        this.mHandler = mHandler;
    }
    
    @Override
    public void run() {
        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost("");//this place should add the login address
        
        List<NameValuePair> list = new ArrayList<NameValuePair>();
        list.add(new BasicNameValuePair("name", "jason"));
        list.add(new BasicNameValuePair("pwd", "123456"));
        
        try {
            post.setEntity(new UrlEncodedFormEntity(list));
            HttpResponse reponse = client.execute(post);
            if(reponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                AbstractHttpClient absClient = (AbstractHttpClient) client;
                List<Cookie> cookies = absClient.getCookieStore().getCookies();
                
                for(Cookie cookie:cookies){
                    if(cookie != null){
                        //TODO
                        //this place would get the cookies
                    }
                }
            }
            
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

因爲這是一個子線程,因此須要在主線程中建立並執行。

同時,由於其實子線程,那麼裏面必須含有一個handler的元素,用來當成功獲取cookie後通知主線程進行同步和保存。初始化這個子線程的時候須要將主線程上的handler給傳過來,隨後在以上代碼的TODO中發送消息,讓主線程記錄cookie,發送的這個消息須要將cookie信息包含進去:

if(cookie != null){
    //TODO
    //this place would get the cookies
    Message msg = new Message();
    msg.obj = cookie;
    if(mHandler != null){
        mHandler.sendMessage(msg);
        return;
    }
}

隨後在主線程中(webview加載登錄界面前),在handler中將會獲取到cookie信息,下面將對該cookie進行保存和同步:

    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) 
        {
            
            CookieSyncManager.createInstance(MainActivity.this);
            CookieManager cookieMgr = CookieManager.getInstance();
            cookieMgr.setAcceptCookie(true);
            cookieMgr.setCookie("", msg.obj.toString());// this place should add the login host address(not the login index address)
            CookieSyncManager.getInstance().sync();
            
            webview.loadUrl("");// login index address
        };
    };

這個時候發現webview加載的login index頁面中能夠自動的登錄了並顯示登錄後的界面。

 

5、 WebView與JavaScript的交互

1. webview調用js

mWebView.loadUrl("javascript:do()");

以上是webview在調用js中的一個叫作do的方法,該js所在的html文件大體以下:

<html>
    <script language="javascript">
        /* This function is invoked by the webview*/
        function do() {
            alert("1");
        }
    </script>
    <body>
        <a onClick="window.demo.clickOnAndroid()"><div style="width:80px;
            margin:0px auto;
            padding:10px;
            text-align:center;
            border:2px solid #111111;" >
                <img id="droid" src="xx.png"/><br>
                Click me!
        </div></a>
    </body>
</html>


2. js調用webview

咱們假設下列的本地類是要給js調用的:

package com.test.webview;
class
DemoJavaScriptInterface { DemoJavaScriptInterface() { } /** * This is not called on the UI thread. Post a runnable to invoke * loadUrl on the UI thread. */ public void clickOnAndroid() { mHandler.post(new Runnable() { public void run() { //TODO } }); } }

首先給webview設置:

mWebview.setJavaScriptEnabled(true);

隨後將本地的類(被js調用的)映射出去:

mWebView.addJavascriptInterface(new DemoJavaScriptInterface(), "demo");

「demo」這個名字就是公佈出去給JS調用的,那麼js久能夠直接用下列代碼調用本地的DemoJavaScriptInterface類中的方法了:

<body onload="javascript:demo.clickOnAndroid()">  
    ...
</body>

 

6、WebView與JavaScript相互調用混淆問題

若webview中的js調用了本地的方法,正常狀況下發布的debug包js調用的時候是沒有問題的,可是一般發佈商業版本的apk都是要通過混淆的步驟,這個時候會發現以前調用正常的js卻沒法正常調用本地方法了。

這是由於混淆的時候已經把本地的代碼的引用給打亂了,致使js中的代碼找不到本地的方法的地址。

解決這個問題很簡單,即在proguard.cfg文件中加上一些代碼,聲明本地中被js調用的代碼不被混淆。下面舉例說明:

第五節中被js調用的那個類DemoJavaScriptInterface的包名爲com.test.webview,那麼就要在proguard.cfg文件中加入:

-keep public class com.test.webview.DemoJavaScriptInterface{
    public <methods>;
}

如果內部類,則大體寫成以下形式:

-keep public class com.test.webview.DemoJavaScriptInterface$InnerClass{
    public <methods>; }

若android版本比較新,可能還須要添加上下列代碼:

-keepattributes *Annotation*  
-keepattributes *JavascriptInterface*
相關文章
相關標籤/搜索