在移動端頁面中,由于屏幕空間有限,導航條扮演著非常重要的角色,提供了快速導航到不同頁面或功能的方式。用戶也通常會在導航條中尋找他們感興趣的內容,因此導航條的曝光率較高。在這樣的背景下,提供一個動態靈活的導航條,為產品賦能,變得尤其重要。
拿iOS原生導航條為例,導航條作為頁面進出棧的根視圖連接器,以及生命周期的管理器。尤其是在作為webView Controller的父容器的時候,面對webview中h5頁面靈活的的路由屬性,以及一些難料的異常情況,原生很難也不便于頻繁操作根試圖容器,因此也產生了一些性能差、體驗差、開發成本高、測試場景難覆蓋等問題。安卓也有類似情況。
?ssr預渲染時,無法對原生導航條進行預加載。對于百億,便宜包郵等使用ssr預渲染的頻道,因為原生導航欄無法進行預加載,導致上屏較慢等問題。
?原生導航條生命周期耦合。原生導航條作為webviewController的根容器,一旦操作時機不當,很可能影響到線上頁面,而且最大的問題在于這種場景測試很難覆蓋。比如:window.href.url使用這種方式更新當前頁面時,由于不同頻道操作同一根導航條,會引發不可預知的問題;
?場景有限。站外場景無法使用原生導航條,一些業務方往往需要單獨處理站內外,造成開發資源浪費。
?webview初始化時會預置一個默認的導航條,然后根據前端配置,再去設置導航條的不同樣式,無法避免的存在一個過渡期,體驗較差。
?window.location.reload()刷新當前頁面的時候,即便是在js中隱藏了導航條,webview為了兼容一個線上問題,執行reload時此時會先展示原生導航條,直到執行了js的隱藏邏輯,才會被隱藏,體驗較差。
?無法擴展交互動效。得益于移動端頁面中,導航條得天獨厚的位置,產品往往希望有更生動的交互性,來提高曝光、粘性、活動觸達率等。比如導航欄上掛載搜索框、以及吸頂、延伸動畫、沉浸式、炫酷的營銷icon等等。遺憾的是原生系統導航條不能全部支持,其實無論從視圖層級上來說,還是從導航條職責上來說,apple并不希望過多操作導航欄上的元素。也就造成了高曝光位置的資源浪費。
?因為要依賴原生JS橋,就一定會存在版本限制問題。造成需求迭代慢,甚至隨著時間的推移,版本卡口原因無跡可尋,代碼調整戰戰兢兢,版本審核慢、周期長等問題。
基于原生導航條現狀,百億補貼頻道沉淀出了通用H5導航條組件@pango/navigation-bar,具有以下優勢:
?支持ssr預渲染,上屏較快。
?人力節省百分之90%以上,以plus 95折為例,對接只需0.5/人日。
?無場景限制??捎糜谡緝韧?,ssr以及csr場景,無需站內外多次開發。
?可配置。 @pango/navigation-bar使用config的形式配置item,這么做的好處是一旦業務需求改動,只需調整配置,無需調整組件邏輯,極大降低開發和測試成本。另外如果你使用主站的webview并且配置了config,那么只需要簡單的改動config,代碼遷移成本低。
?該組件發布在JNPM上,大小只有4.1K,接入簡單。
?導航條在頻道內和其他普通樓層無異,生命周期隔離清晰,不會影響別的頁面,測試成本低。
?單向數據流設計,外部數據變化,組件UI及時響應,不存在原生的操作窗口問題,開發體驗佳;
?生命周期和其他樓層保持同步,規避了原生容器和H5頁面天然的生命周期無法同步的問題,也就不存在兩者之間的過渡問題,體驗佳。
?采用左、中、右、狀態欄、導航欄分層設計的模式,支持傳入React.ReactElement,比原生定制性更強,可靈活定制目前站內絕大部分導航條樣式以及交互動畫,合理高效利用導航條資源;
?參考原生導航欄異形屏適配方案,參考原生絕對布局思路,完美適配折疊屏、異形屏。
?iOS9 - 最新 、Android5 - 最新均兼容性良好,未發現線上兼容異常。
純手工打造,未使用第三方庫,不會對宿主造成依賴沖突,隨時改動隨時發布不存在版本控制,最大程度的降低和隔斷對原生容器的版本依賴。
原生導航條作為根試圖容器,容器內子視圖異常不會影響根試圖的展示,所以不用特殊處理html下載失敗,js執行異常,服務掛掉等異常情況。但是H5導航條遇到這些異常情況,也要保證用戶可以點擊返回按鈕返回上一頁。
目前方案已和通天塔以及hybrid團隊打通,方案如下:
異常場景1:業務js執行異常。
? @pango/navigation-bar組件使用a標簽渲染返回按鈕,保證js執行異常時依然展示返回按鈕,并且能正常響應返回事件。
?業務展示兜底錯誤頁時,會使用導航條兜底數據渲染導航條確保可返回上一級。
異常場景2:webview加載html失敗。
為了消除上面提到的過渡問題,業務鏈接中新增了qurey參數hideNavi=1 ,原生webview會通過該字段在webview出現之前隱藏導航條。但是因此也引發了一個風險:html加載失敗時,會造成無頭的問題。因此需要webview配合改造,一旦監測到html加載失敗,原生webview要展示原生導航條。
異常場景3:通天塔服務異常。
同樣是場景2中的問題,需要通天塔配合改造通天塔服務異常的場景:依據鏈接中hideNavi字段添加返回按鈕或者通知webview展示默認導航條。
觀察多個競品以及兄弟頻道,發現在上述的異常場景2、3下,均未做特別處理,展示無頭錯誤頁。如果此時原生禁用了右滑返回手勢,頁面將無法返回上一級,這無異是一個非常嚴重的缺陷(事實上有些競品頁面以及我們某些頻道確實無法返回上一級)。
目前使用該組件的項目:百億補貼、月黑風高、PLUS95折。
參考原生navigationBar的設計思路,把整個導航欄分為左、右、中三個區域,左、右區域根據內容自適應寬度,剩余空間為中間區域。左右區域接受items數組,可根據item接口協議設置左右的items,協議可自定義圖片、尺寸、事件、間距、下拉菜單、是否動畫響應等,已默認包含了關注、返回、更多、頻道logo等常用元素,當然如有需要也可以自定義一個React.ReactElement。中間區域只接受React.ReactElement,你可以自由定制元素,傳入navigation-bar即可,一張圖片一段文字,或者是一個搜索框……不管是伸縮或者是上滑吸頂,都可自定義。
該組件發布在JNPM上,使用react + ts開發,大小只有4.1K。
文件結構:
npm i @pango/navigation-bar --registry=http://registry.m.jd.com
你可以自由配置items除了"follow", "more","back","logo",這些已知的元素外還可以設置type:"common",是一個通用類型的item;
scrollCallBack會回調上滑比例,可根據該比例做交互動畫;
import {
BACK_ICON,
FEEDBACK_ICON,
FEEDBACK_URL,
INavigationParams,
MORE_ICON,
RULE_ICON,
SHARE_ICON,
} from "@pango/navigation-bar";
setH5NavigationButton=(headerData)=> {
const extend=headerData?.navigationBar?.extend;
const followInfo=headerData?.navigationBar?.followInfo;
const follow={
type: "follow",
collectionId: String(followInfo?.themeId),
gapWidth: 12,
width: 55,
height: 22,
};
const moreItem={
type: "more",
menuBackgroundColor: "white",
img: MORE_ICON,
title: "更多",
menuList: [],
};
moreItem.menuList.push({
icon: RULE_ICON,
title: "規則頁",
menuEventData: extend?.guideUrl,
});
moreItem.menuList.push({
icon: SHARE_ICON,
title: "分享",
type: "share",
menuEventData: extend?.share,
});
const backItem={
type: "back",
img: BACK_ICON,
canClick: !margicWindow,
title: "返回",
};
const backLogo={
type: "logo",
img: DEFAULT_LOGO,
isAnimation: true,
gapWidth: 5,
width: 176,
height: 34
};
const navBarParams: INavigationParams={
leftItems: [],
rightItems: [],
backgroundColor: "#FD4D00",
navHeight: this.status.navHeight,
};
navBarParams.leftItems.push(backItem, backLogo);
navBarParams.rightItems.push(moreItem, follow);
navBarParams.titleImgItem=TitleSearch({});
navBarParams.scrollCallBack=(scale)=> {
this.setStatus({
navigationBarParams: Object.assign(this.status.navigationBarParams, {
titleImgItem: TitleSearch({ isCollapse: scale===1 })
})
});
}
return navBarParams;
};
特別注意titleImgItem,這個屬性是導航條中間區域的展示內容,TitleSearch是百億補貼的搜索框,你可以參考該元素自定義中間區域。
?title-search-view.tsx?
?title-search-view.scss?
import { INavigationParams, NavigationBar } from "@pango/navigation-bar";
import "@pango/navigation-bar/lib/navigation-bar.scss";
css
.nav-bar {
width: 750px;
z-index: 1;
top: 0px;
}
<NavigationBar
className="nav-bar"
params={data.navParams}
barHeight={200} //自定義導航欄高度
event={do somethings}
/>
Q:若原生導航條隱藏,此時異常怎么辦?
異常分為以下3類:
異常場景1:業務js執行異常。
? @pango/navigation-bar組件使用a標簽渲染返回按鈕,保證js執行異常時依然展示該標簽,并且能正常相應出棧事件。
?業務展示兜底錯誤頁時,會使用導航條兜底數據渲染導航條。
異常場景2:webview加載html失敗。
為了消除上面提到的過渡問題,業務鏈接中新增了qurey參數hideNavi=1 ,原生webview會通過該字段在webview出現之前隱藏導航條。但是因此也引發了一個風險:html加載失敗時,會造成無頭的問題。因此需要webview配合改造,一旦監測到html加載失敗,原生webview要展示原生導航條。
異常場景3:通天塔服務異常。
同樣是場景2中的問題,需要通天塔配合改造通天塔服務異常的場景:依據鏈接中hideNavi字段添加返回按鈕或者通知webview展示默認導航條。
若發現其他異常,麻煩提醒我。
Q:折疊屏怎么適配?
折疊屏適配一直是前端適配的噩夢,噩夢的根本原因在于:寬度于高度的比例非常值,前端布局是往往會把px轉換成vw,因此造成了異形屏適配難的問題。
?參考原生系統導航欄的絕對布局方案:@pango/navigation-bar把導航條拆分為狀態欄和導航欄上下兩部分, 導航條寬度屏幕自適應,導航條高度跟隨設備變化,并采用大寫的PX單位來固定元素尺寸。根據協議item寬高、間距仍可自定義,但是大寫的PX保證了item不會隨著屏幕寬度而異常變化。
navigation-bar {
width: 750px; // 會轉換成vw
height: 44PX; // 不會轉換成vw
display: flex;
position: absolute;
.left-items-bg {
margin-left: 16PX; // 不會轉換成vw
height: 22PX;
margin-top: 11PX;
width: fit-content;
display: flex;
align-items: center;
justify-content: center;
}
}
Q:原生導航條優化?
現狀中的幾個異常場景,仍需要webview配合一起整改,所以目前整改方案為:
業務鏈接中新增qurey參數hideNavi=1,此時 webview通過該字段在webview 出現之前隱藏導航條。由webview負責整改,跟版12.1.4。
經安全部門審核之后,會向外開源。
?導航條在移動端頁面中的重要性無需多言,我們最終的目的是面向全集團,和通天塔以及hybrid團隊,一起打造一根規范通用的H5導航欄,如果你在使用過程中發現一些我們沒有考慮到的異常場景或者設計規范,請與我聯系,我們共同完善。
?目前該組件下拉刷新還是要依賴原生的下拉刷新事件,后期會定制H5自己的下拉刷新。
?一個規范的UI組件應該是一個有嚴格UI設計規范的,比如間距,字體大小、圖片規范等。但是一期的設計中我們為了靈活,通過協議把UI把控留給了用戶,也希望后面的迭代開發中融入更多規范的設計語言。
一個輕量級的tabs菜單組件,支持一二級菜單,聯動切場等功能,常用于商品分類展示。
作者:京東零售 張松超
來源:京東云開發者社區 轉載請注明來源
根據Google公布的Android 各個系統版本市場占有率(Google Android dashboards), Android 4.0及其以上系統將近90%左右,發展趨勢必將是未來市面上幾乎是Android 4.0以上系統。本文主要關注Android 4.0及以上系統WebView的實現,從Android WebView實現的Framework層大致可以分為三段Android 4.0系列,Android 4.1---4.3系列,Android 4.4及其以上系列。
WebView差異
WebView是Android系統提供能顯示網頁的系統控件,它是一個特殊的View,同時它也是一個ViewGroup可以有很多其他子View。在Android 4.4以下(不包含4.4)系統WebView底層實現是采用WebKit(http://www.webkit.org/)內核,而在Android 4.4及其以上Google 采用了chromium(http://www.chromium.org/)作為系統WebView的底層內核支持。在這一變化中Android 提供的WebView相關API并沒有發生大變化,在4.4上也兼容低版本的API并且引進了少部分API。這里簡單介紹下基于Chromium 的Webview和基于Webkit webview的差異,基于Chromium Webview提供更廣的HTML5,CSS3,Javascript支持,在目前最新Android 系統版本5.0上基于chromium 37,Webview提供絕大多數的HTML5特性支持。Webkit JavaScript引起采用WebCore Javascript 在Android 4.4上換成了V8能直接提升JavaScript性能。另外Chromium 支持遠程調試(Chrome DevTools)。
WebKit for WebView VS Chromium for WebView性能比對(測試環境 小米2. CM Browser. Android 4.1.1 VS 4.4.3)
Webkit for Webview | Chromium for Webview | 備注 | |
HTML5 | 278 | 434 | http://html5test.com/ |
遠程調試 | 不支持 | 支持 | Android 4.4及以上支持 |
內存占用 | 小 | 大 | 相差20-30M左右 |
WebAudio | 不支持 | 支持 | Android 5.0及以上支持 |
WebGL | 不支持 | 支持 | Android 5.0及以上支持 |
WebRTC | 不支持 | 支持 | Android 5.0及以上支持 |
Android 4.0 WebView結構
Android WebView API層主要提供給我們應用程序的接口,為了兼容向下版本Android在高版本中也是對這一層的API進行支持,因此如果底層發生變化,這些API接口層也不會發生太大變化。Android 平臺不僅提供應用層編程接口也提供native層編程。下面介紹上圖中的三個部分:
1)Android Framework:Android WebView是個特殊控件實現的支持需要Framework的代碼主要在./frameworks/base/core/java/android/webkit目錄下,在Android 4.0實現主要是在WebViewCore.java,BrowserFrame.java等文件。
2) Android JNI:需要有Native代碼支持,因此需要有JNI層實現,Android WebView 4.0的JNI層實現WebView相關代碼在./external/webkit/Source/WebKit/android/jni/目錄下,這一層起到承上啟下的作用,鏈接Framework層以及WebKit層的橋梁,比如相關的一些實現在WebviewCore.cpp,WebCoreFrameBridge.cpp等。
3) WebKit: WebKit內核,其核心主要是解析W3C標準以及渲染排版網頁,他是一個跨平臺的內核引擎,那么需要支持各個平臺,需要我們的平臺實現層,在Android 4.0系統這一部分相關代碼主要在./external/webkit/Source/WebKit/android/WebCoreSupport/目錄下,比如FrameLoaderClientAndroid.cpp,ChromeClientAndroid.cpp,這一層負責WebCore與系統平臺的橋接,具體在不同平臺會有不同的實現。實現網頁的解析排版及渲染由WebCore來實現在Android 4.0源碼當中代碼位于./external/webkit/Source/WebCore/下,下面有WebCore實現的各個模塊功能支持的相關代碼,比如頁面視圖部分在page目錄的chrome.cpp,比如加載頁面需要的資源的loader中得FrameLoader.cpp等,這里不在繼續深入詳解,有興趣的朋友可以下載Android 4.0源碼閱讀。
Android 4.1--4.3 WebView結構
Android 4.1--4.3版本WebView內核實現還是基于WebKit,但在WebView的Framework層發生了變化,引入了工廠模式,目地是為了將內核與上層API接口分離開來,分離的意義不僅僅是抽象接口,更重要的是將來能替換內核部分的實現。 在4.1--4.3這一系列版本native結構基本與4.0版本相同,下圖呈現新的變化:
Android 4.0--4.3 渲染
盡管之前4.0,與4.1--4.3是在不同的結構系列,其兩者之間的差異主要是集中的Framework上的變化,這種變化更多體現在Framework層結構上的變化,WebKit內核極其在Android上的表現機制并沒有發生很大變化,他們的渲染機制是相同的。下面介紹Android 4.0--4.3的渲染機制:
在Android 4.0上已經默認開啟硬件加速,因此WebView的渲染默認是基于硬件渲染的,通過本人分析其在WebView被隱藏的那一幀是采用軟件渲染,目的是減少硬件占用,讓其他UI能及時的響應。在硬件渲染情況下WebView通過onDraw方法傳遞Canvas 并將其轉行為HardwareCanvas ,并生成native的 DrawGLFunction指針,通知native做渲染。在軟件模式下,WebView通過傳遞的Canvas 通知內核webkitDraw將內核的一幀生成picture傳輸到Canvas中,執行Canvas draw bitmap。
Android 4.4 WebView結構
在Android 4.4系統上 Google已經將系統默認的Webkit內核替換成自己的開源項目chromium,通過之前的版本分析,我們可以看到Android 對WebView的Framework 結構進行調整使其更抽象,更重要的目的還是集成自己的開源chromium。下面我們來看看WebView的結構發生了什么樣的變化:
目錄:
./frameworks/base/core/java/android/webkit
./frameworks/webview/chromium/java/com/android/webview/chromium
./external/chromium_org/android_webview
./external/chromium_org/content
為了將chromium項目集成到Android 中,chromium項目抽象出Android webview這一層,之前的接口抽離這時候已經變得很明顯,Android Webview基于chromium content API這一層,第三方瀏覽器廠商也可以采用這種方式,目前所了解的廠商有Opera使用這種方式。Android 4.4WebView的渲染核心目前也沒有發生太大變化,還是基于WebView的Canvas,將Chromium composit 結構繪制到WebView Canvas上。接入chromium內核,WebView瀏覽性能大幅度提升,但是和chrome for Android還是有些不同,主要體現在一下幾點:
1. chrome瀏覽器是多進程架構,Chromium for Android Webview 是單進程架構。
2. chrome瀏覽器 內存占用比 Android WebView大的多。
3. chrome支持更多的HTML5 feature。
Android WebView展望:
Chromium項目編譯"android_webview_apk“ 目前實現是基于Android SurfaceView,其渲染性能高于Android WebView的Canvas,歷史遺留問題以及Android 系統WebView的作用特點,這一塊隨著Chromium 和 Android項目的整合,相信值得大家期待將來的Android WebView 的渲染性能會再次大幅提升。
本博客會持續更新Android WebView后續版本的變化,敬請關注 謝謝!
Android 5.0 Lollipop WebView
Lollipop版本中WebView的內核實現采用Chromium 37版本,這個版本帶來更多的安全性和穩定性。這個版本解決Android 4.4版本網頁當中請求訪問打開本地文件選擇器問題,引入新的回調接口,onShowFileChooser方法,需要此功能的可以在5.0上接上這個回調接口,并實現功能。另外這個版本提供安全許可給用戶選擇,當網頁需要訪問特殊資源時,會通知我們的應用程序,請求允許,回調接口為onPermissionRequest。之前我們也提到這個版本使得WebView默認支持WebAudio,WebGL,WebRTC等標準。
另外Google Android 還將webview做為一個能動態更新的app,能不更新Android版本情況下,更新WebView內核。Android 5.0 Webview默認提供減少內存占用支持,并且智能選擇需要繪制的HTML document部門來提供性能。 當然開發者可以在自己應用程序需要時關閉這個選項(enableSlowWholeDocumentDraw)。
閱讀本文前請先閱讀:Android開發:最全面、最易懂的Webview詳解
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() 消息
2.1 Android通過WebView調用 JS 代碼
對于Android調用JS代碼的方法有2種:
1. 通過WebView的loadUrl()
2. 通過WebView的evaluateJavascript()
方式1:通過WebView的loadUrl()
步驟1:將需要調用的JS代碼以.html格式放到src/main/assets文件夾里
需要加載JS代碼:javascript.html
// 文本名:javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
// JS代碼
<script>
// Android需要調用的方法
function callJS(){
alert("Android調用了JS的callJS方法");
}
</script>
</head>
</html>
步驟2:在Android里通過WebView設置調用JS代碼
Android代碼:MainActivity.java
注釋已經非常清楚
public class MainActivity extends AppCompatActivity {
WebView mWebView;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 設置與Js交互的權限
webSettings.setJavaScriptEnabled(true);
// 設置允許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先載入JS代碼
// 格式規定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
button=(Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通過Handler發送消息
mWebView.post(new Runnable() {
@Override
public void run() {
// 注意調用的JS方法名要對應上
// 調用javascript的callJS()方法
mWebView.loadUrl("javascript:callJS()");
}
});
}
});
// 由于設置了彈窗檢驗調用結果,所以需要支持js對話框
// webview只是載體,內容的渲染需要使用webviewChromClient類去實現
// 通過設置WebChromeClient對象處理JavaScript的對話框
//設置響應js 的Alert()函數
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b=new AlertDialog.Builder(MainActivity.this);
b.setTitle("Alert");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
b.setCancelable(false);
b.create().show();
return true;
}
});
}
}
特別注意:JS代碼調用一定要在 onPageFinished() 回調之后才能調用,否則不會調用。
onPageFinished()屬于WebViewClient類的方法,主要在頁面加載結束時調用
// 只需要將第一種方法的loadUrl()換成下面該方法即可
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此處為 js 返回的結果
}
});
}
2.1.2 方法對比
2.1.3 使用建議
兩種方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2
// Android版本變量
final int version=Build.VERSION.SDK_INT;
// 因為該方法在 Android 4.4 版本才可使用,所以使用時需進行版本判斷
if (version < 18) {
mWebView.loadUrl("javascript:callJS()");
} else {
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此處為 js 返回的結果
}
});
}
對于JS調用Android代碼的方法有3種:
1. 通過WebView的addJavascriptInterface()進行對象映射
2. 通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調攔截 url
3. 通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息
2.2.1 方法分析
方式1:通過 WebView的addJavascriptInterface()進行對象映射
步驟1:定義一個與JS對象映射關系的Android類:AndroidtoJs
AndroidtoJs.java(注釋已經非常清楚)
// 繼承自Object類
public class AndroidtoJs extends Object {
// 定義JS需要調用的方法
// 被JS調用的方法必須加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS調用了Android的hello方法");
}
}
步驟2:將需要調用的JS代碼以.html格式放到src/main/assets文件夾里
需要加載JS代碼:javascript.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
// 由于對象映射,所以調用test對象等于調用Android映射的對象
test.hello("js調用了android中的hello方法");
}
</script>
</head>
<body>
//點擊按鈕則調用callAndroid函數
<button type="button" id="button1" onclick="callAndroid()"></button>
</body>
</html>
步驟3:在Android里通過WebView設置Android類與JS代碼的映射
詳細請看注釋
public class MainActivity extends AppCompatActivity {
WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 設置與Js交互的權限
webSettings.setJavaScriptEnabled(true);
// 通過addJavascriptInterface()將Java對象映射到JS對象
//參數1:Javascript對象名
//參數2:Java對象名
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS類對象映射到js的test對象
// 加載JS代碼
// 格式規定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
僅將Android對象和JS對象映射即可
即JS需要調用Android的方法
以.html格式放到src/main/assets文件夾里
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callAndroid(){
/*約定的url協議為:js://webview?arg1=111&arg2=222*/
document.location="js://webview?arg1=111&arg2=222";
}
</script>
</head>
<!-- 點擊按鈕則調用callAndroid()方法 -->
<body>
<button type="button" id="button1" onclick="callAndroid()">點擊調用Android代碼</button>
</body>
</html>
當該JS通過Android的mWebView.loadUrl("file:///android_asset/javascript.html")加載后,就會回調shouldOverrideUrlLoading (),接下來繼續看步驟2:
步驟2:在Android通過WebViewClient復寫shouldOverrideUrlLoading ()
MainActivity.java
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 設置與Js交互的權限
webSettings.setJavaScriptEnabled(true);
// 設置允許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 步驟1:加載JS代碼
// 格式規定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
// 復寫WebViewClient類的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 步驟2:根據協議的參數,判斷是否是所需要的url
// 一般根據scheme(協議格式) & authority(協議名)判斷(前兩個參數)
//假定傳入進來的 url="js://webview?arg1=111&arg2=222"(同時也是約定好的需要攔截的)
Uri uri=Uri.parse(url);
// 如果url的協議=預先約定的 js 協議
// 就解析往下解析參數
if ( uri.getScheme().equals("js")) {
// 如果 authority=預先約定協議里的 webview,即代表都符合約定的協議
// 所以攔截url,下面JS開始調用Android需要的方法
if (uri.getAuthority().equals("webview")) {
// 步驟3:
// 執行JS所需要調用的邏輯
System.out.println("js調用了Android的方法");
// 可以在協議上帶有參數并傳遞到Android上
HashMap<String, String> params=new HashMap<>();
Set<String> collection=uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
);
}
}
如果JS想要得到Android方法的返回值,只能通過 WebView 的 loadUrl ()去執行 JS 方法把返回值傳遞回去,相關的代碼如下:
// Android:MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");
// JS:javascript.html
function returnResult(result){
alert("result is" + result);
}
在JS中,有三個常用的對話框方法:
方式3的原理:Android通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調分別攔截JS對話框
(即上述三個方法),得到他們的消息內容,然后解析即可。
下面的例子將用攔截 JS的輸入框(即prompt()方法)說明 :
步驟1:加載JS代碼,如下:
javascript.html
以.html格式放到src/main/assets文件夾里
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function clickprompt(){
// 調用prompt()
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<!-- 點擊按鈕則調用clickprompt() -->
<body>
<button type="button" id="button1" onclick="clickprompt()">點擊調用Android代碼</button>
</body>
</html>
當使用mWebView.loadUrl("file:///android_asset/javascript.html")加載了上述JS代碼后,就會觸發回調onJsPrompt(),具體如下:
步驟2:在Android通過WebChromeClient復寫onJsPrompt()
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView=(WebView) findViewById(R.id.webview);
WebSettings webSettings=mWebView.getSettings();
// 設置與Js交互的權限
webSettings.setJavaScriptEnabled(true);
// 設置允許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先加載JS代碼
// 格式規定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebChromeClient(new WebChromeClient() {
// 攔截輸入框(原理同方式2)
// 參數message:代表promt()的內容(不是url)
// 參數result:代表輸入框的返回值
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根據協議的參數,判斷是否是所需要的url(原理同方式2)
// 一般根據scheme(協議格式) & authority(協議名)判斷(前兩個參數)
//假定傳入進來的 url="js://webview?arg1=111&arg2=222"(同時也是約定好的需要攔截的)
Uri uri=Uri.parse(message);
// 如果url的協議=預先約定的 js 協議
// 就解析往下解析參數
if ( uri.getScheme().equals("js")) {
// 如果 authority=預先約定協議里的 webview,即代表都符合約定的協議
// 所以攔截url,下面JS開始調用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//
// 執行JS所需要調用的邏輯
System.out.println("js調用了Android的方法");
// 可以在協議上帶有參數并傳遞到Android上
HashMap<String, String> params=new HashMap<>();
Set<String> collection=uri.getQueryParameterNames();
//參數result:代表消息框的返回值(輸入值)
result.confirm("js調用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
// 通過alert()和confirm()攔截的原理相同,此處不作過多講述
// 攔截JS的警告框
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
// 攔截JS的確認框
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
}
);
}
}
2.2.2 三種方式的對比 & 使用場景
*請認真填寫需求信息,我們會在24小時內與您取得聯系。