目前很多Android app都內置了可以顯示web頁面的界面,會發現這個界面一般都是由一個叫做WebView的組件渲染出來的,學習該組件可以爲你的app開發提升擴展性。
先說下WebView的一些優點:
- 可以直接顯示和渲染web頁面,直接顯示網頁
- webview可以直接用html文件(網絡上或本地assets中)作佈局
- 和JavaScript交互調用
一、基本使用
首先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 οnlοad="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*
另一種講解:
一、基本用法
1、加載在線URL
- void loadUrl(String url)
注意:加載在線網頁地址是會用到聯網permission權限的,所以需要在AndroidManifest.xml中寫入下面代碼申請權限:
- <uses-permission android:name="android.permission.INTERNET" />
從效果圖中可以明顯看出本示例的佈局:
main.xml
- <?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="加載URL"/>
- <WebView
- android:id="@+id/webview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
- </LinearLayout>
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView)findViewById(R.id.webview);
- mBtn = (Button)findViewById(R.id.btn);
- mBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mWebView.loadUrl("http://www.baidu.com");
- }
- });
- }
- }
但如果我們運行上面的代碼,效果卻是利用瀏覽器來打開網址,卻不是使用webview打開網址:
如果我們想實現像示例一樣在webview中打開網址需要怎麼做呢?
我們需要設置WebViewClient:
修改後的代碼爲:
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView)findViewById(R.id.webview);
- mBtn = (Button)findViewById(R.id.btn);
- mWebView.setWebViewClient(new WebViewClient());
- mBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mWebView.loadUrl("http://www.baidu.com");
- }
- });
- }
- }
- mWebView.setWebViewClient(new WebViewClient());
最終的效果圖就與開篇時一樣的了,這裏就不再帖效果圖了,下面我們來看看如何加載本地html網頁
2、加載本地URL
一般而言,我們會將本地html文件放在assets文件夾下,比如:
web.html的內容爲:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <h1>歡迎光臨啓艦的blog</h1>
- </head>
- <body>
- </body>
- </html>
我們同樣在上面的示例的基礎上加以改造,在點擊按鈕的時候加載本地web.html文件
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView)findViewById(R.id.webview);
- mBtn = (Button)findViewById(R.id.btn);
- mBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mWebView.loadUrl("file:///android_asset/web.html");
- }
- });
- }
- }
1、URL類型不一樣
在加載本地URL時,是以“file:///”開頭的,而assets目錄所對應的路徑名爲anroid_asset,寫成其它的將識別不了,這是assets目錄的以file開頭的url形式的固定訪問形式。
2、不需要設置WebViewClient
這裏很明顯沒有設置WebViewClient函數,但仍然是在webview中打開的本地文件。具體原因下篇文章講到WebViewClient時我們會具體解釋。
本例效果圖如下:
所以對於加載URL的總結就是:
1、如果是在線網址記得添加網絡訪問權限
2、在線網址中,如果要使用webview打開,記得設置WebViewClient
3、打開本地html文件時,是不需要設置WebViewClient,對應的asstes目錄的url爲:file:///android_asset/xxxxx
3、WebView基本設置
如果我們需要設置WebView的屬性,是通過WebView.getSettings()獲取設置WebView的WebSettings對象,然後調用WebSettings中的方法來實現的。WebSettings的方法及說明如下:(這裏先列出來所有的方法及解釋,大家可以先忽略,看後面的舉例中所使用的幾個常用方法即可,用到哪個函數的時候再回來查查就可以了)
- /**
- * 是否支持縮放,配合方法setBuiltInZoomControls使用,默認true
- */
- setSupportZoom(boolean support)
- /**
- * 是否需要用戶手勢來播放Media,默認true
- */
- setMediaPlaybackRequiresUserGesture(boolean require)
- /**
- * 是否使用WebView內置的縮放組件,由浮動在窗口上的縮放控制和手勢縮放控制組成,默認false
- */
- setBuiltInZoomControls(boolean enabled)
- /**
- * 是否顯示窗口懸浮的縮放控制,默認true
- */
- setDisplayZoomControls(boolean enabled)
- /**
- * 是否允許訪問WebView內部文件,默認true
- */
- setAllowFileAccess(boolean allow)
- /**
- * 是否允許獲取WebView的內容URL ,可以讓WebView訪問ContentPrivider存儲的內容。 默認true
- */
- setAllowContentAccess(boolean allow)
- /**
- * 是否啓動概述模式瀏覽界面,當頁面寬度超過WebView顯示寬度時,縮小頁面適應WebView。默認false
- */
- setLoadWithOverviewMode(boolean overview)
- /**
- * 是否保存表單數據,默認false
- */
- setSaveFormData(boolean save)
- /**
- * 設置頁面文字縮放百分比,默認100%
- */
- setTextZoom(int textZoom)
- /**
- * 是否支持ViewPort的meta tag屬性,如果頁面有ViewPort meta tag 指定的寬度,則使用meta tag指定的值,否則默認使用寬屏的視圖窗口
- */
- setUseWideViewPort(boolean use)
- /**
- * 是否支持多窗口,如果設置爲true ,WebChromeClient#onCreateWindow方法必須被主程序實現,默認false
- */
- setSupportMultipleWindows(boolean support)
- /**
- * 指定WebView的頁面佈局顯示形式,調用該方法會引起頁面重繪。默認LayoutAlgorithm#NARROW_COLUMNS
- */
- setLayoutAlgorithm(LayoutAlgorithm l)
- /**
- * 設置標準的字體族,默認”sans-serif”。font-family 規定元素的字體系列。
- * font-family 可以把多個字體名稱作爲一個“回退”系統來保存。如果瀏覽器不支持第一個字體,
- * 則會嘗試下一個。也就是說,font-family 屬性的值是用於某個元素的字體族名稱或/及類族名稱的一個
- * 優先表。瀏覽器會使用它可識別的第一個值。
- */
- setStandardFontFamily(String font)
- /**
- * 設置混合字體族。默認”monospace”
- */
- setFixedFontFamily(String font)
- /**
- * 設置SansSerif字體族。默認”sans-serif”
- */
- setSansSerifFontFamily(String font)
- /**
- * 設置SerifFont字體族,默認”sans-serif”
- */
- setSerifFontFamily(String font)
- /**
- * 設置CursiveFont字體族,默認”cursive”
- */
- setCursiveFontFamily(String font)
- /**
- * 設置FantasyFont字體族,默認”fantasy”
- */
- setFantasyFontFamily(String font)
- /**
- * 設置最小字體,默認8. 取值區間[1-72],超過範圍,使用其上限值。
- */
- setMinimumFontSize(int size)
- /**
- * 設置最小邏輯字體,默認8. 取值區間[1-72],超過範圍,使用其上限值。
- */
- setMinimumLogicalFontSize(int size)
- /**
- * 設置默認字體大小,默認16,取值區間[1-72],超過範圍,使用其上限值。
- */
- setDefaultFontSize(int size)
- /**
- * 設置默認填充字體大小,默認16,取值區間[1-72],超過範圍,使用其上限值。
- */
- setDefaultFixedFontSize(int size)
- /**
- * 設置是否加載圖片資源,注意:方法控制所有的資源圖片顯示,包括嵌入的本地圖片資源。
- * 使用方法setBlockNetworkImage則只限制網絡資源圖片的顯示。值設置爲true後,
- * webview會自動加載網絡圖片。默認true
- */
- setLoadsImagesAutomatically(boolean flag)
- /**
- * 是否加載網絡圖片資源。注意如果getLoadsImagesAutomatically返回false,則該方法沒有效果。
- * 如果使用setBlockNetworkLoads設置爲false,該方法設置爲false,也不會顯示網絡圖片。
- * 當值從true改爲false時。WebView會自動加載網絡圖片。
- */
- setBlockNetworkImage(boolean flag)
- /**
- * 設置是否加載網絡資源。注意如果值從true切換爲false後,WebView不會自動加載,
- * 除非調用WebView#reload().如果沒有android.Manifest.permission#INTERNET權限,
- * 值設爲false,則會拋出java.lang.SecurityException異常。
- * 默認值:有android.Manifest.permission#INTERNET權限時爲false,其他爲true。
- */
- setBlockNetworkLoads(boolean flag)
- /**
- * 設置是否允許執行JS。
- */
- setJavaScriptEnabled(boolean flag)
- /**
- * 是否允許Js訪問任何來源的內容。包括訪問file scheme的URLs。考慮到安全性,
- * 限制Js訪問範圍默認禁用。注意:該方法隻影響file scheme類型的資源,其他類型資源如圖片類型的,
- * 不會受到影響。ICE_CREAM_SANDWICH_MR1版本以及以下默認爲true,JELLY_BEAN版本
- * 以上默認爲false
- */
- setAllowUniversalAccessFromFileURLs(boolean flag)
- /**
- * 是否允許Js訪問其他file scheme的URLs。包括訪問file scheme的資源。考慮到安全性,
- * 限制Js訪問範圍默認禁用。注意:該方法隻影響file scheme類型的資源,其他類型資源如圖片類型的,
- * 不會受到影響。如果getAllowUniversalAccessFromFileURLs爲true,則該方法被忽略。
- * ICE_CREAM_SANDWICH_MR1版本以及以下默認爲true,JELLY_BEAN版本以上默認爲false
- */
- setAllowFileAccessFromFileURLs(boolean flag)
- /**
- * 設置存儲定位數據庫的位置,考慮到位置權限和持久化Cache緩存,Application需要擁有指定路徑的
- * write權限
- */
- setGeolocationDatabasePath(String databasePath)
- /**
- * 是否允許Cache,默認false。考慮需要存儲緩存,應該爲緩存指定存儲路徑setAppCachePath
- */
- setAppCacheEnabled(boolean flag)
- /**
- * 設置Cache API緩存路徑。爲了保證可以訪問Cache,Application需要擁有指定路徑的write權限。
- * 該方法應該只調用一次,多次調用自動忽略。
- */
- setAppCachePath(String appCachePath)
- /**
- * 是否允許數據庫存儲。默認false。查看setDatabasePath API 如何正確設置數據庫存儲。
- * 該設置擁有全局特性,同一進程所有WebView實例共用同一配置。注意:保證在同一進程的任一WebView
- * 加載頁面之前修改該屬性,因爲在這之後設置WebView可能會忽略該配置
- */
- setDatabaseEnabled(boolean flag)
- /**
- * 是否存儲頁面DOM結構,默認false。
- */
- setDomStorageEnabled(boolean flag)
- /**
- * 是否允許定位,默認true。注意:爲了保證定位可以使用,要保證以下幾點:
- * Application 需要有android.Manifest.permission#ACCESS_COARSE_LOCATION的權限
- * Application 需要實現WebChromeClient#onGeolocationPermissionsShowPrompt的回調,
- * 接收Js定位請求訪問地理位置的通知
- */
- setGeolocationEnabled(boolean flag)
- /**
- * 是否允許JS自動打開窗口。默認false
- */
- setJavaScriptCanOpenWindowsAutomatically(boolean flag)
- /**
- * 設置頁面的編碼格式,默認UTF-8
- */
- setDefaultTextEncodingName(String encoding)
- /**
- * 設置WebView代理,默認使用默認值
- */
- setUserAgentString(String ua)
- /**
- * 通知WebView是否需要設置一個節點獲取焦點當
- * WebView#requestFocus(int,android.graphics.Rect)被調用的時候,默認true
- */
- setNeedInitialFocus(boolean flag)
- /**
- * 基於WebView導航的類型使用緩存:正常頁面加載會加載緩存並按需判斷內容是否需要重新驗證。
- * 如果是頁面返回,頁面內容不會重新加載,直接從緩存中恢復。setCacheMode允許客戶端根據指定的模式來
- * 使用緩存。
- * LOAD_DEFAULT 默認加載方式
- * LOAD_CACHE_ELSE_NETWORK 按網絡情況使用緩存
- * LOAD_NO_CACHE 不使用緩存
- * LOAD_CACHE_ONLY 只使用緩存
- */
- setCacheMode(int mode)
- /**
- * 設置加載不安全資源的WebView加載行爲。KITKAT版本以及以下默認爲MIXED_CONTENT_ALWAYS_ALLOW方
- * 式,LOLLIPOP默認MIXED_CONTENT_NEVER_ALLOW。強烈建議:使用MIXED_CONTENT_NEVER_ALLOW
- */
- setMixedContentMode(int mode)
示例1:在WebView中啓用JavaScript:
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView) findViewById(R.id.webview);
- mBtn = (Button) findViewById(R.id.btn);
- WebSettings webSettings = mWebView.getSettings();
- webSettings.setJavaScriptEnabled(true);
- }
- }
示例2:設置緩存
優先使用緩存- webView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
- webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
示例3:打開頁面時, 自適應屏幕:
- WebSettings webSettings = mWebView .getSettings();
- webSettings.setUseWideViewPort(true);//設置此屬性,可任意比例縮放
- webSettings.setLoadWithOverviewMode(true);
示例4:使頁面支持縮放:
- WebSettings webSettings = mWebView.getSettings();
- //開啓javascript支持
- webSettings.setJavaScriptEnabled(true);
- // 設置可以支持縮放
- webSettings.setSupportZoom(true);
- // 設置出現縮放工具
- webSettings.setBuiltInZoomControls(true);
示例5:.如果webView中需要用戶手動輸入用戶名、密碼或其他,則webview必須設置支持獲取手勢焦點
- webview.requestFocusFromTouch();
二、JS調用Java代碼
在看了如何設置webview以後,我們來看下如何讓Webview與網頁中的JS代碼交互的問題。1、概述
更多時候,網頁中需要通過JS代碼來調用本地的Android代碼,比如H5頁面需要判斷當前用戶是否登錄等。利用JS代碼調用JAVA代碼,主要是用到WebView下面的一個函數:
- public void addJavascriptInterface(Object obj, String interfaceName)
- Object obj:interfaceName所綁定的對象
- String interfaceName:所綁定的對象所對應的名稱
2、示例
下面同樣是上面的示例,我們對它加以更改,效果圖如下:
在原來html上面添加了一個按鈕,當點擊按鈕時調用Android的Toast函數彈出一個toast消息。
先看Android代碼:
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView) findViewById(R.id.webview);
- WebSettings webSettings = mWebView.getSettings();
- webSettings.setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(this, "android");
- mWebView.loadUrl("file:///android_asset/web.html");
- }
- public void toastMessage(String message) {
- Toast.makeText(getApplicationContext(), "通過Natvie傳遞的Toast:"+message, Toast.LENGTH_LONG).show();
- }
- }
- mWebView.addJavascriptInterface(this, "android");
下面我們看看html代碼:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <h1>歡迎光臨啓艦的blog</h1>
- <input type="button" value="js調native" onclick="ok()">
- </head>
- <body>
- <script type="text/javascript">
- function ok() {
- android.toastMessage("哈哈,i m webview msg");
- }
- </script>
- </body>
- </html>
- function ok() {
- android.toastMessage("哈哈,i m webview msg");
- }
3、addJavascriptInterface自定義作用對象
在上面的示例中mWebView.addJavascriptInterface(this, “android”);我們直接通過this,把當前整個類作爲對象傳給WebView了,但這會有很大風險,因爲我們這個類中可能會有各種函數,而這些函數是隻有本地Native代碼纔會用到,WebView是根本用不到的。所以如果通過全部注入給WebView的話,那麼一些存心不良的同學就可以任意調用我們這個類中的方法,給我們APP帶來危害。所以,一般而言,我們很少直接會傳this,把整個類注入給WebView,而是單獨寫一個類專門用於JS交互,比如:
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView) findViewById(R.id.webview);
- mBtn = (Button) findViewById(R.id.btn);
- WebSettings webSettings = mWebView.getSettings();
- webSettings.setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(new JSBridge(), "android");
- mWebView.loadUrl("file:///android_asset/web.html");
- }
- public class JSBridge{
- public void toastMessage(String message) {
- Toast.makeText(getApplicationContext(), "通過Natvie傳遞的Toast:"+message, Toast.LENGTH_LONG).show();
- }
- }
- }
- mWebView.addJavascriptInterface(new JSBridge(), "android");
大家可以自己嘗試下;
然後對應的html代碼:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <h1>歡迎光臨啓艦的blog</h1>
- <input type="button" value="js調native" onclick="ok()">
- </head>
- <body>
- <script type="text/javascript">
- function ok() {
- android.toastMessage("哈哈,i m webview msg");
- }
- </script>
- </body>
- </html>
4、addJavascriptInterface注入漏洞
上面我們說了在addJavascriptInterface注入時,爲了防止WebView調用我們不想被它調用的函數,所以我們需要單獨爲WebView交互定義一個類,讓它只執行這個類裏面的函數但……這真的能擋住黑客的攻擊嗎?
當然是NO……,不然我就不會寫這一段了……
- mWebView.addJavascriptInterface(new JSBridge(), "android");
具體的細節我就不講了,不在本篇範圍,給大家找了篇文章,有興趣的同學可以參考下:《Android WebView的Js對象注入漏洞解決方案》
5、JavascriptInterface註解
爲了解決addJavascriptInterface()函數的安全問題,在android:targetSdkVersion數值爲17(Android4.2)及以上的APP中,JS只能訪問帶有 @JavascriptInterface註解的Java函數,所以如果你的android:targetSdkVersion是17+,與JS交互的Native函數中,必須添加JavascriptInterface註解,不然無效,比如:- public class JSBridge {
- @JavascriptInterface
- public void toastMessage(String message) {
- Toast.makeText(getApplicationContext(), "通過Natvie傳遞的Toast:" + message, Toast.LENGTH_LONG).show();
- }
- }
注意:雖然在target 17以後,已經修復了這個安全問題,但目前大多數APP都還是target 17以前的,所以大家可以嘗試着找一些APP來演示下這個漏洞哦……
三、JAVA調用JS代碼
1、JAVA調用JS代碼
前面給大家演示瞭如何通過JS調用Java代碼,這裏就反過來看看,如何在Native中調用JS的代碼本例的效果圖如下:
在點擊“加載URL”按鈕時,調用webview中的JavaScript求和函數,將結果顯示在標題中。
先看html代碼:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <h1 id="h">歡迎光臨啓艦的blog</h1>
- <input type="button" value="js調native" onclick="ok()">
- </head>
- <body>
- <script type="text/javascript">
- function sum(i,m)
- {
- document.getElementById("h").innerHTML= (i+m);
- }
- </script>
- </body>
- </html>
結果中就是把h1標籤的內容改爲求和後的結果值。
再來看看JAVA的調用代碼:
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView) findViewById(R.id.webview);
- mBtn = (Button) findViewById(R.id.btn);
- WebSettings webSettings = mWebView.getSettings();
- webSettings.setJavaScriptEnabled(true);
- mWebView.loadUrl("file:///android_asset/web.html");
- mBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mWebView.loadUrl("javascript:sum(3,8)");
- }
- });
- }
- }
- mWebView.loadUrl("javascript:sum(3,8)");
- String url = "javascript:methodName(params……);"
- webView.loadUrl(url);
中間methodName是JavaScript中實現的函數
jsonParams是傳入的參數列表
使用起來難度不大,就不再多講了
2、JAVA中如何得到JS中的返回值(Android4.4以前)
現在我們再考慮一下,如果我們要在JAVA中需要得到JS的結果返回值要怎麼辦?比如在上面的例子中,我們需要在JAVA中得到在計算後的結果值Android在4.4之前並沒有提供直接調用js函數並獲取值的方法,也就是說,我們只能調用JS中的函數,並不能得到該函數的返回值,想得到返回值我們就得想其它辦法,所以在此之前,常用的思路是 java調用js方法,js方法執行完畢,再次調用java代碼將值返回。
1.Java調用js代碼
- webView.addJavascriptInterface(this, "android");
- mWebView.loadUrl("javascript:sum(3,8)");
2.js函數處理,並將結果通過調用java方法返回
- function sum(i,m){
- var result = i+m;
- document.getElementById("h").innerHTML= result;
- android.onSumResult(result)
- }
- public void onSumResult(int result) {
- Log.i(LOGTAG, "onSumResult result=" + result);
- }
下面我們就完整地看一下代碼:
JS代碼:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <h1 id="h">歡迎光臨啓艦的blog</h1>
- <input type="button" value="js調native" onclick="ok()">
- </head>
- <body>
- <script type="text/javascript">
- function sum(i,m){
- var result = i+m;
- document.getElementById("h").innerHTML= result;
- android.onSumResult(result);
- }
- </script>
- </body>
- </html>
然後再來看看JAVA代碼:
- public class MyActivity extends Activity {
- private WebView mWebView;
- private Button mBtn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mWebView = (WebView) findViewById(R.id.webview);
- mBtn = (Button) findViewById(R.id.btn);
- WebSettings webSettings = mWebView.getSettings();
- webSettings.setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(this, "android");
- mWebView.loadUrl("file:///android_asset/web.html");
- mBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mWebView.loadUrl("javascript:sum(3,8)");
- }
- });
- }
- public void onSumResult(int result) {
- Toast.makeText(this,"received result:"+result,Toast.LENGTH_SHORT).show();
- }
- }
第一,通過addJavascriptInterface注入MyActivity對象,以便JS訪問其中的函數
- mWebView.addJavascriptInterface(this, "android");
- public void onSumResult(int result) {
- Toast.makeText(this,"received result:"+result,Toast.LENGTH_SHORT).show();
- }
3、JAVA中如何得到JS中的返回值(Android4.4之後)
Android 4.4之後使用evaluateJavascript即可。這裏展示一個簡單的交互示例先寫一個具有返回值的js方法
- function getGreetings() {
- return 1;
- }
- private void testEvaluateJavascript(WebView webView) {
- webView.evaluateJavascript("getGreetings()", new ValueCallback() {
- @Override
- public void onReceiveValue(String value) {
- Log.i(LOGTAG, "onReceiveValue value=" + value);
- }
- });
- }
注意:
- 上面限定了結果返回結果爲String,對於簡單的類型會嘗試轉換成字符串返回,對於複雜的數據類型,建議以字符串形式的json返回。
- evaluateJavascript方法必須在UI線程(主線程)調用,因此onReceiveValue也執行在主線程。
四、jadx-gui反編譯
在遇到jadx-gui反編譯之前,都是使用apktools進行反編譯的,apktools有些是反編譯不出來的,而且……難用……想知道jadx-gui有多強?它都可以反編譯淘寶、微信代碼,是不是夠強了。下面我們就來看看它是如何反編譯的;
1、下載、配置
jadx-gui是開源的,項目地址:《https://github.com/skylot/jadx》mac電腦:
打開終端,切到某個路徑下,輸入以下命令:
- git clone https://github.com/skylot/jadx.git
- cd jadx
- ./gradlew dist
第一,使用git命令將 項目clone下來(這裏需要配置git環境,如果沒有,請先搜資料配置git環境,然後再來)
然後,執行jadx目錄 下gradlew腳本,這個是shell腳本
windows電腦:
- git clone https://github.com/skylot/jadx.git
- cd jadx
- gradlew.bat dist
可見作者有多牛X,shell腳本和bat腳本都會寫,真是屌的不能直視
整個編譯過程是比較慢的,這裏需要耐心等待下。因爲目前會使用gradle 2.7來編譯項目,如果沒有在環境變量中環境gradle 2.7的環境變量,會自己下載gradle 2.7
編譯成功後會打出BUILD SUCCESS字樣,如下圖所示:
在編譯成功後,在jadx目錄下,會生成一個build目錄,其中包含jadx目錄和一個jadx-xxx-dev.zip的打包文件。在build/jadx目錄下,就是源碼編譯出的jadx工具及所用jar包。jadx-xxx-dev.zip解壓後的內容與build/jadx內容一樣,只是將其打包了一下而已,方便移值,可見作者有多用心。build目錄結構如下圖所示:
2、開始反編譯
等完畢後,可以開始了,我就介紹個最簡單最常用的用法(1)、把apk改成zip
(2)、解壓zip獲取class.dex文件
(3)、將class.dex文件放到jadx目錄下
- cd build/jadx/
- bin/jadx -d out classes.dex # 反編譯後放入out文件夾下(如果out不存在它會自動創建)
- #or
- bin/jadx-gui classes.dex # 會反編譯,並且使用gui打開
在使用jadx-gui反編譯時,左下角會顯示當前反編譯的進度:
在反編譯完成後,在左側就可以看到目錄結構和對應的代碼,目錄結構中顯示的a,b,c,d這些字母是由於在生成apk時使用proguard混淆造成的,proguard混淆的類名是沒辦法反編譯出對應的原類名的,這也是反編譯代碼中比較蛋疼的地方,下面給大家演示下結果:(還可以通過點擊搜索按鈕,搜索其中的代碼)
有關jadx-gui工具的更多用法,只有靠大家自己去研究啦,到這裏我們的源碼就已經反編譯出來了。
如果本文有幫到你,記得加關注哦
源碼下載地址:http://download.csdn.net/detail/harvic880925/9526801
請大家尊重原創者版權,轉載請標明出處:http://blog.csdn.net/harvic880925/article/details/51464687 謝謝