前言
現(xiàn)如今,大部分APP都是原生+h5形式的,原生的流暢+h5的內(nèi)容多樣性,使得APP內(nèi)容豐富多彩,因此android開發(fā)中,常會遇到與H5交互的形式,其原理說白了無非是:原生方法和JS方法的互調(diào),該文主要簡單談?wù)勍ㄓ脀eb展示界面的封裝,APP內(nèi)需要用到h5的地方直接用它就行了。
常用的交互場景
有哪些常用交互場景呢?這里簡單做了個總結(jié),可能會有不全面的地方。
場景名稱 | 場景介紹 | js->原生 | 原生->js |
---|---|---|---|
標題內(nèi)容以及隱藏和展示 | 有的界面需要原生標題 有的界面需要展示web端的標題 |
● | ○ |
標題欄和狀態(tài)欄顏色修改 | 不同web界面可能有不同的主題效果 可以參考《京東金融》app |
● | ○ |
社交分享 | 帶彈窗的 直接分享的(web提供彈窗) |
● | ○ |
跳轉(zhuǎn)內(nèi)部 | 如跳轉(zhuǎn)到登陸界面 | ● | ○ |
跳轉(zhuǎn)外部 | 如選擇用什么地圖打開 | ● | ○ |
類微信更多彈窗 | 刷新、復制鏈接、瀏覽器打開等 | ● | ○ |
控制更多彈窗的內(nèi)容數(shù)量 | a界面只有分享 b界面有瀏覽器打開 |
● | ○ |
地圖轉(zhuǎn)換 | 點擊web地圖模塊跳轉(zhuǎn)到原生地圖界面 | ● | ○ |
支付 | 調(diào)用原生微信、支付寶支付 | ● | ○ |
圖片預覽 | 調(diào)用原生圖片預覽控件效果更佳 | ● | ○ |
返回機制 | 該情況只有在隱藏原生標題欄時用得到 | ● | ○ |
隱藏底部導航欄 | 如首頁fragment內(nèi)容點擊詳情時 需要隱藏底部的tab,防止遮擋 |
● | ○ |
撥號、系統(tǒng)彈窗 | 很常見的場景 | ● | ○ |
文件操作 | 上傳圖片、文件等 | ● | ○ |
同步登陸信息 | 原生登陸登出,web登陸狀態(tài)變更 | ○ | ● |
同步定位信息 | h5直接用原生定位信息 | ○ | ● |
... | ... | ... | ... |
準備工作
既然是js和原生的交互,webview要設(shè)置一番才行。
WebSettings webSettings = webView.getSettings();
//支持javascript
webSettings.setJavaScriptEnabled(true);
//將圖片調(diào)整到適合webview的大小
webSettings.setUseWideViewPort(true);
// 縮放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
//縮放操作
//支持縮放,默認為true。是下面那個的前提。
webSettings.setSupportZoom(true);
//設(shè)置內(nèi)置的縮放控件。若為false,則該WebView不可縮放
webSettings.setBuiltInZoomControls(true);
//隱藏原生的縮放控件
webSettings.setDisplayZoomControls(false);
// 文件操作
webSettings.setAllowFileAccess(true);
//啟用數(shù)據(jù)庫
webSettings.setDatabaseEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//不設(shè)置可能會導致js調(diào)用失敗
webSettings.setDomStorageEnabled(true);
//加載速度優(yōu)化:先加載文字后加載圖片
webSettings.setBlockNetworkImage(true);
webSettings.setLoadsImagesAutomatically(true);
//設(shè)置UA,讓瀏覽器知道使用者設(shè)備
String ua = webSettings.getUserAgentString();
if (!ua.contains(YYNATIVE)) {
webSettings.setUserAgentString(webSettings.getUserAgentString() + "; " + YYNATIVE);
}
webSettings.setAppCacheEnabled(true);
webSettings.setAppCacheMaxSize(1024 * 1024 * 8);
webSettings.setAppCachePath(getApplication().getCacheDir().getAbsolutePath());
//向web端注入對象
webView.addJavascriptInterface(this, YYNATIVE);
到此,webview的參數(shù)配置完了,需要注意的是webSettings.setJavaScriptEnabled(true);
和webSettings.setUserAgentString(webSettings.getUserAgentString() + "; " + YYNATIVE);
以及webView.addJavascriptInterface(this, YYNATIVE);
這三行代碼是必不可少的。第一行,設(shè)置js支持,不設(shè)置js不鳥你;第二行,設(shè)置UA標識,不設(shè)置web端不知道這是android還是ios或者微信web端;第三行不注入對象,web端也是無法喚醒Java方法的。
實現(xiàn)場景案例
js調(diào)用android
標題欄的隱藏和展示
android端代碼方法的命名需要和web端約定,web端調(diào)用方式是:window.約定對象名.方法名(參數(shù))
如window.yynative.setTopVisiable(true)
:
/**
* 設(shè)置標題
*
* @param title
*/
@JavascriptInterface
public void setTopTitle(String title) {
AppUtils.runOnUIThread(() -> tvTitle.setText(title));
}
/**
* 設(shè)置標題欄可見與否
*
* @param visiable
*/
@JavascriptInterface
public void setTopVisiable(boolean visiable) {
AppUtils.runOnUIThread(() -> llTop.setVisibility(visiable ? View.VISIBLE : View.GONE));
}
注意:@JavascriptInterface
該注解必不可少,否則js會提示找不到方法,另外UI操作需要在UI線程里進行。
標題欄和狀態(tài)欄顏色修改
/**
* 設(shè)置標題背景色
*
* @param
*/
@JavascriptInterface
public void setTopBgColor(String color) {
AppUtils.runOnUIThread(() -> llTop.setBackgroundColor(Color.parseColor(color)));
....//修改標題字體顏色
....//修改icon
}
/**
* 設(shè)置狀態(tài)欄背景色
*
* @param
*/
@JavascriptInterface
public void setStatusBgColor(String color) {
AppUtils.runOnUIThread(() ->StatusBarUtils.setColor(Color.parseColor(color)));
}
注意:狀態(tài)欄的顏色修改,避免狀態(tài)欄字體看不清;標題欄顏色修改時,要防止背景色和圖標、字體顏色重復。
以上2種場景為大多數(shù)js調(diào)用android的模式,還有一些是不需要我們自己定義方法,系統(tǒng)幫給我們方法或者另辟蹊徑,需要我們用到WebViewClient
和WebChromeClient
,他們的區(qū)別是:
- WebViewClient:在影響【View】的事件到來時,會通過WebViewClient中的方法回調(diào)通知用戶,主要幫助WebView處理各種通知、請求事件的;
- WebChromeClient:當影響【瀏覽器】的事件到來時,就會通過WebChromeClient中的方法回調(diào)通知用法,主要輔助WebView處理Javascript的對話框、網(wǎng)站圖標、網(wǎng)站title、加載進度,文件處理等。
撥號處理
使用WebViewClient
實現(xiàn)shouldOverrideUrlLoading
方法
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String s) {
//撥號處理
if (s.startsWith(TEL)) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(s));
startActivity(intent);
return true;
}
webView.loadUrl(s);
return true;
}
js系統(tǒng)彈窗
使用WebChromeClient
實現(xiàn)onJsAlert
、onJsConfirm
、onJsPrompt
方法,這里給出onJsAlert
的實現(xiàn)代碼:
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
// return super.onJsAlert(view, url, message, result);
final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
builder.setTitle("溫馨提示")
.setMessage(message)
.setPositiveButton("確定", null);
// 不需要綁定按鍵事件
// 屏蔽keycode等于84之類的按鍵
builder.setOnKeyListener((dialog, keyCode, event) -> true);
// 禁止響應按back鍵的事件
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
result.confirm();// 因為沒有綁定事件,需要強行confirm,否則頁面會變黑顯示不了內(nèi)容。
return true;
}
文件處理
同樣,用到WebChromeClient
,實現(xiàn)onShowFileChooser
方法(android5.0+,android4.1.1+實現(xiàn)openFileChooser
),然后自己實現(xiàn)文件處理邏輯。
//回調(diào)的接口
public interface OpenFileChooserCallBack {
void openFileChooserCallBack(ValueCallback<Uri> uploadMsg, String acceptType);
void openFileChooser5CallBack(WebView webView, ValueCallback<Uri[]> valueCallback,
FileChooserParams fileChooserParams);
}
//針對 Android 5.0+
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback,
FileChooserParams fileChooserParams) {
mOpenFileChooserCallBack.openFileChooser5CallBack(webView, valueCallback, fileChooserParams);
return true;
}
//在實現(xiàn)里做自己的邏輯
@Override
public void openFileChooser5CallBack(WebView webView, ValueCallback<Uri[]> valueCallback, WebChromeClient.FileChooserParams fileChooserParams) {
mValueCallback = valueCallback;
//文件處理函數(shù)
showOptions();
}
使用WebChromeClient
還可以處理加載進度,視頻橫豎屏切換等,這里不做詳細介紹。
android調(diào)用js
同步信息
假設(shè)需要同步的信息是用戶的token
,token
有效視為已登陸,失效需要重新登陸。一定要區(qū)分清楚h5什么時候需要你給的參數(shù),h5的內(nèi)容需要登陸后才能展示,那token
傳遞需要在webview加載時傳給h5,不能在WebViewClient
中的onPageFinished
方法中傳遞,我們需要在加載時調(diào)用,應該在onLoadResource
中傳遞,反之在onPageFinished
中調(diào)用;同理,定位信息也是。
@Override
public void onLoadResource(WebView webView, String s) {
if (mFirstLoad) {
String token = getUserIdAuthKey();
HashMap<String, String> cityMap = new HashMap<>();
cityMap.put("city", CityBean.city);
cityMap.put("lat", CityBean.Latitude + "");
cityMap.put("lon", CityBean.Longitude + "");
String cityInfo = JsonUtils.serialize(cityMap);
webView.evaluateJavascript("javascript:window.getUserIdFromApp(" + "\"" + token + "\"" + ")", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
webView.evaluateJavascript("javascript:window.getlocationInfoFromApp(" + cityInfo + ")", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
}
super.onLoadResource(webView, s);
}
注意:evaluateJavascript
在android4.4后才能使用,在此之前可以使用webview.loadUrl
方法。
總結(jié)
android和h5的交互場景大致就是這些,很多場景是類似的,因此不做過多分析,可以舉一反三,只是其中有些細節(jié)需要大家注意,比如:
- 支付結(jié)束后需要把支付結(jié)果和狀態(tài)回調(diào)給h5,即android調(diào)js,讓h5來操作后續(xù);
- 又如圖片預覽,如果傳過來的是多組圖片,h5還需要傳遞用戶點擊的圖片索引給webview,我們預覽時可以顯示當前圖片所在位置。
- 通過h5跳轉(zhuǎn)登陸成功后,要記得立馬同步登陸信息給h5,否則會繼續(xù)跳轉(zhuǎn)登陸
....
....
希望這篇博客能給新手少走彎路,如有錯誤之處還望指正!