浏览代码

增加dsbridgex5版本

tags/A.2.3.0.0
李俊才 2 年前
父节点
当前提交
94ab226876
共有 25 个文件被更改,包括 2088 次插入69 次删除
  1. +1
    -0
      .idea/gradle.xml
  2. +3
    -3
      app/build.gradle
  3. +20
    -0
      app/src/main/AndroidManifest.xml
  4. +132
    -0
      app/src/main/assets/dsbridge.js
  5. +108
    -0
      app/src/main/assets/js-call-native.html
  6. +62
    -0
      app/src/main/assets/native-call-js.html
  7. +10
    -7
      app/src/main/java/com/aispeech/nativedemo/BaseMainActivity.java
  8. +145
    -0
      app/src/main/java/com/aispeech/nativedemo/CallJavascriptActivity.java
  9. +22
    -0
      app/src/main/java/com/aispeech/nativedemo/JavascriptCallNativeActivity.java
  10. +67
    -0
      app/src/main/java/com/aispeech/nativedemo/JsApi.java
  11. +24
    -0
      app/src/main/java/com/aispeech/nativedemo/JsEchoApi.java
  12. +2
    -2
      app/src/main/java/com/aispeech/nativedemo/config/Config.java
  13. +1
    -0
      app/src/main/java/com/aispeech/nativedemo/entity/Skill.java
  14. +12
    -57
      app/src/main/java/com/aispeech/nativedemo/widget/CustomWebViewActivity.java
  15. +106
    -0
      app/src/main/res/layout/activity_call_javascript.xml
  16. +12
    -0
      app/src/main/res/layout/activity_js_call_native.xml
  17. +1
    -0
      dsbridge/.gitignore
  18. +30
    -0
      dsbridge/build.gradle
  19. +330
    -0
      dsbridge/proguard-rules.pro
  20. +12
    -0
      dsbridge/src/main/AndroidManifest.xml
  21. +11
    -0
      dsbridge/src/main/java/wendu/dsbridge/CompletionHandler.java
  22. +964
    -0
      dsbridge/src/main/java/wendu/dsbridge/DWebView.java
  23. +9
    -0
      dsbridge/src/main/java/wendu/dsbridge/OnReturnValue.java
  24. +3
    -0
      dsbridge/src/main/res/values/strings.xml
  25. +1
    -0
      settings.gradle

+ 1
- 0
.idea/gradle.xml 查看文件

@@ -13,6 +13,7 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/dsbridge" />
<option value="$PROJECT_DIR$/test" />
</set>
</option>


+ 3
- 3
app/build.gradle 查看文件

@@ -128,12 +128,12 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.facebook.fresco:fresco:1.5.0'
// H5原生通信
api 'com.github.wendux:DSBridge-Android:3.0.0'
//api 'com.github.wendux:DSBridge-Android:3.0.0'
//支持腾讯x5浏览器内核
api 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'
//api 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.tencent.tbs:tbssdk:44286'
implementation project(path: ':test')
api project(path: ':dsbridge')

implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'


+ 20
- 0
app/src/main/AndroidManifest.xml 查看文件

@@ -56,6 +56,26 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".JavascriptCallNativeActivity"
android:screenOrientation="portrait"
android:exported="true">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->

<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
</activity>
<activity
android:name=".CallJavascriptActivity"
android:screenOrientation="portrait"
android:exported="true">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->

<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
</activity>

<service android:name=".DDSService" />
<service android:name=".PhoneCallService" />


+ 132
- 0
app/src/main/assets/dsbridge.js 查看文件

@@ -0,0 +1,132 @@
var bridge = {
default:this,// for typescript
call: function (method, args, cb) {
var ret = '';
if (typeof args == 'function') {
cb = args;
args = {};
}
var arg={data:args===undefined?null:args}
if (typeof cb == 'function') {
var cbName = 'dscb' + window.dscb++;
window[cbName] = cb;
arg['_dscbstub'] = cbName;
}
arg = JSON.stringify(arg)

//if in webview that dsBridge provided, call!
if(window._dsbridge){
ret= _dsbridge.call(method, arg)
}else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){
ret = prompt("_dsbridge=" + method, arg);
}

return JSON.parse(ret||'{}').data
},
register: function (name, fun, asyn) {
var q = asyn ? window._dsaf : window._dsf
if (!window._dsInit) {
window._dsInit = true;
//notify native that js apis register successfully on next event loop
setTimeout(function () {
bridge.call("_dsb.dsinit");
}, 0)
}
if (typeof fun == "object") {
q._obs[name] = fun;
} else {
q[name] = fun
}
},
registerAsyn: function (name, fun) {
this.register(name, fun, true);
},
hasNativeMethod: function (name, type) {
return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"});
},
disableJavascriptDialogBlock: function (disable) {
this.call("_dsb.disableJavascriptDialogBlock", {
disable: disable !== false
})
}
};

!function () {
if (window._dsf) return;
var ob = {
_dsf: {
_obs: {}
},
_dsaf: {
_obs: {}
},
dscb: 0,
dsBridge: bridge,
close: function () {
bridge.call("_dsb.closePage")
},
_handleMessageFromNative: function (info) {
var arg = JSON.parse(info.data);
var ret = {
id: info.callbackId,
complete: true
}
var f = this._dsf[info.method];
var af = this._dsaf[info.method]
var callSyn = function (f, ob) {
ret.data = f.apply(ob, arg)
bridge.call("_dsb.returnValue", ret)
}
var callAsyn = function (f, ob) {
arg.push(function (data, complete) {
ret.data = data;
ret.complete = complete!==false;
bridge.call("_dsb.returnValue", ret)
})
f.apply(ob, arg)
}
if (f) {
callSyn(f, this._dsf);
} else if (af) {
callAsyn(af, this._dsaf);
} else {
//with namespace
var name = info.method.split('.');
if (name.length<2) return;
var method=name.pop();
var namespace=name.join('.')
var obs = this._dsf._obs;
var ob = obs[namespace] || {};
var m = ob[method];
if (m && typeof m == "function") {
callSyn(m, ob);
return;
}
obs = this._dsaf._obs;
ob = obs[namespace] || {};
m = ob[method];
if (m && typeof m == "function") {
callAsyn(m, ob);
return;
}
}
}
}
for (var attr in ob) {
window[attr] = ob[attr]
}
bridge.register("_hasJavascriptMethod", function (method, tag) {
var name = method.split('.')
if(name.length<2) {
return !!(_dsf[name]||_dsaf[name])
}else{
// with namespace
var method=name.pop()
var namespace=name.join('.')
var ob=_dsf._obs[namespace]||_dsaf._obs[namespace]
return ob&&!!ob[method]
}
})
}();

module.exports = bridge;

+ 108
- 0
app/src/main/assets/js-call-native.html 查看文件

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html>
<head lang="zh-cmn-Hans">
<meta charset="UTF-8">
<title>DSBridge Test</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=0.5,user-scalable=no"/>
<!--require dsbridge init js-->
<script src="https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js"> </script>
</head>
<style>
.btn {
text-align: center;
background: #d8d8d8;
color: #222;
padding: 20px;
margin: 30px;
font-size: 24px;
border-radius: 4px;
box-shadow: 4px 2px 10px #999;
}

.btn:active {
opacity: .7;
box-shadow: 4px 2px 10px #555;
}

</style>
<body>
<div class="btn" onclick="callSyn()">Synchronous call</div>
<div class="btn" onclick="callAsyn()">Asynchronous call</div>
<div class="btn" onclick="callNoArgSyn()">Sync call without argument</div>
<div class="btn" onclick="callNoArgAsyn()">Async call without argument</div>
<div class="btn" onclick="echoSyn()">echo.syn</div>
<div class="btn" onclick="echoAsyn()">echo.asyn</div>
<div class="btn" onclick="callAsyn_()">Stress test,2K times consecutive asynchronous API calls</div>
<div class="btn" onclick="callNever()">Never call because without @JavascriptInterface annotation<br/>( This test is
just for Android ,should be ignored in IOS )
</div>
<div class="btn" onclick="callProgress()">call progress <span id='progress'></span></div>
<div class="btn" onclick="hasNativeMethod('xx')">hasNativeMethod("xx")</div>
<div class="btn" onclick="hasNativeMethod('yy')">hasNativeMethod("yy")</div>
<div class="btn" onclick="hasNativeMethod('testSyn')">hasNativeMethod("testSyn")</div>

<script>
function callSyn() {
alert(dsBridge.call("testSyn", "testSyn"))
}

function callAsyn() {
dsBridge.call("testAsyn","testAsyn", function (v) {
alert(v)
})
}

function callAsyn_() {
for (var i = 0; i < 2000; i++) {
dsBridge.call("testAsyn", "js+" + i, function (v) {
if (v == "js+1999 [ asyn call]") {
alert("All tasks completed!")
}
})
}
}

function callNoArgSyn() {
alert(dsBridge.call("testNoArgSyn"));
}

function callNoArgAsyn() {
dsBridge.call("testNoArgAsyn", function (v) {
alert(v)
});
}

function callNever() {
alert(dsBridge.call("testNever", {msg: "testSyn"}))
}

function echoSyn() {
// call function with namespace
var ret=dsBridge.call("echo.syn",{msg:" I am echoSyn call", tag:1});
alert(JSON.stringify(ret))
}

function echoAsyn() {
// call function with namespace
dsBridge.call("echo.asyn",{msg:" I am echoAsyn call",tag:2},function (ret) {
alert(JSON.stringify(ret));
})
}

function callProgress() {
dsBridge.call("callProgress", function (value) {
if(value==0) value="";
document.getElementById("progress").innerText = value
})
}

function hasNativeMethod(name) {
alert(dsBridge.hasNativeMethod(name))
}


</script>
</body>
</html>

+ 62
- 0
app/src/main/assets/native-call-js.html 查看文件

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html>
<head lang="zh-cmn-Hans">
<meta charset="UTF-8">
<title>DSBridge Test</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=0.5,user-scalable=no"/>
<!--require dsbridge init js-->
<script src="https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js"> </script>
</head>
<body>

<script>

dsBridge.register('addValue', function (r, l) {
return r + l;
})

dsBridge.registerAsyn('append', function (arg1, arg2, arg3, responseCallback) {
responseCallback(arg1 + " " + arg2 + " " + arg3);
})

dsBridge.registerAsyn('startTimer', function (responseCallback) {
var t = 0;
var timer = setInterval(function () {
if (t == 5) {
responseCallback(t)
clearInterval(timer)
} else {
// if the 2nd argument is false, the java callback handler will be not removed!
responseCallback(t++, false)
}
}, 1000)

})

// namespace test for syn functions
dsBridge.register("syn", {
tag: "syn",
addValue:function (r,l) {
return r+l;
},
getInfo: function () {
return {tag: this.tag, value:8}
}
})

// namespace test for asyn functions
dsBridge.registerAsyn("asyn", {
tag: "asyn",
addValue:function (r,l, responseCallback) {
responseCallback(r+l);
},
getInfo: function (responseCallback) {
responseCallback({tag: this.tag, value:8})
}
})

</script>
</body>
</html>

+ 10
- 7
app/src/main/java/com/aispeech/nativedemo/BaseMainActivity.java 查看文件

@@ -73,7 +73,9 @@ import com.aispeech.nativedemo.ui.InitView;
import com.aispeech.nativedemo.upload.UploadManager;
import com.aispeech.nativedemo.utils.CommandExecution;
import com.aispeech.nativedemo.utils.PermissionUtil;
import com.aispeech.nativedemo.utils.ScreenUtils;
import com.aispeech.nativedemo.utils.StatusUtils;
import com.aispeech.nativedemo.utils.Utils;
import com.aispeech.nativedemo.utils.suspension.SuspensionHelper;
import com.aispeech.nativedemo.utils.suspension.SuspensionImpl;
import com.aispeech.nativedemo.widget.AlertWindowView;
@@ -423,11 +425,12 @@ public class BaseMainActivity extends Activity implements DuiUpdateObserver.Upda
// mWebView.loadUrl(Config.DEV_H5_URL + "player/Public/player.html");

mWebView2 = findViewById(R.id.webview2);
setWebViewAutoVideo(mWebView2);
Log.e("testtest", mWebView2.getIsX5Core() ?
"X5内核: " + QbSdk.getTbsVersion(this) : "SDK系统内核");
mWebCore.setText(mWebView2.getIsX5Core() ?
"X5内核: " + QbSdk.getTbsVersion(this) : "SDK系统内核");

String log = mWebView2.getIsX5Core() ?
"X5内核: " + QbSdk.getTbsVersion(this) : "SDK系统内核";
Log.e("testtest", log);
String wh = "width " + ScreenUtils.getScreenWidth(BaseMainActivity.this) + " height " + ScreenUtils.getScreenHeight(BaseMainActivity.this);
mWebCore.setText(wh);
mWebView2.setBackgroundColor(0);
if(mWebView2.getBackground() != null){
mWebView2.getBackground().setAlpha(0);
@@ -476,8 +479,8 @@ public class BaseMainActivity extends Activity implements DuiUpdateObserver.Upda
webSettings.setDomStorageEnabled(true);
webSettings.setMediaPlaybackRequiresUserGesture(false);

mWebView2.loadUrl("http://39.107.77.235:48088/index_config.html?devId=" + StatusUtils.getSerialNumber());
//mWebView2.loadUrl("http://192.168.10.244:48087/index_config.html?devId=" + StatusUtils.getSerialNumber()); // 非联通测试推流
//mWebView2.loadUrl("http://39.107.77.235:48088/index_config.html?devId=" + StatusUtils.getSerialNumber());
mWebView2.loadUrl("http://192.168.10.244:48087/index_config.html?devId=" + StatusUtils.getSerialNumber()); // 非联通测试推流
}

private void setWebViewAutoVideo(WebView webView) {


+ 145
- 0
app/src/main/java/com/aispeech/nativedemo/CallJavascriptActivity.java 查看文件

@@ -0,0 +1,145 @@
package com.aispeech.nativedemo;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import org.json.JSONObject;

import ai.dui.sdk.log.Log;
import wendu.dsbridge.DWebView;
import wendu.dsbridge.OnReturnValue;

public class CallJavascriptActivity extends AppCompatActivity implements View.OnClickListener {

DWebView dWebView;

public <T extends View> T getView(int viewId) {
View view = findViewById(viewId);
return (T) view;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call_javascript);
getView(R.id.addValue).setOnClickListener(this);
getView(R.id.append).setOnClickListener(this);
getView(R.id.startTimer).setOnClickListener(this);
getView(R.id.synAddValue).setOnClickListener(this);
getView(R.id.synGetInfo).setOnClickListener(this);
getView(R.id.asynAddValue).setOnClickListener(this);
getView(R.id.asynGetInfo).setOnClickListener(this);
getView(R.id.hasMethodAddValue).setOnClickListener(this);
getView(R.id.hasMethodXX).setOnClickListener(this);
getView(R.id.hasMethodAsynAddValue).setOnClickListener(this);
getView(R.id.hasMethodAsynXX).setOnClickListener(this);
DWebView.setWebContentsDebuggingEnabled(true);
dWebView= getView(R.id.webview);
dWebView.loadUrl("file:///android_asset/native-call-js.html");


}

void showToast(Object o) {
Log.e("testjs", "show toast " + o.toString());
Toast.makeText(this, o.toString(), Toast.LENGTH_SHORT).show();
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.addValue:
dWebView.callHandler("addValue", new Object[]{3, 4}, new OnReturnValue<Integer>() {
@Override
public void onValue(Integer retValue) {
showToast(retValue);
}
});
break;
case R.id.append:
dWebView.callHandler("append", new Object[]{"I", "love", "you"}, new OnReturnValue<String>() {
@Override
public void onValue(String retValue) {
showToast(retValue);
}
});
break;
case R.id.startTimer:
dWebView.callHandler("startTimer", new OnReturnValue<Integer>() {
@Override
public void onValue(Integer retValue) {
showToast(retValue);
}
});
break;
case R.id.synAddValue:
dWebView.callHandler("syn.addValue", new Object[]{5, 6}, new OnReturnValue<Integer>() {
@Override
public void onValue(Integer retValue) {
showToast(retValue);
}
});
break;
case R.id.synGetInfo:
dWebView.callHandler("syn.getInfo", new OnReturnValue<JSONObject>() {
@Override
public void onValue(JSONObject retValue) {
showToast(retValue);
}
});
break;
case R.id.asynAddValue:
dWebView.callHandler("asyn.addValue", new Object[]{5, 6}, new OnReturnValue<Integer>() {
@Override
public void onValue(Integer retValue) {
showToast(retValue);
}
});
break;
case R.id.asynGetInfo:
dWebView.callHandler("asyn.getInfo", new OnReturnValue<JSONObject>() {
@Override
public void onValue(JSONObject retValue) {
showToast(retValue);
}
});
break;
case R.id.hasMethodAddValue:
dWebView.hasJavascriptMethod("addValue", new OnReturnValue<Boolean>() {
@Override
public void onValue(Boolean retValue) {
showToast(retValue);
}
});
break;
case R.id.hasMethodXX:
dWebView.hasJavascriptMethod("XX", new OnReturnValue<Boolean>() {
@Override
public void onValue(Boolean retValue) {
showToast(retValue);
}
});
break;
case R.id.hasMethodAsynAddValue:
dWebView.hasJavascriptMethod("asyn.addValue", new OnReturnValue<Boolean>() {
@Override
public void onValue(Boolean retValue) {
showToast(retValue);
}
});
break;
case R.id.hasMethodAsynXX:
dWebView.hasJavascriptMethod("asyn.XX", new OnReturnValue<Boolean>() {
@Override
public void onValue(Boolean retValue) {
showToast(retValue);
}
});
break;
}

}
}

+ 22
- 0
app/src/main/java/com/aispeech/nativedemo/JavascriptCallNativeActivity.java 查看文件

@@ -0,0 +1,22 @@
package com.aispeech.nativedemo;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import wendu.dsbridge.DWebView;

public class JavascriptCallNativeActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_js_call_native);
final DWebView dwebView= (DWebView) findViewById(R.id.webview);
// set debug mode
DWebView.setWebContentsDebuggingEnabled(true);
dwebView.addJavascriptObject(new JsApi(), null);
dwebView.addJavascriptObject(new JsEchoApi(),"echo");
dwebView.loadUrl("file:///android_asset/js-call-native.html");
}
}

+ 67
- 0
app/src/main/java/com/aispeech/nativedemo/JsApi.java 查看文件

@@ -0,0 +1,67 @@
package com.aispeech.nativedemo;

import android.os.CountDownTimer;
import android.webkit.JavascriptInterface;

import org.json.JSONException;
import org.json.JSONObject;

import wendu.dsbridge.CompletionHandler;

/**
* Created by du on 16/12/31.
*/

public class JsApi{
@JavascriptInterface
public String yy(Object msg) {
return msg + "[syn call]";
}
@JavascriptInterface
public String testSyn(Object msg) {
return msg + "[syn call]";
}

@JavascriptInterface
public void testAsyn(Object msg, CompletionHandler<String> handler){
handler.complete(msg+" [ asyn call]");
}

@JavascriptInterface
public String testNoArgSyn(Object arg) throws JSONException {
return "testNoArgSyn called [ syn call]";
}

@JavascriptInterface
public void testNoArgAsyn(Object arg,CompletionHandler<String> handler) {
handler.complete( "testNoArgAsyn called [ asyn call]");
}


//@JavascriptInterface
//without @JavascriptInterface annotation can't be called
public String testNever(Object arg) throws JSONException {
JSONObject jsonObject= (JSONObject) arg;
return jsonObject.getString("msg") + "[ never call]";
}

@JavascriptInterface
public void callProgress(Object args, final CompletionHandler<Integer> handler) {

new CountDownTimer(11000, 1000) {
int i=10;
@Override
public void onTick(long millisUntilFinished) {
//setProgressData can be called many times util complete be called.
handler.setProgressData((i--));

}
@Override
public void onFinish() {
//complete the js invocation with data; handler will be invalid when complete is called
handler.complete(0);

}
}.start();
}
}

+ 24
- 0
app/src/main/java/com/aispeech/nativedemo/JsEchoApi.java 查看文件

@@ -0,0 +1,24 @@
package com.aispeech.nativedemo;

import android.webkit.JavascriptInterface;

import org.json.JSONException;

import wendu.dsbridge.CompletionHandler;

/**
* Created by du on 16/12/31.
*/

public class JsEchoApi {

@JavascriptInterface
public Object syn(Object args) throws JSONException {
return args;
}

@JavascriptInterface
public void asyn(Object args,CompletionHandler handler){
handler.complete(args);
}
}

+ 2
- 2
app/src/main/java/com/aispeech/nativedemo/config/Config.java 查看文件

@@ -7,8 +7,8 @@ public class Config {
public static final int BITMAP_WIDTH = 1920;

public static final String PROD_BASE_URL= "http://123.57.75.177:8080/";
public static final String TEST_BASE_URL= "http://39.107.77.235:8078/";
//public static final String TEST_BASE_URL= "http://39.107.77.235:8080/"; // 非联通测试推流
//public static final String TEST_BASE_URL= "http://39.107.77.235:8078/";
public static final String TEST_BASE_URL= "http://39.107.77.235:8080/"; // 非联通测试推流
public static final String DEV_BASE_URL= "http://192.168.10.244:8080/";
public static final String DEV_H5_URL= "http://192.168.10.244:48087/";
public static final String CURRENT_URL= TEST_BASE_URL;


+ 1
- 0
app/src/main/java/com/aispeech/nativedemo/entity/Skill.java 查看文件

@@ -29,6 +29,7 @@ public class Skill implements Serializable {
obj.put("skillCode", skillCode);
obj.put("info", info);
obj.put("resp", resp);
obj.put("leavingResp", leavingResp + "");
obj.put("motionId", motionId);
obj.put("motionName", motionName);
} catch (JSONException e) {


+ 12
- 57
app/src/main/java/com/aispeech/nativedemo/widget/CustomWebViewActivity.java 查看文件

@@ -5,7 +5,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@@ -14,15 +13,6 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;

import androidx.annotation.Nullable;
@@ -30,6 +20,16 @@ import androidx.annotation.RequiresApi;

import com.aispeech.nativedemo.DuiApplication;
import com.aispeech.nativedemo.databinding.ActivityCustomWebBinding;
import com.tencent.smtt.export.external.interfaces.SslError;
import com.tencent.smtt.export.external.interfaces.SslErrorHandler;
import com.tencent.smtt.export.external.interfaces.WebResourceError;
import com.tencent.smtt.export.external.interfaces.WebResourceRequest;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import android.webkit.JavascriptInterface;

import wendu.dsbridge.CompletionHandler;

@@ -43,7 +43,6 @@ public class CustomWebViewActivity extends Activity {
private ActivityCustomWebBinding mBinding;
private String mUrl;
private View mVideoView;
private WebChromeClient.CustomViewCallback mVideoViewCallback;

/**
* 分享:页面分享的URL
@@ -143,7 +142,7 @@ public class CustomWebViewActivity extends Activity {
mBinding.webView.addJavascriptObject(new JsApi(), null);

if (Build.VERSION.SDK_INT >= 21) {
mBinding.webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
mBinding.webView.getSettings().setMixedContentMode(WebSettings.LOAD_NORMAL); // MIXED_CONTENT_ALWAYS_ALLOW
}

//错误重试
@@ -222,17 +221,9 @@ public class CustomWebViewActivity extends Activity {
//加载进度
mBinding.webView.setWebChromeClient(new WebChromeClient(){

/*** 视频播放相关的方法 **/
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
super.onShowCustomView(view, callback);
showCustomView(view, callback);
}

@Override
public void onHideCustomView() {
super.onHideCustomView();
hideCustomView();
}

/**
@@ -249,40 +240,6 @@ public class CustomWebViewActivity extends Activity {
});
}

/** 视频播放全屏 **/
private void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
if (mVideoView != null) {
callback.onCustomViewHidden();
return;
}

mBinding.flFullVideoContainer.setVisibility(View.VISIBLE);
mBinding.webView.setVisibility(View.GONE);
mBinding.flFullVideoContainer.addView(view);

FrameLayout.LayoutParams flLayoutParams = (FrameLayout.LayoutParams)view.getLayoutParams();
flLayoutParams.gravity = Gravity.CENTER;
view.setLayoutParams(flLayoutParams);

mVideoView = view;
mVideoViewCallback = callback;
setStatusBarVisibility(false);
}

/** 隐藏视频全屏 */
private void hideCustomView() {
if (mVideoView == null) {
return;
}

setStatusBarVisibility(true);
mBinding.flFullVideoContainer.removeView(mVideoView);
mBinding.flFullVideoContainer.setVisibility(View.GONE);
mVideoView = null;
mVideoViewCallback.onCustomViewHidden();
mBinding.webView.setVisibility(View.VISIBLE);
}

private void setStatusBarVisibility(boolean visible) {
int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -339,9 +296,7 @@ public class CustomWebViewActivity extends Activity {
//网页后退
if (keyCode == KeyEvent.KEYCODE_BACK) {
/** 回退键 事件处理 优先级:视频播放全屏-网页回退-关闭页面 */
if (mVideoView != null) {
hideCustomView();
} else if (mBinding.webView.canGoBack()) {
if (mBinding.webView.canGoBack()) {
mBinding.webView.goBack();
} else {
finish();


+ 106
- 0
app/src/main/res/layout/activity_call_javascript.xml 查看文件

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<Button
android:id="@+id/addValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="addValue(3,4)"
android:textAllCaps="false" />

<Button
android:id="@+id/append"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="append('I','love','you')"
android:textAllCaps="false" />

<Button
android:id="@+id/startTimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="startTimer()"
android:textAllCaps="false" />

<Button
android:id="@+id/synAddValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="syn.addValue(5,6)"
android:textAllCaps="false" />

<Button
android:id="@+id/synGetInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="syn.getInfo()"
android:textAllCaps="false" />

<Button
android:id="@+id/asynAddValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="asyn.addValue(5,6)"
android:textAllCaps="false" />

<Button
android:id="@+id/asynGetInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="asyn.getInfo()"
android:textAllCaps="false" />

<Button
android:id="@+id/hasMethodAddValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hasJavascriptMethod('addValue')"
android:textAllCaps="false" />

<Button
android:id="@+id/hasMethodXX"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hasJavascriptMethod('XX')"
android:textAllCaps="false" />

<Button
android:id="@+id/hasMethodAsynAddValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hasJavascriptMethod('asyn.addValue')"
android:textAllCaps="false" />
<Button
android:id="@+id/hasMethodAsynXX"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hasJavascriptMethod('asyn.XX')"
android:textAllCaps="false" />
<wendu.dsbridge.DWebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />

</LinearLayout>
</ScrollView>


</LinearLayout>


+ 12
- 0
app/src/main/res/layout/activity_js_call_native.xml 查看文件

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<wendu.dsbridge.DWebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>

+ 1
- 0
dsbridge/.gitignore 查看文件

@@ -0,0 +1 @@
/build

+ 30
- 0
dsbridge/build.gradle 查看文件

@@ -0,0 +1,30 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion 30
buildToolsVersion '30.0.3'

defaultConfig {
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

lintOptions {
abortOnError false
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
api 'com.tencent.tbs:tbssdk:44286'
}

+ 330
- 0
dsbridge/proguard-rules.pro 查看文件

@@ -0,0 +1,330 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/du/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

#-optimizationpasses 7
#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keepattributes *JavascriptInterface*
-keepattributes Signature
-keepattributes *Annotation*
-dontoptimize
-dontusemixedcaseclassnames
-verbose
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontwarn dalvik.**
-dontwarn com.tencent.smtt.**
#-overloadaggressively

#@proguard_debug_start
# ------------------ Keep LineNumbers and properties ---------------- #
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
-renamesourcefileattribute TbsSdkJava
-keepattributes SourceFile,LineNumberTable
#@proguard_debug_end

# --------------------------------------------------------------------------
# Addidional for x5.sdk classes for apps

-keep class com.tencent.smtt.export.external.**{
*;
}

-keep class com.tencent.tbs.video.interfaces.IUserStateChangedListener {
*;
}

-keep class com.tencent.smtt.sdk.CacheManager {
public *;
}

-keep class com.tencent.smtt.sdk.CookieManager {
public *;
}

-keep class com.tencent.smtt.sdk.WebHistoryItem {
public *;
}

-keep class com.tencent.smtt.sdk.WebViewDatabase {
public *;
}

-keep class com.tencent.smtt.sdk.WebBackForwardList {
public *;
}

-keep public class com.tencent.smtt.sdk.WebView {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebView$HitTestResult {
public static final <fields>;
public java.lang.String getExtra();
public int getType();
}

-keep public class com.tencent.smtt.sdk.WebView$WebViewTransport {
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebView$PictureListener {
public <fields>;
public <methods>;
}


-keepattributes InnerClasses

-keep public enum com.tencent.smtt.sdk.WebSettings$** {
*;
}

-keep public enum com.tencent.smtt.sdk.QbSdk$** {
*;
}

-keep public class com.tencent.smtt.sdk.WebSettings {
public *;
}


-keepattributes Signature
-keep public class com.tencent.smtt.sdk.ValueCallback {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebViewClient {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.DownloadListener {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebChromeClient {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebChromeClient$FileChooserParams {
public <fields>;
public <methods>;
}

-keep class com.tencent.smtt.sdk.SystemWebChromeClient{
public *;
}
# 1. extension interfaces should be apparent
-keep public class com.tencent.smtt.export.external.extension.interfaces.* {
public protected *;
}

# 2. interfaces should be apparent
-keep public class com.tencent.smtt.export.external.interfaces.* {
public protected *;
}

-keep public class com.tencent.smtt.sdk.WebViewCallbackClient {
public protected *;
}

-keep public class com.tencent.smtt.sdk.WebStorage$QuotaUpdater {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebIconDatabase {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.WebStorage {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.DownloadListener {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.QbSdk {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.QbSdk$PreInitCallback {
public <fields>;
public <methods>;
}
-keep public class com.tencent.smtt.sdk.CookieSyncManager {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.Tbs* {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.utils.LogFileUtils {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.utils.TbsLog {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.utils.TbsLogClient {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.CookieSyncManager {
public <fields>;
public <methods>;
}

# Added for game demos
-keep public class com.tencent.smtt.sdk.TBSGamePlayer {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.TBSGamePlayerClient* {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.TBSGamePlayerClientExtension {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.TBSGamePlayerService* {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.utils.Apn {
public <fields>;
public <methods>;
}
-keep class com.tencent.smtt.** {
*;
}
# end


-keep public class com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension {
public <fields>;
public <methods>;
}

-keep class MTT.ThirdAppInfoNew {
*;
}

-keep class com.tencent.mtt.MttTraceEvent {
*;
}

# Game related
-keep public class com.tencent.smtt.gamesdk.* {
public protected *;
}

-keep public class com.tencent.smtt.sdk.TBSGameBooter {
public <fields>;
public <methods>;
}

-keep public class com.tencent.smtt.sdk.TBSGameBaseActivity {
public protected *;
}

-keep public class com.tencent.smtt.sdk.TBSGameBaseActivityProxy {
public protected *;
}

-keep public class com.tencent.smtt.gamesdk.internal.TBSGameServiceClient {
public *;
}
#---------------------------------------------------------------------------


#------------------ 下方是android平台自带的排除项,这里不要动 ----------------

-keep public class * extends android.app.Activity{
public <fields>;
public <methods>;
}
-keep public class * extends android.app.Application{
public <fields>;
public <methods>;
}
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepattributes *Annotation*

-keepclasseswithmembernames class *{
native <methods>;
}

-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

#------------------ 下方是共性的排除项目 ----------------
# 方法名中含有“JNI”字符的,认定是Java Native Interface方法,自动排除
# 方法名中含有“JRI”字符的,认定是Java Reflection Interface方法,自动排除

-keepclasseswithmembers class * {
... *JNI*(...);
}

-keepclasseswithmembernames class * {
... *JRI*(...);
}

-keep class **JNI* {*;}



+ 12
- 0
dsbridge/src/main/AndroidManifest.xml 查看文件

@@ -0,0 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="wendu.dsbridge">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application android:allowBackup="true" android:label="@string/app_name"
android:supportsRtl="true">
</application>

</manifest>

+ 11
- 0
dsbridge/src/main/java/wendu/dsbridge/CompletionHandler.java 查看文件

@@ -0,0 +1,11 @@
package wendu.dsbridge;

/**
* Created by du on 16/12/31.
*/

public interface CompletionHandler<T> {
void complete(T retValue);
void complete();
void setProgressData(T value);
}

+ 964
- 0
dsbridge/src/main/java/wendu/dsbridge/DWebView.java 查看文件

@@ -0,0 +1,964 @@
package wendu.dsbridge;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.JavascriptInterface;
import android.widget.EditText;
import android.widget.FrameLayout;

import com.tencent.smtt.export.external.interfaces.ConsoleMessage;
import com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback;
import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient;
import com.tencent.smtt.export.external.interfaces.JsPromptResult;
import com.tencent.smtt.export.external.interfaces.JsResult;
import com.tencent.smtt.sdk.CookieManager;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebSettings;
import com.tencent.smtt.sdk.WebStorage;
import com.tencent.smtt.sdk.WebView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;

import androidx.annotation.Keep;
import androidx.appcompat.app.AlertDialog;


/**
* Created by du on 16/12/29.
*/

public class DWebView extends WebView {
private static final String BRIDGE_NAME = "_dsbridge";
private static final String LOG_TAG = "dsBridge";
private static boolean isDebug = false;
private Map<String, Object> javaScriptNamespaceInterfaces = new HashMap();
private String APP_CACHE_DIRNAME;
int callID = 0;
private WebChromeClient webChromeClient;
private volatile boolean alertBoxBlock = true;
private JavascriptCloseWindowListener javascriptCloseWindowListener = null;
private ArrayList<CallInfo> callInfoList;
private InnerJavascriptInterface innerJavascriptInterface = new InnerJavascriptInterface();
private Handler mainHandler = new Handler(Looper.getMainLooper());


class InnerJavascriptInterface {

private void PrintDebugInfo(String error) {
Log.d(LOG_TAG, error);
if (isDebug) {
evaluateJavascript(String.format("alert('%s')", "DEBUG ERR MSG:\\n" + error.replaceAll("\\'", "\\\\'")));
}
}

@Keep
@JavascriptInterface
public String call(String methodName, String argStr) {
String error = "Js bridge called, but can't find a corresponded " +
"JavascriptInterface object , please check your code!";
String[] nameStr = parseNamespace(methodName.trim());
methodName = nameStr[1];
Object jsb = javaScriptNamespaceInterfaces.get(nameStr[0]);
JSONObject ret = new JSONObject();
try {
ret.put("code", -1);
} catch (JSONException e) {
e.printStackTrace();
}
if (jsb == null) {
PrintDebugInfo(error);
return ret.toString();
}
Object arg = null;
Method method = null;
String callback = null;

try {
JSONObject args = new JSONObject(argStr);
if (args.has("_dscbstub")) {
callback = args.getString("_dscbstub");
}
if (args.has("data")) {
arg = args.get("data");
}

} catch (JSONException e) {
error = String.format("The argument of \"%s\" must be a JSON object string!", methodName);
PrintDebugInfo(error);
e.printStackTrace();
return ret.toString();
}


Class<?> cls = jsb.getClass();
boolean asyn = false;
try {
method = cls.getMethod(methodName,
new Class[]{Object.class, CompletionHandler.class});
asyn = true;
} catch (Exception e) {
try {
method = cls.getMethod(methodName, new Class[]{Object.class});
} catch (Exception ex) {

}
}

if (method == null) {
error = "Not find method \"" + methodName + "\" implementation! please check if the signature or namespace of the method is right ";
PrintDebugInfo(error);
return ret.toString();
}

JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class);
if (annotation == null) {
error = "Method " + methodName + " is not invoked, since " +
"it is not declared with JavascriptInterface annotation! ";
PrintDebugInfo(error);
return ret.toString();
}

Object retData;
method.setAccessible(true);
try {
if (asyn) {
final String cb = callback;
method.invoke(jsb, arg, new CompletionHandler() {

@Override
public void complete(Object retValue) {
complete(retValue, true);
}

@Override
public void complete() {
complete(null, true);
}

@Override
public void setProgressData(Object value) {
complete(value, false);
}

private void complete(Object retValue, boolean complete) {
try {
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("data", retValue);
//retValue = URLEncoder.encode(ret.toString(), "UTF-8").replaceAll("\\+", "%20");
if (cb != null) {
//String script = String.format("%s(JSON.parse(decodeURIComponent(\"%s\")).data);", cb, retValue);
String script = String.format("%s(%s.data);", cb, ret.toString());
if (complete) {
script += "delete window." + cb;
}
evaluateJavascript(script);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
} else {
retData = method.invoke(jsb, arg);
ret.put("code", 0);
ret.put("data", retData);
return ret.toString();
}
} catch (Exception e) {
e.printStackTrace();
error = String.format("Call failed:The parameter of \"%s\" in Java is invalid.", methodName);
PrintDebugInfo(error);
return ret.toString();
}
return ret.toString();
}

}


Map<Integer, OnReturnValue> handlerMap = new HashMap<>();

public interface JavascriptCloseWindowListener {
/**
* @return If true, close the current activity, otherwise, do nothing.
*/
boolean onClose();
}

@Deprecated
public interface FileChooser {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
void openFileChooser(ValueCallback valueCallback, String acceptType);

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
void openFileChooser(ValueCallback<Uri> valueCallback,
String acceptType, String capture);
}

public DWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public DWebView(Context context) {
super(context);
init();
}

/**
* Set debug mode. if in debug mode, some errors will be prompted by a dialog
* and the exception caused by the native handlers will not be captured.
*
* @param enabled
*/
public static void setWebContentsDebuggingEnabled(boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(enabled);
}
isDebug = enabled;
}

@Keep
private void init() {
APP_CACHE_DIRNAME = getContext().getFilesDir().getAbsolutePath() + "/webcache";
WebSettings settings = getSettings();
settings.setDomStorageEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);
settings.setMixedContentMode(MIXED_CONTENT_ALWAYS_ALLOW);
}
settings.setAllowFileAccess(false);
settings.setAppCacheEnabled(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setJavaScriptEnabled(true);
settings.setLoadWithOverviewMode(true);
settings.setAppCachePath(APP_CACHE_DIRNAME);
settings.setUseWideViewPort(true);
super.setWebChromeClient(mWebChromeClient);
addInternalJavascriptObject();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
super.addJavascriptInterface(innerJavascriptInterface, BRIDGE_NAME);
} else {
// add bridge tag in lower android version
settings.setUserAgentString(settings.getUserAgentString() + " _dsbridge");
}

}

private String[] parseNamespace(String method) {
int pos = method.lastIndexOf('.');
String namespace = "";
if (pos != -1) {
namespace = method.substring(0, pos);
method = method.substring(pos + 1);
}
return new String[]{namespace, method};
}

@Keep
private void addInternalJavascriptObject() {
addJavascriptObject(new Object() {

@Keep
@JavascriptInterface
public boolean hasNativeMethod(Object args) throws JSONException {

JSONObject jsonObject = (JSONObject) args;
String methodName = jsonObject.getString("name").trim();
String type = jsonObject.getString("type").trim();
String[] nameStr = parseNamespace(methodName);
Log.e("testjs", " method name " + methodName);
Object jsb = javaScriptNamespaceInterfaces.get(nameStr[0]);
if (jsb != null) {
Class<?> cls = jsb.getClass();
boolean asyn = false;
Method method = null;
try {
method = cls.getMethod(nameStr[1],
new Class[]{Object.class, CompletionHandler.class});
asyn = true;
} catch (Exception e) {
try {
method = cls.getMethod(nameStr[1], new Class[]{Object.class});
} catch (Exception ex) {

}
}
if (method != null) {
JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class);
if (annotation != null) {
if ("all".equals(type) || (asyn && "asyn".equals(type) || (!asyn && "syn".equals(type)))) {
return true;
}
}
}
}
return false;
}

@Keep
@JavascriptInterface
public String closePage(Object object) throws JSONException {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (javascriptCloseWindowListener == null
|| javascriptCloseWindowListener.onClose()) {
Context context = getContext();
if (context instanceof Activity) {
((Activity) getContext()).onBackPressed();
}
}
}
});
return null;
}

@Keep
@JavascriptInterface
public void disableJavascriptDialogBlock(Object object) throws JSONException {
JSONObject jsonObject = (JSONObject) object;
alertBoxBlock = !jsonObject.getBoolean("disable");
}

@Keep
@JavascriptInterface
public void dsinit(Object jsonObject) {
DWebView.this.dispatchStartupQueue();
}

@Keep
@JavascriptInterface
public void returnValue(final Object obj) throws JSONException {
runOnMainThread(new Runnable() {
@Override
public void run() {
JSONObject jsonObject = (JSONObject) obj;
Object data = null;
try {
int id = jsonObject.getInt("id");
boolean isCompleted = jsonObject.getBoolean("complete");
OnReturnValue handler = handlerMap.get(id);
if (jsonObject.has("data")) {
data = jsonObject.get("data");
}
if (handler != null) {
handler.onValue(data);
if (isCompleted) {
handlerMap.remove(id);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}

}, "_dsb");
}

private void _evaluateJavascript(String script) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.e("testjs", "_evaluateJavascript " + script);
DWebView.super.evaluateJavascript(script, null);
} else {
Log.e("testjs", "_evaluateJavascript loadUrl " + script);
super.loadUrl("javascript:" + script);
}
}

/**
* This method can be called in any thread, and if it is not called in the main thread,
* it will be automatically distributed to the main thread.
*
* @param script
*/
public void evaluateJavascript(final String script) {
runOnMainThread(new Runnable() {
@Override
public void run() {
_evaluateJavascript(script);
}
});
}

/**
* This method can be called in any thread, and if it is not called in the main thread,
* it will be automatically distributed to the main thread.
*
* @param url
*/
@Override
public void loadUrl(final String url) {
runOnMainThread(new Runnable() {
@Override
public void run() {
callInfoList = new ArrayList<>();
DWebView.super.loadUrl(url);
}
});
}

/**
* This method can be called in any thread, and if it is not called in the main thread,
* it will be automatically distributed to the main thread.
*
* @param url
* @param additionalHttpHeaders
*/
@Override
public void loadUrl(final String url, final Map<String, String> additionalHttpHeaders) {
runOnMainThread(new Runnable() {
@Override
public void run() {
callInfoList = new ArrayList<>();
DWebView.super.loadUrl(url, additionalHttpHeaders);
}
});
}

@Override
public void reload() {
runOnMainThread(new Runnable() {
@Override
public void run() {
callInfoList = new ArrayList<>();
DWebView.super.reload();
}
});
}

/**
* set a listener for javascript closing the current activity.
*/
public void setJavascriptCloseWindowListener(JavascriptCloseWindowListener listener) {
javascriptCloseWindowListener = listener;
}


private class CallInfo {
public CallInfo(String handlerName, int id, Object[] args) {
if (args == null) args = new Object[0];
data = new JSONArray(Arrays.asList(args)).toString();
callbackId = id;
method = handlerName;
}

@Override
public String toString() {
JSONObject jo = new JSONObject();
try {
jo.put("method", method);
jo.put("callbackId", callbackId);
jo.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return jo.toString();
}

public String data = null;
public int callbackId;
public String method;
}

private synchronized void dispatchStartupQueue() {
if (callInfoList != null) {
for (CallInfo info : callInfoList) {
dispatchJavascriptCall(info);
}
callInfoList = null;
}
}

private void dispatchJavascriptCall(CallInfo info) {
evaluateJavascript(String.format("window._handleMessageFromNative(%s)", info.toString()));
}

public synchronized <T> void callHandler(String method, Object[] args, final OnReturnValue<T> handler) {

CallInfo callInfo = new CallInfo(method, callID++, args);
if (handler != null) {
handlerMap.put(callInfo.callbackId, handler);
}

if (callInfoList != null) {
callInfoList.add(callInfo);
} else {
dispatchJavascriptCall(callInfo);
}

}

public void callHandler(String method, Object[] args) {
callHandler(method, args, null);
}

public <T> void callHandler(String method, OnReturnValue<T> handler) {
callHandler(method, null, handler);
}


/**
* Test whether the handler exist in javascript
*
* @param handlerName
* @param existCallback
*/
public void hasJavascriptMethod(String handlerName, OnReturnValue<Boolean> existCallback) {
callHandler("_hasJavascriptMethod", new Object[]{handlerName}, existCallback);
}

/**
* Add a java object which implemented the javascript interfaces to dsBridge with namespace.
* Remove the object using {@link #removeJavascriptObject(String) removeJavascriptObject(String)}
*
* @param object
* @param namespace if empty, the object have no namespace.
*/
public void addJavascriptObject(Object object, String namespace) {
if (namespace == null) {
namespace = "";
}
if (object != null) {
javaScriptNamespaceInterfaces.put(namespace, object);
}
}

/**
* remove the javascript object with supplied namespace.
*
* @param namespace
*/
public void removeJavascriptObject(String namespace) {
if (namespace == null) {
namespace = "";
}
javaScriptNamespaceInterfaces.remove(namespace);

}


public void disableJavascriptDialogBlock(boolean disable) {
alertBoxBlock = !disable;
}

@Override
public void setWebChromeClient(WebChromeClient client) {
webChromeClient = client;
}

private WebChromeClient mWebChromeClient = new WebChromeClient() {

@Override
public void onProgressChanged(WebView view, int newProgress) {
if (webChromeClient != null) {
webChromeClient.onProgressChanged(view, newProgress);
} else {
super.onProgressChanged(view, newProgress);
}
}

@Override
public void onReceivedTitle(WebView view, String title) {
if (webChromeClient != null) {
webChromeClient.onReceivedTitle(view, title);
} else {
super.onReceivedTitle(view, title);
}
}

@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
if (webChromeClient != null) {
webChromeClient.onReceivedIcon(view, icon);
} else {
super.onReceivedIcon(view, icon);
}
}

@Override
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
if (webChromeClient != null) {
webChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
} else {
super.onReceivedTouchIconUrl(view, url, precomposed);
}
}

@Override
public void onShowCustomView(View view, IX5WebChromeClient.CustomViewCallback callback) {
if (webChromeClient != null) {
webChromeClient.onShowCustomView(view, callback);
} else {
super.onShowCustomView(view, callback);
}
}

@Override
public void onShowCustomView(View view, int requestedOrientation,
IX5WebChromeClient.CustomViewCallback callback) {
if (webChromeClient != null) {
webChromeClient.onShowCustomView(view, requestedOrientation, callback);
} else {
super.onShowCustomView(view, requestedOrientation, callback);
}
}

@Override
public void onHideCustomView() {
if (webChromeClient != null) {
webChromeClient.onHideCustomView();
} else {
super.onHideCustomView();
}
}

@Override
public boolean onCreateWindow(WebView view, boolean isDialog,
boolean isUserGesture, Message resultMsg) {
if (webChromeClient != null) {
return webChromeClient.onCreateWindow(view, isDialog,
isUserGesture, resultMsg);
}
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
}

@Override
public void onRequestFocus(WebView view) {
if (webChromeClient != null) {
webChromeClient.onRequestFocus(view);
} else {
super.onRequestFocus(view);
}
}

@Override
public void onCloseWindow(WebView window) {
if (webChromeClient != null) {
webChromeClient.onCloseWindow(window);
} else {
super.onCloseWindow(window);
}
}


@Override
public boolean onJsAlert(WebView view, String url, final String message, final JsResult result) {

if (!alertBoxBlock) {
result.confirm();
}
if (webChromeClient != null) {
if (webChromeClient.onJsAlert(view, url, message, result)) {
return true;
}
}
Dialog alertDialog = new AlertDialog.Builder(getContext()).
setMessage(message).
setCancelable(false).
setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if (alertBoxBlock) {
result.confirm();
}
}
})
.create();
alertDialog.show();
return true;
}

@Override
public boolean onJsConfirm(WebView view, String url, String message,
final JsResult result) {
if (!alertBoxBlock) {
result.confirm();
}
if (webChromeClient != null && webChromeClient.onJsConfirm(view, url, message, result)) {
return true;
} else {
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (alertBoxBlock) {
if (which == Dialog.BUTTON_POSITIVE) {
result.confirm();
} else {
result.cancel();
}
}
}
};
new AlertDialog.Builder(getContext())
.setMessage(message)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, listener)
.setNegativeButton(android.R.string.cancel, listener).show();
return true;

}

}

@Override
public boolean onJsPrompt(WebView view, String url, final String message,
String defaultValue, final JsPromptResult result) {

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
String prefix = "_dsbridge=";
if (message.startsWith(prefix)) {
result.confirm(innerJavascriptInterface.call(message.substring(prefix.length()), defaultValue));
return true;
}
}

if (!alertBoxBlock) {
result.confirm();
}

if (webChromeClient != null && webChromeClient.onJsPrompt(view, url, message, defaultValue, result)) {
return true;
} else {
final EditText editText = new EditText(getContext());
editText.setText(defaultValue);
if (defaultValue != null) {
editText.setSelection(defaultValue.length());
}
float dpi = getContext().getResources().getDisplayMetrics().density;
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (alertBoxBlock) {
if (which == Dialog.BUTTON_POSITIVE) {
result.confirm(editText.getText().toString());
} else {
result.cancel();
}
}
}
};
new AlertDialog.Builder(getContext())
.setTitle(message)
.setView(editText)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, listener)
.setNegativeButton(android.R.string.cancel, listener)
.show();
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
int t = (int) (dpi * 16);
layoutParams.setMargins(t, 0, t, 0);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
editText.setLayoutParams(layoutParams);
int padding = (int) (15 * dpi);
editText.setPadding(padding - (int) (5 * dpi), padding, padding, padding);
return true;
}

}

@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
if (webChromeClient != null) {
return webChromeClient.onJsBeforeUnload(view, url, message, result);
}
return super.onJsBeforeUnload(view, url, message, result);
}

@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
long estimatedDatabaseSize,
long totalQuota,
WebStorage.QuotaUpdater quotaUpdater) {
if (webChromeClient != null) {
webChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota,
estimatedDatabaseSize, totalQuota, quotaUpdater);
} else {
super.onExceededDatabaseQuota(url, databaseIdentifier, quota,
estimatedDatabaseSize, totalQuota, quotaUpdater);
}
}

@Override
public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
if (webChromeClient != null) {
webChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
}
super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
}

@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissionsCallback callback) {
if (webChromeClient != null) {
webChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
} else {
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
}

@Override
public void onGeolocationPermissionsHidePrompt() {
if (webChromeClient != null) {
webChromeClient.onGeolocationPermissionsHidePrompt();
} else {
super.onGeolocationPermissionsHidePrompt();
}
}


@Override
public boolean onJsTimeout() {
if (webChromeClient != null) {
return webChromeClient.onJsTimeout();
}
return super.onJsTimeout();
}


@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
if (webChromeClient != null) {
return webChromeClient.onConsoleMessage(consoleMessage);
}
return super.onConsoleMessage(consoleMessage);
}

@Override
public Bitmap getDefaultVideoPoster() {

if (webChromeClient != null) {
return webChromeClient.getDefaultVideoPoster();
}
return super.getDefaultVideoPoster();
}

@Override
public View getVideoLoadingProgressView() {
if (webChromeClient != null) {
return webChromeClient.getVideoLoadingProgressView();
}
return super.getVideoLoadingProgressView();
}

@Override
public void getVisitedHistory(ValueCallback<String[]> callback) {
if (webChromeClient != null) {
webChromeClient.getVisitedHistory(callback);
} else {
super.getVisitedHistory(callback);
}
}

@Override
public void openFileChooser(ValueCallback<Uri> valueCallback, String s, String s1) {
if (webChromeClient != null) {
webChromeClient.openFileChooser(valueCallback, s, s1);
return;
}
super.openFileChooser(valueCallback, s, s1);
}

@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
if (webChromeClient != null) {
return webChromeClient.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}
return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}


@Keep
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
if (webChromeClient instanceof FileChooser) {
((FileChooser) webChromeClient).openFileChooser(valueCallback, acceptType);
}
}

};

@Override
public void clearCache(boolean includeDiskFiles) {
super.clearCache(includeDiskFiles);
CookieManager.getInstance().removeAllCookie();
Context context = getContext();
try {
context.deleteDatabase("webview.db");
context.deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}

File appCacheDir = new File(APP_CACHE_DIRNAME);
File webviewCacheDir = new File(context.getCacheDir()
.getAbsolutePath() + "/webviewCache");

if (webviewCacheDir.exists()) {
deleteFile(webviewCacheDir);
}

if (appCacheDir.exists()) {
deleteFile(appCacheDir);
}
}

public void deleteFile(File file) {
if (file.exists()) {
if (file.isFile()) {
file.delete();
} else if (file.isDirectory()) {
File files[] = file.listFiles();
for (int i = 0; i < files.length; i++) {
deleteFile(files[i]);
}
}
file.delete();
} else {
Log.e("Webview", "delete file no exists " + file.getAbsolutePath());
}
}

private void runOnMainThread(Runnable runnable) {
if (Looper.getMainLooper() == Looper.myLooper()) {
runnable.run();
return;
}
mainHandler.post(runnable);
}


}

+ 9
- 0
dsbridge/src/main/java/wendu/dsbridge/OnReturnValue.java 查看文件

@@ -0,0 +1,9 @@
package wendu.dsbridge;

/**
* Created by du on 16/12/31.
*/

public interface OnReturnValue<T> {
void onValue( T retValue);
}

+ 3
- 0
dsbridge/src/main/res/values/strings.xml 查看文件

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">JsBridge</string>
</resources>

+ 1
- 0
settings.gradle 查看文件

@@ -15,5 +15,6 @@ dependencyResolutionManagement {
}

include ':test'
include ':dsbridge'
include ':app'
rootProject.name = "nativedemo"

正在加载...
取消
保存