前言:隨着市場需求的不斷變化,原生安卓已經沒法知足客戶的須要了,如今不少app都在使用Android和h5的交互實現某些功能,好比商品詳情頁,文章詳情頁面,商品點評頁面,還有某些複雜的展現頁面等等,設置登錄頁面都有多是和js交互作到的。經過交互能夠很快速的達到效果,原生的安卓去作的話就會很麻煩。今天我就簡單講一下使用WebView作到js代碼和安卓的交互,經過一個小demo教你學會js和Android的交互。javascript
## 首先來看看這篇博客要講解內容的大綱(這個圖是我本身畫的,網上找不到的)css
WebView是一個基於webkit引擎、展示web頁面的控件。Webview在低版本和高版本採用了不一樣的webkit版本內核,4.4後直接使用了Chrome。 WebView控件功能強大,除了具備通常View的屬性和設置外,還能夠對url請求、頁面加載**(直接使用html文件**(網絡上或本地assets中)做佈局**)、渲染Wb頁面、頁面交互(和js交互)**進行強大的處理。html
(一)經常使用方法java
- (1) WebView的狀態
//激活WebView爲活躍狀態,能正常執行網頁的響應
webView.onResume() ;
//當頁面被失去焦點被切換到後臺不可見狀態,須要執行onPause
//經過onPause動做通知內核暫停全部的動做,好比DOM的解析、plugin的執行、JavaScript執行。
webView.onPause();
//當應用程序(存在webview)被切換到後臺時,這個方法不只僅針對當前的webview而是全局的全應用程序的webview
//它會暫停全部webview的layout,parsing,javascripttimer。下降CPU功耗。
webView.pauseTimers()
//恢復pauseTimers狀態
webView.resumeTimers();
//銷燬Webview
//在關閉了Activity時,若是Webview的音樂或視頻,還在播放。就必須銷燬Webview
//可是注意:webview調用destory時,webview仍綁定在Activity上
//這是因爲自定義webview構建時傳入了該Activity的context對象
//所以須要先從父容器中移除webview,而後再銷燬webview:
rootLayout.removeView(webView);
webView.destroy();
複製代碼
- (2) 關於前進 / 後退網頁
//是否能夠後退
Webview.canGoBack()
//後退網頁
Webview.goBack()
//是否能夠前進
Webview.canGoForward()
//前進網頁
Webview.goForward()
//以當前的index爲起始點前進或者後退到歷史記錄中指定的steps
//若是steps爲負數則爲後退,正數則爲前進
Webview.goBackOrForward(intsteps)
複製代碼
常見用法:Back鍵控制網頁後退node
問題:在不作任何處理前提下 ,瀏覽網頁時點擊系統的「Back」鍵,整個 Browser 會調用 finish()而結束自身
目標:點擊返回後,是網頁回退而不是推出瀏覽器
解決方案:在當前Activity中處理並消費掉該 Back 事件
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) {
mWebView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
複製代碼
- (3) 清除緩存數據
//清除網頁訪問留下的緩存
//因爲內核緩存是全局的所以這個方法不只僅針對webview而是針對整個應用程序.
Webview.clearCache(true);
//清除當前webview訪問的歷史記錄
//只會webview訪問歷史記錄裏的全部記錄除了當前訪問記錄
Webview.clearHistory();
//這個api僅僅清除自動完成填充的表單數據,並不會清除WebView存儲到本地的數據
Webview.clearFormData();
複製代碼
(二)經常使用類android
- (1) WebSettings類(主要做用是:對WebView進行配置和管理)
//生成一個WebView組件(兩種方式)
//方式1:直接在在Activity中生成
WebView webView = new WebView(this)
//方法2:在Activity的layout文件裏添加webview控件:
WebView webview = (WebView) findViewById(R.id.webView1);
//聲明WebSettings子類
WebSettings webSettings = webView.getSettings();
//若是訪問的頁面中要與Javascript交互,則webview必須設置支持Javascript
webSettings.setJavaScriptEnabled(true);
//支持插件
webSettings.setPluginsEnabled(true);
//設置自適應屏幕,二者合用(下面這兩個方法合用)
webSettings.setUseWideViewPort(true); //將圖片調整到適合webview的大小
webSettings.setLoadWithOverviewMode(true); // 縮放至屏幕的大小
//縮放操做
webSettings.setSupportZoom(true); //支持縮放,默認爲true。是下面那個的前提。
webSettings.setBuiltInZoomControls(true); //設置內置的縮放控件。若爲false,則該WebView不可縮放
webSettings.setDisplayZoomControls(false); //隱藏原生的縮放控件
//其餘細節操做
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //關閉webview中緩存
webSettings.setAllowFileAccess(true); //設置能夠訪問文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持經過JS打開新窗口
webSettings.setLoadsImagesAutomatically(true); //支持自動加載圖片
webSettings.setDefaultTextEncodingName("utf-8");//設置編碼格式
複製代碼
//設置WebView緩存(當加載 html 頁面時,WebView會在/data/data/包名目錄下生成 database 與 cache 兩個文件夾,請求的 URL記錄保存在 WebViewCache.db,而 URL的內容是保存在 WebViewCache 文件夾下)web
//優先使用緩存:
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//緩存模式以下:
//LOAD_CACHE_ONLY: 不使用網絡,只讀取本地緩存數據
//LOAD_DEFAULT: (默認)根據cache-control決定是否從網絡上取數據。
//LOAD_NO_CACHE: 不使用緩存,只從網絡獲取數據.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,不管是否過時,或者no-cache,都使用緩存中的數據。
//不使用緩存:
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
複製代碼
//結合使用(離線加載)(注意:每一個 Application 只調用一次 WebSettings.setAppCachePath(),WebSettings.setAppCacheMaxSize())chrome
if (NetStatusUtil.isConnected(getApplicationContext())) {//判斷網絡是否鏈接
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根據cache-control決定是否從網絡上取數據。
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//沒網,則從本地獲取,即離線加載
}
webSettings.setDomStorageEnabled(true); // 開啓 DOM storage API 功能
webSettings.setDatabaseEnabled(true); //開啓 database storage API 功能
webSettings.setAppCacheEnabled(true);//開啓 Application Caches 功能
String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); //設置 Application Caches 緩存目錄
複製代碼
- (2) WebViewClient類(主要做用是:處理各類通知 & 請求事件)
//步驟1. 定義Webview組件
Webview webview = (WebView) findViewById(R.id.webView1);
//步驟2. 選擇加載方式
//方式a. 加載一個網頁:
webView.loadUrl("http://www.google.com/");
//方式b:加載apk包中的html頁面
webView.loadUrl("file:///android_asset/test.html");
//方式c:加載手機本地的html頁面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
//步驟3. 複寫shouldOverrideUrlLoading()方法,
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//使得打開網頁時不調用系統瀏覽器, 而是在本WebView中顯示
view.loadUrl(url);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//設定加載開始的操做
}
@Override
public void onPageFinished(WebView view, String url) {
//設定加載結束的操做
}
@Override
public boolean onLoadResource(WebView view, String url) {
//設定加載資源的操做
}
@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;
}
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {//處理https請
handler.proceed(); //表示等待證書響應
// handler.cancel(); //表示掛起鏈接,爲默認方式
// handler.handleMessage(null); //可作其餘處理
}
});
複製代碼
- (3) WebChromeClient類( 做用:輔助 WebView 處理 Javascript 的對話框,網站圖標,網站標題等等。)
webview.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress < 100) {
String progress = newProgress + "%";
progress.setText(progress);
}else{
// to do something...
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
titleview.setText(title);
}
});
複製代碼
- 一個demo示範一下以上幾個類的用法:
activity_main.xml以下:api
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.carson_ho.webview_demo.MainActivity">
<!-- 獲取網站的標題-->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
<!--開始加載提示-->
<TextView
android:id="@+id/text_beginLoading"
android:layout_below="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
<!--獲取加載進度-->
<TextView
android:layout_below="@+id/text_beginLoading"
android:id="@+id/text_Loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
<!--結束加載提示-->
<TextView
android:layout_below="@+id/text_Loading"
android:id="@+id/text_endLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
<!--顯示網頁區域-->
<WebView
android:id="@+id/webView1"
android:layout_below="@+id/text_endLoading"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginTop="10dp" />
</RelativeLayout>
複製代碼
java以下:瀏覽器
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
WebView mWebview;
WebSettings mWebSettings;
TextView beginLoading,endLoading,loading,mtitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebview = (WebView) findViewById(R.id.webView1);
beginLoading = (TextView) findViewById(R.id.text_beginLoading);
endLoading = (TextView) findViewById(R.id.text_endLoading);
loading = (TextView) findViewById(R.id.text_Loading);
mtitle = (TextView) findViewById(R.id.title);
mWebSettings = mWebview.getSettings();
mWebview.loadUrl("http://www.baidu.com/");
//設置不用系統瀏覽器打開,直接顯示在當前Webview
mWebview.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
//設置WebChromeClient類
mWebview.setWebChromeClient(new WebChromeClient() {
//獲取網站標題
@Override
public void onReceivedTitle(WebView view, String title) {
System.out.println("標題在這裏");
mtitle.setText(title);
}
//獲取加載進度
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress < 100) {
String progress = newProgress + "%";
loading.setText(progress);
} else if (newProgress == 100) {
String progress = newProgress + "%";
loading.setText(progress);
}
}
});
//設置WebViewClient類
mWebview.setWebViewClient(new WebViewClient() {
//設置加載前的函數
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
System.out.println("開始加載了");
beginLoading.setText("開始加載了");
}
//設置結束加載函數
@Override
public void onPageFinished(WebView view, String url) {
endLoading.setText("結束加載了");
}
});
}
//點擊返回上一頁面而不是退出瀏覽器
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mWebview.canGoBack()) {
mWebview.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
//銷燬Webview
@Override
protected void onDestroy() {
if (mWebview != null) {
mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mWebview.clearHistory();
((ViewGroup) mWebview.getParent()).removeView(mWebview);
mWebview.destroy();
mWebview = null;
}
super.onDestroy();
}
}
複製代碼
1.不在xml中定義 Webview ,而是在須要的時候在Activity中建立,而且Context使用 getApplicationgContext()
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);
複製代碼
2.在 Activity 銷燬( WebView )的時候,先讓 WebView 加載null內容,而後移除 WebView,再銷燬 WebView,最後置空。
@Override
protected void onDestroy() {
if (mWebView != null) {
mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mWebView.clearHistory();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
複製代碼
WebView中,主要漏洞有三類:
1.任意代碼執行漏洞
2.密碼明文存儲漏洞
3.域控制不嚴格漏洞
複製代碼
(一)任意代碼執行漏洞
1. 漏洞產生緣由:
js調用Android的其中一個方式是經過addJavascriptInterface接口進行對象映射:
webView.addJavascriptInterface(new JSObject(), "myObj");
// 參數1:Android的本地對象
// 參數2:JS的對象
// 經過對象映射將Android中的本地對象和JS中的對象進行關聯,從而實現JS調用Android的對象和方法
因此,漏洞產生緣由是:當JS拿到android這個對象後,就能夠調用這個Android對象中全部的方法,包括系統類(Java.lang.Runtime 類),
從而進行任意代碼執行。(好比**咱們能夠執行命令獲取本地設備的SD卡中的文件等信息從而形成信息泄露**)
複製代碼
具體獲取系統類的描述:(結合 Java 反射機制)
如下是攻擊的Js核心代碼:
function execute(cmdArgs) {
// 步驟1:遍歷 window 對象
// 目的是爲了找到包含 getClass ()的對象
// 由於Android映射的JS對象也在window中,因此確定會遍歷到
for (var obj in window) {
if ("getClass" in window[obj]) {
// 步驟2:利用反射調用forName()獲得Runtime類對象
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime")
// 步驟3:之後,就能夠調用靜態方法來執行一些命令,好比訪問文件的命令
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
// 從執行命令後返回的輸入流中獲得字符串,有很嚴重暴露隱私的危險。
// 如執行完訪問文件的命令以後,就能夠獲得文件名的信息了。
}
}
}
複製代碼
當一些 APP 經過掃描二維碼打開一個外部網頁時,攻擊者就能夠執行這段 js 代碼進行漏洞攻擊。在微信盛行、掃一掃行爲普及的狀況下,該漏洞的危險性很是大
2.解決方法
Android 4.2版本以後:Google 在Android 4.2 版本中規定對被調用的函數以 @JavascriptInterface進行註解從而避免漏洞攻擊
Android 4.2版本以前:採用攔截prompt()進行漏洞修復。 具體步驟以下:
1.繼承 WebView ,重寫 addJavascriptInterface 方法,而後在內部本身維護一個對象映射關係的 Map ( 將須要添加的 JS 接口放入該Map中 )
2.每次當 WebView 加載頁面前加載一段本地的 JS 代碼,原理是:
1) 讓JS調用一Javascript方法:該方法是經過調用prompt()把JS中的信息(含特定標識,方法名稱等)傳遞到Android端;
2) 在Android的onJsPrompt()中 ,解析傳遞過來的信息,再經過反射機制調用Java對象的方法,這樣實現安全的JS調用Android代碼。
關於Android返回給JS的值:可經過prompt()把Java中方法的處理結果返回到Js中
複製代碼
具體須要加載的JS代碼以下:
javascript:(function JsAddJavascriptInterface_(){
// window.jsInterface 表示在window上聲明瞭一個Js對象
// jsInterface = 註冊的對象名
// 它註冊了兩個方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2)
// 若是有返回值,就添加上return
if (typeof(window.jsInterface)!='undefined') {
console.log('window.jsInterface_js_interface_name is exist!!');}
else {
window.jsInterface = {
// 聲明方法形式:方法名: function(參數)
onButtonClick:function(arg0) {
// prompt()返回約定的字符串
// 該字符串可本身定義
// 包含特定的標識符MyApp和 JSON 字符串(方法名,參數,對象名等)
return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));
},
onImageClick:function(arg0,arg1,arg2) {
return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',
args:[arg0,arg1,arg2]}));
},
};
}
}
)()
// 當JS調用 onButtonClick() 或 onImageClick() 時,就會回調到Android中的 onJsPrompt ()
// 咱們解析出方法名,參數,對象名
// 再經過反射機制調用Java對象的方法
複製代碼
關於採用攔截prompt()進行漏洞修復須要注意的兩點細節:
細節1:加載上述JS代碼的時機
因爲當 WebView 跳轉到下一個頁面時,以前加載的 JS 可能已經失效,因此,一般須要在如下方法中加載js:
onLoadResource();
doUpdateVisitedHistory();
onPageStarted();
onPageFinished();
onReceivedTitle();
onProgressChanged();
複製代碼
細節2:須要過濾掉 Object 類的方法
因爲最終是經過反射獲得Android指定對象的方法,因此同時也會獲得基類的其餘方法(最頂層的基類是 Object類)
爲了避免把 getClass()等方法注入到 JS 中,咱們須要把 Object 的共有方法過濾掉,須要過濾的方法列表以下:
getClass()
hashCode()
notify()
notifyAl()
equals()
toString()
wait()
複製代碼
1. 產生緣由
1) 在Android 3.0如下,Android系統會默認經過searchBoxJavaBridge_的Js接口給 WebView 添加一個JS映射對象:
searchBoxJavaBridge_對象
2) 該接口可能被利用,實現遠程任意代碼。
複製代碼
2. 解決方法
刪除searchBoxJavaBridge_接口
// 經過調用該方法刪除接口removeJavascriptInterface();
複製代碼
1. 產生緣由
1) 在Android 3.0如下,Android系統會默認經過searchBoxJavaBridge_的Js接口給 WebView 添加一個JS映射對象:
searchBoxJavaBridge_對象
2) 該接口可能被利用,實現遠程任意代碼。
複製代碼
2. 解決方法
刪除searchBoxJavaBridge_接口
// 經過調用該方法刪除接口removeJavascriptInterface();
複製代碼
(二)密碼明文存儲漏洞
(1)問題分析
//WebView默認開啓密碼保存功能 : mWebView.setSavePassword(true) 開啓後,在用戶輸入密碼時,會彈出提示框:詢問用戶是否保存密碼; 若是選擇」是」,密碼會被明文保到 /data/data/com.package.name/databases/webview.db 中,這樣就有被盜取密碼的危險
(2)解決方案
//關閉密碼保存提醒 WebSettings.setSavePassword(false)
(三)域控制不嚴格漏洞
先看Android裏的WebViewActivity.java:
public class WebViewActivity extends Activity {
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
webView = (WebView) findViewById(R.id.webView);
//webView.getSettings().setAllowFileAccess(false); (1)
//webView.getSettings().setAllowFileAccessFromFileURLs(true); (2)
//webView.getSettings().setAllowUniversalAccessFromFileURLs(true); (3)
Intent i = getIntent();
String url = i.getData().toString(); //url = file:///data/local/tmp/attack.html
webView.loadUrl(url);
}
}
/**Mainifest.xml**/
// 將該 WebViewActivity 在Mainifest.xml設置exported屬性
// 表示:當前Activity是否能夠被另外一個Application的組件啓動
android:exported="true"
複製代碼
上述demo中:即 A 應用能夠經過 B 應用導出的 Activity 讓 B 應用加載一個惡意的 file 協議的 url,從而能夠獲取 B 應用的內部私有文件,從而帶來數據泄露威脅
**具體:**當其餘應用啓動此 Activity 時, intent 中的 data 直接被看成 url 來加載(假定傳進來的 url 爲 file:///data/local/tmp/attack.html ),其餘 APP 經過使用顯式 ComponentName 或者其餘相似方式就能夠很輕鬆的啓動該 WebViewActivity 並加載惡意url。
下面咱們着重分析WebView中getSettings類的方法對 WebView 安全性的影響:
setAllowFileAccess()
setAllowFileAccessFromFileURLs()
setAllowUniversalAccessFromFileURLs()
複製代碼
(2) setAllowFileAccess()
// 設置是否容許 WebView 使用 File 協議,默認設置爲true,即容許在 File 域下執行任意 JavaScript 代碼 webView.getSettings().setAllowFileAccess(true);
可是同時也限制了 WebView 的功能,使其不能加載本地的 html 文件,( 移動版的 Chrome 默認禁止加載 file 協議的文件 ) ,以下圖:
解決方案:
1) 對於不須要使用 file 協議的應用,禁用 file 協議;
setAllowFileAccess(false);
2) 對於須要使用 file 協議的應用,禁止 file 協議加載 JavaScript。
setAllowFileAccess(true);
// 禁止 file 協議加載 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
複製代碼
設置是否容許經過 file url 加載的 Js代碼讀取其餘的本地文件 , 在Android 4.1前默認容許 , 在Android 4.1後默認禁止
webView.getSettings().setAllowFileAccessFromFileURLs(true);
複製代碼
當AllowFileAccessFromFileURLs()設置爲 true 時,攻擊者的JS代碼爲 ( 經過該代碼可成功讀取 /etc/hosts 的內容數據 ) :
<script>
function loadXMLDoc(){
var arm = "file:///etc/hosts";
var xmlhttp;
if (window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest();
}
xmlhttp.onreadystatechange=function(){
//alert("status is"+xmlhttp.status);
if (xmlhttp.readyState==4){
console.log(xmlhttp.responseText);
}
}
xmlhttp.open("GET",arm);
xmlhttp.send(null);
}
loadXMLDoc();
</script>
複製代碼
解決方案:
設置setAllowFileAccessFromFileURLs(false);
複製代碼
當設置成爲 false 時,上述JS的攻擊代碼執行會致使錯誤,表示瀏覽器禁止從 file url 中的 JavaScript 讀取其它本地文件。
設置是否容許經過 file url 加載的 Javascript 能夠訪問其餘的源(包括http、https等源),在Android 4.1前默認容許(setAllowFileAccessFromFileURLs()不起做用),在Android 4.1後默認禁止
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
複製代碼
當AllowFileAccessFromFileURLs()被設置成true時,攻擊者的JS代碼是:
// 經過該代碼可成功讀取 http://www.so.com 的內容
<script>
function loadXMLDoc(){
var arm = "http://www.so.com";
var xmlhttp;
if (window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest();
}
xmlhttp.onreadystatechange=function(){
//alert("status is"+xmlhttp.status);
if (xmlhttp.readyState==4){
console.log(xmlhttp.responseText);
}
}
xmlhttp.open("GET",arm);
xmlhttp.send(null);
}
loadXMLDoc();
</script>
複製代碼
解決方案:
設置setAllowUniversalAccessFromFileURLs(false);
複製代碼
設置是否容許 WebView 使用 JavaScript(默認是不容許),但不少應用(包括移動瀏覽器)爲了讓 WebView 執行 http 協議中的 JavaScript,都會主動設置爲true,不區別對待是很是危險的,以下代碼所示:
webView.getSettings().setJavaScriptEnabled(true);
複製代碼
即便把setAllowFileAccessFromFileURLs()和setAllowUniversalAccessFromFileURLs()都設置爲 false,經過 file URL 加載的 javascript仍然有方法訪問其餘的本地文件:符號連接跨源攻擊(前提是容許 file URL 執行 javascript,即webView.getSettings().setJavaScriptEnabled(true);)
緣由分析:
這一攻擊能奏效的緣由是:經過 javascript 的延時執行和將當前文件替換成指向其它文件的軟連接就能夠讀取到被符號連接所指的文件。
複製代碼
具體攻擊步驟:(在該命令執行前 xx.html 是不存在的;執行完這條命令以後,就生成了這個文件,而且將 Cookie 文件連接到了 xx.html 上。) 1. 把惡意的 js 代碼輸出到攻擊應用的目錄下,隨機命名爲 xx.html,修改該目錄的權限; 2. 修改後休眠 1s,讓文件操做完成; 3. 完成後經過系統的 Chrome 應用去打開該 xx.html 文件 4. 等待 4s 讓 Chrome 加載完成該 html,最後將該 html 刪除,而且使用 ln -s 命令爲 Chrome 的 Cookie 文件建立軟鏈接, 因而就可經過連接來訪問 Chrome 的 Cookie
注意事項: Google 沒有進行修復,只是讓Chrome 最新版本默認禁用 file 協議,因此這一漏洞在最新版的 Chrome 中並不存在。 可是,在平常大量使用 WebView 的App和瀏覽器,都有可能受到此漏洞的影響。經過利用此漏洞,容易出現數據泄露的危險 若是是 file 協議,禁用 javascript 能夠很大程度上減少跨源漏洞對 WebView 的威脅。 但並不能徹底杜絕跨源文件泄露。例:應用實現了下載功能,對於沒法加載的頁面,會自動下載到 sd 卡中;因爲 sd 卡中的文件全部應用均可以訪問,因而能夠經過構造一個 file URL 指向被攻擊應用的私有文件,而後用此 URL 啓動被攻擊應用的 WebActivity,這樣因爲該 WebActivity 沒法加載該文件,就會將該文件下載到 sd 卡下面,而後就能夠從 sd 卡上讀取這個文件了
1)對於不須要使用 file 協議的應用,禁用 file 協議;
// 禁用 file 協議;
setAllowFileAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
複製代碼
2)對於須要使用 file 協議的應用,禁止 file 協議加載 JavaScript。
// 須要使用 file 協議
setAllowFileAccess(true);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
// 禁止 file 協議加載 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
複製代碼
Android與js經過WebView互相調用方法,兩者溝通的橋樑是WebView,其實是:
對於 Android調用JS代碼 的方法有2種: 1. 經過WebView的loadUrl() 2. 經過WebView的evaluateJavascript()
對於 JS調用Android代碼 的方法有3種: 1. 經過WebView的addJavascriptInterface()進行對象映射 2. 經過 WebViewClient 的shouldOverrideUrlLoading ()方法回調攔截 url 3. 經過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息
(1) 爲何Webview打開一個頁面,播放一段音樂,退出Activity時音樂還在後臺播放?
◆◆ 解決方案 1:
//銷燬Webview
@Override
protected void onDestroy() {
if (mWebview != null) {
mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mWebview.clearHistory();
((ViewGroup) mWebview.getParent()).removeView(mWebview);
mWebview.destroy();
mWebview = null;
}
super.onDestroy();
}
複製代碼
還有別問我爲何要移除,等你Error: WebView.destroy() called while still attached!以後你就知道了。
◆◆ 解決方案 2:
@Override
protected void onPause() {
h5_webview.onPause();
h5_webview.pauseTimers();
super.onPause();
}
@Override
protected void onResume() {
h5_webview.onResume();
h5_webview.resumeTimers();
super.onResume();
}
複製代碼
Webview的onPause()方法官網是這麼解釋的:
Does a best-effort attempt to pause any processing that can be paused safely, such as animations
and geolocation. Note that this call does not pause JavaScript. To pause JavaScript globally, use
pauseTimers(). To resume WebView, call onResume().
【翻譯:】通知內核嘗試中止全部處理,如動畫和地理位置,可是不能中止Js,若是想全局中止Js,
能夠調用pauseTimers()全局中止Js,調用onResume()恢復。
複製代碼
(2) 怎麼用網頁的標題來設置本身的標題欄?
◆◆ 解決方案:
WebChromeClient mWebChromeClient = new WebChromeClient() {
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
txtTitle.setText(title);
}
};
mWedView.setWebChromeClient(mWebChromeClient());
複製代碼
★★ 注意事項:
● 1.可能當前頁面沒有標題,獲取到的是null,那麼你能夠在跳轉到該Activity的時候本身帶一個標題,或者有一個默認標題。
● 2.在一些機型上面,Webview.goBack()後,這個方法不必定會調用,因此標題仍是以前頁面的標題。那麼
你就須要用一個ArrayList來保持加載過的url,一個HashMap保存url及對應的title.而後就是用WebView.canGoBack()來作判斷處理了。
複製代碼
(3) 爲何打包以後JS調用失敗(或者WebView與JavaScript相互調用時,若是是debug沒有配置混淆時,調用時沒問題的,可是當設置混淆後發現沒法正常調用了)?
◆◆ 解決方案:在proguard-rules.pro中添加混淆。
-keepattributes *Annotation*
-keepattributes *JavascriptInterface*
-keep public class org.mq.study.webview.DemoJavaScriptInterface{
public <methods>;
}
#假如是內部類,混淆以下:
-keepattributes *JavascriptInterface*
-keep public class org.mq.study.webview.webview.DemoJavaScriptInterface$InnerClass{
public <methods>;
}
其中org.mq.study.webview.DemoJavaScriptInterface 是不須要混淆的類名
複製代碼
(4) 5.0 之後的WebView加載的連接爲Https開頭,可是連接裏面的內容,好比圖片爲Http連接,這時候,圖片就會加載不出來,怎麼解決?
★★ 緣由分析:緣由是Android 5.0上Webview默認不容許加載Http與Https混合內容:
◆◆ 解決方案:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//二者均可以
webSetting.setMixedContentMode(webSetting.getMixedContentMode());
//mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
複製代碼
★★ 參數說明:
● MIXED_CONTENT_ALWAYS_ALLOW 容許從任何來源加載內容,即便起源是不安全的;
● MIXED_CONTENT_NEVER_ALLOW 不容許Https加載Http的內容,即不容許從安全的起源去加載一個不安全的
資源;
● MIXED_CONTENT_COMPLTIBILITY_MODE 當涉及到混合式內容時,WebView會嘗試去兼容最新Web瀏覽器的
風格;
複製代碼
另外:在認證證書不被Android所接受的狀況下,咱們能夠經過設置重寫WebViewClient的onReceivedSslError方法在其中設置接受全部網站的證書來解決,具體代碼以下:
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
//super.onReceivedSslError(view, handler, error);注意必定要去除這行代碼,不然設置無效。
// handler.cancel();// Android默認的處理方式
handler.proceed();// 接受全部網站的證書
// handleMessage(Message msg);// 進行其餘處理
}
});
複製代碼
(5) WebView調用手機系統相冊來上傳圖片,開發過程當中發如今不少機器上沒法正常喚起系統相冊來選擇圖片。怎麼解決?
★★ 緣由分析:由於Google攻城獅們對setWebChromeClient的回調方法openFileChooser作了屢次修改,5.0如下openFileChooser有幾種重載方法,在5.0以上將回調方法該爲了onShowFileChooser。
◆◆ 解決方案:爲了兼容各個版本,咱們須要對openFileChooser()進行重載,同時針對5.0及以上重寫onShowFileChooser()方法:
上一段示例代碼,給你們看看:
public class MainActivity extends AppCompatActivity {
private ValueCallback<Uri> uploadMessage;
private ValueCallback<Uri[]> uploadMessageAboveL;
private final static int FILE_CHOOSER_RESULT_CODE = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webview = (WebView) findViewById(R.id.web_view);
assert webview != null;
WebSettings settings = webview.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setJavaScriptEnabled(true);
webview.setWebChromeClient(new WebChromeClient() {
// android 3.0如下:用的這個方法
public void openFileChooser(ValueCallback<Uri> valueCallback) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
// android 3.0以上,android4.0如下:用的這個方法
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
//android 4.0 - android 4.3 安卓4.4.4也用的這個方法
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType,
String capture) {
uploadMessage = valueCallback;
openImageChooserActivity();
}
//android4.4 無方法。。。
// Android 5.0及以上用的這個方法
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]>
filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
uploadMessageAboveL = filePathCallback;
openImageChooserActivity();
return true;
}
});
String targetUrl = "file:///android_asset/up.html";
webview.loadUrl(targetUrl);
}
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "Image Chooser"),
FILE_CHOOSER_RESULT_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_CHOOSER_RESULT_CODE) {
if (null == uploadMessage && null == uploadMessageAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (uploadMessage != null) {
uploadMessage.onReceiveValue(result);
uploadMessage = null;
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
uploadMessageAboveL.onReceiveValue(results);
uploadMessageAboveL = null;
}
複製代碼
}
重點坑:針對Android4.4,系統把openFileChooser方法去掉了,怎麼解決?
詳情請見 博客 http://blog.csdn.net/xiexie758/article/details/52446937 我這裏就很少說了。
(6) WebView調用手機系統相冊來上傳圖片,處理好第六點說的方法,咱們打好release包測試的時候卻又發現仍是無法選擇圖片了。怎麼解決?
★★ 緣由分析:無奈去翻WebChromeClient的源碼,發現openFileChooser()是系統API,咱們的release包是開啓了混淆的,因此在打包的時候混淆了openFileChooser(),這就致使沒法回調openFileChooser()了。
◆◆ 解決方案也很簡單,直接不混淆openFileChooser()就行了。
-keepclassmembers class * extends android.webkit.WebChromeClient{
public void openFileChooser(...);
}
複製代碼
(7)怎麼在 WebView 中長按保存圖片?
1. 給 WebView添加監聽
mWebview.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
}
});
複製代碼
2. 獲取點擊的圖片地址
先獲取類型,根據相應的類型來處理對應的數據。
//首先判斷點擊的類型
WebView.HitTestResult result = ((WebView) v).getHitTestResult();
int type = result.getType();
//獲取具體信息,圖片這裏就是圖片地址
String imgurl = result.getExtra();
複製代碼
type有這幾種類型:
3. 操做圖片
你能夠彈出保存圖片,或者點擊以後跳轉到顯示圖片的頁面。
最後整理一下代碼:
mWebView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
WebView.HitTestResult result = ((WebView)v).getHitTestResult();
if (null == result)
return false;
int type = result.getType();
if (type == WebView.HitTestResult.UNKNOWN_TYPE)
return false;
// 這裏能夠攔截不少類型,咱們只處理圖片類型就能夠了
switch (type) {
case WebView.HitTestResult.PHONE_TYPE: // 處理撥號
break;
case WebView.HitTestResult.EMAIL_TYPE: // 處理Email
break;
case WebView.HitTestResult.GEO_TYPE: // 地圖類型
break;
case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超連接
break;
case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
break;
case WebView.HitTestResult.IMAGE_TYPE: // 處理長按圖片的菜單項
// 獲取圖片的路徑
String saveImgUrl = result.getExtra();
// 跳轉到圖片詳情頁,顯示圖片
Intent i = new Intent(MainActivity.this, ImageActivity.class);
i.putExtra("imgUrl", saveImgUrl);
startActivity(i);
break;
default:
break;
}
}
});
複製代碼
(8) WebView 開啓硬件加速致使的問題?
WebView有不少問題,好比:不能打開pdf,播放視屏也只能打開硬件加速才能支持,在某些機型上會崩潰。 下面看一下硬件加速, 硬件加速 分爲四個級別:
<application android:hardwareAccelerated="true"...>
複製代碼
<activity android:hardwareAccelerated="true"...>
複製代碼
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
複製代碼
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
複製代碼
WebView開啓硬件加速致使屏幕花屏問題的解決:
★★ 緣由分析: 4.0以上的系統咱們開啓硬件加速後,WebView渲染頁面更加快速,拖動也更加順滑。但有個反作用就是,當WebView視圖被總體遮住一塊,而後忽然恢復時(好比使用SlideMenu將WebView從側邊滑出來時),這個過渡期會出現白塊同時界面閃爍。
◆◆ 解決方案: 在過渡期前將WebView的硬件加速臨時關閉,過渡期後再開啓,代碼以下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
複製代碼
Android 4.0+ 版本中的EditText字符重疊問題: 作的軟件,在一些機器上,打字的時候,EditText中的內容會出現重疊,而大部分機器沒有,因此感受不是代碼的問題,一直沒有頭緒。
出現緣由:JellyBean的硬件加速bug,在此咱們關掉硬件加速便可。 解決方案:在EditText中加入一句:
android:layerType=」software」
複製代碼
圖片沒法顯示: 作的程序裏有的時候會須要加載大圖,可是硬件加速中 OpenGL對於內存是有限制的。若是遇到了這個限制,LogCat只會報一個Warning: Bitmap too large to be uploaded into a texture (587x7696, max=2048x2048)
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:hardwareAccelerated="false"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
複製代碼
隨後我就發現,雖然圖片能夠顯示了,可是ListView和WebView等控件顯得特別的卡,這說明硬件加速對於程序的性能提高是很明顯的。因此我就改成對於Activity的關閉。
<activity
android:name="icyfox.webviewimagezoomertest.MainActivity"
android:label="@string/app_name"
android:hardwareAccelerated="false"
複製代碼
(9) ViewPager裏非首屏WebView點擊事件不響應是什麼緣由?
若是你的多個WebView是放在ViewPager裏一個個加載出來的,那麼就會遇到這樣的問題。ViewPager首屏WebView的建立是在前臺,點擊時沒有問題;而其餘非首屏的WebView是在後臺建立,滑動到它後點擊頁面會出現以下錯誤日誌:
20955-20968/xx.xxx.xxx E/webcoreglue﹕ Should not happen: no rect-based-test nodes found
複製代碼
◆◆ 解決方案: 這個問題的辦法是繼承WebView類,在子類覆蓋onTouchEvent方法,填入以下代碼:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
}
return super.onTouchEvent(ev);
}
複製代碼
WebView新增了一些很是有用的API,可使用和chrome瀏覽器相似的API來實現對惡意網站的檢測來保護web瀏覽的安全性,爲此須要在manifest中添加以下meta-data標籤:
<manifest>
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowing"
android:value="true" />
<!-- ... -->
</manifest>
複製代碼
WebView還增長了關於多進程的API,可使用多進程來加強安全性和健壯性,若是render進程崩潰了,你還可使用Termination Handler API來檢測到崩潰並作出相應處理。
(1)給WebView加一個加載進度條
用Webview加載一個網頁時,若是加載時間長,界面會一直空白,體驗不太好,因此加個進度條更好看一下,主流APP也都有進度條效果,大概思路我來講一下: 首先自定義一個HorizontalProgressView繼承View,而後自定義一個MyWebView繼承WebView,而後初始化的時候經過addView方法把前面自定義HorizontalProgressView,而後在MyWebView裏面寫一個內部類繼承WebChromeClient,大體代碼以下:
private class MyWebCromeClient extends WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
//加載完畢進度條消失
progressView.setVisibility(View.GONE);
} else {
//更新進度
progressView.setProgress(newProgress);
}
super.onProgressChanged(view, newProgress);
}
}
複製代碼
主要是經過MyWebCromeClient 的onProgressChanged方法裏面的進度值調用 progressView.setProgress()方法去更新進度條,當加載100%的時候讓進度條消失。 具體實現大家本身去處理吧。
(2)加快HTML網頁加載完成的速度,等頁面finish再加載圖片
默認狀況html代碼下載到WebView後,webkit開始解析網頁各個節點,發現有外部樣式文件或者外部腳本文件時,會異步發起網絡請求下載文件,但若是在這以前也有解析到image節點,那勢必也會發起網絡請求下載相應的圖片。在網絡狀況較差的狀況下,過多的網絡請求就會形成帶寬緊張,影響到css或js文件加載完成的時間,形成頁面空白loading太久。解決的方法就是告訴WebView先不要自動加載圖片,等頁面finish後再發起圖片加載。
◆◆ 解決辦法:
在WebView初始化時設置以下代碼:
public void int () {
if(Build.VERSION.SDK_INT >= 19) {
webView.getSettings().setLoadsImagesAutomatically(true);
} else {
webView.getSettings().setLoadsImagesAutomatically(false);
}
}
複製代碼
同時在WebView的WebViewClient實例中的onPageFinished()方法添加以下代碼:
@Override
public void onPageFinished(WebView view, String url) {
if(!webView.getSettings().getLoadsImagesAutomatically()) {
webView.getSettings().setLoadsImagesAutomatically(true);
}
}
複製代碼
(3)自定義WebView頁面加載出錯界面
當WebView加載頁面出錯時(通常爲404 NOT FOUND),安卓WebView會默認顯示一個賣萌的出錯界面。但咱們怎麼能讓用戶發現原來我使用的是網頁應用呢,咱們指望的是用戶在網頁上獲得是如原生般應用的體驗,那就先要從幹掉這個默認出錯頁面開始。當WebView加載出錯時,咱們會在WebViewClient實例中的onReceivedError()方法接收到錯誤,咱們就在這裏作些手腳:
@Override
public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mErrorFrame.setVisibility(View.VISIBLE);
}
複製代碼
從上面能夠看出,咱們先使用loadDataWithBaseURL清除掉默認錯誤頁內容,再讓咱們自定義的View獲得顯示(mErrorFrame爲蒙在WebView之上的一個LinearLayout佈局,默認爲View.GONE)。
(4) 怎麼知道WebView是否已經滾動到頁面底端?
◆◆ 解決方案:
if (mWebView.getContentHeight() * mWebView.getScale() == (mWebView.getHeight() +
mWebView.getScrollY())) {
//說明已經到底了
}
複製代碼
具體以下:
@Override
protected void onScrollChanged(int newX, int newY, int oldX, int oldY) {
super.onScrollChanged(newX, newY, oldX, oldY);
if (newY != oldY) {
float contentHeight = getContentHeight() * getScale();
// 當前內容高度下從未觸發過, 瀏覽器存在滾動條且滑動到將抵底部位置
if (mCurrContentHeight != contentHeight && newY > 0 && contentHeight <= newY + getHeight() + mThreshold) {
// TODO Something...
mCurrContentHeight = contentHeight;
}
}
}
複製代碼
★★ 相關API介紹:
● getContentHeight() @return the height of the HTML content
● getScale() @return the current scale
● getHeight() @return The height of your view
● getScrollY() @return The top edge of the displayed part of your view, in pixels.
複製代碼
(5) 怎麼知道WebView是否存在滾動條?
當咱們作相似上拉加載下一頁這樣的功能的時候,頁面初始的時候須要知道當前WebView是否存在縱向滾動條,若是有則不加載下一頁,若是沒有則加載下一頁直到其出現縱向滾動條。 首先繼承WebView類,在子類添加下面的代碼:
public boolean existVerticalScrollbar () {
return computeVerticalScrollRange() > computeVerticalScrollExtent();
}
複製代碼
computeVerticalScrollRange獲得的是可滑動的最大高度,computeVerticalScrollExtent獲得的是滾動把手自身的高,當不存在滾動條時,二者的值是相等的。當有滾動條時前者必定是大於後者的。
參考博文: http://blog.csdn.net/carson_ho/article/details/64904691 http://bbs.csdn.net/topics/390905615 http://blog.csdn.net/cyuyanshujujiegou/article/details/52267817