├── app
├── .gitignore
├── simple.jks
├── app-release.apk
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ └── layout
│ │ │ │ ├── content_main.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── fragment_webview.xml
│ │ ├── assets
│ │ │ ├── connect_error.html
│ │ │ ├── bridge_demo.html
│ │ │ └── bridge.js
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── bridge
│ │ │ │ └── view
│ │ │ │ ├── ResponseStatus.java
│ │ │ │ ├── IInvokeJS.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── JavaInterfaces4JS.java
│ │ │ │ └── fragment
│ │ │ │ └── WebViewFragment.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── bridge
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── bridge
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── simplejsjavabridgeLib
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── simplejsjavabridge
│ │ │ │ └── lib
│ │ │ │ ├── IJavaCallback2JS.java
│ │ │ │ ├── exception
│ │ │ │ └── SimpleJSBridgeException.java
│ │ │ │ ├── annotation
│ │ │ │ ├── InvokeJSInterface.java
│ │ │ │ ├── JavaCallback4JS.java
│ │ │ │ ├── JavaInterface4JS.java
│ │ │ │ ├── ParamCallback.java
│ │ │ │ ├── ParamResponseStatus.java
│ │ │ │ └── Param.java
│ │ │ │ ├── MethodHandler.java
│ │ │ │ ├── SimpleJavaJSWebChromeClient.java
│ │ │ │ ├── RequestResponseBuilder.java
│ │ │ │ ├── Params.java
│ │ │ │ └── SimpleJavaJsBridge.java
│ │ ├── AndroidManifest.xml
│ │ └── assets
│ │ │ └── js_native_bridge.js
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── simplejsjavabridge
│ │ │ └── lib
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── simplejsjavabridge
│ │ └── lib
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/simplejsjavabridgeLib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':simplejsjavabridgeLib'
2 |
--------------------------------------------------------------------------------
/app/simple.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/simple.jks
--------------------------------------------------------------------------------
/app/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/app-release.apk
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/simplejsjavabridgeLib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
例子:
15 | *
16 | * :@InvokeJSInterface("exam")
17 | * public void exam():
18 | *
19 | * 该例子表明java会调用js提供的{@code exam}这样的接口
20 | *
21 | */
22 | @Target(ElementType.METHOD)
23 | @Retention(RetentionPolicy.RUNTIME)
24 | @Documented
25 | @Inherited
26 | public @interface InvokeJSInterface {
27 | String value();
28 | }
29 |
--------------------------------------------------------------------------------
/simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/annotation/JavaCallback4JS.java:
--------------------------------------------------------------------------------
1 | package com.simplejsjavabridge.lib.annotation;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Inherited;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 |
11 | /**
12 | * java在主动调用js的时候,同时会给js传递一个回调,该回调的作用就是为了监听js的返回结果,
13 | * 该注解的主要作用是为了标记java为js提供的回调方法.
14 | * 例子:
15 | *
16 | *
17 | * new Object{
18 | * ;@JavaCallback4JS
19 | * public void callback4JS()
20 | * }
21 | *
22 | * 上面的例子表明{@code callback4JS}方法是提供给js的回调方法
23 | *
24 | *
25 | *
26 | */
27 | @Target(ElementType.METHOD)
28 | @Retention(RetentionPolicy.RUNTIME)
29 | @Documented
30 | @Inherited
31 | public @interface JavaCallback4JS {
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/simplejsjavabridgeLib/src/androidTest/java/com/simplejsjavabridge/lib/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.simplejsjavabridge.lib;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.simplejsjavabridge.lib.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/simplejsjavabridgeLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | compile 'com.android.support:appcompat-v7:23.4.0'
30 | testCompile 'junit:junit:4.12'
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 | 例子:
13 | *
14 | *
15 | * :@JavaInterface4JS("test")
16 | * public void test(@Param("msg") String msg);
17 | *
18 | * 上面的例子,表明java为js提供一个名字为{@code test}的接口
19 | *
20 | * Created by niuxiaowei on 2015/10/27.
21 | * @see Param
22 | */
23 | @Target(ElementType.METHOD)
24 | @Retention(RetentionPolicy.RUNTIME)
25 | @Documented
26 | @Inherited
27 | public @interface JavaInterface4JS {
28 | /**
29 | * 代表java与js之间约定好的接口名字
30 | * @return
31 | */
32 | String value();
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 | 例子1:java主动调用js,提供给js的回调方法
15 | *
16 | *
17 | * :@InvokeJSInterface("test")
18 | * public void invokeJSTest(@ParamCallback Object callback);
19 | *
20 | * 使用:invokeJSTest(new Object(){
21 | * :@JavaCallback4JS
22 | * public void callback(){
23 | *
24 | * }
25 | * });
26 | *
27 | *
28 | * 例子2:js主动调用java,提供给java的回调方法
29 | *
30 | * //声明一个回调接口
31 | * interface IExamCallback{
32 | * void examCallback();
33 | * }
34 | *
35 | * :@JavaInterface4JS("exam")
36 | * public void examInterface4JS(@ParamCallback IExamCallback iExamCallback){
37 | * iExamCallback.examCallback();
38 | * }
39 | *
40 | *
41 | * @see JavaCallback4JS,JavaInterface4JS,InvokeJSInterface
42 | */
43 | @Target(ElementType.PARAMETER)
44 | @Retention(RetentionPolicy.RUNTIME)
45 | @Documented
46 | @Inherited
47 | public @interface ParamCallback {
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 | 因此该注解是用来对responseStatus进行标注的,responseStatus格式({status:1, msg:"ok"}。{@link #value()}就是status或msg这些key值
15 | *例子:
16 | *
17 | *
18 | * public void receiveResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg){
19 | *
20 | * }
21 | *
22 | *
23 | *
24 | * 例子:
25 | *
26 | * :@AllFiledsConvert
27 | * public enum ResponseStatus{
28 | * private int status;
29 |
30 | private String msg;
31 |
32 | ResponseStatus(int status, String msg) {
33 | this.status = status;
34 | this.msg = msg;
35 | }
36 |
37 | public int getStatus() {
38 | return status;
39 | }
40 |
41 | public String getMsg() {
42 | return msg;
43 | }
44 | * }
45 | *
46 | * public void receiveResponse(@ParamResponseStatus ResponseStatus responseStatus){
47 | *
48 | * }
49 | *
50 | *
51 | *
52 | * Created by niuxiaowei on 2015/10/27.
53 | */
54 | @Target(ElementType.PARAMETER)
55 | @Retention(RetentionPolicy.RUNTIME)
56 | @Documented
57 | @Inherited
58 | public @interface ParamResponseStatus {
59 | /**
60 | * responseStatus格式({status:1, msg:"ok"}。{@link #value()}就是status或msg这些key值
61 | * @return
62 | */
63 | String value() default "";
64 |
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_webview.xml:
--------------------------------------------------------------------------------
1 |
2 | 把参数值转化为json格式
18 | *例子1:
19 | *
20 | * :@InvokeJSInterface("test")
21 | * public void test(@Param("msg") String content);
22 | *
23 | * test方法主要是让使用者来调用js暴露的test接口时,最终会把{@code @Param("msg") String content}转化为{msg:"content"}的json
24 | *
25 | *
26 | * 例子2:
27 | *
28 | * :@InvokeJSInterface("test")
29 | * public void test(@Param String content);
30 | *
31 | * 最终把参数转化为{"content"}的json
32 | *
33 | *
34 | * 例子3:参数是{@link org.json.JSONObject}不可以直接存放的,{@link #value()}设置了值
35 | *
36 | *
37 | * class User{
38 | * :@Param("userId")
39 | * String userId;
40 | *
41 | * :@Param("name")
42 | * String userName;
43 | * }
44 | *
45 | * :@InvokeJSInterface("test")
46 | * public void test(@Param(value="userInfo") User userInfo);
47 | *
48 | * 最终把参数转化为{userInfo:{userId:"userId", name:"userName"}}的json
49 | *
50 | *
51 | * 例子4:参数是{@link org.json.JSONObject}不可以直接存放的
52 | *
53 | *
54 | * class User{
55 | * :@Param("userId")
56 | * String userId;
57 | *
58 | * :@Param("name")
59 | * String userName;
60 | * }
61 | *
62 | * :@InvokeJSInterface("test")
63 | * public void test(@Param User userInfo);
64 | *
65 | * 最终把参数转化为{userId:"userId", name:"userName"}的json
66 | *
67 | *
68 | * 把json转化为参数值
69 | *例子1:简单类型
70 | *
71 | *
72 | * :@JavaInterface4JS("exam")
73 | * public void test(@Param("msg") String content){
74 | *
75 | * }
76 | *
77 | * test方法是java提供给js的接口,js调用该接口时,假如传递的json是{msg:"你好java"},那会把该json中的"你好java"赋值给test方法的content参数
78 | *
79 | * 例子2:{@link org.json.JSONObject}不能直接存放的类型
80 | *
81 | *
82 | * class User{
83 | * :@Param("userId")
84 | * String userId;
85 | *
86 | * :@Param("name")
87 | * String userName;
88 | * }
89 | * :@JavaInterface4JS("exam")
90 | * public void test(@Param("userInfo") User userInfo){
91 | *
92 | * }
93 | *
94 | * 假如传递的json是{userInfo:{userId:"11", name:"nihao"}},那会把该json中的userInfo:{userId:"11", name:"nihao"}赋值给test方法的userInfo参数
95 | *
96 | */
97 | @Target(value = {ElementType.PARAMETER, ElementType.FIELD})
98 | @Retention(RetentionPolicy.RUNTIME)
99 | @Documented
100 | @Inherited
101 | public @interface Param {
102 | /**
103 | * json中一般是以{key:value, key1:value1}的格式组织数据,
104 | * {@link #value()}就代表key,key1这些值,@Param标注的参数或类的实例属性代表value或value1这些值;
105 | * {@link #value()}的值可以不设置,这种情况主要基于该注解所标注的对象实例是{@link org.json.JSONObject}不可以直接进行存取的 106 | * 其他情况建议设置{@link #value()}的值 107 | * 108 | * @return 109 | */ 110 | String value() default ""; 111 | 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/JavaInterfaces4JS.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view; 2 | 3 | 4 | import com.bridge.view.fragment.WebViewFragment; 5 | import com.simplejsjavabridge.lib.annotation.JavaInterface4JS; 6 | import com.simplejsjavabridge.lib.annotation.Param; 7 | import com.simplejsjavabridge.lib.annotation.ParamCallback; 8 | import com.simplejsjavabridge.lib.annotation.ParamResponseStatus; 9 | 10 | 11 | /** 12 | * java提供给js的接口 13 | * Created by niuxiaowei on 16/7/22. 14 | */ 15 | public class JavaInterfaces4JS { 16 | 17 | 18 | private WebViewFragment mWebViewFragment; 19 | 20 | public JavaInterfaces4JS(WebViewFragment webViewFragment) { 21 | mWebViewFragment = webViewFragment; 22 | } 23 | 24 | 25 | /** 26 | * 必须有无参构造函数 27 | */ 28 | public static class Person { 29 | @Param("name") 30 | String name; 31 | @Param("age") 32 | public int age; 33 | 34 | public Person() { 35 | } 36 | 37 | public Person(String name, int age) { 38 | this.name = name; 39 | this.age = age; 40 | } 41 | 42 | 43 | } 44 | 45 | /** 46 | * 发送响应状态的接口 47 | */ 48 | public interface IResponseStatusCallback { 49 | void callbackResponse(@ParamResponseStatus ResponseStatus responseStatus); 50 | } 51 | 52 | public interface ITestJSCallback extends IResponseStatusCallback { 53 | void callback(@ParamResponseStatus ResponseStatus responseStatus, @Param("content") String content); 54 | } 55 | 56 | public interface ITest1JSCallback extends IResponseStatusCallback { 57 | void callback(@ParamResponseStatus ResponseStatus responseStatus, @Param Person person); 58 | } 59 | 60 | @JavaInterface4JS("test") 61 | public void test(@Param("msg") String msg, @ParamCallback ITestJSCallback jsCallback) { 62 | mWebViewFragment.setResult("js传递数据: " + msg); 63 | jsCallback.callbackResponse(ResponseStatus.FAILED); 64 | } 65 | 66 | 67 | 68 | @JavaInterface4JS("test1") 69 | public void test(@Param Person personInfo, @ParamCallback ITest1JSCallback jsCallback) { 70 | 71 | if (personInfo != null) { 72 | mWebViewFragment.setResult("native的test1接口被调用,js传递数据: " + "name=" + personInfo.name + " age=" + personInfo.age); 73 | 74 | } 75 | jsCallback.callback(ResponseStatus.OK, new Person("niuxiaowei", 30)); 76 | } 77 | 78 | 79 | @JavaInterface4JS("test2") 80 | public void test2(@Param(value = "person") Person personInfo, @ParamCallback ITest1JSCallback jsCallback) { 81 | 82 | if (personInfo != null) { 83 | mWebViewFragment.setResult("native的test2接口被调用,js传递数据: " + "name=" + personInfo.name + " age=" + personInfo.age); 84 | 85 | } 86 | jsCallback.callback(ResponseStatus.OK, new Person("niuxiaowei", 30)); 87 | } 88 | 89 | @JavaInterface4JS("test3") 90 | public void test3(@Param("jiguan") String jiguan, @Param(value = "person") Person personInfo, @ParamCallback ITest1JSCallback jsCallback) { 91 | 92 | if (personInfo != null) { 93 | mWebViewFragment.setResult("native的test3接口被调用,js传递的数据: " + "jiguan=" + jiguan + " name=" + personInfo.name + " age=" + personInfo.age); 94 | 95 | } 96 | jsCallback.callback(ResponseStatus.OK, new Person("niuxiaowei", 30)); 97 | } 98 | 99 | @JavaInterface4JS("test4") 100 | public void test3(@ParamCallback IResponseStatusCallback jsCallback) { 101 | 102 | mWebViewFragment.setResult("native的test4无参接口被调用"); 103 | 104 | jsCallback.callbackResponse(ResponseStatus.OK); 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/assets/bridge_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
63 | * {
64 | * "handlerName":"test",
65 | * "callbackId":"c_111111",
66 | * "params":{
67 | * ....
68 | * }
69 | * }
70 | *
71 | * hanlerName 代表java与js之间给对方暴漏的接口的名称,
72 | * callbackId 代表对方在发起请求时,会为回调方法生产一个唯一的id值,它就代表这个唯一的id值
73 | * params 代表传递的数据
74 | *
75 | * }
76 | */
77 | private static class Request {
78 |
79 | private static String sRequestInterfaceName = "handlerName";
80 | private static String sRequestCallbackIdName = "callbackId";
81 | private static String sRequestValuesName = "params";
82 |
83 | /*request相关的属性*/
84 | private String interfaceName;
85 | private String callbackId;
86 | private JSONObject requestValues;
87 | private IJavaCallback2JS iJavaCallback2JS;
88 |
89 | private static void init(String requestInterfaceName, String requestCallbackIdName, String requestValuesName) {
90 | if (!TextUtils.isEmpty(requestCallbackIdName)) {
91 | Request.sRequestCallbackIdName = requestCallbackIdName;
92 | }
93 |
94 | if (!TextUtils.isEmpty(requestValuesName)) {
95 | Request.sRequestValuesName = requestValuesName;
96 | }
97 | if (!TextUtils.isEmpty(requestInterfaceName)) {
98 | Request.sRequestInterfaceName = requestInterfaceName;
99 | }
100 | }
101 |
102 | private void parseRequest(JSONObject json) {
103 | if (json != null) {
104 | callbackId = json.optString(sRequestCallbackIdName);
105 | interfaceName = json.optString(sRequestInterfaceName);
106 | requestValues = json.optJSONObject(sRequestValuesName);
107 | }
108 | }
109 |
110 | @Override
111 | public String toString() {
112 | JSONObject jsonObject = new JSONObject();
113 | try {
114 | jsonObject.put(sRequestCallbackIdName, callbackId);
115 | jsonObject.put(sRequestInterfaceName, interfaceName);
116 | if (requestValues != null) {
117 | jsonObject.put(sRequestValuesName, requestValues);
118 | }
119 | } catch (JSONException e) {
120 | e.printStackTrace();
121 | }
122 | return "'" + jsonObject.toString() + "'";
123 | }
124 | }
125 |
126 | /**
127 | *
128 | * response数据格式:
129 | *
130 | * {
131 | * "responseId":"iii",
132 | * "data":{
133 | * "status":"1",
134 | * "msg":"ok",
135 | * "values":{
136 | * ......
137 | * }
138 | * }
139 | * }
140 | *
141 | * responseId 代表request中的callbackId
142 | * data 代表响应的数据
143 | * status 代表响应状态
144 | * msg 代表响应状态对应的消息
145 | * values 代表响应数据包含的值
146 | *
147 | */
148 | private static class Response {
149 | private static String sResponseIdName = "responseId";
150 | private static String sResponseValuesName = "values";
151 | private static String sResponseName = "data";
152 |
153 | private String responseId;
154 | private JSONObject response = new JSONObject();
155 | private JSONObject responseValues;
156 |
157 | private static void init(String responseIdName, String responseName, String responseValuesName) {
158 | if (!TextUtils.isEmpty(responseValuesName)) {
159 |
160 | Response.sResponseValuesName = responseValuesName;
161 | }
162 | if (!TextUtils.isEmpty(responseIdName)) {
163 | Response.sResponseIdName = responseIdName;
164 | }
165 |
166 | if (!TextUtils.isEmpty(responseName)) {
167 |
168 | Response.sResponseName = responseName;
169 | }
170 |
171 | }
172 |
173 | private void parseResponse(JSONObject json) {
174 | if (json != null) {
175 | responseId = json.optString(sResponseIdName);
176 | response = json.optJSONObject(sResponseName);
177 | if (response != null) {
178 | responseValues = response.optJSONObject(sResponseValuesName);
179 | }
180 | }
181 | }
182 |
183 | @Override
184 | public String toString() {
185 | JSONObject jsonObject = new JSONObject();
186 | try {
187 | jsonObject.put(sResponseIdName, responseId);
188 | if (responseValues != null) {
189 | response.put(sResponseValuesName, responseValues);
190 | }
191 | jsonObject.put(sResponseName, response);
192 | } catch (JSONException e) {
193 | e.printStackTrace();
194 | }
195 |
196 | return "'" + jsonObject.toString() + "'";
197 | }
198 | }
199 |
200 | /**
201 | * 获取请求时为回调函数生成的 callbackId
202 | * @return
203 | */
204 | public String getCallbackId(){
205 | return mRequest == null?null: mRequest.callbackId;
206 | }
207 |
208 | /**
209 | * 获取请求的接口的名字
210 | * @return
211 | */
212 | public String getInterfaceName() {
213 | return mRequest == null ? null : mRequest.interfaceName;
214 | }
215 |
216 | public void setRequestCallback(IJavaCallback2JS callback) {
217 | initRequest();
218 | this.mRequest.iJavaCallback2JS = callback;
219 | }
220 |
221 | private void initRequest() {
222 | if (mRequest == null) {
223 | mRequest = new Request();
224 | }
225 | }
226 |
227 | /**
228 | * 设置请求的接口的名字
229 | * @param interfaceName
230 | */
231 | public void setInterfaceName(String interfaceName) {
232 | initRequest();
233 | this.mRequest.interfaceName = interfaceName;
234 | }
235 |
236 | /**
237 | * 为回调方法设置回调id
238 | * @param callbackId
239 | */
240 | public void setCallbackId(String callbackId) {
241 | initRequest();
242 | this.mRequest.callbackId = callbackId;
243 | }
244 |
245 |
246 | /**
247 | * 获取request或者response的 values值
248 | * @return
249 | */
250 | public JSONObject getValues() {
251 | if (mIsBuildRequest) {
252 | return mRequest == null ? null : mRequest.requestValues;
253 | } else {
254 | return mResponse == null ? null : mResponse.responseValues;
255 | }
256 | }
257 |
258 | /**
259 | * 往request或者response中存放 数据
260 | * @param key
261 | * @param value
262 | */
263 | public void putValue(String key, Object value) {
264 | if(TextUtils.isEmpty(key) || value == null){
265 | return;
266 | }
267 | JSONObject values = null;
268 | if (mIsBuildRequest) {
269 | initRequest();
270 | if (mRequest.requestValues == null) {
271 | mRequest.requestValues = new JSONObject();
272 | }
273 | values = mRequest.requestValues;
274 | } else {
275 | initResponse();
276 | if (mResponse.responseValues == null) {
277 | mResponse.responseValues = new JSONObject();
278 | }
279 | values = mResponse.responseValues;
280 | }
281 |
282 | try {
283 | values.put(key, value);
284 | } catch (JSONException e) {
285 | e.printStackTrace();
286 | }
287 |
288 | }
289 |
290 | public IJavaCallback2JS getCallback() {
291 | return mRequest == null ? null : mRequest.iJavaCallback2JS;
292 | }
293 |
294 | /**
295 | * @param responseIdName
296 | * @param responseName
297 | * @param responseValuesName
298 | */
299 | public static void init(String responseIdName, String responseName, String responseValuesName, String requestInterfaceName, String requestCallbackIdName, String requestValuesName) {
300 | Response.init(responseIdName, responseName, responseValuesName);
301 | Request.init(requestInterfaceName, requestCallbackIdName, requestValuesName);
302 | }
303 |
304 |
305 | private void initResponse() {
306 | if (mResponse == null) {
307 | mResponse = new Response();
308 | }
309 | }
310 |
311 | public String getResponseId() {
312 | return mResponse == null ? null : mResponse.responseId;
313 | }
314 |
315 | public void setResponseId(String responseId) {
316 | initResponse();
317 | this.mResponse.responseId = responseId;
318 | }
319 |
320 | /**
321 | * 获取response的 状态数据
322 | * @return
323 | */
324 | public JSONObject getResponseStatus() {
325 | return mResponse == null ? null : mResponse.response;
326 | }
327 |
328 | /**
329 | * 往response中存放 数据
330 | * @param key
331 | * @param value
332 | */
333 | public void putResponseStatus(String key, Object value) {
334 | if(TextUtils.isEmpty(key) || value == null){
335 | return;
336 | }
337 | initResponse();
338 | try {
339 | mResponse.response.put(key, value);
340 | } catch (JSONException e) {
341 | e.printStackTrace();
342 | }
343 | }
344 |
345 |
346 | /**
347 | * 从json中创建一个{@link RequestResponseBuilder}对象,其实最终创建的是一个 request或者response
348 | * @param json
349 | * @return
350 | */
351 | static RequestResponseBuilder create(JSONObject json) {
352 | if (json == null) {
353 | return null;
354 | }
355 | RequestResponseBuilder requestResponseBuilder = null;
356 | /*响应数据*/
357 | if (json.has(Response.sResponseIdName)) {
358 | requestResponseBuilder = new RequestResponseBuilder(false, json);
359 | } else {
360 | requestResponseBuilder = new RequestResponseBuilder(true, json);
361 | }
362 |
363 | return requestResponseBuilder;
364 | }
365 |
366 | /**
367 | * 是否构建的时request数据
368 | * @return
369 | */
370 | public boolean isBuildRequest() {
371 | return mIsBuildRequest;
372 | }
373 |
374 | @Override
375 | public String toString() {
376 | if (mIsBuildRequest) {
377 | return mRequest == null ? super.toString() : mRequest.toString();
378 | } else {
379 | return mResponse == null ? super.toString() : mResponse.toString();
380 | }
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #前言
2 | 最近接触android中js与java交互的东西很多,当然它们之间的交互方式有几种,但是我觉得这几种交互方式都存在一定的不足,这是我决定编写SimpleJavaJsBridge这个库的关键原因。
3 |
4 | 我会按以下顺序进行本文章:
5 |
6 | 1. 现有js与java通信方案及不足
7 | 2. js与java完美通信方案设计
8 | 3. SimpleJavaJsBridge
9 |
10 | 现在进入正题
11 | #1. 现有js与java通信方案及不足
12 | 先来说明一点js与java通信,指的是js既可以给java发送消息,同时java也可以给js发送消息。那就来屡屡它们之间的通信方案。
13 |
14 | **1.1 java给js发送消息**
15 |
16 | 官方唯一指定方法是通过webview的loadUrl(String)方法进行的,看下的伪代码:
17 |
18 | //例子:调用js的test(param)方法
19 | webView.loadUrl("javascript:test(1)");
20 |
21 |
22 |
23 | 调用方法非常的简单,"javascript:"+js方法的名字+方法的参数值拼接成一个字符串就可以给js发送消息了,犹如是在直接调用js的方法。
24 |
25 | **1.2 js给java发送消息**
26 |
27 | js给java发送消息实际上只有2种方案,依次来分析下这2种方案。
28 |
29 | **1.2.1 官方方法**
30 |
31 | 先看下伪代码:
32 |
33 | //该类封装了提供给js调用的方法
34 | public class JSBridge{
35 | //提供给js的方法
36 | public void invokeByJs(String msg){
37 |
38 | }
39 | }
40 |
41 | //把JSBridge对象注入WebView中,同时起一个别名
42 | webView.addJavascriptInterface(new JSBridge(),"jsBridge");
43 |
44 | //js调用java方法
45 | window.jsBridge.invokeByJs('hello java');
46 |
47 |
48 |
49 | 这种方法其实是把一个对象注入到WebView中,js给java发送消息的方式是
50 |
51 | window.注入WebView的java对象的所对应name值.javaMethod(param...);
52 |
53 | 这其实也犹如在java代码中调用java的方法,因为java提供给js的方法名,方法参数是啥,js在发送消息时,方法名与参数必须保持一致,这也是这些java代码不能进行混淆的原因。
54 |
55 | 但是这种方法存在一个严重的漏洞,虽然官方在android4.4的时候给出了相应的解决方案,但是android4.4以下的版本还得解决该漏洞,因此一些巨人们就开始琢磨着解决这个坑,第二种方法由此诞生。
56 |
57 | **1.2.2 js传递约定好的字符串给java**
58 |
59 | 这种方案的主要原理是:
60 | - 找到一个js可以给java发送消息的入口(这个入口有onJsPrompt,onJsAlert等等)
61 | - js通过入口把消息按既定好的规则拼接成字符串传递给java
62 | - java按照既定好的规则对字符串进行解析
63 | - 根据解析数据,通过反射来调用自己相应方法
64 |
65 | 这种方法使用起来要比官方方法(第一种方法)麻烦。
66 |
67 | **1.3 存在的不足**
68 |
69 | 上面介绍了js与java的通信方法,那我就来分析下我认为存在的不足。
70 |
71 | **1.3.1 java给js发送消息方法和js给java发送消息的官方方法存在的不足**
72 |
73 | **1.3.1.1 强依赖**
74 |
75 | java给js发送消息的方法,和js给java发送消息的官方方法都存在着**强依赖**的问题,都要高度依赖对方的方法名字,方法参数。强依赖发生于同一模块内,个人觉得不是问题甚至是高内聚的体现。但是java与js可以说是处于两个不同的模块或者叫两个不同世界,只要js提供给java的方法发生变化,java也得改动,同理java提供给js的方法也如此。处于两个不同模块知道对方的细节越少越好,这样耦合性就会降低,耦合性降低的好处就不用说了。
76 |
77 | **1.3.1.2 强依赖导致js需要兼容不同的系统**
78 |
79 | 先看段伪代码:
80 |
81 | function location(){
82 | //是ios系统,采用给ios发送消息的方法
83 | if(isIOS){
84 | 给ios发送消息;
85 | }else if(isAndroid){
86 | 给android发送消息;
87 | }
88 | }
89 | 上面的代码展示的是js使用native的定位功能的代码,因为js在给不同的系统发送消息的方式不一样,就会出现if else if 这样的兼容语句。当前js代码只被ios和android使用,假如还会被wp或pc来使用,那if else if岂不是要恶心死。产生该问题的主要原因是:js代码在针对不同的系统自己独有的通信方式进行通信。
90 |
91 | **1.3.1.3 给不存在的接口发送消息没反馈**
92 |
93 | java在给js的一个不存在接口发送消息时,java根本不知道该接口不存在,java只会傻傻的等待。同理js在给java的一个不存在接口发送消息时,js是可以通过捕获异常来知道该接口不存在,但是这不是最好的解决方案。
94 | 给不存在接口发送消息没反馈会导致js代码充斥着if else if语句,看段伪代码:
95 |
96 | //调用java的定位方法
97 | function location(){
98 | //1.1版本以上才会调用定位功能
99 | if(androidAppVersion > '1.1'){
100 | 发送消息给java;
101 | }else{
102 | 给用户提示,暂不支持定位功能;
103 | }
104 | }
105 |
106 | 这是一段调用java进行定位的js代码,android app在版本1.1的时候才增加了定位的功能,因此对于1.1以下版本是不支持这功能的,因此js代码里面非常有必要根据版本号进行判断。这只是由于版本问题导致if else if的一个小小的缩影。还有一些其他情况导致if else if的产生比如一份js代码被多个业务使用。
107 |
108 | **1.3.2 js给java发送消息的第二种方法存在不足**
109 |
110 | 上文提到的js给java发送消息的第二种方法,它解决了存在的漏洞,但是这种方法,使用起来要比第一种方法复杂,java会多做以下工作:
111 | - 解析js传递给java的字符串,把调用的接口,参数解析出来
112 | - 把调用的接口,参数映射到相应的方法
113 |
114 | 不论js传递给java的字符串是json格式还是其他格式,解析这样的字符串肯定是一件无趣的重复的体力劳动。
115 |
116 |
117 | 若想解决以上的问题,我们有必要设计一套完美的通信方案。
118 |
119 | #2. js与java完美通信方案设计
120 | **2.1 一套完美的js与java的通信方案应满足以下几点:**
121 |
122 | - js与java知道对方的细节越少越好,越少它们的耦合性越低。那到底多少为好呢?我个人觉得互相暴漏给对方一个接口足矣。这样js与native的通信就类似于通过一个管道通信或者说类似于socket通信(降低强依赖)
123 |
124 | - js与java之间通信,需要定义好一套通信协议或者叫通信规则,在管道之间传递通信协议。这样它们之间的通信是针对一套定义好的协议进行的,而不是针对每个系统自己独有的通信方式(好处js就不会出现兼容不同的系统的if else if代码)
125 |
126 | - 主动发送消息给对方时,对方必须对该消息予以反馈,即使主动发送消息者对反馈消息不感兴趣,(反馈信息可以去掉由于版本兼容等带来的if else if兼容代码)
127 |
128 |
129 | **2.2 那我们就开始设计js与java之间的通信方案**
130 |
131 | **2.2.1 互相暴漏给对方一个接口**
132 |
133 | - js为java提供一个唯一的接口,这个接口可以是在java端写死的,也可以是js传递过来的(这样更灵活)。所有发送给js的消息(请求消息和反馈消息)都通过该接口
134 | - java为js提供的一个唯一的接口,因为官方的方法存在漏洞,我们采用在onJsPrompt方法中接收js发送的所有消息,当然大家还可以选择其他方法来接收js的消息,这不是重点。
135 |
136 | **2.2.2 js与java之间通信协议的制定**
137 |
138 | js与java之间的通信特别类似于网络请求,主动发起消息的行为可以称为request(请求消息),对该消息的反馈可以称为response(响应消息)。
139 |
140 | **request**
141 |
142 | 一个request封装了请求对方的哪个接口,以及该接口所需要的参数。
143 |
144 | **response**
145 |
146 | 一个response封装了状态信息(可以知道处理的结果是成功还是失败)和处理结果。
147 |
148 | **如何接收对方发送的response消息?**
149 |
150 | 大家都应该都会想到,在发送消息的时候传递一个回调接口就行了,但是因为js与java之间是跨语言的,尤其是java是不可能把回调接口传递给js,js虽然可以传递过来但是会有问题,所以这时候有一种解决办法:
151 | - 在给对方发送request消息时,为回调接口生成一个唯一的id值,把id值存入request中发出。
152 | - 同时把回调接口缓存起来。
153 | - 在接收到response时,从response解析这个id值,根据id值查找到回调接口。
154 |
155 | 因此request和response中还得包含回调id这个值。
156 |
157 | **通信协议的格式**
158 |
159 | request数据格式:
160 |
161 | {
162 | //接口名称
163 | "interfaceName":"test",
164 | //回调id值
165 | "callbackId":"c_111111",
166 | //传递的参数
167 | "params":{
168 | ....
169 | }
170 | }
171 |
172 | response数据格式:
173 |
174 | {
175 | //回调id,同时这也是response的标志
176 | "responseId":"c_111111",
177 | //response数据
178 | "data":{
179 | //状态数据
180 | "status":"1",
181 | "msg":"ok",
182 | //response的处理结果数据
183 | "values":{
184 | ......
185 | }
186 | }
187 | }
188 |
189 | 到此通信协议就已经定义好了。
190 |
191 | **2.2.3 让繁琐的无趣的重复的苦力活儿不再有**
192 |
193 | 大家可以看到通信协议request和response都是json格式,从json中解析数据或者把数据封装为json都是重复的苦力活儿。
194 | 这也是我一直想着力解决的痛点,解决之道是从retrofit中获得启发的,应用注解来解决以上问题。
195 |
196 | 关于js与java完美通信的设计思想到此为止,这也是SimpleJavaJsBridge这个库的核心思想,那我们就来看下SimpleJavaJsBridge。
197 |
198 | #3. SimpleJavaJsBridge
199 | SimpleJavaJsBridge我为什么要起一个这样的名字,首先它解决了上文中提到的**让繁琐的无趣的重复的苦力活儿不再有**的问题,对于不管是从json中解析数据还是把数据封装成json,使用者都不需要关心,让使用者很省心;并且它使用起来也非常的简单,在稍后的例子中大家会体会到,所以用了simple这个词儿。通过它java可以给js发送消息,并且接收js的响应消息;同时js也可以给java发送消息,同样接收java的响应消息。因此它是java与js之间通信的桥梁,因此它的名字叫为SimpleJavaJsBridge。
200 |
201 | **3.1 如何解决繁琐的无趣的重复的苦力活儿?**
202 |
203 | 解决这个问题思路来自于鼎鼎有名的Retrofit,Retrofit通过注解的方式解决了构建request和解析response的问题,因此注解也可以解决我现在遇到的问题。那我们就来认识下这些注解。
204 | **InvokeJSInterface**用来标注java给js发送消息的方法,它的value值代表js提供的功能的接口名字
205 |
206 | **JavaCallback4JS**用来标注java提供给js的回调方法的
207 |
208 | **JavaInterface4JS**用来标注java提供给js的接口,它的value值代表功能的接口名字
209 |
210 | **Param**用来标注参数或者类的实例属性,它的value值代表参数被存入json中的key值,它的needConvert代表当前的参数是否需要进行转换,因为通过JsonObject类往json中存放的数据是有要求的,JsonObject中只能存放基本数据和JsonObject和JsonArray这些数据类型,对于其他的类型就得进行转换了。因此只要是不能直接通过JsonObject存放的类型该值必须为true
211 |
212 | **ParamCallback**用来标注回调类型的参数,比如发送request给js的方法中,需要有一个回调参数,那这个参数必须用它来标注
213 |
214 | **ParamResponseStatus**用来标注响应状态类型的参数,比如:statusCode,StatusMsg这些参数,它的value值是json中的key值。
215 |
216 |
217 | **3.2 SimpleJavaJsBridge使用**
218 |
219 | **3.2.1 构建一个SimpleJavaJsBridge实例**
220 |
221 | SimpleJavaJsBridge instance = new SimpleJavaJsBridge.Builder()
222 | .addJavaInterface4JS(javaInterfaces4JS)
223 | .setWebView(webView)
224 | .setJSMethodName4Java("_JSBridge._handleMessageFromNative")
225 | .setProtocol("niu","receive_msg").create();
226 |
227 | 通过SimpleJavaJsBridge.Builder来构建一个SimpleJavaJsBridge对象,
228 | - addJavaInterface4JS用来添加java提供给js的接口
229 | - setWebView 设置WebView这是必须进行设置的
230 | - setJSMethodName4Java 设置js为java唯一暴漏的方法名字
231 | - setProtocol设置协议字段,这也是必须的,这个字段主要是为了ios而设置的
232 |
233 | 当然还可以调用其他的一些方法对SimpleJavaJsBridge进行设置
234 |
235 | **3.2.2 js给java发送消息**
236 |
237 | **js给java的无参接口发送消息**
238 |
239 | /** * 给js发送响应消息的接口*/
240 | public interface IResponseStatusCallback {
241 | void callbackResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg);
242 | }
243 |
244 | //java提供给js的"tes4"接口,@ParamCallback标注的是给js发送消息的回调
245 | @JavaInterface4JS("test4")
246 | public void test3(@ParamCallback IResponseStatusCallback jsCallback) {
247 | 进行相应处理...;
248 | //给js发送响应消息
249 | jsCallback.callbackResponse(1, "ok");
250 | }
251 |
252 | //下面是js代码,js给java的"test4"接口发送消息
253 | _JSNativeBridge._doSendRequest("test4", {}, function(responseData){
254 |
255 | });
256 |
257 | **js给java的有参接口发送消息**
258 |
259 | /** * 给js发送响应消息的接口*/
260 | public interface IResponseStatusCallback {
261 | void callbackResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg);
262 | }
263 |
264 | /** * 必须有无参构造函数 ,只有被@Param注解的属性才会存入json中*/
265 | public static class Person {
266 | @Param("name")
267 | String name;
268 |
269 | @Param("age")
270 | public int age;
271 |
272 | public Person() { }
273 |
274 | public Person(String name, int age) {
275 | this.name = name;
276 | this.age = age;
277 | }
278 | }
279 |
280 | //java提供给js的“test1”接口,Person是无法直接往JsonObject中存放的,
281 | //所以needConvert必须为true,会自动把Person中用注解标注的属性放入json中
282 | @JavaInterface4JS("test1")
283 | public void test(@Param(needConvert = true) Person personInfo, @ParamCallback IResponseStatusCallback jsCallback) {
284 | 对收到的数据进行处理....;
285 | jsCallback.callback(1, "ok");
286 | }
287 |
288 | //下面是js代码,js给java的"test1"接口发送消息
289 | _JSNativeBridge._doSendRequest("test1", {"name":"niu","age":10}, function(responseData){
290 |
291 | });
292 |
293 | **3.2.3 java给js发送消息**
294 |
295 | //给js发送消息的方法要定义在一个interface中,这个过程是模仿Retrofit的
296 | public interface IInvokeJS {
297 |
298 | //复杂类型,只有用@Param标注的属性才会放入json中
299 | public static class City{
300 | @Param("cityName")
301 | public String cityName;
302 |
303 | @Param("cityProvince")
304 | public String cityProvince;
305 |
306 | public int cityId;
307 | }
308 |
309 | //给js的“exam”接口发送数据,参数是需要传递的数据
310 | @InvokeJSInterface("exam")
311 | void exam(@Param("test") String testContent, @Param("id") int id,@ParamCallback IJavaCallback2JS iJavaCallback2JS);
312 |
313 | //给js的“exam1”接口发送数据,参数同样也是需要传递的数据
314 | @InvokeJSInterface("exam1")
315 | void exam1(@Param(needConvert = true) City city, @ParamCallback IJavaCallback2JS iJavaCallback2JS);
316 | }
317 |
318 |
319 | //使用,使用方式和Retrofit一样,先使用SimpleJavaJsBridge的
320 | //createInvokJSCommand实例方法生成一个IInvokeJS实例
321 | IInvokeJS invokeJs = simpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class);
322 |
323 | //给js的"exam"发送消息,发送的是基本数据类型
324 | invokeJs.exam("hello js",20, new IJavaCallback2JS{
325 | //接收js发送的响应数据的回调方法,该方法的名字可以任意,但必须用@JavaCallback4JS标注
326 | @JavaCallback4JS
327 | public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) {
328 |
329 | }
330 | });
331 |
332 | City city = new City();
333 | city.cityName = "长治";
334 | city.cityId = 11;
335 | city.cityProvince = "山西";
336 | //给js的“exam1”发送消息,city是一个复杂对象
337 | invokeJs.exam1(city, new IJavaCallback2JS{
338 | @JavaCallback4JS
339 | public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) {
340 |
341 | }
342 | });
343 |
344 | #总结
345 | SimpleJavaJsBridge库在js与java的通信中带来以下优点:
346 | - js代码中不再有由于系统或者app版本甚至业务原因产生的if else if的兼容语句
347 | - java不需要再关心数据封装为json或者从json中解析数据的繁琐工作
348 | - 让js与java之间的通信更简单
349 |
350 | 若你动心了可以下载试用:[SimpleJavaJsBridge](https://github.com/niuxiaowei/SimpleJavaJsBridge.git)
351 |
352 | 参考:大头鬼的[https://github.com/lzyzsd/JsBridge.git](https://github.com/lzyzsd/JsBridge.git)
353 |
354 |
--------------------------------------------------------------------------------
/simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/Params.java:
--------------------------------------------------------------------------------
1 | package com.simplejsjavabridge.lib;
2 |
3 | import android.text.TextUtils;
4 |
5 |
6 | import com.simplejsjavabridge.lib.annotation.Param;
7 | import com.simplejsjavabridge.lib.annotation.ParamCallback;
8 | import com.simplejsjavabridge.lib.annotation.ParamResponseStatus;
9 | import com.simplejsjavabridge.lib.exception.SimpleJSBridgeException;
10 |
11 | import org.json.JSONArray;
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import java.lang.annotation.Annotation;
16 | import java.lang.reflect.Field;
17 | import java.lang.reflect.InvocationHandler;
18 | import java.lang.reflect.Method;
19 | import java.lang.reflect.Modifier;
20 | import java.lang.reflect.Proxy;
21 | import java.util.Iterator;
22 |
23 | /**
24 | * 该类会把{@link Method}的用{@link Param},{@link ParamCallback},{@link ParamResponseStatus}这几个注解标注的param解析出来,
25 | * {@link Param}解析为{@link ParamItem},{@link ParamCallback}解析为{@link ParamCallbackItem},{@link ParamResponseStatus}
26 | * 解析为{@link ParamResponseStatusItem}。
27 | * 同时该类还有把一个json转化为参数值的功能,和把参数值转化为json的功能
28 | *
29 | * Created by niuxiaowei on 16/7/14.
30 | */
31 | public class Params {
32 |
33 | /**
34 | * 解析出来的所有注解item
35 | */
36 | private BaseParamItem[] mParamItems;
37 | private static SimpleJavaJsBridge sSimpleJavaJsBridge;
38 |
39 | Params() {
40 | }
41 |
42 | /**
43 | * 初始化方法
44 | *
45 | * @param simpleJavaJsBridge
46 | */
47 | public static void init(SimpleJavaJsBridge simpleJavaJsBridge) {
48 | sSimpleJavaJsBridge = simpleJavaJsBridge;
49 | }
50 |
51 | /**
52 | * 把json转化为参数值
53 | * @param requestResponseBuilder 包含了一系列的json数据,json数据是request或者response
54 | * @return
55 | */
56 | public Object[] convertJson2ParamValues(RequestResponseBuilder requestResponseBuilder) {
57 | if (requestResponseBuilder == null || mParamItems == null) {
58 | return null;
59 | }
60 | Object[] result = new Object[mParamItems.length];
61 | BaseParamItem paramItem = null;
62 | for (int i = 0; i < mParamItems.length; i++) {
63 | paramItem = mParamItems[i];
64 | if (paramItem != null) {
65 |
66 | result[i] = paramItem.convertJson2ParamValue(requestResponseBuilder);
67 | }
68 | }
69 | return result;
70 |
71 | }
72 |
73 | /**
74 | * 把参数值转化为json
75 | * @param requestResponseBuilder
76 | * @param paramValues 参数值
77 | */
78 | public void convertParamValues2Json(RequestResponseBuilder requestResponseBuilder, Object[] paramValues) {
79 | if (requestResponseBuilder == null || paramValues == null) {
80 | return;
81 | }
82 | BaseParamItem paramItem = null;
83 | for (int i = 0; i < mParamItems.length; i++) {
84 | paramItem = mParamItems[i];
85 | if (paramItem != null) {
86 | paramItem.convertParamValue2Json(requestResponseBuilder, paramValues[i]);
87 | }
88 | }
89 | }
90 |
91 |
92 | /**
93 | * 基础类,定义了一些基础的数据
94 | */
95 | private static abstract class BaseParamItem {
96 | /**
97 | * 参数所对应的类型{@link Class}
98 | */
99 | protected Class paramType;
100 | /**
101 | * 因为参数是由{@link Param},{@link ParamCallback},{@link ParamResponseStatus}其中的一个注解标注的,
102 | * 注解标注的参数,会以{key:value}的格式存入json中,key值就是注解的value()值,因此{@link #paramKey}来代表key值
103 | */
104 | protected String paramKey;
105 |
106 | public BaseParamItem(Class paramType, String paramKey) {
107 | this.paramType = paramType;
108 | this.paramKey = paramKey;
109 | }
110 |
111 | /**
112 | * json的格式{key:value},该方法会从json中把value给解析出来,作为参数值
113 | * @param requestResponseBuilder
114 | * @return
115 | */
116 | public abstract Object convertJson2ParamValue(RequestResponseBuilder requestResponseBuilder);
117 |
118 | /**
119 | * 该方法会把参数值以{key:value}的格式存入json中
120 | * @param requestResponseBuilder
121 | * @param obj
122 | */
123 | public abstract void convertParamValue2Json(RequestResponseBuilder requestResponseBuilder, Object obj);
124 | }
125 |
126 | /**
127 | * 对应{@link Param}注解标注的参数
128 | */
129 | private static class ParamItem extends BaseParamItem {
130 |
131 |
132 | public ParamItem(String paramKey, Class paramClass) {
133 | super(paramClass, paramKey);
134 | }
135 |
136 | protected void onReceiveKeyValue(RequestResponseBuilder requestResponseBuilder, String key, Object value) {
137 | requestResponseBuilder.putValue(key, value);
138 | }
139 |
140 | protected JSONObject getJson(RequestResponseBuilder requestResponseBuilder) {
141 | return requestResponseBuilder.getValues();
142 | }
143 |
144 | @Override
145 | public Object convertJson2ParamValue(RequestResponseBuilder requestResponseBuilder) {
146 | if (requestResponseBuilder == null || requestResponseBuilder.getValues() == null) {
147 | return null;
148 | }
149 | JSONObject jsonObject = getJson(requestResponseBuilder);
150 | if (jsonObject != null) {
151 | if (!isObjectDirectPut2Json(paramType)) {
152 | try {
153 | JSONObject value = !TextUtils.isEmpty(paramKey) ? (JSONObject) jsonObject.opt(paramKey) : jsonObject;
154 | if (value == null) {
155 | return null;
156 | }
157 | Object instance = paramType.newInstance();
158 | Field[] fields = paramType.getDeclaredFields();
159 | for (Field field : fields
160 | ) {
161 | Param p = field.getAnnotation(Param.class);
162 | if (p != null) {
163 | /*可以访问不可以访问的变量*/
164 | field.setAccessible(true);
165 | field.set(instance, value.opt(p.value()));
166 | }
167 | }
168 | return instance;
169 | } catch (InstantiationException e) {
170 | e.printStackTrace();
171 | } catch (IllegalAccessException e) {
172 | e.printStackTrace();
173 | }
174 | } else {
175 | return jsonObject.opt(paramKey);
176 | }
177 | }
178 | return null;
179 | }
180 |
181 | @Override
182 | public void convertParamValue2Json(RequestResponseBuilder requestResponseBuilder, Object obj) {
183 |
184 | if (requestResponseBuilder == null || obj == null) {
185 | return;
186 | }
187 | if (!isObjectDirectPut2Json(obj)) {
188 | JSONObject json = convertObjectFileds2Json(obj);
189 | if (json == null) {
190 | return;
191 | }
192 | if (!TextUtils.isEmpty(paramKey)) {
193 | onReceiveKeyValue(requestResponseBuilder, paramKey, json);
194 | } else {
195 | Iterator 生成{@link SimpleJavaJsBridge}的实例: 调用js接口的例子:
29 | * SimpleJavaJsBridge instance = new SimpleJavaJsBridge.Builder().addJavaInterface4JS(javaInterfaces4JS)
30 | .setWebView(mWebView)
31 | .setJSMethodName4Java("_JSBridge._handleMessageFromNative")
32 | .setProtocol("didiam://__QUEUE_MESSAGE__/?").create();
33 | *
34 | * 这样可以创建一个SimpleJavaJsBridge的实例,当然还可以设置其他参数,但是上面的几个参数是必须设定的,
35 | * 发送给对方的数据,我给起了个名字叫request(请求数据),
36 | * request的格式是:
37 | *
65 | *
66 | *
38 | * {
39 | * "handlerName":"test",
40 | * "callbackId":"c_111111",
41 | * "params":{
42 | * ....
43 | * }
44 | * }
45 | *
46 | * request里面的这三个关键的key值的名字是可以重新定义的,比如"handlerName"可以使用 new SimpleJavaJsBridge.Builder().setHandlerName("interfaceName")
47 | * 来进行自定义。
48 | *
49 | * 接收到对方的数据,我给起个名字叫response(响应数据),
50 | * response的格式是:
51 | *
52 | * {
53 | * "responseId":"iii",
54 | * "data":{
55 | * "status":"1",
56 | * "msg":"ok",
57 | * "values":{
58 | * ......
59 | * }
60 | * }
61 | * }
62 | *
63 | * 同理response里面的"responseId","data","values"这三个关键的key值的名字也是可以调用SimpleJavaJsBridge.Builder进行自定义的
64 | *
68 | * //声明一个调用js的interface
69 | * interface IInvokeJS{
70 | * //声明一个调用js的 exam4 的接口,该接口不需要传递参数
71 | * :@InvokeJSInterface("exam4")
72 | * void exam4(@ParamCallback IJavaCallback2JS iJavaCallback2JS);
73 | * }
74 | *
75 | * //开始调用js的接口
76 | * IInvokeJs invokeJs = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJs.class);
77 | * invokeJS.exam4(new IJavaCallback2JS{
78 | * :@JavaCallback4JS
79 | * public void callback(@ParamResponseStatus("status") String status){
80 | *
81 | * }
82 | * });
83 | *
84 | * 以上就是一个调用js的exam4接口的例子,其实该过程是模仿了retrofit的。这样做的好处是上层使用者完全不需要关心从json中解析数据这
85 | * 一繁琐的重复的体力劳动了
86 | *
87 | *
88 | *
89 | * Created by niuxiaowei on 16/6/15.
90 | */
91 | public class SimpleJavaJsBridge {
92 |
93 | private static final String TAG = SimpleJavaJsBridge.class.getSimpleName();
94 |
95 | private static final String JAVASCRIPT = "javascript:";
96 | /**
97 | * java调用js的功能时,java会为js提供回调函数,但是不可能把回调函数传递给js,
98 | * 所以为回调函数提供一个唯一的id,
99 | */
100 | private static int sUniqueCallbackId = 1;
101 |
102 |
103 | /**
104 | * 保证发送给js数据时在ui线程中执行
105 | */
106 | private Handler mMainHandler = new Handler(Looper.getMainLooper());
107 |
108 | /**
109 | * 缓存java为js提供的接口
110 | */
111 | private HashMap
194 | * response格式:
195 | * {
196 | * "responseId":"iii",
197 | * "data":{
198 | * "status":"1",
199 | * "msg":"ok",
200 | * "values":{
201 | * ......
202 | * }
203 | * }
204 | * }
205 | * responseId 代表request中的callbackId
206 | * data 代表响应的数据
207 | * status 代表响应状态
208 | * msg 代表响应状态对应的消息
209 | * values 代表响应数据包含的值
210 | *
211 | *
212 | *
213 | * responseName的默认名字是"data",可以对这个名字进行设置
214 | *
215 | *
216 | * @param responseName
217 | * @return
218 | */
219 | public Builder setResponseName(String responseName) {
220 | mResponseName = responseName;
221 | return this;
222 | }
223 |
224 | /**
225 | * responseValuesName的默认名字是"values",可以对这个名字进行设置
226 | *
227 | * @param responseValuesName
228 | * @return
229 | * @see #setResponseName(String)
230 | */
231 | public Builder setResponseValuesName(String responseValuesName) {
232 | mResponseValuesName = responseValuesName;
233 | return this;
234 | }
235 |
236 | /**
237 | * response中responseIdName的默认名字是"responseId",可以对起进行设置
238 | * @param responseIdName
239 | * @return
240 | * @see #setResponseName(String)
241 | */
242 | public Builder setResponseIdName(String responseIdName) {
243 | mResponseIdName = responseIdName;
244 | return this;
245 | }
246 |
247 | /**
248 | *
249 | * {
250 | * "handlerName":"test",
251 | * "callbackId":"c_111111",
252 | * "params":{
253 | * ....
254 | * }
255 | * }
256 | *
257 | * hanlerName 代表java与js之间给对方暴漏的接口的名称,
258 | * callbackId 代表对方在发起请求时,会为回调方法生产一个唯一的id值,它就代表这个唯一的id值
259 | * params 代表传递的数据
260 | *
261 | *
262 | * requestInterfaceName的默认值是"handlerName",可以进行设置它
263 | *
264 | *
265 | * @param requestInterfaceName
266 | * @return
267 | */
268 | public Builder setRequestInterfaceName(String requestInterfaceName) {
269 | mRequestInterfaceName = requestInterfaceName;
270 | return this;
271 | }
272 |
273 | /**
274 | *
275 | * 同理requestCallbackIdName的默认值是"callbackId",可以对它进行设置
276 | *
277 | * @param requestCallbackIdName
278 | * @return
279 | * @see #setRequestInterfaceName(String)
280 | */
281 | public Builder setRequestCallbackIdName(String requestCallbackIdName) {
282 | mRequestCallbackIdName = requestCallbackIdName;
283 | return this;
284 | }
285 |
286 | /**
287 | * 同理requestValuesName的默认值是"params",可以对它进行设置
288 | *
289 | * @param requestValuesName
290 | * @return
291 | * @see #setRequestInterfaceName(String)
292 |
293 | */
294 | public Builder setRequestValuesName(String requestValuesName) {
295 | mRequestValuesName = requestValuesName;
296 | return this;
297 | }
298 |
299 | /**
300 | * 设置js为java暴漏的方法的名字,只需要提供方法名字即可,具体的关于"()"和参数不需要提供,因为该方法接收的是一个json字符串
301 | *
302 | * @param JSMethodName 方法名字 比如:handleMsgFromJava
303 | * @return
304 | */
305 | public Builder setJSMethodName4Java(String JSMethodName) {
306 | mJSMethodName4Java = JSMethodName;
307 | if (!TextUtils.isEmpty(mJSMethodName4Java) && !mJSMethodName4Java.startsWith(JAVASCRIPT)) {
308 | mJSMethodName4Java = JAVASCRIPT + mJSMethodName4Java ;
309 | if(!mJSMethodName4Java.contains("%s")){
310 | mJSMethodName4Java = mJSMethodName4Java + "(%s)";
311 | }
312 | }
313 | return this;
314 | }
315 |
316 | /**
317 | * 设置协议,协议格式:scheme://host?,协议是必须进行设置的,否则报错
318 | *
319 | * @param scheme 比如 file或http等
320 | * @param host
321 | * @return
322 | */
323 | public Builder setProtocol(String scheme,String host) {
324 | if(TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)){
325 | return this;
326 | }
327 | mProtocol = scheme+"://"+host+"?";
328 | return this;
329 | }
330 |
331 | public Builder setWebChromeClient(WebChromeClient webChromeClient) {
332 | mWebChromeClient = webChromeClient;
333 | return this;
334 | }
335 |
336 | /**
337 | * 添加java提供给js的接口
338 | * @param javaMethod4JS
339 | * @return
340 | */
341 | public Builder addJavaInterface4JS(Object javaMethod4JS) {
342 | if (javaMethod4JS == null) {
343 | return this;
344 | }
345 | if (mJavaMethod4JS == null) {
346 | mJavaMethod4JS = new ArrayList();
347 | }
348 | mJavaMethod4JS.add(javaMethod4JS);
349 | return this;
350 | }
351 |
352 | /**
353 | * 必须进行设置
354 | *
355 | * @param webView
356 | * @return
357 | */
358 | public Builder setWebView(WebView webView) {
359 | mWebView = webView;
360 | return this;
361 | }
362 |
363 | /**
364 | * 检测协议是否符合要求
365 | *
366 | * @return
367 | * @throws SimpleJSBridgeException
368 | */
369 | private void checkProtocol() {
370 | if (TextUtils.isEmpty(mProtocol)) {
371 | throw new SimpleJSBridgeException("必须调用setProtocol(String)设置协议");
372 | }
373 | Uri uri = Uri.parse(mProtocol);
374 | if (TextUtils.isEmpty(uri.getScheme()) || TextUtils.isEmpty(uri.getHost()) || !mProtocol.endsWith("?")) {
375 | throw new IllegalArgumentException("协议的格式必须是 scheme://host? 这种格式");
376 | }
377 | }
378 |
379 | private void checkJSMethod() {
380 | if (TextUtils.isEmpty(mJSMethodName4Java)) {
381 | throw new IllegalArgumentException("必须调用 setJSMethodName4Java(String) 方法对给js发送消息的方法进行设置");
382 | }
383 |
384 | }
385 |
386 | public SimpleJavaJsBridge create() {
387 | /*检查协议是否设置,并设置正确了*/
388 | checkProtocol();
389 | checkJSMethod();
390 | if (mWebView == null) {
391 | throw new IllegalArgumentException("必须调用 setWebView(WebView) 方法设置Webview");
392 | }
393 | return new SimpleJavaJsBridge(this);
394 | }
395 | }
396 |
397 |
398 | /**
399 | * 生成调用js的命令,在调用js之前必须得调用该方法,该模式是模仿retrofit的
400 | * @param tClass 必须是一个interface
401 | * @param