├── .gitignore ├── Js.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xfans │ │ └── jsbridge │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── index.html │ ├── jsapi.js │ └── pic.jpg │ ├── java │ └── com │ │ └── xfans │ │ └── jsbridge │ │ ├── api │ │ ├── AndroidApi.java │ │ └── CMD.java │ │ ├── bridge │ │ ├── AndroidJsBridge.java │ │ ├── ContextQueue.java │ │ ├── RequestContent.java │ │ ├── Utils.java │ │ ├── XfansActivityInterface.java │ │ ├── XfansWebChromeClientBridge.java │ │ ├── XfansWebView.java │ │ └── XfansWebViewClientBridge.java │ │ ├── sample │ │ ├── MainActivity.java │ │ └── TestActivity.java │ │ └── widget │ │ └── XfansBaseActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_main.xml │ └── activity_test.xml │ ├── menu │ └── menu_main.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Windows thumbnail db 19 | Thumbs.db 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Android Studio 29 | .idea 30 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 31 | .gradle 32 | build/ -------------------------------------------------------------------------------- /Js.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Js-Android-bridge 2 | ================= 3 | 更新日志: 4 | 2015/01/09:修改XfansWebView.java,AndroidJsBridge.java兼容4.4. 5 | 6 | js 调用android native方法。 7 | 8 | 如: 9 | 10 | 1. js调用android相机,拍照成功后异步返回图片路径。 11 | 12 | 2. url定义跳转Activity的路径,从html页面跳转到Activity。 13 | 14 | 3. 打电话,发短信等。 15 | 16 | 参考了部分开源项目,Cordova等。 17 | 18 | 该项目使用Gradle构建。 19 | 20 | ------------------ 21 | 22 | ##js调用native方式: 23 | 24 | 1. prompt方式: 25 | 26 | js端: 27 | 28 | ```javascript 29 | callAndroidSync : function (cmd, args) {//同步调用 prompt 方式 30 | return prompt(cmd,args); 31 | } 32 | ``` 33 | 34 | native端: 35 | 36 | ```Java 37 | @Override 38 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { 39 | Log.d("XfanWebChromeClient",url+":"+ message+":"+ defaultValue+":"+ result); 40 | return androidApi.callAndroidSync(view, url, message, defaultValue ,result); 41 | } 42 | ``` 43 | 2. JavascriptInterface方式: 44 | 45 | js端: 46 | 47 | ```javascript 48 | AndroidJsBridge.callNative(cmd,args,key) 49 | ``` 50 | 51 | Native端: 52 | 53 | ```Java 54 | public void callNative( String cmd, String agrs, String key){ 55 | Log.d("Api", cmd + ":" + agrs + ":" + key); 56 | RequestContent requestContent = new RequestContent(cmd, agrs, key, webView); 57 | ContextQueue.reqMap.put(key,requestContent); 58 | androidApi.callAndroidAsync(this, requestContent); 59 | } 60 | ``` 61 | 62 | ##Native调用js 63 | 64 | 1. prompt方式: 65 | 66 | js端: 67 | ```javascript 68 | return prompt(cmd,args) 69 | ``` 70 | 71 | Native端: 72 | ```Java 73 | result.confirm("callAndroidSync"); 74 | ``` 75 | 76 | 2. loadurl方式: 77 | 78 | js端: 79 | ```javascript 80 | function show(str){ 81 | var doc = document.getElementById("cont"); 82 | doc.innerHTML = str; 83 | } 84 | ``` 85 | Native端: 86 | ```java 87 | webView.loadUrl("javascript:show('hello')") 88 | ``` 89 | 90 | 91 | ------------- 92 | ##使用该项目: 93 | 94 | 修改`AndroidApi.java`,`CMD.java`即可。 95 | 96 | 97 | ------------- 98 | ##注意 99 | 100 | 未处理webview的安全问题。 101 | 102 | [WebView中接口隐患与手机挂马利用](http://drops.wooyun.org/papers/548 "WebView中接口隐患与手机挂马利用") 103 | 104 | ------------- 105 | ##License 106 | Copyright 2016 xfans 107 | 108 | Licensed under the Apache License, Version 2.0 (the "License"); 109 | you may not use this file except in compliance with the License. 110 | You may obtain a copy of the License at 111 | 112 | http://www.apache.org/licenses/LICENSE-2.0 113 | 114 | Unless required by applicable law or agreed to in writing, software 115 | distributed under the License is distributed on an "AS IS" BASIS, 116 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 117 | See the License for the specific language governing permissions and 118 | limitations under the License. 119 | 120 | Js-Android bridge 121 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion "21.1.1" 6 | defaultConfig { 7 | applicationId "com.xfans.js" 8 | minSdkVersion 14 9 | targetSdkVersion 15 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | } 25 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xfans/jsbridge/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | 30 |
test
31 | 33 | 35 | 36 |
37 | tel:10010
38 | gonative
39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/assets/jsapi.js: -------------------------------------------------------------------------------- 1 | var JsApi = { 2 | callback_success : {}, // 输出的结果成功时调用的方法 3 | callback_fail : {}, // 输出的结果失败时调用的方法 4 | callAndroidSync : function (cmd, args) {//同步调用 prompt 方式 5 | return prompt(cmd,args); 6 | }, 7 | callAndroidAsync : function (cmd, args, success, fail) {//异步调用 addJavascriptInterface方式 8 | var strKey = new String(new Date().getTime()).substring(5,13); 9 | key = '5'+strKey;//防止第一位为0 10 | JsApi.callback_success[key] = success; 11 | JsApi.callback_fail[key] = fail; 12 | var doc = document.getElementById("cont"); 13 | doc.innerHTML = key; 14 | AndroidJsBridge.callNative(cmd,args,key) 15 | }, 16 | jsCallback : function (code,result, key) { 17 | if(code = 1){ //1 seccuss 18 | setTimeout( "JsApi.callback_success['" +key+"']('" + result + "')", 0); 19 | }else{ 20 | setTimeout( "JsApi.callback_fail['" +key+"']('" + result + "')", 0); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/assets/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfans/Js-Android-bridge/99fd676b1f86b03e294396491944473716becf64/app/src/main/assets/pic.jpg -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/api/AndroidApi.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.api; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Environment; 10 | import android.provider.MediaStore; 11 | import android.util.Log; 12 | import android.view.KeyEvent; 13 | import android.webkit.JsPromptResult; 14 | import android.webkit.JsResult; 15 | import android.webkit.WebView; 16 | 17 | import com.xfans.jsbridge.bridge.AndroidJsBridge; 18 | import com.xfans.jsbridge.bridge.RequestContent; 19 | import com.xfans.jsbridge.bridge.Utils; 20 | 21 | import java.io.File; 22 | 23 | /** 24 | * Created by xfans on 2014/12/12. 25 | */ 26 | public class AndroidApi { 27 | 28 | /** 29 | * 同步 30 | * @param webView 31 | * @param url 32 | *@param message 33 | * @param jsonStr 34 | * @param result @return 35 | */ 36 | public boolean callAndroidSync(WebView webView, String url, String message, String jsonStr, JsPromptResult result) { 37 | result.confirm("callAndroidSync"); 38 | return true; 39 | } 40 | 41 | /** 42 | * url调用 43 | * @param webView 44 | * @param url 45 | */ 46 | public boolean callUrl(WebView webView, String url){ 47 | Log.d("AndroidApi",url); 48 | if(url != null){ 49 | Context context = webView.getContext(); 50 | String args = url.substring(url.indexOf(":")+1,url.length()); 51 | String cmdStr = url.substring(0,url.indexOf(":")); 52 | CMD cmd = Utils.getCmd(cmdStr); 53 | switch (cmd){ 54 | case GONATIVE: 55 | goActivity(context, args); 56 | break; 57 | case TEL: 58 | goTel(url, context); 59 | break; 60 | case GEO: 61 | getGeo(url, context); 62 | break; 63 | case MAILTO: 64 | goMailTo(url,context); 65 | break; 66 | case SMS: 67 | goSms(url, context); 68 | break; 69 | case MARKET: 70 | goMarket(url, context); 71 | break; 72 | default: 73 | goOther(url, context); 74 | break; 75 | } 76 | } 77 | return true; 78 | } 79 | 80 | private void goOther(String url, Context context) { 81 | Intent intent = new Intent(Intent.ACTION_VIEW); 82 | intent.setData(Uri.parse(url)); 83 | context.startActivity(intent); 84 | } 85 | 86 | private void goMarket(String url, Context context) { 87 | Intent intent = new Intent(Intent.ACTION_VIEW); 88 | intent.setData(Uri.parse(url)); 89 | context.startActivity(intent); 90 | } 91 | 92 | private void goSms(String url, Context context) { 93 | Intent intent = new Intent(Intent.ACTION_VIEW); 94 | // Get address 95 | String address = null; 96 | int parmIndex = url.indexOf('?'); 97 | if (parmIndex == -1) { 98 | address = url.substring(4); 99 | } 100 | else { 101 | address = url.substring(4, parmIndex); 102 | 103 | // If body, then set sms body 104 | Uri uri = Uri.parse(url); 105 | String query = uri.getQuery(); 106 | if (query != null) { 107 | if (query.startsWith("body=")) { 108 | intent.putExtra("sms_body", query.substring(5)); 109 | } 110 | } 111 | } 112 | intent.setData(Uri.parse("sms:" + address)); 113 | intent.putExtra("address", address); 114 | intent.setType("vnd.android-dir/mms-sms"); 115 | context.startActivity(intent); 116 | } 117 | 118 | private void goMailTo(String url, Context context) { 119 | Intent intent = new Intent(Intent.ACTION_VIEW); 120 | intent.setData(Uri.parse(url)); 121 | context.startActivity(intent); 122 | } 123 | 124 | private void getGeo(String url, Context context) { 125 | Intent intent = new Intent(Intent.ACTION_VIEW); 126 | intent.setData(Uri.parse(url)); 127 | context.startActivity(intent); 128 | } 129 | 130 | /** 131 | * 异步 132 | * @param androidJsBridge 133 | * @param requestContent 134 | */ 135 | public void callAndroidAsync(AndroidJsBridge androidJsBridge, RequestContent requestContent) { 136 | Log.d("AndroidApi","callAndroidAsync:"); 137 | if(requestContent != null && androidJsBridge != null){ 138 | Context context = androidJsBridge.getWebView().getContext(); 139 | String cmdStr = requestContent.getCmd(); 140 | CMD cmd = Utils.getCmd(cmdStr); 141 | switch (cmd){ 142 | case CAMERA: 143 | goCamera(context, requestContent); 144 | break; 145 | case PICTURE: 146 | goPicture(context, requestContent); 147 | break; 148 | 149 | case NULL: 150 | break; 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * 打电话 157 | * @param url 158 | * @param context 159 | */ 160 | private void goTel(String url, Context context) { 161 | Intent intent = new Intent(Intent.ACTION_DIAL); 162 | intent.setData(Uri.parse(url)); 163 | context.startActivity(intent); 164 | } 165 | /** 166 | * 跳转页面 167 | * @param context 168 | * @param args 169 | */ 170 | private void goActivity(Context context, String args) { 171 | Log.d("AndroidApi","goActivity: "+context.getPackageName()+":"+args); 172 | Intent intent = new Intent(); 173 | intent.setClassName(context,args); 174 | context.startActivity(intent); 175 | } 176 | 177 | /** 178 | * 调用相册 179 | * @param context 180 | * @param requestContent 181 | */ 182 | private void goPicture(Context context, RequestContent requestContent) { 183 | Log.d("AndroidApi","goPicture"); 184 | Intent intent = new Intent(Intent.ACTION_PICK); 185 | intent.setType("image/*"); 186 | intent.setAction(Intent.ACTION_GET_CONTENT); 187 | ((Activity)context).startActivityForResult(intent, Integer.parseInt(requestContent.getKey())); 188 | } 189 | 190 | /** 191 | * 调用相机 192 | * @param context 193 | * @param requestContent 194 | */ 195 | private void goCamera(Context context, RequestContent requestContent) { 196 | Log.d("AndroidApi","goCamera"); 197 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 198 | File file = new File(Environment.getExternalStorageDirectory(), System.currentTimeMillis()+".jpg"); 199 | requestContent.setResult(file.toString()); 200 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 201 | ((Activity)context).startActivityForResult(intent, Integer.parseInt(requestContent.getKey())); 202 | } 203 | 204 | /** 205 | * onJsAlert 206 | * @param view 207 | * @param url 208 | * @param message 209 | * @param result 210 | * @return 211 | */ 212 | public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { 213 | Context context = view.getContext(); 214 | AlertDialog.Builder dlg = new AlertDialog.Builder(context); 215 | dlg.setMessage(message); 216 | dlg.setTitle("Alert"); 217 | //Don't let alerts break the back button 218 | dlg.setCancelable(true); 219 | dlg.setPositiveButton(android.R.string.ok, 220 | new AlertDialog.OnClickListener() { 221 | public void onClick(DialogInterface dialog, int which) { 222 | result.confirm(); 223 | } 224 | }); 225 | dlg.setOnCancelListener( 226 | new DialogInterface.OnCancelListener() { 227 | public void onCancel(DialogInterface dialog) { 228 | result.cancel(); 229 | } 230 | }); 231 | dlg.setOnKeyListener(new DialogInterface.OnKeyListener() { 232 | //DO NOTHING 233 | public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { 234 | if (keyCode == KeyEvent.KEYCODE_BACK) 235 | { 236 | result.confirm(); 237 | return false; 238 | } 239 | else 240 | return true; 241 | } 242 | }); 243 | dlg.create(); 244 | dlg.show(); 245 | return true; 246 | } 247 | 248 | /** 249 | * onJsConfirm 250 | * @param view 251 | * @param url 252 | * @param message 253 | * @param result 254 | * @return 255 | */ 256 | public boolean onJsConfirm(WebView view, String url, String message,final JsResult result) { 257 | Context context = view.getContext(); 258 | AlertDialog.Builder dlg = new AlertDialog.Builder(context); 259 | dlg.setMessage(message); 260 | dlg.setTitle("Confirm"); 261 | dlg.setCancelable(true); 262 | dlg.setPositiveButton(android.R.string.ok, 263 | new DialogInterface.OnClickListener() { 264 | public void onClick(DialogInterface dialog, int which) { 265 | result.confirm(); 266 | } 267 | }); 268 | dlg.setNegativeButton(android.R.string.cancel, 269 | new DialogInterface.OnClickListener() { 270 | public void onClick(DialogInterface dialog, int which) { 271 | result.cancel(); 272 | } 273 | }); 274 | dlg.setOnCancelListener( 275 | new DialogInterface.OnCancelListener() { 276 | public void onCancel(DialogInterface dialog) { 277 | result.cancel(); 278 | } 279 | }); 280 | dlg.setOnKeyListener(new DialogInterface.OnKeyListener() { 281 | //DO NOTHING 282 | public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { 283 | if (keyCode == KeyEvent.KEYCODE_BACK) 284 | { 285 | result.cancel(); 286 | return false; 287 | } 288 | else 289 | return true; 290 | } 291 | }); 292 | dlg.create(); 293 | dlg.show(); 294 | return true; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/api/CMD.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.api; 2 | 3 | /** 4 | * Created by xfans on 2014/12/13. 5 | */ 6 | public enum CMD { 7 | CAMERA,//相机 8 | PICTURE,//相册 9 | GONATIVE,//跳转到应用页面 10 | TEL,//电话uri 11 | NULL,//错误 12 | GEO,//地图 13 | MAILTO,//邮件 14 | SMS,//短信 15 | MARKET,//android市场 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/AndroidJsBridge.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.app.Activity; 4 | import android.os.Build; 5 | import android.util.Log; 6 | import android.webkit.JavascriptInterface; 7 | import android.webkit.WebView; 8 | 9 | import com.xfans.jsbridge.api.AndroidApi; 10 | 11 | /** 12 | * Created by xfans on 2014/12/12. 13 | */ 14 | public class AndroidJsBridge { 15 | 16 | private XfansWebView webView; 17 | private AndroidApi androidApi; 18 | 19 | public WebView getWebView() { 20 | return webView; 21 | } 22 | 23 | public AndroidJsBridge(XfansWebView webView, AndroidApi androidApi) { 24 | this.webView = webView; 25 | this.androidApi = androidApi; 26 | } 27 | @JavascriptInterface 28 | public void callNative( String cmd, String agrs, String key){ 29 | Log.d("AndroidJsBridge", cmd + ":" + agrs + ":" + key); 30 | RequestContent requestContent = new RequestContent(cmd, agrs, key, webView); 31 | ContextQueue.reqMap.put(key,requestContent); 32 | androidApi.callAndroidAsync(this, requestContent); 33 | } 34 | 35 | /** 36 | * @param code 1 success other fail 37 | * @param result json 38 | */ 39 | public void jsResult(String code, String result,String key) { 40 | if(ContextQueue.reqMap.size()>0){ 41 | String js = "('" + code + "','" + result + "','" + key + "')"; 42 | runJs(js); 43 | }else{ 44 | String js = "('0','0','0')"; 45 | runJs(js); 46 | } 47 | } 48 | 49 | /** 50 | * run js 51 | * @param jsStr like:javascript: JsApi.jsCallback('1','{name:"xfans"}','2')" 52 | */ 53 | private void runJs(String jsStr){ 54 | ((Activity)webView.getContext()).runOnUiThread(new RunJsRunnable(jsStr)); 55 | } 56 | 57 | private class RunJsRunnable implements Runnable{ 58 | private String jsStr; 59 | 60 | RunJsRunnable(String jsStr) { 61 | this.jsStr = jsStr; 62 | } 63 | 64 | @Override 65 | public void run() { 66 | Log.d("AndroidJsBridge", jsStr); 67 | loadUrlForVersion(jsStr); 68 | } 69 | } 70 | private void loadUrlForVersion(String jsStr) { 71 | //fix bugs: 1. loadUrl may hide keyboard when your focus in a input. 2. loadUrl cannot be called too often. 72 | if (Build.VERSION.SDK_INT keys = new ArrayDeque(); 11 | public static Map reqMap = new HashMap(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/RequestContent.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.webkit.WebView; 4 | 5 | /** 6 | * Created by xfans on 2014/12/12. 7 | */ 8 | public class RequestContent { 9 | private String cmd; 10 | private String args; 11 | private String key; 12 | private String result; 13 | private WebView webView; 14 | 15 | public RequestContent(String cmd, String args, String key, WebView webView) { 16 | this.cmd = cmd; 17 | this.args = args; 18 | this.key = key; 19 | this.webView = webView; 20 | } 21 | 22 | public String getResult() { 23 | return result; 24 | } 25 | 26 | public void setResult(String result) { 27 | this.result = result; 28 | } 29 | 30 | public WebView getWebView() { 31 | return webView; 32 | } 33 | 34 | public void setWebView(WebView webView) { 35 | this.webView = webView; 36 | } 37 | 38 | public String getCmd() { 39 | return cmd.toUpperCase(); 40 | } 41 | 42 | public void setCmd(String cmd) { 43 | this.cmd = cmd; 44 | } 45 | 46 | public String getArgs() { 47 | return args; 48 | } 49 | 50 | public void setArgs(String args) { 51 | this.args = args; 52 | } 53 | 54 | public String getKey() { 55 | return key; 56 | } 57 | 58 | public void setKey(String key) { 59 | this.key = key; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/Utils.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.util.Log; 4 | 5 | import com.xfans.jsbridge.api.CMD; 6 | 7 | /** 8 | * Created by xfans on 2014/12/13. 9 | * 工具类 10 | */ 11 | public class Utils { 12 | /** 13 | * String to Enum 14 | * @param cmdStr 15 | * @return 16 | */ 17 | public static CMD getCmd(String cmdStr) { 18 | Log.d("Utils", "cmdStr:"+cmdStr); 19 | CMD cmd = CMD.NULL; 20 | try { 21 | cmd = CMD.valueOf(cmdStr.toUpperCase()); 22 | } catch (IllegalArgumentException e) { 23 | e.printStackTrace(); 24 | } 25 | return cmd; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/XfansActivityInterface.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.content.Intent; 4 | 5 | /** 6 | * Created by xfans on 2014/12/13. 7 | */ 8 | public interface XfansActivityInterface { 9 | abstract void setWebView(XfansWebView webView); 10 | abstract void setActivity(); 11 | abstract void onActivityResult(int requestCode, int resultCode, Intent data); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/XfansWebChromeClientBridge.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.util.Log; 4 | import android.webkit.JsPromptResult; 5 | import android.webkit.JsResult; 6 | import android.webkit.WebChromeClient; 7 | import android.webkit.WebView; 8 | 9 | import com.xfans.jsbridge.api.AndroidApi; 10 | 11 | /** 12 | * Created by xfans on 2014/12/11. 13 | * WebChromeClient 14 | */ 15 | public class XfansWebChromeClientBridge extends WebChromeClient { 16 | private AndroidApi androidApi; 17 | 18 | public XfansWebChromeClientBridge(AndroidApi androidApi) { 19 | this.androidApi = androidApi; 20 | } 21 | 22 | @Override 23 | public boolean onJsAlert(WebView view, String url, String message, JsResult result) { 24 | return androidApi.onJsAlert(view,url,message,result); 25 | } 26 | 27 | @Override 28 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { 29 | Log.d("XfanWebChromeClient",url+":"+ message+":"+ defaultValue+":"+ result); 30 | 31 | return androidApi.callAndroidSync(view, url, message, defaultValue ,result); 32 | } 33 | 34 | @Override 35 | public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { 36 | return androidApi.onJsConfirm(view, url, message, result); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/XfansWebView.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.annotation.TargetApi; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.os.Build; 8 | import android.os.Message; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.webkit.ValueCallback; 12 | import android.webkit.WebView; 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | 16 | /** 17 | * Created by xfans on 2014/12/13. 18 | */ 19 | public class XfansWebView extends WebView { 20 | private Activity activity; 21 | Method sendMessageMethod; 22 | Object webViewCore; 23 | boolean initFailed = false;; 24 | 25 | public XfansWebView(Context context) { 26 | super(context); 27 | this.initReflection(); 28 | } 29 | 30 | public XfansWebView(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | this.initReflection(); 33 | } 34 | 35 | public XfansWebView(Context context, AttributeSet attrs, int defStyle) { 36 | super(context, attrs, defStyle); 37 | this.initReflection(); 38 | } 39 | 40 | public Activity getActivity() { 41 | return activity; 42 | } 43 | 44 | public void setActivity(Activity activity) { 45 | this.activity = activity; 46 | } 47 | 48 | @SuppressWarnings("rawtypes") 49 | private void initReflection() { 50 | Object webViewObject = this; 51 | Class webViewClass = WebView.class; 52 | try { 53 | Field f = webViewClass.getDeclaredField("mProvider"); 54 | f.setAccessible(true); 55 | webViewObject = f.get(this); 56 | webViewClass = webViewObject.getClass(); 57 | } catch (Throwable e) { 58 | // mProvider is only required on newer Android releases. 59 | } 60 | 61 | try { 62 | Field f = webViewClass.getDeclaredField("mWebViewCore"); 63 | f.setAccessible(true); 64 | webViewCore = f.get(webViewObject); 65 | if (webViewCore != null) { 66 | sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class); 67 | sendMessageMethod.setAccessible(true); 68 | } 69 | } catch (Throwable e) { 70 | initFailed = true; 71 | Log.e("XfansWebView", "PrivateApiBridgeMode failed to find the expected APIs.", e); 72 | } 73 | } 74 | 75 | /** 76 | * 默认加载js方式 77 | * @param url 78 | */ 79 | @Override 80 | public void loadUrl(String url) { 81 | Log.d("XfansWebView","url"); 82 | super.loadUrl(url); 83 | } 84 | 85 | /** 86 | * 反射方式加载js 87 | * @param jsStr 88 | */ 89 | public void loadUrlReflection(String jsStr) { 90 | Log.d("XfansWebView","loadUrlReflection:"+jsStr); 91 | if (sendMessageMethod == null && !initFailed) { 92 | initReflection(); 93 | } 94 | Message execJsMessage = Message.obtain(null, 194, jsStr);//194? 95 | // webViewCore is lazily initialized, and so may not be available right away. 96 | if (sendMessageMethod != null) { 97 | try { 98 | sendMessageMethod.invoke(webViewCore, execJsMessage); 99 | } catch (Throwable e) { 100 | Log.e("XfansWebView", "Reflection message bridge failed.", e); 101 | } 102 | } 103 | } 104 | /** 105 | * 4.4新方法 106 | * @param jsStr 107 | */ 108 | @SuppressLint("NewApi") 109 | public void evaJsForKitkat(String jsStr) { 110 | evaluateJavascript(jsStr,new ValueCallback() { 111 | @Override 112 | public void onReceiveValue(String s) { 113 | Log.d("AndroidJsBridge", "onReceiveValue:" + s); 114 | } 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/bridge/XfansWebViewClientBridge.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.bridge; 2 | 3 | import android.util.Log; 4 | import android.webkit.WebView; 5 | import android.webkit.WebViewClient; 6 | 7 | import com.xfans.jsbridge.api.AndroidApi; 8 | 9 | /** 10 | * Created by xfans on 2014/12/13. 11 | */ 12 | public class XfansWebViewClientBridge extends WebViewClient{ 13 | private AndroidApi androidApi; 14 | 15 | public XfansWebViewClientBridge(AndroidApi androidApi) { 16 | this.androidApi = androidApi; 17 | } 18 | 19 | @Override 20 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 21 | Log.d("XfansWebViewClientBridge","shouldOverrideUrlLoading:"+url); 22 | if(url == null){return false; } 23 | return androidApi.callUrl(view,url); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.sample; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.view.View; 8 | import android.widget.Button; 9 | 10 | import com.xfans.jsbridge.R; 11 | import com.xfans.jsbridge.bridge.XfansWebView; 12 | import com.xfans.jsbridge.widget.XfansBaseActivity; 13 | 14 | 15 | public class MainActivity extends XfansBaseActivity { 16 | private XfansWebView webView; 17 | private Button button; 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | webView = (XfansWebView) findViewById(R.id.webView); 23 | button = (Button) findViewById(R.id.btn); 24 | setWebView(webView); 25 | button.setOnClickListener(new View.OnClickListener() { 26 | @Override 27 | public void onClick(View view) { 28 | Intent intent = new Intent(); 29 | intent.setClassName(MainActivity.this,"com.xfans.js.sample.TestActivity"); 30 | MainActivity.this.startActivity(intent); 31 | } 32 | }); 33 | webView.loadUrl("file:///android_asset/index.html"); 34 | } 35 | 36 | @Override 37 | public boolean onCreateOptionsMenu(Menu menu) { 38 | // Inflate the menu; this adds items to the action bar if it is present. 39 | getMenuInflater().inflate(R.menu.menu_main, menu); 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean onOptionsItemSelected(MenuItem item) { 45 | // Handle action bar item clicks here. The action bar will 46 | // automatically handle clicks on the Home/Up button, so long 47 | // as you specify a parent activity in AndroidManifest.xml. 48 | int id = item.getItemId(); 49 | 50 | //noinspection SimplifiableIfStatement 51 | if (id == R.id.action_settings) { 52 | return true; 53 | } 54 | return super.onOptionsItemSelected(item); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/sample/TestActivity.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.sample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import com.xfans.jsbridge.R; 7 | 8 | /** 9 | * Created by xfans on 2014/12/13. 10 | */ 11 | public class TestActivity extends Activity { 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_test); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/xfans/jsbridge/widget/XfansBaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.xfans.jsbridge.widget; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.util.Log; 8 | import android.webkit.WebSettings; 9 | 10 | import com.xfans.jsbridge.api.AndroidApi; 11 | import com.xfans.jsbridge.api.CMD; 12 | import com.xfans.jsbridge.bridge.AndroidJsBridge; 13 | import com.xfans.jsbridge.bridge.ContextQueue; 14 | import com.xfans.jsbridge.bridge.RequestContent; 15 | import com.xfans.jsbridge.bridge.Utils; 16 | import com.xfans.jsbridge.bridge.XfansActivityInterface; 17 | import com.xfans.jsbridge.bridge.XfansWebChromeClientBridge; 18 | import com.xfans.jsbridge.bridge.XfansWebView; 19 | import com.xfans.jsbridge.bridge.XfansWebViewClientBridge; 20 | 21 | /** 22 | * Created by xfans on 2014/12/12. 23 | */ 24 | public class XfansBaseActivity extends Activity implements XfansActivityInterface { 25 | private XfansWebView webView; 26 | private AndroidJsBridge androidJsBridge; 27 | 28 | @Override 29 | public void setWebView(XfansWebView webView){ 30 | this.webView = webView; 31 | AndroidApi androidApi = new AndroidApi(); 32 | androidJsBridge = new AndroidJsBridge(webView,androidApi); 33 | XfansWebChromeClientBridge xfansWebChromeClientBridge = new XfansWebChromeClientBridge(androidApi); 34 | XfansWebViewClientBridge xfansWebViewClientBridge = new XfansWebViewClientBridge(androidApi); 35 | WebSettings webSettings = webView.getSettings(); 36 | webSettings.setJavaScriptEnabled(true); 37 | webView.addJavascriptInterface(androidJsBridge, "AndroidJsBridge"); 38 | webView.setWebChromeClient(xfansWebChromeClientBridge); 39 | webView.setWebViewClient(xfansWebViewClientBridge); 40 | } 41 | 42 | @Override 43 | public void setActivity() { 44 | webView.setActivity(this); 45 | } 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | } 51 | 52 | @Override 53 | protected void onResume() { 54 | super.onResume(); 55 | if(webView == null){ 56 | try { 57 | throw new Exception("请先在onCreate中调用setWebView"); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 66 | super.onActivityResult(requestCode, resultCode, data); 67 | Log.d("XfansBaseActivity","onActivityResult"); 68 | if (resultCode != 0){ 69 | RequestContent requestContent = ContextQueue.reqMap.get(requestCode+""); 70 | if(requestContent != null){ 71 | String cmdStr = requestContent.getCmd(); 72 | CMD cmd = Utils.getCmd(cmdStr); 73 | switch (cmd){ 74 | case CAMERA: 75 | String path = requestContent.getResult(); 76 | androidJsBridge.jsResult("1", path, requestCode + ""); 77 | break; 78 | case PICTURE: 79 | Uri imageUri= data.getData();//TODO 解析正确路径 80 | androidJsBridge.jsResult("1", imageUri.getPath(), requestCode + ""); 81 | break; 82 | case NULL: 83 | break; 84 | } 85 | } 86 | }else{ 87 | androidJsBridge.jsResult("0","0","0"); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfans/Js-Android-bridge/99fd676b1f86b03e294396491944473716becf64/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfans/Js-Android-bridge/99fd676b1f86b03e294396491944473716becf64/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfans/Js-Android-bridge/99fd676b1f86b03e294396491944473716becf64/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfans/Js-Android-bridge/99fd676b1f86b03e294396491944473716becf64/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 |