Android WebView使用詳解包括js互調(by 星空武哥)
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
目前很多android app都內建了可以顯示web頁面的介面,會發現這個介面一般都是由一個叫做WebView的元件渲染出來的,學習該元件可以為你的app開發提升擴充套件性。
先說下WebView的一些優點:
- 可以直接顯示和渲染web頁面,直接顯示網頁
- webview可以直接用html檔案(網路上或本地assets中)作佈局
- 和JavaScript互動呼叫
推薦兩個學習的webView的文章:
http://blog.csdn.net/fengyuzhengfan/article/details/38326861
http://www.jianshu.com/p/3fcf8ba18d7f
一、基本使用
首先layout中即為一個基本的簡單控制元件:
<WebView android:id="@+id/webView1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginTop="10dp" />
同時,因為要房訪問網路,所以manifest中必須要加uses-permission:
<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內頁面的載入進度:
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 } });
二、通過webview控制元件下載檔案
通常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); } }
三、錯誤處理
當我們使用瀏覽器的時候,通常因為載入的頁面的伺服器的各種原因導致各種出錯的情況,最平常的比如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即可。
四、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頁面中可以自動的登陸了並顯示登陸後的介面。
五、 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>
六、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*
Android 中Webview 自適應螢幕
webview中右下角的縮放按鈕能不能去掉
settings.setDisplayZoomControls(false); //隱藏webview縮放按鈕
讓Webview載入的頁面居中顯示有我知道的幾種方法
第一種方法:
WebSettings settings = webView.getSettings();settings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);
LayoutAlgorithm是一個列舉用來控制頁面的佈局,有三個型別:
1.NARROW_COLUMNS:可能的話使所有列的寬度不超過螢幕寬度
2.NORMAL:正常顯示不做任何渲染
3.SINGLE_COLUMN:把所有內容放大webview等寬的一列中
用SINGLE_COLUMN型別可以設定頁面居中顯示,頁面可以放大縮小,但這種方法不怎麼好,有時候會讓你的頁面佈局走樣而且我測了一下,只能顯示中間那一塊,超出螢幕的部分都不能顯示。
第二種方法:
//設定載入進來的頁面自適應手機螢幕
settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(true);
第一個方法設定webview推薦使用的視窗,設定為true。第二個方法是設定webview載入的頁面的模式,也設定為true。
這方法可以讓你的頁面適應手機螢幕的解析度,完整的顯示在螢幕上,可以放大縮小。
兩種方法都試過,推薦使用第二種方法
第三種方法:(主要用於平板,針對特定螢幕程式碼調整解析度)
DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); int mDensity = metrics.densityDpi; if (mDensity == 120) { settings.setDefaultZoom(ZoomDensity.CLOSE); }else if (mDensity == 160) { settings.setDefaultZoom(ZoomDensity.MEDIUM); }else if (mDensity == 240) { settings.setDefaultZoom(ZoomDensity.FAR); }<span style="font-size:14px;"></span>
另一種講解:
一、基本用法
1、載入線上URL
[java] view plain copy- void loadUrl(String url)
注意:載入線上網頁地址是會用到聯網permission許可權的,所以需要在AndroidManifest.xml中寫入下面程式碼申請許可權:
[html] view plain copy
- <uses-permission android:name="android.permission.INTERNET" />
從效果圖中可以明顯看出本示例的佈局:
main.xml
[html] view plain copy
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <Button
- android:id="@+id/btn"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text