Created
December 8, 2018 13:20
-
-
Save pabloko/cda14ee68f2031612af19612174dd974 to your computer and use it in GitHub Desktop.
Lets have any JSON serializable type and methods to callback on Android WebView's JSInterface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//How it works: all the script relies on a tiny javascript stub injected to webView's document that uses Proxy api and json serialicing stuff. Calls to our internal objects are proxified to a single method "__mm_handle_method", that using reflection find the target method and populate arguments exchanged in json, plus a custom interface for methods passed as argument. | |
package es.pabloko.webviewbridge; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.support.v7.app.AppCompatActivity; | |
import android.os.Bundle; | |
import android.util.Log; | |
import android.webkit.JavascriptInterface; | |
import android.webkit.WebView; | |
import org.json.JSONArray; | |
import org.json.JSONException; | |
import org.json.JSONObject; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
public class MainActivity extends AppCompatActivity { | |
private String JSINTERFACE="Android"; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
//setContentView(R.layout.activity_main); | |
//WebView webView = findViewById(R.id.web); | |
WebView webView = new WebView(this); | |
webView.getSettings().setJavaScriptEnabled(true); | |
webView.addJavascriptInterface(new JSInterface(this, webView, JSINTERFACE), JSINTERFACE+"_stub"); | |
String html = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><script>" | |
+ "var __mm_cb={};function __mm_guid(){function _(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return\"__mm_method_\"+_()+_()+\"-\"+_()+\"-\"+_()+\"-\"+_()+\"-\"+_()+_()+_()}function __mm_callback(){var _=Array.from(arguments),n=_.shift();__mm_cb[n]&&__mm_cb[n].apply(null,_)}var __mm_method_handler="+JSINTERFACE+"_stub.__mm_method_handler.bind("+JSINTERFACE+"_stub),"+JSINTERFACE+"=new Proxy("+JSINTERFACE+"_stub,{get:function(_,r){return function(){for(var _=0;_<arguments.length;_++)if(\"[object Function]\"==={}.toString.call(arguments[_])){var n=__mm_guid();\"\"!=arguments[_].name&&(n=\"__mm_method_\"+arguments[_].name),__mm_cb[n]=arguments[_],arguments[_]=n}var m=__mm_method_handler(r,JSON.stringify(arguments));return JSON.parse(m).value}}});" | |
+ "</script></head><body>Android WebView JavascriptInterface Test</body></html>"; | |
webView.loadData(html, "text/html", "UTF-8"); | |
WebView.setWebContentsDebuggingEnabled(true); | |
setContentView(webView); | |
} | |
public class JSCallback { | |
private Context caller; | |
private WebView wb; | |
private String guid; | |
public JSCallback(Context caller, WebView wb, String guid) { | |
this.caller = caller; | |
this.wb=wb; | |
this.guid=guid; | |
} | |
public void call(Object... o) { | |
StringBuilder stringBuilder = new StringBuilder(); | |
stringBuilder.append("try{"); | |
String separator = ""; | |
if (guid.startsWith("__mm_method_")) { | |
stringBuilder.append("__mm_callback"); | |
stringBuilder.append("("); | |
stringBuilder.append("'" + guid + "'"); | |
separator = ","; | |
} else { | |
stringBuilder.append(guid); | |
stringBuilder.append("("); | |
} | |
for (Object param : o) { | |
stringBuilder.append(separator); | |
separator = ","; | |
if (param instanceof String) { | |
stringBuilder.append("'"); | |
stringBuilder.append(((String) param).replace("'", "\'")); | |
stringBuilder.append("'"); | |
} else | |
stringBuilder.append(param); | |
} | |
stringBuilder.append(")}catch(error){console.error(error.message);}"); | |
final String call = stringBuilder.toString(); | |
((Activity)caller).runOnUiThread(new Runnable() { | |
@Override | |
public void run() { | |
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { | |
wb.evaluateJavascript(call, null); | |
} else { | |
wb.loadUrl("javascript:" + call); | |
} | |
} | |
}); | |
} | |
} | |
public class JSInterface { | |
private Context caller; | |
private WebView wb; | |
private String jsinterfacename; | |
public JSInterface(Context caller, WebView wb, String jsc) { | |
this.wb = wb; | |
this.jsinterfacename = jsc; | |
this.caller = caller; | |
} | |
@JavascriptInterface | |
public String __mm_method_handler(String method, String data) { | |
Method[] methods = this.getClass().getMethods(); | |
for (Method meth : methods) { | |
if (meth.getName().equals(method)) { | |
try { | |
JSONObject obj = new JSONObject(data); | |
Object[] params = new Object[obj.length()]; | |
for (int uc=0; uc<obj.length();uc++) { | |
params[uc] = obj.get(uc + ""); | |
if (params[uc].toString().startsWith("__mm_method_")) { | |
params[uc] = new JSCallback(caller, wb, params[uc].toString()); | |
} | |
} | |
Object ret = meth.invoke(this, params); | |
JSONObject jsr = new JSONObject(); | |
jsr.put("value", ret); | |
return jsr.toString(); | |
} catch (JSONException e) { | |
e.printStackTrace(); | |
} catch (IllegalAccessException e) { | |
e.printStackTrace(); | |
} catch (InvocationTargetException e) { | |
e.printStackTrace(); | |
} | |
return ""; | |
} | |
} | |
return ""; | |
} | |
//PUBLIC METHODS HERE | |
public int kkk (String s, int s1, int s2, final JSCallback uc) { | |
Log.e("JSInterface",(s1+s2)+" = goof kkk "+s); | |
uc.call("AAAAAAAAAAAAAAAAAA"); | |
final int[] ix = {1}; | |
new Thread(new Runnable() { | |
public void run(){ | |
while (ix[0] <20) { | |
try { | |
Thread.sleep(1000); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
uc.call("AAAAAAAAAAAAAAAAAA" + ix[0]); | |
ix[0]++; | |
} | |
} | |
}).start(); | |
return 9; | |
} | |
//sample console output for this call: | |
/* | |
Android.kkk("ayy",1,2,function(a){console.log("bbbaaaaaaaaaaabbbbb"+a)}) | |
9 | |
(logcat) >>> E/JSInterface: 3 = goof kkk ayy | |
VM30:1 bbbaaaaaaaaaaabbbbbAAAAAAAAAAAAAAAAAA | |
VM30:1 bbbaaaaaaaaaaabbbbbAAAAAAAAAAAAAAAAAA1 (+1.000s) | |
VM30:1 bbbaaaaaaaaaaabbbbbAAAAAAAAAAAAAAAAAA2 (+1.000s) | |
VM30:1 bbbaaaaaaaaaaabbbbbAAAAAAAAAAAAAAAAAA3 (+1.000s) | |
... | |
*/ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment