借助现有接口技术,js可以执行原生java代码中的方法,可以得到方法的返回值,可以让原生java代码在主线程中动态的操作UI;但是借助该接口,原生java代码,采用webview.loadUrl("javascript: JsFunctionName"),只能做到执行js中的方法,如果想获取js中定义的全局变量,或者获取某个js函数的返回值,这种方式无法做到,webview也没有提供别的函数来可供使用。
为了实现该功能,我们分析application framework的源代码发现,从webview类loadurl()方法一路追踪,最终在WebViewCore.java中找到如下代码:
private native void passToJs(int frame, int node, int x, int y, int gen,
String currentText, int keyCode, int keyValue, boolean down,
boolean cap, boolean fn, boolean sym);
在BrowserFrame中,追踪到:
private native void nativeAddJavascriptInterface(int nativeFramePointer,
Object obj, String interfaceName);
至此我们知道android的webview实现,使用的是开源的webkit浏览器内核,该内核是用c语言(webcore)和c++语言(jscore)实现的,android的webview底层实现最终是调用的webkit内核代码,如果该内核提供了直接读取js全局变量或者函数返回值的方法,那么我们可以使用JNI(Java Native Interface)的方式来读取出来。
在android.webkit包中有个BrowserFrame私有类,该类中有个Native方法:
public native String stringByEvaluatingJavaScriptFromString(String script);
这个和苹果中的类似:
Public NSString stringByEvaluatingJavaScriptFromString(NSString script);
虽然该类是私有的,但是我们可以利用反射技术来执行这个方法,从而取得js全局变量和函数返回值;
步骤:
1、 扩展WebView,派生出MyWebView类,添加
public String stringByEvaluatingJavaScriptFromString(String script)方法,该方法体中最终利用反射技术实现;
2、 修改布局中的WebView为com.appeon.test.MyWebView类型;
3、 在页面load完成的情况下,编码取得JS变量或函数返回值;
MyWebView.java:
package com.appeon.test;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public String stringByEvaluatingJavaScriptFromString(String script) {
try {
//由webview取到webviewcore
Field field_webviewcore = WebView.class.getDeclaredField("mWebViewCore");
field_webviewcore.setAccessible(true);
Object obj_webviewcore = field_webviewcore.get(this);
//由webviewcore取到BrowserFrame
Field field_BrowserFrame = obj_webviewcore.getClass().getDeclaredField("mBrowserFrame");
field_BrowserFrame.setAccessible(true);
Object obj_frame = field_BrowserFrame.get(obj_webviewcore);
//获取BrowserFrame对象的stringByEvaluatingJavaScriptFromString方法
Method method_stringByEvaluatingJavaScriptFromString = obj_frame.getClass().getMethod("stringByEvaluatingJavaScriptFromString", String.class);
//执行stringByEvaluatingJavaScriptFromString方法
Object obj_value = method_stringByEvaluatingJavaScriptFromString.invoke(obj_frame, script);
//返回执行结果
return String.valueOf(obj_value);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content"android:layout_height="wrap_content"></Button>
<com.appeon.test.MyWebView android:id="@+id/webView1" android:layout_width="fill_parent"android:layout_height="fill_parent"></ com.appeon.test.MyWebView>
</LinearLayout>
被测试js:
<script type="text/javascript">
var myvalue = "jjjjj";
function fun1() {
return "function return test";
}
</script>
测试代码:
class WebViewListener extends WebViewClient {
@Override
public void onPageFinished(WebView view ,String url) {
//页面内容载入完成时执行
Toast.makeText(AndroidSampleActivity.this,web.stringByEvaluatingJavaScriptFromString("myvalue"),Toast.LENGTH_SHORT).show();
}
}
代码中的web为MyWebView的对象:
web = (MyWebView)this.findViewById(R.id.webView1);
除了采用反射方式能访问到私有类BrowserFrame中的stringByEvaluatingJavaScriptFromString方法之外,采用JNI技术,也能做到;下面我们采用JNI技术来实现20.4.1中的MyWebView类。
原理:java->C->java,具体到这里就是mywebview.java调用bridge.c,bridge.c再调用BrowserFrame.java
MyWebView.java:
package com.example.hellojni;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public native String stringByEvaluatingJavaScriptFromString(String script);
static {
System.loadLibrary("bridge");
}
}
bridge.c:
#include <string.h>
#include <jni.h>
#include <dlfcn.h>
jstring
Java_com_example_hellojni_MyWebView_stringByEvaluatingJavaScriptFromString( JNIEnv* env,jobject thiz,jstring script )
{
//---------------------c调用java测试------------------------------------
jstring str = NULL;
//由webview获取webviewcore
jclass class_webview = (*env)->GetObjectClass(env, thiz);
jfieldID fid_WebViewCore = (*env)->GetFieldID(env, class_webview, "mWebViewCore", "Landroid/webkit/WebViewCore;");
jobject obj_WebViewCore = (*env)->GetObjectField(env,thiz, fid_WebViewCore);
//由webviewcore获取webframe
jclass class_webviewcore = (*env)->FindClass(env, "android/webkit/WebViewCore");
jfieldID fid_frame = (*env)->GetFieldID(env, class_webviewcore, "mBrowserFrame", "Landroid/webkit/BrowserFrame;");
jobject obj_frame = (*env)->GetObjectField(env,obj_WebViewCore, fid_frame);
//获取webframe的stringByEvaluatingJavaScriptFromString方法ID
jclass class_webframe = (*env)->FindClass(env, "android/webkit/BrowserFrame");
jmethodID mid = (*env)->GetMethodID(env, class_webframe, "stringByEvaluatingJavaScriptFromString", "(Ljava/lang/String;)Ljava/lang/String;");
if (mid) {
//执行webframe对象的stringByEvaluatingJavaScriptFromString方法
str = (*env)->CallObjectMethod(env, obj_frame, mid, script);
}
//返回执行结果
return str;//(*env)->NewStringUTF(env, str);
//------------------------------------------------------------
}
Android.mk添加如下代码:
#-----------------------定义bridge共享库的编译(c)------------------
LOCAL_CPP_EXTENSION := .c
include $(CLEAR_VARS)
LOCAL_MODULE := bridge
LOCAL_SRC_FILES := bridge.c
include $(BUILD_SHARED_LIBRARY)
*****************************************************************
至此,我们实现了mywebview,并为mywebview定义了本地方法和该本地方法的c语言实现,在c语言的具体实现时,又采用jni技术,调用了private类型的BrowserFrame对象中的native类型的stringByEvaluatingJavaScriptFromString方法。
剩下的就是如何使用mywebview定义布局了(静态或动态),具体实现和20.4.1一样。
注意:jni的实现,可以借助NDK框架来简化开发,具体实现参看22节《JNI和NDK》,本例中采用的就是NDK框架,如果不采用NDK也可实现,但原理不变。
在bridge.c中,注意方法的命名,必须是包名+类名+方法名,类名和包名分别对应定义native方法的类和所在的包名称。
*****************************************************************
researching
直接扩展WebCore,扩展JSBridge,实现JS的数据类型到JAVA数据类型的转换,确切的说是相互转换,这里面JAVA部分可以用反射机制来做到。
Researching
采用插件的方式实现。