Android的WebView與H5前端JS代碼交互的實例代碼
前段時間項目有深度和前端對接過,也是碰了一些坑,現(xiàn)在有時間就拿出來分享下
JS調(diào)用原生不外乎就兩種,一種是傳假的url,也就是url攔截的方式,類似于下面這種:
//js代碼
function sendCommand(param){
var url="js-call://"+param;
document.location = url;
}
sendCommand("PlaySnake");
//Java代碼
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("js-call:")) {
if (url.contains("PlaySnake")) {
Log.d("X5WebViewActivity", "玩蛇");
} else if (url.contains("WhatDoesTheFoxSay")) {
Log.d("X5WebViewActivity", "叮鈴鈴鈴叮鈴鈴");
} else {
showInfoAsToast("龜兒娃,你調(diào)得不對");
}
return false;
}
view.loadUrl(url);
return true;
}
});
這種方法來調(diào)用原生,好處就是集成比較迅速,約定一個標識,類似于示例中的“js-call”,再約定一波Type,比如“玩蛇”之類的,代碼很簡單,畢竟大家都很忙。
但是如果你打算長期把這個項目做下去的話,這種方式還是不要了吧,缺點太明顯了。首先是給原生傳數(shù)據(jù),只能是字符串;然后業(yè)務擴展起來,你的else if越寫越多,里面再加一大把switch,代碼越來臃腫,維護起來那感覺真的酸爽。
另一種就是通過谷歌提供的JS與Java綁定的接口,約定好要交互的對象名,類似于下面的“App”
//通過WebView提供的addJavascriptInterface這行代碼,我們在瀏覽器的JS環(huán)境中創(chuàng)建了一個"App"對象
//這個對象下的函數(shù)就是自定義接口類里面通過 @JavascriptInterface注解的Java方法轉(zhuǎn)換而來的
mWebView.addJavascriptInterface(new JavaFuckJSInterface(this), "App");
/**
* 自定義的交互接口類
*/
public class JavaFuckJSInterface{
private WeakReference<X5WebViewActivity> x5WebViewActivity;
public JavaFuckJSInterface(X5WebViewActivity context) {
x5WebViewActivity = new WeakReference<>(context);
}
//通過這個@JavascriptInterface轉(zhuǎn)化成綁定的“App”對象下的同名函數(shù),js代碼可以直接調(diào)用
@JavascriptInterface
public void presentCamera(String data) {
//拍照上傳
x5WebViewActivity.get().presentCamera(data);
}
}
//js代碼
var parameter = {};
parameter.size = "1024*768";
parameter.format = "JPEG";
var parameterStr = JSON.stringify(parameter);
App.presentCamera(parameterStr);
這樣寫的話,規(guī)范了不少,即使函數(shù)再多,這個接口里面也是一目了然,調(diào)函數(shù)就是調(diào)函數(shù),傳參數(shù)就是傳參數(shù),相比于之前那個方法,可讀性高了不少
不過上面寫的這些破玩意網(wǎng)上資料一大把,我特么是吃多了么,再寫一遍?
NoNoNo,這些東西確實足夠我們與JS交互了,但是前端不想搞JSON.stringify(parameter)這種操作啊,他要直接傳對象過來。為什么別人IOS都可以拿到我的對象,你拿的就是undefined?為什么別人IOS能給我對象,你就不給我對象,偏要給我字符串?憑什么別人IOS能拿到我的匿名回調(diào)函數(shù)來調(diào)用,你偏偏讓我寫一個回調(diào)函數(shù)給你調(diào)?
ok,也不是不能做到,不過這就需要通過注入JS代碼來完成了
talk is cheap , show me the code
下面這個微型的SDK能夠?qū)崿F(xiàn)互調(diào)傳JSON對象,調(diào)用js傳入的匿名函數(shù)
//需要注入的js代碼,加//"是因為簡書會忽略\"這個回引號,不加的話后面的代碼都是字符串的顏色了
//原理是通過這個SDKNativeEvents來保存?zhèn)魅氲哪涿瘮?shù)callback,等原生做完該做的操作之后
//接著去調(diào)用sdk_nativeCallback這個函數(shù)來運行存進去的callback
var SDKNativeEvents = {}
function sdk_launchFunc(funcName,data,callback){
if(!data){
alert(\"必須傳入data\");//"
return;
}
if(!callback){
alert(\"必須傳入回調(diào)function\");//"
return;
}
SDKNativeEvents[funcName] = callback;
var jsObj={};
jsObj.funcName=funcName;
jsObj.data=JSON.stringify(data);
var str = JSON.stringify(jsObj);
App.native_launchFunc(str) //這個函數(shù)要在JavascriptInterface里申明
}
function sdk_nativeCallback(funcName,data){
var obj= JSON.parse(data);
if(SDKNativeEvents[funcName]){
SDKNativeEvents[funcName](obj);
if(funcName != \"updateLocation\"){//定位回調(diào)會不定時去重復觸發(fā),不做置空操作"
SDKNativeEvents[funcName] = null;
}
}
}
//下面實現(xiàn)的功能和通過@JavascriptInterface注解的Java方法是一樣的,App為約定好的注入對象名
//App.xxx為暴露給前端的js函數(shù)
App.login = function(data,callback){
sdk_launchFunc(\"login\",data,callback);//"
}
App.xxxxxxxxxxxxx = function(data,callback){
sdk_launchFunc(\"xxxxxxxxxxxxx\",data,callback);//"
}
...
上面那些App.xxx的函數(shù)其實也可以不用注入,實現(xiàn)起來就是把 sdk_launchFunc這個函數(shù)注入到App對象下面,讓前端直接調(diào)用,這樣不用增加一個調(diào)用就多注入一個函數(shù),前端只用改funcName就能實現(xiàn)所有的調(diào)用。但是我覺得,調(diào)函數(shù)就是調(diào)函數(shù),傳參數(shù)就是傳參數(shù),將每個功能拆成function可以提高代碼的可讀性
注入JS代碼也很簡單,把上面那些js代碼都粘貼到string這個資源文件里面,再通過mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code1))來注入就行,其中js_sdk_code1就是js代碼的字符串
示例代碼:
//在網(wǎng)頁加載時提前注入,可以保證頁面一旦加載完畢前端就能立即調(diào)到函數(shù)
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView webView, int i) {
super.onProgressChanged(webView, i);
if (i >= 10 && canInject) {
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code1));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code2));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code3));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code4));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code5));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code6));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code7));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code8));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code9));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code10));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code11));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code12));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code13));
mWebView.loadUrl("javascript:" + getString(R.string.js_sdk_code14));
canInject = false;
}
if (i == 100) {
canInject = true;
}
}
});
這個時候有人就要問了,怎么注入這么多次,我也不想啊,這里有個坑的,一次注入的代碼超過三行左右(分號結(jié)束為一行)吧,就會有幾率出現(xiàn)注入失敗,會造成所有js代碼都沒法注入進去,我就干脆直接一次注入一行代碼來跳出這個坑,比如下面的js_sdk_code3就可以注入,雖然這個function內(nèi)部有好幾行代碼,但是整體來說也算一行代碼,這行代碼定義了這個function。然而我又試了,在這個function里面再多加一行代碼就會注入失敗,搞得現(xiàn)在我也不確定他失敗的零界點在哪里,反正盡量拆開注入吧。

將要注入的js代碼拆開注入
細心的同學已經(jīng)發(fā)現(xiàn)了,搞了這么多花里胡哨的,最關鍵的原生怎么來響應js的調(diào)用還沒說明,別急,下面上代碼
//@JavascriptInterface的代碼應該放在哪里不用我講了吧
//通過與js交互的接口類來拿到做什么事,以及傳過來的JSON對象轉(zhuǎn)成的字符串
@JavascriptInterface
public void native_launchFunc(String data) {
try {
JSONObject jsonObject = new JSONObject(data);
String funcName = jsonObject.getString("funcName");
String dataStr = jsonObject.getString("data");
switchName(funcName, dataStr);
} catch (JSONException e) {
e.printStackTrace();
}
}
private void switchName(String funcName, String dataStr) {
if (funcName == null) {
return;
}
switch (funcName) {
case "login":
x5WebViewActivity.get().login(data);
break;
case "xxx":
x5WebViewActivity.get().xxx(data);
break;
}
}
//這里演示調(diào)用了login讓原生來登陸,等登陸成功之后,我們?nèi)フ{(diào)用js的匿名回調(diào),并傳入token
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("token", PreferencesHelper.getInstance().getToken());
String js = "javascript:sdk_nativeCallback(\'login\',\'" + jsonObject + "\')";
mWebView.loadUrl(js);
Android原生調(diào)用JS代碼也有兩種,一種是通過上面的loadUrl,一種是下面這種:
String script = "sdk_nativeCallback(\'login\',\'" + jsonObject + "\')";
mWebView.evaluateJavascript(script, responseJson -> {
if (!TextUtils.isEmpty(responseJson)) {
//拿到js函數(shù)的返回值
}
});
區(qū)別就是一個能拿到js函數(shù)的返回值,一個拿不到,這個根據(jù)自己的需求來選用
前端js調(diào)用原生傳入匿名回調(diào)的示例代碼:
//js代碼
var fucker = {};
fucker.name = "pdd";
fucker.age = 18;
App.login(fucker, function (data) {
if (data.err) {
alert(data.err);
}
alert(data.token);
});
我們可以看到,前端給我們傳入的是對象和匿名回調(diào)函數(shù),匿名回調(diào)需要的參數(shù)依然是個對象,我們通過注入的SDK保存了這個回調(diào)函數(shù),并自己做了對象和字符串轉(zhuǎn)換,實際上Java代碼最終拿到和傳出去還都是字符串,我們通過這個sdk統(tǒng)一的進行了轉(zhuǎn)換,前端js代碼那邊不用判斷手機是iPhone或者是Android,統(tǒng)一發(fā)出和接受對象,傳入回調(diào)函數(shù),能夠減少他們很多工作量。
對了,因為Android版本不一致,webview的兼容性參差不齊,選用了騰訊的X5內(nèi)核瀏覽器來加載,其中有個坑就是全屏播放視頻會有qq瀏覽器的廣告,這個可以通過代碼去掉,也拿出來分享下吧:
//去掉QQ瀏覽器廣告
private void removeTbsAd() {
getWindow().getDecorView().addOnLayoutChangeListener
((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
ArrayList<View> outView = new ArrayList<>();
View decorView = getWindow().getDecorView();
decorView.findViewsWithText(outView, "相關視頻", View.FIND_VIEWS_WITH_TEXT);
decorView.findViewsWithText(outView, "QQ瀏覽器", View.FIND_VIEWS_WITH_TEXT);
if (outView.size() > 0) {
outView.get(0).setVisibility(View.GONE);
}
});
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android?PopUpWindow實現(xiàn)卡片式彈窗
大家好,本篇文章主要講的是Android?PopUpWindow實現(xiàn)卡片式彈窗,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01
Android實戰(zhàn)打飛機游戲之菜單頁面設計(1)
這篇文章主要為大家詳細介紹了Android實戰(zhàn)打飛機游戲之菜單頁面設計,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-07-07
kotlin項目加入Glide圖片加載庫并使用GlideApp的方法
這篇文章主要給大家介紹了關于kotlin項目加入Glide圖片加載庫并使用GlideApp的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧2019-01-01

