由於文章太長,官方不讓發佈,所以文章就一分爲二了
WebViewClient相關方法
- shouldOverrideUrlLoading(WebView view, String url)和shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
shouldOverrideUrlLoading執行時機是重定向時(網頁自動重定向或手動點擊網頁內部鏈接)
mywebview.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//過時
Log.d("yunchong", "shouldOverrideUrlLoading1:"+url);
if(!TextUtils.isEmpty(url)){
if(url.startsWith("http://") || url.startsWith("https://")){
view.loadUrl(url);
} else{
//跳轉到第三方
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
MainActivity.this.startActivity(intent);
}catch (Exception e){
//如果拋出異常,則說明本地沒有安裝第三方
e.printStackTrace();
}
}
return true;
}
return false;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Log.d("yunchong", "shouldOverrideUrlLoading2:"+url);
String url = view.getUrl();
if(!TextUtils.isEmpty(url)){
if(url.startsWith("http://") || url.startsWith("https://")){
view.loadUrl(url);
} else{
//跳轉到第三方
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
MainActivity.this.startActivity(intent);
}catch (Exception e){
//如果拋出異常,則說明本地沒有安裝第三方
e.printStackTrace();
}
}
return true;
}
return false;
}
主要注意以下幾點:
(1)前者在API24(Android 7.0)之後已經過時,雖然已經過時了,但是我們還是需要用到這個方法的,原因是API24之前的手機只執行前者,後者只有在API24之後纔會執行;
(2)以上代碼中,後者的返回值有true和false兩種, 第三種返回值是默認返回值
return super.shouldOverrideUrlLoading(view, request);
這個返回值最終還是會執行前者方法。
2.shouldInterceptRequest(WebView view, String url)和shouldInterceptRequest(final WebView view, WebResourceRequest request)
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//過時
Log.d("yunchong", url);
return super.shouldInterceptRequest(view, url);
}
捕獲的日誌如下:
此方法廢棄於API21,調用於非UI線程,雖然廢棄了,但是我在Android4.4、Android6.0、Android8.0的手機經過測試,依然可以攔截到加載資源
攔截資源請求並返回響應數據,返回null時WebView將繼續加載資源
注意:API21以下的AJAX請求會走onLoadResource,無法通過此方法攔截
在API21之後新增一個新的方法:
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest request) {
view.post(new Runnable() {
@Override
public void run() {
Log.d("yunchong", "shouldInterceptRequest2:"+view.getUrl());
}
});
return super.shouldInterceptRequest(view, request);
}
用於替代前者。
3.onLoadResource(WebView view, String url)
這個方法和shouldInterceptRequest一樣,同樣可以攔截到加載資源,他們的區別是:shouldInterceptRequest已過時,onLoadResource沒有過時。
3.onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg)
這個方法是爲了解決一些重定向問題,已經徹底廢棄,現在處理重定向問題是用shouldOverrideUrlLoading方法了。
4.onPageStarted和onPageFinished
網頁的加載開始和加載結束。
5.onPageCommitVisible(WebView view, String url)
// 這個回調添加於API23,僅用於主框架的導航
// 通知應用導航到之前頁面時,其遺留的WebView內容將不再被繪製。
// 這個回調可以用來決定哪些WebView可見內容能被安全地回收,以確保不顯示陳舊的內容
// 它最早被調用,以此保證WebView.onDraw不會繪製任何之前頁面的內容,隨後繪製背景色或需要加載的新內容。
// 當HTTP響應body已經開始加載並體現在DOM上將在隨後的繪製中可見時,這個方法會被調用。
// 這個回調發生在文檔加載的早期,因此它的資源(css,和圖像)可能不可用。
// 如果需要更細粒度的視圖更新,查看 postVisualStateCallback(long, WebView.VisualStateCallback).
// 請注意這上邊的所有條件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)
@Override
public void onPageCommitVisible(WebView view, String url) {
Log.d("yunchong", "onPageCommitVisible:"+url);
super.onPageCommitVisible(view, url);
}
跟蹤日誌發現任意的網頁跳轉都會執行onPageCommitVisible方法,參數url就是當前頁面的url。
6.onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
// 此方法添加於API23
// 在加載資源(iframe,image,js,css,ajax...)時收到了 HTTP 錯誤(狀態碼>=400)
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
}
7.onFormResubmission(WebView view, Message dontResend, Message resend)
// 是否重新提交表單,默認不重發
@Override
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
super.onFormResubmission(view, dontResend, resend);
}
8.doUpdateVisitedHistory(WebView view, String url, boolean isReload)
// 通知應用可以將當前的url存儲在數據庫中,意味着當前的訪問url已經生效並被記錄在內核當中。
// 此方法在網頁加載過程中只會被調用一次,網頁前進後退並不會回調這個函數。
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
super.doUpdateVisitedHistory(view, url, isReload);
}
9.onReceivedClientCertRequest(WebView view, ClientCertRequest request)
// 此方法添加於API21,在UI線程被調用
// 處理SSL客戶端證書請求,必要的話可顯示一個UI來提供KEY。
// 有三種響應方式:proceed()/cancel()/ignore(),默認行爲是取消請求
// 如果調用proceed()或cancel(),Webview 將在內存中保存響應結果且對相同的"host:port"不會再次調用 onReceivedClientCertRequest
// 多數情況下,可通過KeyChain.choosePrivateKeyAlias啓動一個Activity供用戶選擇合適的私鑰
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
super.onReceivedClientCertRequest(view, request); //默認
//request.cancel();//取消
//request.ignore();//忽視
//request.proceed(param1, param2);//接受
}
10.onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)
// 處理HTTP認證請求,默認行爲是取消請求
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
Log.d("yunchong", "onReceivedHttpAuthRequest");
super.onReceivedHttpAuthRequest(view, handler, host, realm);//默認
//handler.cancel();//取消
//handler.proceed(username, password);//接受
}
11.shouldOverrideKeyEvent(WebView view, KeyEvent event)
// 給應用一個機會處理按鍵事件
// 如果返回true,WebView不處理該事件,否則WebView會一直處理,默認返回false
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return super.shouldOverrideKeyEvent(view, event);
}
12.onUnhandledKeyEvent(WebView view, KeyEvent event)
// 處理未被WebView消費的按鍵事件
// WebView總是消費按鍵事件,除非是系統按鍵或shouldOverrideKeyEvent返回true
// 此方法在按鍵事件分派時被異步調用
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
super.onUnhandledKeyEvent(view, event);
}
13.onScaleChanged(WebView view, float oldScale, float newScale)
// 通知應用頁面縮放係數變化
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
}
當webview支持雙指縮放時,onScaleChanged回調方法可以監聽webview的縮放係數。
14.onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args)
//通知應用有個已授權賬號自動登陸了
@Override
public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) {
super.onReceivedLoginRequest(view, realm, account, args);
}
15.onRenderProcessGone(WebView view, RenderProcessGoneDetail detail)
//這個API處理一個WebView對象的渲染程序消失的情況,要麼是因爲系統殺死了渲染器以回收急需的內存,要麼是因爲渲染程序本身崩潰了。
// 通過使用這個API,您可以讓您的應用程序繼續執行,即使渲染過程已經消失了。
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
//RenderProcessGoneDetail : 此類提供了有關渲染進程退出原因的更具體信息。應用程序可以使用它來決定如何處理這種情況。
//RenderProcessGoneDetail提供了兩個方法
//didCrash():指示是否觀察到呈現進程崩潰,或者是否被系統終止。
//rendererPriorityAtExit():返回在渲染器退出時設置的渲染器優先級。
if(!detail.didCrash()){
//由於系統內存不足,渲染器被終止。
//通過創建新的WebView實例,應用程序可以正常恢復
if (mywebview != null) {
ViewGroup webViewContainer = (ViewGroup) findViewById(R.id.mywebview);
webViewContainer.removeView(mywebview);
mywebview.destroy();
mywebview = null;
}
return true;
}
return false;
}
16.onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback)
//通知主應用程序加載URL已被安全瀏覽標記。
@Override
public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
callback.backToSafety(true);//類似於用戶點擊了“返回安全”按鈕
callback.proceed(true);//類似於用戶單擊了“訪問此不安全站點”按鈕一樣。
callback.showInterstitial(true);//如果爲true則顯示報告複選框
}
super.onSafeBrowsingHit(view, request, threatType, callback);
}
以上的註釋是由谷歌官方文檔翻譯而來,可悲的是我嘗試了7.0、8.0、8.1的手機仍然沒有執行到該方法。沒有發現此方法的執行時機。
17.onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
當加載https網頁的情況下,默認不進行證書校驗,當服務器要求webview必須進行證書校驗的時候,默認情況下加載網頁是一片空白,這個時候我們需要在onReceivedSslError方法裏做一些處理
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
也就是說,接受所有的https證書。
然而這樣做是不安全的,我們需要對證書做ssl校驗,代碼如下:(我們事先將crt證書放入assets目錄下)
@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
String currentUrl = view.getUrl();
if(TextUtils.isEmpty(currentUrl)){//個別手機獲取到的url是空的
currentUrl = homeUrl;//這裏的homeUrl是從其他地方獲取到的
}
SslManager.webviewSsl(currentUrl, new SslListener() {
@Override
public void onResponse() {
handler.proceed();
}
@Override
public void onFailure() {
handler.cancel();
}
});
}
public class SslManager {
/**
* webview ssl校驗
* @param url
*/
public static void webviewSsl(String url, final SslListener sslListener) {
if(!TextUtils.isEmpty(url)){
getOkHttpBuilder(url).build().newCall( new Request.Builder().url(url).build()).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
sslListener.onFailure();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
sslListener.onResponse();
}
});
}
}
public static OkHttpClient.Builder getOkHttpBuilder(String url){
OkHttpClient.Builder builder = null;
String crt;
if(!TextUtils.isEmpty(url)){
crt = "crt/crtname.crt";
try {
builder = setCertificates(new OkHttpClient.Builder(), IMApp.getAppContext().getResources().getAssets().open(crt));
} catch (IOException e) {
builder = new OkHttpClient.Builder();
}
}
return builder;
}
private static OkHttpClient.Builder setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
e.printStackTrace();
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
client.sslSocketFactory(sslSocketFactory, trustManager);
//hostName驗證
client.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
String peerHost = session.getPeerHost();//服務器返回的域名
try {
X509Certificate[] peerCertificates = (X509Certificate[]) session.getPeerCertificates();
for (X509Certificate c : peerCertificates) {
X500Principal subjectX500Principal = c.getSubjectX500Principal();
String name = new X500p(subjectX500Principal).getName();
String[] split = name.split(",");
for (String s : split) {
if (s.startsWith("CN")) {
if (s.contains(hostname) && s.contains(peerHost)) {
return true;
}
}
}
}
} catch (SSLPeerUnverifiedException e) {
e.printStackTrace();
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
}
WebChromeClient相關方法
1.onProgressChanged(WebView view, int newProgress)
這是webview加載網頁的進度監聽,常用作於添加瀏覽器的進度條。
//監聽加載網頁的進度情況
mywebview.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
2.onReceivedTitle(WebView view, String title)
//獲取網頁的標題
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
3.onReceivedIcon(WebView view, Bitmap icon)
//字面上的意思是:當前網頁接收到icon的時候執行此方法
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
4.onReceivedTouchIconUrl(WebView view, String url, boolean precomposed)
//字面上的意思是:觸摸當前網頁接收到icon的時候執行此方法
@Override
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
super.onReceivedTouchIconUrl(view, url, precomposed);
}
5.onShowCustomView和onHideCustomView
這兩個方法往往是一起使用,常用於解決webview不能全屏播放視頻的問題。(有必要的話對當前activity開啓硬件加速)
代碼如下:
private View customView;//默認view
private FrameLayout fullscreenContainer;//全屏view
/** 視頻全屏參數 */
private FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
private WebChromeClient.CustomViewCallback customViewCallback;
//顯示自定義View:常用於視頻全屏展示
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (customView != null) {
callback.onCustomViewHidden();
return;
}
//MainActivity.this.getWindow().getDecorView();
FrameLayout decor = (FrameLayout) getWindow().getDecorView();
fullscreenContainer = new FullscreenHolder(MainActivity.this);
fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
customView = view;
setStatusBarVisibility(false);
customViewCallback = callback;
}
//顯示自定義View:常用於視頻全屏展示
@Override
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
onShowCustomView(view, callback);
}
//隱藏自定義View:
@Override
public void onHideCustomView() {
hideCustomView();
}
//視頻從全屏還原到原來
private void hideCustomView(){
if (customView == null) {
return;
}
setStatusBarVisibility(true);
FrameLayout decor = (FrameLayout) getWindow().getDecorView();
decor.removeView(fullscreenContainer);
fullscreenContainer = null;
customView = null;
customViewCallback.onCustomViewHidden();
mywebview.setVisibility(View.VISIBLE);
}
這裏還需要注意的是,點擊返回鍵應該退出全屏
@Override
public void onBackPressed() {
if(customView != null){
hideCustomView();
}else if(mywebview != null && mywebview.canGoBack()){
mywebview.goBack();
}else{
finish();
}
}
6.onCreateWindow和onCloseWindow
//請求主機應用創建一個新窗口。
//如果主機應用選擇響應這個請求,則該方法返回true,並創建一個新的WebView,
//將其插入到視圖系統中,並將其提供的resultMsg作爲參數提供給新的WebView。
//如果主機應用選擇不響應這個請求時,則該方法返回false。
//默認情況下,該方法不做任何處理並返回false。
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
}
//通知主機主機應用WebView關閉了,並在需要的時候從view系統中移除它。
//此時,WebCore已經停止窗口中的所有加載進度,並在javascript中移除了所有cross-scripting的功能。
@Override
public void onCloseWindow(WebView window) {
super.onCloseWindow(window);
}
7.onRequestFocus(WebView view)
@Override
public void onRequestFocus(WebView view) {
super.onRequestFocus(view);
}
不太清楚它的執行時機,其他博客的解釋是:請求獲得WebView的焦點,這可能由於另一個WebView打開一個連接,需要被展示.
8.三種提示框Alert、Confirm、Prompt
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
常用的提示框是Alert和Confirm。
網上也是有相關博客 js中三種彈出框。
9.onJsBeforeUnload(WebView view, String url, String message, JsResult result)
//JS相關操作,會在頁面關閉或刷新調用,觸發事件時,彈出對話框是否關閉,確定則關閉頁面,取消則保持該頁面。
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
return super.onJsBeforeUnload(view, url, message, result);
}
10.onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater)
//使webview支持建立html5本地緩存數據庫
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(estimatedDatabaseSize * 2);
}
11.onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)
//已被廢棄,不用管
@Override
public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
}
12.webview定位權限提示框
@Override
public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
final boolean remember = false;//是否記住
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("位置信息");
builder.setMessage(origin + "允許獲取您的地理位置信息嗎?")
.setCancelable(true)
.setPositiveButton("允許", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int id) {
callback.invoke(origin, true, remember);
} })
.setNegativeButton("不允許", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int id) {
callback.invoke(origin, false, remember);
}
});
AlertDialog alert = builder.create();
alert.show();
}
@Override
public void onGeolocationPermissionsHidePrompt() {
super.onGeolocationPermissionsHidePrompt();
}
13.來自網頁的權限請求
@Override
public void onPermissionRequest(PermissionRequest request) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
request.deny();//調用此方法以拒絕請求(默認)
request.getOrigin();//調用此方法以獲取試圖訪問受限資源的網頁的來源。
request.getResources();//調用此方法獲取網頁試圖訪問的資源。
request.grant(request.getResources());//調用此方法授予Origin訪問給定資源的權限。
}
}
@Override
public void onPermissionRequestCanceled(PermissionRequest request) {
super.onPermissionRequestCanceled(request);
}
默認是拒絕所有請求,開發者可以根據需求授予指定網頁的權限請求。
14.onJsTimeout()
//已過時,不用管
@Override
public boolean onJsTimeout() {
return super.onJsTimeout();
}
15.onConsoleMessage
//三個參數的方法已經廢棄,被一個參數的方法替代
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
super.onConsoleMessage(message, lineNumber, sourceID);
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
return super.onConsoleMessage(consoleMessage);
}
可以捕獲到網頁打印在控制檯的日誌,有利於日誌的跟蹤。
16.getDefaultVideoPoster()
//Html中,視頻(video)控件在沒有播放的時候將給用戶展示一張“海報”圖片(預覽圖)。
// 其預覽圖是由Html中video標籤的poster屬性來指定的。如果開發者沒有設置poster屬性, 則可以通過這個方法來設置默認的預覽圖。
@Override
public Bitmap getDefaultVideoPoster() {
Bitmap bitmap = super.getDefaultVideoPoster();
if(bitmap == null){
return BitmapFactory.decodeResource(getApplicationContext().getResources(), R.mipmap.ic_launcher);
}
return super.getDefaultVideoPoster();
}
17.getVideoLoadingProgressView()
//播放視頻時,在第一幀呈現之前,需要花一定的時間來進行數據緩衝。
//ChromeClient可以使用這個函數來提供一個在數據緩衝時顯示的視圖。
// 例如,ChromeClient可以在緩衝時顯示一個轉輪動畫。
@Override
public View getVideoLoadingProgressView() {
return super.getVideoLoadingProgressView();
}
18.getVisitedHistory(ValueCallback<String[]> callback)
//獲得所有訪問歷史項目的列表,用於鏈接着色。
@Override
public void getVisitedHistory(ValueCallback<String[]> callback) {
super.getVisitedHistory(callback);
}
19.讓webview支持inputfile控件
public ValueCallback<Uri> mUploadMessage;
public ValueCallback<Uri[]> mUploadMessageForAndroid5;
public final static int FILECHOOSER_RESULTCODE = 1;
public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
//android3.0以前
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//android3.0到android4.0
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//android4.0到android4.3
public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
mUploadMessage = filePathCallback;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//android5.0以上
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (mUploadMessageForAndroid5 != null) {
mUploadMessageForAndroid5.onReceiveValue(null);
mUploadMessageForAndroid5 = null;
}
mUploadMessageForAndroid5 = filePathCallback;
Intent intent = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
intent = fileChooserParams.createIntent();
}
try {
startActivityForResult(intent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
} catch (ActivityNotFoundException e) {
mUploadMessageForAndroid5 = null;
return false;
}
return true;
}
});
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent intent) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
}
if (mUploadMessageForAndroid5 != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mUploadMessageForAndroid5.onReceiveValue(null);
mUploadMessageForAndroid5 = null;
}
}
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage){
return;
}
Uri result = intent == null || resultCode != RESULT_OK ? null:intent.getData();
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
} else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5){
if (null == mUploadMessageForAndroid5){
return;
}
Uri result = (intent == null || resultCode != RESULT_OK) ? null: intent.getData();
if (result != null) {
mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
} else {
mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
}
mUploadMessageForAndroid5 = null;
}
}
Android有個很大的坑,添加以上代碼幾乎可以支持Android的所有版本了,唯獨Android4.4.*不支持,這裏推薦H5和客戶端判斷Android的版本,採用JS的方式支持inputfile。
webview之JS交互
- Android調用JS代碼
(1)方法一:採用loadUrl方式
第一步:準備好html靜態頁面,寫好JS方法
<!DOCTYPE html>
<html>
<head> <meta charset="utf-8">
<title>Carson_Ho</title> // JS代碼
<script>
function callJS(){
alert("Android調用了JS的callJS方法");
}
</script>
</head>
</html>
第二步:加載該頁面
mywebview.loadUrl("file:///android_asset/htmldemo.html");
第三步:點擊某按鈕,加載JS代碼
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_js:
mywebview.loadUrl("javascript:callJS()");
break;
}
}
效果展示:
弊端:會刷新當前頁面,執行效率慢。
(2)方法二:採用evaluateJavascript方式
其步驟和展示效果和方法一一樣
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_js:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mywebview.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
}else {
mywebview.loadUrl("javascript:callJS()");
}
break;
}
}
優點:不會刷新當前頁面
弊端:Andorid4.4之後才能使用(不過現在4.4之前的手機幾乎已被淘汰)
- JS調用Android代碼
方法一:採用addJavascriptInterface映射
步驟一:定義接口
public class AndoridToJsInterface {
@JavascriptInterface
public void hello(){
System.out.println("hello");
}
}
AndoridToJsInterface 類專門負責聲明接口,接口方法必須添加“@JavascriptInterface”修飾,該註解將接口暴露給JS。其作用是爲JS代碼提供調用的接口。JS代碼想要調用Android代碼聲明的接口,就必須給JS創建一個映射
//AndroidtoJS類對象映射到js的test對象
mywebview.addJavascriptInterface(new AndoridToJsInterface(), "android");
第一個參數是AndoridToJsInterface 對象,第二個參數就是AndoridToJsInterface對象的別名,也就是給JS使用的映射。
現在開始編寫JS代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
android.hello();
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">點擊調用Android的hello方法</button>
</body>
</html>
優點:使用簡單
缺點:註解使Android的接口暴露出來,是一個高危漏洞
方法二:採用shouldOverrideUrlLoading攔截URl
編寫好JS代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callAndroid(){
//事先預定的URI
document.location = "android://webview?name=zhangsan&age=22";
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()">點擊調用Android代碼</button>
</body>
</html>
其中"android://webview?name=zhangsan&age=22"是事先預定好的格式,然後在點擊網頁上的按鈕觸發JS代碼,並在Android端捕獲
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if(uri.getScheme().equals("android") && uri.getAuthority().equals("webview")){
Set<String> paraNames = uri.getQueryParameterNames();
Iterator it = paraNames.iterator();
while (it.hasNext()){
String name = (String) it.next();
Log.d("aaa", "name:"+name);
}
}
return super.shouldOverrideUrlLoading(view,url);
}
優點:沒有方法一的安全漏洞
缺點:使用複雜,並且將所有的JS處理都放在shouldOverrideUrlLoading中略顯臃腫
方法三:採用onJsAlert、onJsConfirm、onJsPrompt捕獲URL
和方法二原理類似,這裏就不在描述
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
優點:沒有方法一的安全漏洞
缺點:使用複雜,並且將所有的JS處理都放在onJsAlert、onJsConfirm、onJsPrompt中略顯臃腫。
JS交互總結:
- Android調用JS代碼推薦使用evaluateJavascript,他的好處顯而易見,既不會重新刷新頁面,也可以獲取返回值
- 三種JS調用Android代碼的方法都不推薦使用,addJavascriptInterface映射的方式是一個高危漏洞,其他兩種使用太過複雜,這裏推薦使用JsBridge實現JS交互。
最後分享一個github上開源的JsBridge案例: