20.4读取js全局变量或函数返回值
借助现有接口技术,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)的方式来读取出来。
20.4.1反射读取方式
在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 (IllegalArgumentExceptione) {
// 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:
encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:id="@+id/webView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
com.appeon.test.MyWebView>
被测试js:
var myvalue = "jjjjj";
function fun1() {
return "function return test";
}
测试代码:
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
#include
#include
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方法的类和所在的包名称。
*****************************************************************
20.4.3扩展webkit方式
researching
直接扩展WebCore,扩展JSBridge,实现JS的数据类型到JAVA数据类型的转换,确切的说是相互转换,这里面JAVA部分可以用反射机制来做到。
Researching
采用插件的方式实现。