WebView使用詳解(一)——Native與JS相互調用

轉自:http://blog.csdn.net/harvic880925/article/details/51464687

一直在用WebView,還沒有系統的總結過它的用法,下面就係統的總結下,分享給大家

一、基本用法

1、加載在線URL

[java] view plain copy
  1. void loadUrl(String url)  
這個函數主要加載url所對應的網頁地址,或者用於調用網頁中的指定的JS方法(調用js方法的用法,後面會講),但有一點必須注意的是:loadUrl()必須在主線程中執行!!!否則就會報錯!!!。
注意:加載在線網頁地址是會用到聯網permission權限的,所以需要在AndroidManifest.xml中寫入下面代碼申請權限:
[html] view plain copy
  1. <uses-permission android:name="android.permission.INTERNET" />  
本示例效果爲:

從效果圖中可以明顯看出本示例的佈局: 
main.xml

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.               android:orientation="vertical"  
  4.               android:layout_width="fill_parent"  
  5.               android:layout_height="fill_parent"  
  6.         >  
  7.     <Button  
  8.             android:id="@+id/btn"  
  9.             android:layout_width="match_parent"  
  10.             android:layout_height="wrap_content"  
  11.             android:text="加載URL"/>  
  12.   
  13.     <WebView  
  14.             android:id="@+id/webview"  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="match_parent"/>  
  17. </LinearLayout>  
對應的處理代碼如下
[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.main);  
  9.   
  10.         mWebView = (WebView)findViewById(R.id.webview);  
  11.         mBtn = (Button)findViewById(R.id.btn);  
  12.   
  13.         mBtn.setOnClickListener(new View.OnClickListener() {  
  14.             @Override  
  15.             public void onClick(View v) {  
  16.                 mWebView.loadUrl("http://www.baidu.com");  
  17.             }  
  18.         });  
  19.     }  
  20. }  
代碼很簡單,就是在點擊按鈕的時候加載網址,但需要注意的是:網址必須完整即以http://或者ftp://等協議開頭,不能省略!不然將加載不出來,這是因爲webview是沒有自動補全協議功能的,所以如果我們不加,它將識別不出來網址類型,也就加載不出來了。 
但如果我們運行上面的代碼,效果卻是利用瀏覽器來打開網址,卻不是使用webview打開網址:

如果我們想實現像示例一樣在webview中打開網址需要怎麼做呢? 
我們需要設置WebViewClient: 
修改後的代碼爲:

[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.main);  
  9.   
  10.         mWebView = (WebView)findViewById(R.id.webview);  
  11.         mBtn = (Button)findViewById(R.id.btn);  
  12.   
  13.         mWebView.setWebViewClient(new WebViewClient());  
  14.   
  15.         mBtn.setOnClickListener(new View.OnClickListener() {  
  16.             @Override  
  17.             public void onClick(View v) {  
  18.                 mWebView.loadUrl("http://www.baidu.com");  
  19.             }  
  20.         });  
  21.     }  
  22. }  
在上面的基礎上,我們添加了下面一段代碼:
[java] view plain copy
  1. mWebView.setWebViewClient(new WebViewClient());  
在這裏我們利用mWebView.setWebViewClient()函數僅僅設置了一個WebViewClient實例,就可以實現在WebView中打開鏈接了,至於原因我們下篇會講到,這裏就先忽略了,大家只需要知道要在WebView中打開鏈接,就必須要設置WebViewClient; 
最終的效果圖就與開篇時一樣的了,這裏就不再帖效果圖了,下面我們來看看如何加載本地html網頁 
源碼在文章底部給出

2、加載本地URL

一般而言,我們會將本地html文件放在assets文件夾下,比如:


web.html的內容爲:

[html] view plain copy
  1. <!DOCTYPE html>  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="UTF-8">  
  5.     <title>Title</title>  
  6.     <h1>歡迎光臨啓艦的blog</h1>  
  7. </head>  
  8. <body>  
  9. </body>  
  10. </html>  
即大標題顯示一段文字 
我們同樣在上面的示例的基礎上加以改造,在點擊按鈕的時候加載本地web.html文件
[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.main);  
  9.   
  10.         mWebView = (WebView)findViewById(R.id.webview);  
  11.         mBtn = (Button)findViewById(R.id.btn);  
  12.   
  13.         mBtn.setOnClickListener(new View.OnClickListener() {  
  14.             @Override  
  15.             public void onClick(View v) {  
  16.                 mWebView.loadUrl("file:///android_asset/web.html");  
  17.             }  
  18.         });  
  19.     }  
  20. }      
從這裏可以看到與加載在線URL有兩點不同: 
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的方法及說明如下:(這裏先列出來所有的方法及解釋,大家可以先忽略,看後面的舉例中所使用的幾個常用方法即可,用到哪個函數的時候再回來查查就可以了)

[java] view plain copy
  1. /** 
  2.  * 是否支持縮放,配合方法setBuiltInZoomControls使用,默認true 
  3.  */  
  4. setSupportZoom(boolean support)  
  5.   
  6. /** 
  7.  * 是否需要用戶手勢來播放Media,默認true 
  8.  */  
  9. setMediaPlaybackRequiresUserGesture(boolean require)  
  10.   
  11. /** 
  12.  * 是否使用WebView內置的縮放組件,由浮動在窗口上的縮放控制和手勢縮放控制組成,默認false 
  13.  */  
  14. setBuiltInZoomControls(boolean enabled)  
  15.   
  16. /** 
  17.  * 是否顯示窗口懸浮的縮放控制,默認true 
  18.  */  
  19. setDisplayZoomControls(boolean enabled)  
  20.   
  21. /** 
  22.  * 是否允許訪問WebView內部文件,默認true 
  23.  */  
  24. setAllowFileAccess(boolean allow)  
  25.   
  26. /** 
  27.  * 是否允許獲取WebView的內容URL ,可以讓WebView訪問ContentPrivider存儲的內容。 默認true 
  28.  */  
  29. setAllowContentAccess(boolean allow)  
  30.   
  31. /** 
  32.  * 是否啓動概述模式瀏覽界面,當頁面寬度超過WebView顯示寬度時,縮小頁面適應WebView。默認false 
  33.  */  
  34. setLoadWithOverviewMode(boolean overview)  
  35.   
  36. /** 
  37.  * 是否保存表單數據,默認false 
  38.  */  
  39. setSaveFormData(boolean save)  
  40.   
  41. /** 
  42.  * 設置頁面文字縮放百分比,默認100% 
  43.  */  
  44. setTextZoom(int textZoom)  
  45.   
  46. /** 
  47.  * 是否支持ViewPort的meta tag屬性,如果頁面有ViewPort meta tag 指定的寬度,則使用meta tag指定的值,否則默認使用寬屏的視圖窗口 
  48.  */  
  49. setUseWideViewPort(boolean use)  
  50.   
  51.   
  52. /** 
  53.  * 是否支持多窗口,如果設置爲true ,WebChromeClient#onCreateWindow方法必須被主程序實現,默認false 
  54.  */  
  55. setSupportMultipleWindows(boolean support)  
  56.   
  57. /** 
  58.  * 指定WebView的頁面佈局顯示形式,調用該方法會引起頁面重繪。默認LayoutAlgorithm#NARROW_COLUMNS 
  59.  */  
  60. setLayoutAlgorithm(LayoutAlgorithm l)  
  61.   
  62. /** 
  63.  * 設置標準的字體族,默認”sans-serif”。font-family 規定元素的字體系列。 
  64.  * font-family 可以把多個字體名稱作爲一個“回退”系統來保存。如果瀏覽器不支持第一個字體, 
  65.  * 則會嘗試下一個。也就是說,font-family 屬性的值是用於某個元素的字體族名稱或/及類族名稱的一個 
  66.  * 優先表。瀏覽器會使用它可識別的第一個值。 
  67.  */  
  68. setStandardFontFamily(String font)  
  69.   
  70. /** 
  71.  * 設置混合字體族。默認”monospace” 
  72.  */  
  73. setFixedFontFamily(String font)  
  74.   
  75. /** 
  76.  * 設置SansSerif字體族。默認”sans-serif” 
  77.  */  
  78. setSansSerifFontFamily(String font)  
  79.   
  80. /** 
  81.  * 設置SerifFont字體族,默認”sans-serif” 
  82.  */  
  83. setSerifFontFamily(String font)  
  84.   
  85. /** 
  86.  * 設置CursiveFont字體族,默認”cursive” 
  87.  */  
  88. setCursiveFontFamily(String font)  
  89.   
  90. /** 
  91.  * 設置FantasyFont字體族,默認”fantasy” 
  92.  */  
  93. setFantasyFontFamily(String font)  
  94.   
  95. /** 
  96.  * 設置最小字體,默認8. 取值區間[1-72],超過範圍,使用其上限值。 
  97.  */  
  98. setMinimumFontSize(int size)  
  99.   
  100. /** 
  101.  * 設置最小邏輯字體,默認8. 取值區間[1-72],超過範圍,使用其上限值。 
  102.  */  
  103. setMinimumLogicalFontSize(int size)  
  104.   
  105. /** 
  106.  * 設置默認字體大小,默認16,取值區間[1-72],超過範圍,使用其上限值。 
  107.  */  
  108. setDefaultFontSize(int size)  
  109.   
  110. /** 
  111.  * 設置默認填充字體大小,默認16,取值區間[1-72],超過範圍,使用其上限值。 
  112.  */  
  113. setDefaultFixedFontSize(int size)  
  114.   
  115. /** 
  116.  * 設置是否加載圖片資源,注意:方法控制所有的資源圖片顯示,包括嵌入的本地圖片資源。 
  117.  * 使用方法setBlockNetworkImage則只限制網絡資源圖片的顯示。值設置爲true後, 
  118.  * webview會自動加載網絡圖片。默認true 
  119.  */  
  120. setLoadsImagesAutomatically(boolean flag)  
  121.   
  122. /** 
  123.  * 是否加載網絡圖片資源。注意如果getLoadsImagesAutomatically返回false,則該方法沒有效果。 
  124.  * 如果使用setBlockNetworkLoads設置爲false,該方法設置爲false,也不會顯示網絡圖片。 
  125.  * 當值從true改爲false時。WebView會自動加載網絡圖片。 
  126.  */  
  127. setBlockNetworkImage(boolean flag)  
  128.   
  129. /** 
  130.  * 設置是否加載網絡資源。注意如果值從true切換爲false後,WebView不會自動加載, 
  131.  * 除非調用WebView#reload().如果沒有android.Manifest.permission#INTERNET權限, 
  132.  * 值設爲false,則會拋出java.lang.SecurityException異常。 
  133.  * 默認值:有android.Manifest.permission#INTERNET權限時爲false,其他爲true。 
  134.  */  
  135. setBlockNetworkLoads(boolean flag)  
  136.   
  137. /** 
  138.  * 設置是否允許執行JS。 
  139.  */  
  140. setJavaScriptEnabled(boolean flag)  
  141.   
  142. /** 
  143.  * 是否允許Js訪問任何來源的內容。包括訪問file scheme的URLs。考慮到安全性, 
  144.  * 限制Js訪問範圍默認禁用。注意:該方法隻影響file scheme類型的資源,其他類型資源如圖片類型的, 
  145.  * 不會受到影響。ICE_CREAM_SANDWICH_MR1版本以及以下默認爲true,JELLY_BEAN版本 
  146.  * 以上默認爲false 
  147.  */  
  148. setAllowUniversalAccessFromFileURLs(boolean flag)  
  149.   
  150.   
  151. /** 
  152.  * 是否允許Js訪問其他file scheme的URLs。包括訪問file scheme的資源。考慮到安全性, 
  153.  * 限制Js訪問範圍默認禁用。注意:該方法隻影響file scheme類型的資源,其他類型資源如圖片類型的, 
  154.  * 不會受到影響。如果getAllowUniversalAccessFromFileURLs爲true,則該方法被忽略。 
  155.  * ICE_CREAM_SANDWICH_MR1版本以及以下默認爲true,JELLY_BEAN版本以上默認爲false 
  156.  */  
  157. setAllowFileAccessFromFileURLs(boolean flag)  
  158.   
  159. /** 
  160.  * 設置存儲定位數據庫的位置,考慮到位置權限和持久化Cache緩存,Application需要擁有指定路徑的 
  161.  * write權限 
  162.  */  
  163. setGeolocationDatabasePath(String databasePath)  
  164.   
  165. /** 
  166.  * 是否允許Cache,默認false。考慮需要存儲緩存,應該爲緩存指定存儲路徑setAppCachePath 
  167.  */  
  168. setAppCacheEnabled(boolean flag)  
  169.   
  170. /** 
  171.  * 設置Cache API緩存路徑。爲了保證可以訪問Cache,Application需要擁有指定路徑的write權限。 
  172.  * 該方法應該只調用一次,多次調用自動忽略。 
  173.  */  
  174. setAppCachePath(String appCachePath)  
  175.   
  176. /** 
  177.  * 是否允許數據庫存儲。默認false。查看setDatabasePath API 如何正確設置數據庫存儲。 
  178.  * 該設置擁有全局特性,同一進程所有WebView實例共用同一配置。注意:保證在同一進程的任一WebView 
  179.  * 加載頁面之前修改該屬性,因爲在這之後設置WebView可能會忽略該配置 
  180.  */  
  181. setDatabaseEnabled(boolean flag)  
  182.   
  183. /** 
  184.  * 是否存儲頁面DOM結構,默認false。 
  185.  */  
  186. setDomStorageEnabled(boolean flag)  
  187.   
  188. /** 
  189.  * 是否允許定位,默認true。注意:爲了保證定位可以使用,要保證以下幾點: 
  190.  * Application 需要有android.Manifest.permission#ACCESS_COARSE_LOCATION的權限 
  191.  * Application 需要實現WebChromeClient#onGeolocationPermissionsShowPrompt的回調, 
  192.  * 接收Js定位請求訪問地理位置的通知 
  193.  */  
  194. setGeolocationEnabled(boolean flag)  
  195.   
  196. /** 
  197.  * 是否允許JS自動打開窗口。默認false 
  198.  */  
  199. setJavaScriptCanOpenWindowsAutomatically(boolean flag)  
  200.   
  201. /** 
  202.  * 設置頁面的編碼格式,默認UTF-8 
  203.  */  
  204. setDefaultTextEncodingName(String encoding)  
  205.   
  206. /** 
  207.  * 設置WebView代理,默認使用默認值 
  208.  */  
  209. setUserAgentString(String ua)  
  210.   
  211. /** 
  212.  * 通知WebView是否需要設置一個節點獲取焦點當 
  213.  * WebView#requestFocus(int,android.graphics.Rect)被調用的時候,默認true 
  214.  */  
  215. setNeedInitialFocus(boolean flag)  
  216.   
  217. /** 
  218.  * 基於WebView導航的類型使用緩存:正常頁面加載會加載緩存並按需判斷內容是否需要重新驗證。 
  219.  * 如果是頁面返回,頁面內容不會重新加載,直接從緩存中恢復。setCacheMode允許客戶端根據指定的模式來 
  220.  * 使用緩存。 
  221.  * LOAD_DEFAULT 默認加載方式 
  222.  * LOAD_CACHE_ELSE_NETWORK 按網絡情況使用緩存 
  223.  * LOAD_NO_CACHE 不使用緩存 
  224.  * LOAD_CACHE_ONLY 只使用緩存 
  225.  */  
  226. setCacheMode(int mode)  
  227.   
  228. /** 
  229.  * 設置加載不安全資源的WebView加載行爲。KITKAT版本以及以下默認爲MIXED_CONTENT_ALWAYS_ALLOW方 
  230.  * 式,LOLLIPOP默認MIXED_CONTENT_NEVER_ALLOW。強烈建議:使用MIXED_CONTENT_NEVER_ALLOW 
  231.  */  
  232. setMixedContentMode(int mode)  
下面我們就舉個例子來看下用法 

示例1:在WebView中啓用JavaScript:

[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.   
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.   
  11.         mWebView = (WebView) findViewById(R.id.webview);  
  12.         mBtn = (Button) findViewById(R.id.btn);  
  13.   
  14.         WebSettings webSettings = mWebView.getSettings();  
  15.         webSettings.setJavaScriptEnabled(true);  
  16.     }  
  17. }  

示例2:設置緩存

優先使用緩存
[java] view plain copy
  1. webView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  
不使用緩存:
[java] view plain copy
  1. webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);  

示例3:打開頁面時, 自適應屏幕:

[java] view plain copy
  1. WebSettings webSettings =   mWebView .getSettings();         
  2. webSettings.setUseWideViewPort(true);//設置此屬性,可任意比例縮放  
  3. webSettings.setLoadWithOverviewMode(true);  
效果圖如下:(所使用的網址爲:http://www.w3school.com.cn/)

注意:對於我們自己寫的網頁代碼,不必利用這處函數來做頁面縮放適配,因爲對於有些手機存在適配問題,只需要在HTML中做寬度100%自適應屏幕就行了

示例4:使頁面支持縮放:

[java] view plain copy
  1. WebSettings webSettings = mWebView.getSettings();  
  2. //開啓javascript支持  
  3. webSettings.setJavaScriptEnabled(true);   
  4. // 設置可以支持縮放  
  5. webSettings.setSupportZoom(true);  
  6. // 設置出現縮放工具  
  7. webSettings.setBuiltInZoomControls(true);  

示例5:.如果webView中需要用戶手動輸入用戶名、密碼或其他,則webview必須設置支持獲取手勢焦點

[java] view plain copy
  1. webview.requestFocusFromTouch();  
其它的設置就靠大家自己去嘗試啦,這裏就不再一一綴述了,大家只需要記着,如果需要設置webview就從WebSettings裏面去找就可以啦。 
源碼在文章底部給出

二、JS調用Java代碼

在看了如何設置webview以後,我們來看下如何讓Webview與網頁中的JS代碼交互的問題。

1、概述

更多時候,網頁中需要通過JS代碼來調用本地的Android代碼,比如H5頁面需要判斷當前用戶是否登錄等。 
利用JS代碼調用JAVA代碼,主要是用到WebView下面的一個函數:
[java] view plain copy
  1. public void addJavascriptInterface(Object obj, String interfaceName)  
這個函數有兩個參數:
  • Object obj:interfaceName所綁定的對象
  • String interfaceName:所綁定的對象所對應的名稱
它有意義就是向WebView注入一個interfaceName的對象,這個對象綁定的是obj對象,通過interfaceName就可以調用obj對象中的方法,這個表述可能大家不太理解,因爲interfaceName是一個String,怎麼被你說成對象了,理解不了沒關係,下面有具體示例

2、示例

下面同樣是上面的示例,我們對它加以更改,效果圖如下:


在原來html上面添加了一個按鈕,當點擊按鈕時調用Android的Toast函數彈出一個toast消息。 
先看Android代碼:

[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.   
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.   
  11.         mWebView = (WebView) findViewById(R.id.webview);  
  12.   
  13.         WebSettings webSettings = mWebView.getSettings();  
  14.         webSettings.setJavaScriptEnabled(true);  
  15.         mWebView.addJavascriptInterface(this"android");  
  16.         mWebView.loadUrl("file:///android_asset/web.html");  
  17.     }  
  18.   
  19.     public void toastMessage(String message) {  
  20.         Toast.makeText(getApplicationContext(), "通過Natvie傳遞的Toast:"+message, Toast.LENGTH_LONG).show();  
  21.     }  
  22. }      
這裏最主要是的下面這句:
[java] view plain copy
  1. mWebView.addJavascriptInterface(this"android");  
這句的意思是把MyActivity對象注入到WebView中,在WebView中的對象別名叫android;另外,我們還在MyActivity中額外寫了一個函數toastMessage(String message),用於彈出MSG 
下面我們看看html代碼:
[html] view plain copy
  1. <!DOCTYPE html>  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="UTF-8">  
  5.     <title>Title</title>  
  6.     <h1>歡迎光臨啓艦的blog</h1>  
  7.     <input type="button" value="js調native" onclick="ok()">  
  8. </head>  
  9. <body>  
  10. <script type="text/javascript">  
  11. function ok() {  
  12.  android.toastMessage("哈哈,i m webview msg");  
  13. }  
  14. </script>  
  15. </body>  
  16. </html>  
在這個html中,我添加了一個button按鈕,當點擊時調用ok函數:
[html] view plain copy
  1. function ok() {  
  2.  android.toastMessage("哈哈,i m webview msg");  
  3. }  
它的意義就是調用android對象裏的toastMessage方法,這個android就是我們利用mWebView.addJavascriptInterface(this, “android”)注入到WebView的android,它所對應的對象就將MyActivity;所以在JS中,我們就可以通過android這個別名來調用MyActivity對象中的任何public方法。 
源碼在文章底部給出

3、addJavascriptInterface自定義作用對象

在上面的示例中mWebView.addJavascriptInterface(this, “android”);我們直接通過this,把當前整個類作爲對象傳給WebView了,但這會有很大風險,因爲我們這個類中可能會有各種函數,而這些函數是隻有本地Native代碼纔會用到,WebView是根本用不到的。所以如果通過全部注入給WebView的話,那麼一些存心不良的同學就可以任意調用我們這個類中的方法,給我們APP帶來危害。 
所以,一般而言,我們很少直接會傳this,把整個類注入給WebView,而是單獨寫一個類專門用於JS交互,比如:
[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.   
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.   
  11.         mWebView = (WebView) findViewById(R.id.webview);  
  12.         mBtn = (Button) findViewById(R.id.btn);  
  13.   
  14.         WebSettings webSettings = mWebView.getSettings();  
  15.         webSettings.setJavaScriptEnabled(true);  
  16.         mWebView.addJavascriptInterface(new JSBridge(), "android");  
  17.         mWebView.loadUrl("file:///android_asset/web.html");  
  18.     }  
  19.   
  20.     public class JSBridge{  
  21.         public void toastMessage(String message) {  
  22.             Toast.makeText(getApplicationContext(), "通過Natvie傳遞的Toast:"+message, Toast.LENGTH_LONG).show();  
  23.         }  
  24.     }  
  25. }      
在這段代碼中,在通過addJavascriptInterface注入時:
[java] view plain copy
  1. mWebView.addJavascriptInterface(new JSBridge(), "android");  
指定android對象綁定的是JSBridge對象!所以在WebView中,通過JS只能訪問JSBridge中所定義的對象,如果訪問其它類的函數,比如MyActivity中的函數,就會報下面的錯誤(即方法找不到)

大家可以自己嘗試下; 
然後對應的html代碼:

[html] view plain copy
  1. <!DOCTYPE html>  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="UTF-8">  
  5.     <title>Title</title>  
  6.     <h1>歡迎光臨啓艦的blog</h1>  
  7.     <input type="button" value="js調native" onclick="ok()">  
  8. </head>  
  9. <body>  
  10. <script type="text/javascript">  
  11. function ok() {  
  12.   android.toastMessage("哈哈,i m webview msg");  
  13.  }  
  14. </script>  
  15. </body>  
  16. </html>  
由於在注入時的對象別名和所調用的函數名都沒有變,所以HTML中的JS代碼是無需更改的。效果圖與上節一樣,就不再帖出了。 
源碼在文章底部給出

4、addJavascriptInterface注入漏洞

上面我們說了在addJavascriptInterface注入時,爲了防止WebView調用我們不想被它調用的函數,所以我們需要單獨爲WebView交互定義一個類,讓它只執行這個類裏面的函數 
但……這真的能擋住黑客的攻擊嗎? 
當然是NO……,不然我就不會寫這一段了……
[java] view plain copy
  1. mWebView.addJavascriptInterface(new JSBridge(), "android");  
在注入時,我們已經把對象傳給了JS,在JS中當然可以通過反射得到APP中的各種類的實例!現在反編譯Android代碼可不是什麼難事(本文結尾附jadx反編譯方法),很容易拿到你有哪些類,有哪些函數,通過這些就可以想執行哪個執行哪個了,有沒有細思極恐…… 
具體的細節我就不講了,不在本篇範圍,給大家找了篇文章,有興趣的同學可以參考下:《Android WebView的Js對象注入漏洞解決方案》

5、JavascriptInterface註解

爲了解決addJavascriptInterface()函數的安全問題,在android:targetSdkVersion數值爲17(Android4.2)及以上的APP中,JS只能訪問帶有 @JavascriptInterface註解的Java函數,所以如果你的android:targetSdkVersion是17+,與JS交互的Native函數中,必須添加JavascriptInterface註解,不然無效,比如:
[java] view plain copy
  1. public class JSBridge {  
  2.     @JavascriptInterface  
  3.     public void toastMessage(String message) {  
  4.         Toast.makeText(getApplicationContext(), "通過Natvie傳遞的Toast:" + message, Toast.LENGTH_LONG).show();  
  5.     }  
  6. }  
這也就是很多同學在高target上,addJavascriptInterface()函數無效的主要原因。
注意:雖然在target 17以後,已經修復了這個安全問題,但目前大多數APP都還是target 17以前的,所以大家可以嘗試着找一些APP來演示下這個漏洞哦……

三、JAVA調用JS代碼

1、JAVA調用JS代碼

前面給大家演示瞭如何通過JS調用Java代碼,這裏就反過來看看,如何在Native中調用JS的代碼 
本例的效果圖如下:

在點擊“加載URL”按鈕時,調用webview中的JavaScript求和函數,將結果顯示在標題中。 
先看html代碼:

[html] view plain copy
  1. <!DOCTYPE html>  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="UTF-8">  
  5.     <title>Title</title>  
  6.     <h1 id="h">歡迎光臨啓艦的blog</h1>  
  7.     <input type="button" value="js調native" onclick="ok()">  
  8. </head>  
  9. <body>  
  10. <script type="text/javascript">  
  11. function sum(i,m)  
  12. {  
  13.  document.getElementById("h").innerHTML= (i+m);  
  14. }  
  15. </script>  
  16. </body>  
  17. </html>  
在這裏,我們寫了一個求和函數sum(i,m) 
結果中就是把h1標籤的內容改爲求和後的結果值。 
再來看看JAVA的調用代碼:
[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.   
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.   
  11.         mWebView = (WebView) findViewById(R.id.webview);  
  12.         mBtn = (Button) findViewById(R.id.btn);  
  13.   
  14.         WebSettings webSettings = mWebView.getSettings();  
  15.         webSettings.setJavaScriptEnabled(true);  
  16.         mWebView.loadUrl("file:///android_asset/web.html");  
  17.   
  18.         mBtn.setOnClickListener(new View.OnClickListener() {  
  19.             @Override  
  20.             public void onClick(View v) {  
  21.                 mWebView.loadUrl("javascript:sum(3,8)");  
  22.             }  
  23.         });  
  24.      }  
  25. }          
在點擊按鈕時調用JS函數:
[java] view plain copy
  1. mWebView.loadUrl("javascript:sum(3,8)");  
這裏也就是在JAVA中調用JS函數的方法:
[java] view plain copy
  1. String url = "javascript:methodName(params……);"  
  2. webView.loadUrl(url);  
javascript:僞協議讓我們可以通過一個鏈接來調用JavaScript函數 
中間methodName是JavaScript中實現的函數 
jsonParams是傳入的參數列表 
使用起來難度不大,就不再多講了 
源碼在文章底部給出

2、JAVA中如何得到JS中的返回值(Android4.4以前)

現在我們再考慮一下,如果我們要在JAVA中需要得到JS的結果返回值要怎麼辦?比如在上面的例子中,我們需要在JAVA中得到在計算後的結果值 
Android在4.4之前並沒有提供直接調用js函數並獲取值的方法,也就是說,我們只能調用JS中的函數,並不能得到該函數的返回值,想得到返回值我們就得想其它辦法,所以在此之前,常用的思路是 java調用js方法,js方法執行完畢,再次調用java代碼將值返回。 

1.Java調用js代碼

[java] view plain copy
  1. webView.addJavascriptInterface(this"android");  
  2. mWebView.loadUrl("javascript:sum(3,8)");  
注意,這裏通過addJavascriptInterface將MyActiviy所對應的對象注入到WebView中了。 
2.js函數處理,並將結果通過調用java方法返回
[java] view plain copy
  1. function sum(i,m){  
  2.  var result = i+m;  
  3.  document.getElementById("h").innerHTML= result;  
  4.  android.onSumResult(result)  
  5. }  
3..Java在回調方法中獲取js函數返回值
[java] view plain copy
  1. public void onSumResult(int result) {  
  2.   Log.i(LOGTAG, "onSumResult result=" + result);  
  3. }  
先看下效果圖:

下面我們就完整地看一下代碼: 
JS代碼:

[html] view plain copy
  1. <!DOCTYPE html>  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="UTF-8">  
  5.     <title>Title</title>  
  6.     <h1 id="h">歡迎光臨啓艦的blog</h1>  
  7.     <input type="button" value="js調native" onclick="ok()">  
  8. </head>  
  9. <body>  
  10. <script type="text/javascript">  
  11. function sum(i,m){  
  12.  var result = i+m;  
  13.  document.getElementById("h").innerHTMLresult;  
  14.  android.onSumResult(result);  
  15. }  
  16. </script>  
  17. </body>  
  18. </html>  
在function sum(i,m)中,先通過result得到結果,最後通過android.onSumResult(result);將結果傳給Native 
然後再來看看JAVA代碼:
[java] view plain copy
  1. public class MyActivity extends Activity {  
  2.   
  3.     private WebView mWebView;  
  4.     private Button mBtn;  
  5.   
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.   
  11.         mWebView = (WebView) findViewById(R.id.webview);  
  12.         mBtn = (Button) findViewById(R.id.btn);  
  13.   
  14.         WebSettings webSettings = mWebView.getSettings();  
  15.         webSettings.setJavaScriptEnabled(true);  
  16.         mWebView.addJavascriptInterface(this"android");  
  17.         mWebView.loadUrl("file:///android_asset/web.html");  
  18.   
  19.         mBtn.setOnClickListener(new View.OnClickListener() {  
  20.             @Override  
  21.             public void onClick(View v) {  
  22.                 mWebView.loadUrl("javascript:sum(3,8)");  
  23.             }  
  24.         });  
  25.     }  
  26.   
  27.     public void onSumResult(int result) {  
  28.         Toast.makeText(this,"received result:"+result,Toast.LENGTH_SHORT).show();  
  29.     }  
  30. }  
這裏主要做了兩件事: 
第一,通過addJavascriptInterface注入MyActivity對象,以便JS訪問其中的函數
[java] view plain copy
  1. mWebView.addJavascriptInterface(this"android");  
第二:供JS調用,以返回結果的函數onSumResult():
[java] view plain copy
  1. public void onSumResult(int result) {  
  2.     Toast.makeText(this,"received result:"+result,Toast.LENGTH_SHORT).show();  
  3. }  

3、JAVA中如何得到JS中的返回值(Android4.4之後)

Android 4.4之後使用evaluateJavascript即可。這裏展示一個簡單的交互示例
先寫一個具有返回值的js方法
[html] view plain copy
  1. function getGreetings() {  
  2.  return 1;   
  3. }  
java代碼時用evaluateJavascript方法調用:
[java] view plain copy
  1. private void testEvaluateJavascript(WebView webView) {  
  2.    webView.evaluateJavascript("getGreetings()"new ValueCallback() {  
  3.        @Override  
  4.        public void onReceiveValue(String value) {  
  5.            Log.i(LOGTAG, "onReceiveValue value=" + value);  
  6.        }  
  7.    });  
  8. }  
從上面的用法中很明顯看到,通過evaluateJavascript調用JS中的方法,可以向其中添加結果回調,來接收JS的return值。 
注意:
  • 上面限定了結果返回結果爲String,對於簡單的類型會嘗試轉換成字符串返回,對於複雜的數據類型,建議以字符串形式的json返回。
  • evaluateJavascript方法必須在UI線程(主線程)調用,因此onReceiveValue也執行在主線程。
發佈了12 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章