Android WebView 詳解


相關API

相關類介紹

  • WebResourceRequest 添加於API21,封裝了一個Web資源的請求信息,包含:請求地址,請求方法,請求頭,是否主框架,是否用戶點擊,是否重定向
  • WebResourceResponse 封裝了一個Web資源的響應信息,包含:響應數據流,編碼,MIME類型,API21后添加了響應頭,狀態碼與狀態描述
  • WebResourceError 添加於API23,封裝了一個Web資源的錯誤信息,包含錯誤碼和描述
  • CookieManager 管理用於WebView的cookies。。
  • WebViewDatabase 存儲與管理以下幾類瀏覽數據:
    • 表單自動填充的的用戶名與密碼
    • HTTP認證的用戶名與密碼
    • 曾經輸入過的文本(比如自動完成)
  • WebStorage 用於管理WebView提供的JS存儲API,比如Application Cache API,Web SQL Database API,HTML5 Web Storage API
  • GeolocationPermissions 用於管理WebView的JS Geolocation API
  • HttpAuthHandler 表示一個HTTP認證請求,提供了方法操作(proceed/cancel)請求
  • SslErrorHandler 表示一個處理SSL錯誤的請求,提供了方法操作(proceed/cancel)請求
  • ClientCertRequest 表示一個證書請求,提供了方法操作(proceed/cancel/ignore)請求
  • JsResult 用於處理底層JS發起的請求,為客戶端提供一些方法指明應進行的操作,比如確認或取消。

WebView

基本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 獲取當前頁面的URL
public String getUrl();
// 獲取當前頁面的原始URL(重定向后可能當前url不同)
// 就是http headers的Referer參數,loadUrl時為null
public String getOriginalUrl();
// 獲取當前頁面的標題
public String getTitle();
// 獲取當前頁面的favicon
public Bitmap getFavicon();
// 獲取當前頁面的加載進度
public int getProgress();
 
// 通知WebView內核網絡狀態
// 用於設置JS屬性`window.navigator.isOnline`和產生HTML5事件`online/offline`
public void setNetworkAvailable(boolean networkUp)
 
// 設置初始縮放比例
public void setInitialScale(int scaleInPercent);

加載網頁

1
2
3
4
5
6
7
8
9
10
11
12
13
// 加載URL指定的網頁
public void loadUrl(String url);
// 攜帶http headers加載URL指定的網頁
public void loadUrl(String url, Map<String, String> additionalHttpHeaders);
// 使用POST請求加載指定的網頁
public void postUrl(String url, byte[] postData);
// 重新加載當前網頁
public void reload();
 
// 加載內容
public void loadData(String data, String mimeType, String encoding);
// 使用baseUrl加載內容
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl);

Javascript

1
2
3
4
5
6
7
8
// 注入Javascript對象
public void addJavascriptInterface(Object object, String name);
// 移除已注入的Javascript對象,下次加載或刷新頁面時生效
public void removeJavascriptInterface(String name);
 
// 對傳入的JS表達式求值,通過resultCallback返回結果
// 此函數添加於API19,必須在UI線程中調用,回調也將在UI線程
public void evaluateJavascript(String script, ValueCallback<String> resultCallback)

導航(前進后退)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 復制一份BackForwardList
public WebBackForwardList copyBackForwardList();
// 是否可后退
public boolean canGoBack();
// 是否可前進
public boolean canGoForward();
// 是否可前進/后退steps頁,大於0表示前進小於0表示后退
public boolean canGoBackOrForward(int steps);
 
// 后退一頁
public void goBack();
// 前進一頁
public void goForward();
// 前進/后退steps頁,大於0表示前進小於0表示后退
public void goBackOrForward(int steps);
 
// 清除當前webview訪問的歷史記錄
public void clearHistory();

網頁查找功能

1
2
3
4
5
6
7
8
// 設置網頁查找結果回調
public void setFindListener(FindListener listener);
// 異步執行查找網頁內包含的字符串並設置高亮,查找結果會回調.
public void findAllAsync (String find);
// 查找下一個匹配的字符串
public void findNext (boolean forward);
// 清除網頁查找的高亮匹配字符串
public void clearMatches();

截屏/翻頁/縮放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 網頁截屏並保存到指定文件
public void saveWebArchive(String filename);
// 網頁截屏並保存到文件
public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback);
 
// 上翻一頁,即向上滾動WebView高度的一半
public void pageUp(boolean top);
// 下翻一頁,即向下滾動WebView高度的一半
public void pageDown(boolean bottom);
 
// 縮放
public void zoomBy(float factor);
// 放大
public boolean zoomIn();
// 縮放
public boolean zoomOut();

其它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 清除網頁緩存,由於內核緩存是全局的因此這個方法不僅僅針對webview而是針對整個應用程序
public void clearCache(boolean includeDiskFiles);
// 清除自動完成填充的表單數據
public void clearFormData();
// 清除SSL偏好
public void clearSslPreferences();
 
// 查詢文檔中是否有圖片,查詢結果將被發送到msg.getTarget()
// 如果包含圖片,msg.arg1 為1,否則為0
public void documentHasImages(Message msg);
 
// 請求最近輕叩(tapped)的 錨點/圖像 元素的URL,查詢結果將被發送到msg.getTarget()
// msg.getData()中的url是錨點的href屬性,title是錨點的文本,src是圖像的src
public void requestFocusNodeHref(Message msg);
 
// 請求最近觸摸(touched)的 圖像元素的URL,查詢結果將被發送到msg.getTarget()
// msg.getData()中的url是圖像鏈接
public void requestImageRef(Message msg)
 
 
// 清除證書請求偏好,添加於API21
// 在WebView收到`android.security.STORAGE_CHANGED` Intent時會自動清除
public static void clearClientCertPreferences(Runnable onCleared)
 
// 開啟網頁內容(js,css,html...)調試模式,添加於API19
public static void setWebContentsDebuggingEnabled(boolean enabled)

WebSettings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
WebSettings settings = web.getSettings();
 
// 存儲(storage)
// 啟用HTML5 DOM storage API,默認值 false
settings.setDomStorageEnabled( true);
// 啟用Web SQL Database API,這個設置會影響同一進程內的所有WebView,默認值 false
// 此API已不推薦使用,參考:https://www.w3.org/TR/webdatabase/
settings.setDatabaseEnabled( true);
// 啟用Application Caches API,必需設置有效的緩存路徑才能生效,默認值 false
// 此API已廢棄,參考:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Using_the_application_cache
settings.setAppCacheEnabled( true);
settings.setAppCachePath(context.getCacheDir().getAbsolutePath());
 
// 定位(location)
settings.setGeolocationEnabled( true);
 
// 是否保存表單數據
settings.setSaveFormData( true);
// 是否當webview調用requestFocus時為頁面的某個元素設置焦點,默認值 true
settings.setNeedInitialFocus( true);
 
// 是否支持viewport屬性,默認值 false
// 頁面通過`<meta name="viewport" ... />`自適應手機屏幕
settings.setUseWideViewPort( true);
// 是否使用overview mode加載頁面,默認值 false
// 當頁面寬度大於WebView寬度時,縮小使頁面寬度等於WebView寬度
settings.setLoadWithOverviewMode( true);
// 布局算法
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
 
// 是否支持Javascript,默認值false
settings.setJavaScriptEnabled( true);
// 是否支持多窗口,默認值false
settings.setSupportMultipleWindows( false);
// 是否可用Javascript(window.open)打開窗口,默認值 false
settings.setJavaScriptCanOpenWindowsAutomatically( false);
 
// 資源訪問
settings.setAllowContentAccess( true); // 是否可訪問Content Provider的資源,默認值 true
settings.setAllowFileAccess( true); // 是否可訪問本地文件,默認值 true
// 是否允許通過file url加載的Javascript讀取本地文件,默認值 false
settings.setAllowFileAccessFromFileURLs( false);
// 是否允許通過file url加載的Javascript讀取全部資源(包括文件,http,https),默認值 false
settings.setAllowUniversalAccessFromFileURLs( false);
 
// 資源加載
settings.setLoadsImagesAutomatically( true); // 是否自動加載圖片
settings.setBlockNetworkImage( false); // 禁止加載網絡圖片
settings.setBlockNetworkLoads( false); // 禁止加載所有網絡資源
 
// 縮放(zoom)
settings.setSupportZoom( true); // 是否支持縮放
settings.setBuiltInZoomControls( false); // 是否使用內置縮放機制
settings.setDisplayZoomControls( true); // 是否顯示內置縮放控件
 
// 默認文本編碼,默認值 "UTF-8"
settings.setDefaultTextEncodingName( "UTF-8");
settings.setDefaultFontSize( 16); // 默認文字尺寸,默認值16,取值范圍1-72
settings.setDefaultFixedFontSize( 16); // 默認等寬字體尺寸,默認值16
settings.setMinimumFontSize( 8); // 最小文字尺寸,默認值 8
settings.setMinimumLogicalFontSize( 8); // 最小文字邏輯尺寸,默認值 8
settings.setTextZoom( 100); // 文字縮放百分比,默認值 100
 
// 字體
settings.setStandardFontFamily( "sans-serif"); // 標准字體,默認值 "sans-serif"
settings.setSerifFontFamily( "serif"); // 襯線字體,默認值 "serif"
settings.setSansSerifFontFamily( "sans-serif"); // 無襯線字體,默認值 "sans-serif"
settings.setFixedFontFamily( "monospace"); // 等寬字體,默認值 "monospace"
settings.setCursiveFontFamily( "cursive"); // 手寫體(草書),默認值 "cursive"
settings.setFantasyFontFamily( "fantasy"); // 幻想體,默認值 "fantasy"
 
 
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 用戶是否需要通過手勢播放媒體(不會自動播放),默認值 true
settings.setMediaPlaybackRequiresUserGesture( true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 5.0以上允許加載http和https混合的頁面(5.0以下默認允許,5.0+默認禁止)
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 是否在離開屏幕時光柵化(會增加內存消耗),默認值 false
settings.setOffscreenPreRaster( false);
}
 
if (isNetworkConnected(context)) {
// 根據cache-control決定是否從網絡上取數據
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
// 沒網,離線加載,優先加載緩存(即使已經過期)
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
 
// deprecated
settings.setRenderPriority(WebSettings.RenderPriority.HIGH);
settings.setDatabasePath(context.getDir( "database", Context.MODE_PRIVATE).getPath());
settings.setGeolocationDatabasePath(context.getFilesDir().getPath());

通常大部分保持默認值就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
WebSettings settings = web.getSettings();
// 緩存(cache)
settings.setAppCacheEnabled( true); // 默認值 false
settings.setAppCachePath(context.getCacheDir().getAbsolutePath());
 
// 存儲(storage)
settings.setDomStorageEnabled( true); // 默認值 false
settings.setDatabaseEnabled( true); // 默認值 false
 
// 是否支持viewport屬性,默認值 false
// 頁面通過`<meta name="viewport" ... />`自適應手機屏幕
settings.setUseWideViewPort( true);
// 是否使用overview mode加載頁面,默認值 false
// 當頁面寬度大於WebView寬度時,縮小使頁面寬度等於WebView寬度
settings.setLoadWithOverviewMode( true);
 
// 是否支持Javascript,默認值false
settings.setJavaScriptEnabled( true);
 
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 5.0以上允許加載http和https混合的頁面(5.0以下默認允許,5.0+默認禁止)
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
 
if (isNetworkConnected(context)) {
// 根據cache-control決定是否從網絡上取數據
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
// 沒網,離線加載,優先加載緩存(即使已經過期)
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}

WebViewClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// 攔截頁面加載,返回true表示宿主app攔截並處理了該url,否則返回false由當前WebView處理
// 此方法在API24被廢棄,不處理POST請求
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
 
// 攔截頁面加載,返回true表示宿主app攔截並處理了該url,否則返回false由當前WebView處理
// 此方法添加於API24,不處理POST請求,可攔截處理子frame的非http請求
@TargetApi(Build.VERSION_CODES.N)
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return shouldOverrideUrlLoading(view, request.getUrl().toString());
}
 
// 此方法廢棄於API21,調用於非UI線程
// 攔截資源請求並返回響應數據,返回null時WebView將繼續加載資源
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return null;
}
 
// 此方法添加於API21,調用於非UI線程
// 攔截資源請求並返回數據,返回null時WebView將繼續加載資源
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return shouldInterceptRequest(view, request.getUrl().toString());
}
 
// 頁面(url)開始加載
public void onPageStarted(WebView view, String url, Bitmap favicon) {
}
 
// 頁面(url)完成加載
public void onPageFinished(WebView view, String url) {
}
 
// 將要加載資源(url)
public void onLoadResource(WebView view, String url) {
}
 
// 這個回調添加於API23,僅用於主框架的導航
// 通知應用導航到之前頁面時,其遺留的WebView內容將不再被繪制。
// 這個回調可以用來決定哪些WebView可見內容能被安全地回收,以確保不顯示陳舊的內容
// 它最早被調用,以此保證WebView.onDraw不會繪制任何之前頁面的內容,隨后繪制背景色或需要加載的新內容。
// 當HTTP響應body已經開始加載並體現在DOM上將在隨后的繪制中可見時,這個方法會被調用。
// 這個回調發生在文檔加載的早期,因此它的資源(css,和圖像)可能不可用。
// 如果需要更細粒度的視圖更新,查看 postVisualStateCallback(long, WebView.VisualStateCallback).
// 請注意這上邊的所有條件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)
public void onPageCommitVisible(WebView view, String url) {
}
 
// 此方法廢棄於API23
// 主框架加載資源時出錯
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
}
 
// 此方法添加於API23
// 加載資源時出錯,通常意味着連接不到服務器
// 由於所有資源加載錯誤都會調用此方法,所以此方法應盡量邏輯簡單
@TargetApi(Build.VERSION_CODES.M)
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (request.isForMainFrame()) {
onReceivedError(view, error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());
}
}
 
// 此方法添加於API23
// 在加載資源(iframe,image,js,css,ajax...)時收到了 HTTP 錯誤(狀態碼>=400)
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
}
 
 
// 是否重新提交表單,默認不重發
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
dontResend.sendToTarget();
}
 
// 通知應用可以將當前的url存儲在數據庫中,意味着當前的訪問url已經生效並被記錄在內核當中。
// 此方法在網頁加載過程中只會被調用一次,網頁前進后退並不會回調這個函數。
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
}
 
// 加載資源時發生了一個SSL錯誤,應用必需響應(繼續請求或取消請求)
// 處理決策可能被緩存用於后續的請求,默認行為是取消請求
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.cancel();
}
 
// 此方法添加於API21,在UI線程被調用
// 處理SSL客戶端證書請求,必要的話可顯示一個UI來提供KEY。
// 有三種響應方式:proceed()/cancel()/ignore(),默認行為是取消請求
// 如果調用proceed()或cancel(),Webview 將在內存中保存響應結果且對相同的"host:port"不會再次調用 onReceivedClientCertRequest
// 多數情況下,可通過KeyChain.choosePrivateKeyAlias啟動一個Activity供用戶選擇合適的私鑰
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
request.cancel();
}
 
// 處理HTTP認證請求,默認行為是取消請求
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
handler.cancel();
}
 
// 通知應用有個已授權賬號自動登陸了
public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {
}
// 給應用一個機會處理按鍵事件
// 如果返回true,WebView不處理該事件,否則WebView會一直處理,默認返回false
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return false;
}
 
// 處理未被WebView消費的按鍵事件
// WebView總是消費按鍵事件,除非是系統按鍵或shouldOverrideKeyEvent返回true
// 此方法在按鍵事件分派時被異步調用
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
super.onUnhandledKeyEvent(view, event);
}
 
// 通知應用頁面縮放系數變化
public void onScaleChanged(WebView view, float oldScale, float newScale) {
}

WebChromeClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// 獲得所有訪問歷史項目的列表,用於鏈接着色。
public void getVisitedHistory(ValueCallback<String[]> callback) {
}
 
// <video /> 控件在未播放時,會展示為一張海報圖,HTML中可通過它的'poster'屬性來指定。
// 如果未指定'poster'屬性,則通過此方法提供一個默認的海報圖。
public Bitmap getDefaultVideoPoster() {
return null;
}
 
// 當全屏的視頻正在緩沖時,此方法返回一個占位視圖(比如旋轉的菊花)。
public View getVideoLoadingProgressView() {
return null;
}
 
// 接收當前頁面的加載進度
public void onProgressChanged(WebView view, int newProgress) {
}
 
// 接收文檔標題
public void onReceivedTitle(WebView view, String title) {
}
 
// 接收圖標(favicon)
public void onReceivedIcon(WebView view, Bitmap icon) {
}
 
// Android中處理Touch Icon的方案
// http://droidyue.com/blog/2015/01/18/deal-with-touch-icon-in-android/index.html
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
}
 
// 通知應用當前頁進入了全屏模式,此時應用必須顯示一個包含網頁內容的自定義View
public void onShowCustomView(View view, CustomViewCallback callback) {
}
 
// 通知應用當前頁退出了全屏模式,此時應用必須隱藏之前顯示的自定義View
public void onHideCustomView() {
}
 
 
// 顯示一個alert對話框
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return false;
}
 
// 顯示一個confirm對話框
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return false;
}
 
// 顯示一個prompt對話框
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return false;
}
 
// 顯示一個對話框讓用戶選擇是否離開當前頁面
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
return false;
}
 
 
// 指定源的網頁內容在沒有設置權限狀態下嘗試使用地理位置API。
// 從API24開始,此方法只為安全的源(https)調用,非安全的源會被自動拒絕
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
}
 
// 當前一個調用 onGeolocationPermissionsShowPrompt() 取消時,隱藏相關的UI。
public void onGeolocationPermissionsHidePrompt() {
}
 
// 通知應用打開新窗口
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
return false;
}
 
// 通知應用關閉窗口
public void onCloseWindow(WebView window) {
}
 
// 請求獲取取焦點
public void onRequestFocus(WebView view) {
}
 
// 通知應用網頁內容申請訪問指定資源的權限(該權限未被授權或拒絕)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onPermissionRequest(PermissionRequest request) {
request.deny();
}
 
// 通知應用權限的申請被取消,隱藏相關的UI。
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onPermissionRequestCanceled(PermissionRequest request) {
}
 
// 為'<input type="file" />'顯示文件選擇器,返回false使用默認處理
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
return false;
}
 
// 接收JavaScript控制台消息
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
return false;
}

回調順序

頁面加載回調順序:

1
2
3
4
5
6
7
8
9
10
11
12
shouldOverrideUrlLoading
onProgressChanged[ 10]
shouldInterceptRequest
onProgressChanged[...]
onPageStarted
onProgressChanged[...]
onLoadResource
onProgressChanged[...]
onReceivedTitle/onPageCommitVisible
onProgressChanged[ 100]
onPageFinished
onReceivedIcon

資源加載回調:

1
shouldInterceptRequest() -> onLoadResource()

發生重定向時回調:

1
onPageStarted() -> shouldOverrideUrlLoading()

直接loadUrl的回調:

1
2
3
4
// 無重定向
onPageStarted() -> onPageFinished()
// 有重定向,shouldOverrideUrlLoading 返回 true 時 onPageFinished 仍會執行
onPageStarted() -> redirection -> ... -> onPageFinished()

用戶點擊鏈接的回調:

1
2
3
4
5
6
// shouldOverrideUrlLoading 返回 true 時不執行onPageStarted/onPageFinished
shouldOverrideUrlLoading() -> ...
// 無重定向
shouldOverrideUrlLoading() -> onPageStarted() -> onPageFinished()
// 有重定向
shouldOverrideUrlLoading() -> onPageStarted() -> redirection -> ... -> onPageFinished()

后退/前進/刷新 時回調:

1
onPageStarted() -> onPageFinished()

關於 window.location

假設從A頁面跳轉到B頁面

  • 如果頁面B中直接輸出 window.location="http://example.com",那頁面B不會被加入回退棧,回退將直接回到A頁
  • 如果頁面B加載完成后,比如用setTimeout延遲了,那頁面B會被加入回退棧,當回退到頁面A時會再執行跳轉,這會導致回退功能看起來不正常,需要快速回退兩次才能回到A頁面

視口(viewport)

https://developer.android.com/guide/webapps/targeting.html
https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag
https://developer.mozilla.org/zh-CN/docs/Web/CSS/@viewport

視口是一個為網頁提供繪圖區域的矩形。

你可以指定數個視口屬性,比如尺寸和初始縮放系數(initial scale)。其中最重要的是視口寬度,它定義了網頁水平方向的可用像素總數(可用的CSS像素數)。

多數 Android 上的網頁瀏覽器(包括 Chrome)設置默認視口為一個大尺寸(被稱為”wide viewport mode”,寬約 980px)。
也有許多瀏覽器默認會盡可能縮小以顯示完整的視口寬度(被稱為”overview mode“)。

1
2
3
4
5
6
7
// 是否支持viewport屬性,默認值 false
// 頁面通過`<meta name="viewport" ... />`自適應手機屏幕
// 當值為true且viewport標簽不存在或未指定寬度時使用 wide viewport mode
settings.setUseWideViewPort( true);
// 是否使用overview mode加載頁面,默認值 false
// 當頁面寬度大於WebView寬度時,縮小使頁面寬度等於WebView寬度
settings.setLoadWithOverviewMode( true);

viewport 語法

1
2
3
4
5
6
7
8
9
<meta name="viewport"
content="
height = [pixel_value | "device-height"] ,
width = [pixel_value | "device-width"] ,
initial-scale = float_value ,
minimum-scale = float_value ,
maximum-scale = float_value ,
user-scalable = ["yes" | "no"]
" />

指定視口寬度精確匹配設備屏幕寬度同時禁用了縮放

1
2
3
4
<head>
<title>Example</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
</head>

通過WebView設置初始縮放(initial-scale)

1
2
3
4
// 設置初始縮放百分比
// 0表示依賴於setUseWideViewPort和setLoadWithOverviewMode
// 100表示不縮放
web.setInitialScale( 0)

管理 Cookies

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies

Cookie 是服務器發送到用戶瀏覽器並保存在瀏覽器上的一塊數據,它會在瀏覽器下一次發起請求時被攜帶並發送到服務器上。
可通過Cookie保存瀏覽信息來獲得更輕松的在線體驗,比如保持登錄狀態、記住偏好設置,並提供本地的相關內容。

會話Cookie 與 持久Cookie

  • 會話cookie不需要指定Expires和Max-Age,瀏覽器關閉之后它會被自動刪除。
  • 持久cookie指定了Expires或Max-Age,會被存儲到磁盤上,不會因瀏覽器而失效。

第一方Cookie 與 第三方Cookie

每個Cookie都有與之關聯的域,與頁面域一樣的就是第一方Cookie,不一樣的就是第三方Cookie。

1
2
3
4
// 設置接收第三方Cookie
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(vWeb, true);
}

讀取/寫入/移除 Cookie

1
2
3
4
5
6
7
8
9
10
// 獲取指定url關聯的所有Cookie
// 返回值使用"Cookie"請求頭格式:"name=value; name2=value2; name3=value3"
CookieManager.getInstance().getCookie(url);
 
// 為指定的url設置一個Cookie
// 參數value使用"Set-Cookie"響應頭格式,參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
CookieManager.getInstance().setCookie(url, value);
 
// 移除指定url下的指定Cookie
CookieManager.getInstance().setCookie(url, cookieName + "=");

webkit cookie 工具類

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class WebkitCookieUtil {
 
// 移除指定url關聯的所有cookie
public static void remove(String url) {
CookieManager cm = CookieManager.getInstance();
for (String cookie : cm.getCookie(url).split("; ")) {
cm.setCookie(url, cookie.split( "=")[0] + "=");
}
flush();
}
 
// sessionOnly 為true表示移除所有會話cookie,否則移除所有cookie
public static void remove(boolean sessionOnly) {
CookieManager cm = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (sessionOnly) {
cm.removeSessionCookies( null);
} else {
cm.removeAllCookies( null);
}
} else {
if (sessionOnly) {
cm.removeSessionCookie();
} else {
cm.removeAllCookie();
}
}
flush();
}
 
// 寫入磁盤
public static void flush() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().flush();
} else {
CookieSyncManager.getInstance().sync();
}
}
}

同步系統Cookie 與 Webkit Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 將系統級Cookie(比如`new URL(...).openConnection()`的Cookie) 同步到 WebView
public class WebkitCookieHandler extends CookieHandler {
private static final String TAG = WebkitCookieHandler.class.getSimpleName();
private CookieManager wcm;
 
public WebkitCookieHandler() {
this.wcm = CookieManager.getInstance();
}
 
@Override
public void put(URI uri, Map<String, List<String>> headers) throws IOException {
if ((uri == null) || (headers == null)) {
return;
}
String url = uri.toString();
 
for (String headerKey : headers.keySet()) {
if ((headerKey == null) || !(headerKey.equalsIgnoreCase("set-cookie2") || headerKey.equalsIgnoreCase("set-cookie"))) {
continue;
}
for (String headerValue : headers.get(headerKey)) {
Log.e(TAG, headerKey + ": " + headerValue);
this.wcm.setCookie(url, headerValue);
}
}
}
 
@Override
public Map<String, List<String>> get(URI uri, Map<String, List<String>> headers) throws IOException {
if ((uri == null) || (headers == null)) {
throw new IllegalArgumentException("Argument is null");
}
String url = uri.toString();
 
String cookie = this.wcm.getCookie(url);
Log.e(TAG, "cookie: " + cookie);
if (cookie != null) {
return Collections.singletonMap("Cookie", Arrays.asList(cookie));
} else {
return Collections.emptyMap();
}
}
}

緩存(Cache)

設置緩存模式

  • WebSettings.LOAD_DEFAULT 根據cache-control決定是否從網絡上取數據
  • WebSettings.LOAD_CACHE_ELSE_NETWORK 無網,離線加載,優先加載緩存(即使已經過期)
  • WebSettings.LOAD_NO_CACHE 僅從網絡加載
  • WebSettings.LOAD_CACHE_ONLY 僅從緩存加載
1
2
3
4
5
6
// 網絡正常時根據cache-control決定是否從網絡上取數據
if (isNetworkConnected(mActivity)) {
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}

清除緩存

1
2
3
// 傳入true表示同時內存與磁盤,false表示僅清除內存
// 由於內核緩存是全局的因此這個方法不僅僅針對webview而是針對整個應用程序
web.clearCache( true);

預加載(Preload)

一個簡單的預加載示例(shouldInterceptRequest)
點擊 assets/demo.xml 里的鏈接”hello”時會加載本地的 assets/hello.html

assets/demo.xml

1
2
3
4
5
<html>
<body>
<a href="http://demo.com/assets/hello.html">hello</a>
</body>
</html>

assets/hello.html

1
2
3
4
5
<html>
<body>
hello world!
</body>
</html>

重載 shouldInterceptRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return preload("assets/", url);
}
 
WebResourceResponse preload(String path, String url) {
if (!url.contains(path)) {
return null;
}
String local = url.replaceFirst( "^http.*" + path, "");
try {
InputStream is = getApplicationContext().getAssets().open(local);
String ext = MimeTypeMap.getFileExtensionFromUrl(local);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
return new WebResourceResponse(mimeType, "UTF-8", is);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

與Javascript交互

啟用Javascript

1
2
// 是否支持Javascript,默認值false
settings.setJavaScriptEnabled( true);

注入對象到Javascript

1
2
// 注入對象'jsobj',在網頁中通過`jsobj.say(...)`調用
web.addJavascriptInterface( new JSObject(), "jsobj")

在API17后支持白名單,只有添加了@JavascriptInterface注解的方法才會注入JS

1
2
3
4
5
6
public class JSObject {
@JavascriptInterface
public void say(String words) {
// todo
}
}

移除已注入Javascript的對象

1
web.removeJavascriptInterface( "jsobj")

執行JS表達式

1
2
3
4
// 彈出提示框
web.loadUrl( "javascript:alert('hello')");
// 調用注入的jsobj.say方法
web.loadUrl( "javascript:jsobj.say('hello')");

在API19后可異步執行JS表達式,並通過回調返回值

1
2
3
4
5
6
7
8
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
vWeb.evaluateJavascript( "111+222", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// value => "333"
}
});
}

地理位置(Geolocation)

https://developer.mozilla.org/zh-CN/docs/Web/API/Geolocation/Using_geolocation

需要以下權限

1
2
3
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

默認可用

1
settings.setGeolocationEnabled( true);

當H5調用地理位置API時,會先通過WebChromeClient.onGeolocationPermissionsShowPrompt申請授權

1
2
3
4
5
6
7
8
9
10
11
12
// 指定源的網頁內容在沒有設置權限狀態下嘗試使用地理位置API。
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
boolean allow = true; // 是否允許origin使用定位API
boolean retain = false; // 內核是否記住這次制授權
callback.invoke(origin, true, false);
}
 
// 之前調用 onGeolocationPermissionsShowPrompt() 申請的授權被取消時,隱藏相關的UI。
@Override
public void onGeolocationPermissionsHidePrompt() {
}

注:從API24開始,僅支持安全源(https)的請求,非安全源的請求將自動拒絕且不調用 onGeolocationPermissionsShowPrompt 與 onGeolocationPermissionsHidePrompt

彈框(alert/confirm/prompt/onbeforeunload)

在javascript中使用 alert/confirm/prompt 會彈出對話框,可通過重載 WebChromeClient 的下列方法控制彈框的交互,比如替換系統默認的對話框或屏蔽這些對話框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
// 這里處理交互邏輯
// result.cancel(); 表示用戶取消了操作(點擊了取消按鈕)
// result.confirm(); 表示用戶確認了操作(點擊了確認按鈕)
// ...
// 返回true表示自已處理,返回false表示由系統處理
return false;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return false;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return false;
}
 
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
return false;
}

全屏(Fullscreen)

Fullscreen API
https://developer.mozilla.org/zh-CN/docs/DOM/Using_fullscreen_mode

  • 當H5請求全屏時,會回調 WebChromeClient.onShowCustomView 方法
  • 當H5退出全屏時,會回調 WebChromeClient.onHideCustomView 方法

1.manifest

自己處理屏幕尺寸方向的變化(切換屏幕方向時不重建activity)
WebView播放視頻需要開啟硬件加速

1
2
3
4
5
<activity
android:name=".WebViewActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:screenOrientation="portrait" />

2.頁面布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Toolbar.Back"/>
 
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
 
<WebView
android:id="@+id/web"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
 
...
</FrameLayout>
 
</LinearLayout>

3.處理全屏回調

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
CustomViewCallback mCallback;
View vCustom;
 
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
setFullscreen( true);
vCustom = view;
mCallback = callback;
if (vCustom != null) {
ViewGroup parent = (ViewGroup) vWeb.getParent();
parent.addView(vCustom);
}
}
 
@Override
public void onHideCustomView() {
setFullscreen( false);
if (vCustom != null) {
ViewGroup parent = (ViewGroup) vWeb.getParent();
parent.removeView(vCustom);
vCustom = null;
}
if (mCallback != null) {
mCallback.onCustomViewHidden();
mCallback = null;
}
}

4.設置全屏,切換屏幕方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void setFullscreen(boolean fullscreen) {
if (fullscreen) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
vToolbar.setVisibility(View.GONE);
vWeb.setVisibility(View.GONE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
vToolbar.setVisibility(View.VISIBLE);
vWeb.setVisibility(View.VISIBLE);
}
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}

內存泄漏

直接 new WebView 並傳入 application context 代替在 XML 里面聲明以防止 activity 引用被濫用,能解決90+%的 WebView 內存泄漏。

1
2
vWeb = new WebView(getContext().getApplicationContext());
container.addView(vWeb);

銷毀 WebView

1
2
3
4
5
6
7
8
9
10
if (vWeb != null) {
vWeb.setWebViewClient( null);
vWeb.setWebChromeClient( null);
vWeb.loadDataWithBaseURL( null, "", "text/html", "utf-8", null);
vWeb.clearHistory();
 
((ViewGroup) vWeb.getParent()).removeView(vWeb);
vWeb.destroy();
vWeb = null;
}

參考

https://developer.android.com/reference/android/webkit/package-summary.html

Fullscreen API 全屏顯示網頁
http://calefy.org/2012/06/03/fullscreen-web-page-width-fullscreen-api.html

WebView實現全屏播放的一種方法
https://segmentfault.com/a/1190000007561455

第一方Cookie和第三方Cookie區別
https://www.biaodianfu.com/first-party-cookie-and-third-party-cookie.html

Android WebView的Js對象注入漏洞解決方案
http://blog.csdn.net/leehong2005/article/details/11808557

Android安全開發之WebView中的地雷
http://yaq.qq.com/blog/10

Android WebView:性能優化不得不說的事
https://juejin.im/entry/57d6434067f3560057e50b20


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM