作者:潜易


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,

          

          

在BrowserFrame中,追踪到:

face(int nativeFramePointer,

          

至此我们知道android的webview实现,使用的是开源的webkit浏览器内核,该内核是用c语言(webcore)和c++语言(jscore)实现的,android的webview底层实现最终是调用的webkit内核代码,如果该内核提供了直接读取js全局变量或者函数返回值的方法,那么我们可以使用JNI(Java Native Interface)的方式来读取出来。

20.4.1反射读取方式

在android.webkit包中有个BrowserFrame私有类,该类中有个Native方法:

riptFromString(String script);

这个和苹果中的类似:

riptFromString(NSString script);

虽然该类是私有的,但是我们可以利用反射技术来执行这个方法,从而取得js全局变量和函数返回值;

步骤:

1、 扩展WebView,派生出MyWebView类,添加

public String stringByEvaluatingJavaScriptFromString(String script)方法,该方法体中最终利用反射技术实现;

2、 修改布局中的WebView为com.appeon.test.MyWebView类型;

在页面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_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
         
      } catch (NoSuchFieldException e) {
          // TODO Auto-generated catch block
         
      } catch (IllegalArgumentException
          // TODO Auto-generated catch block
         
      } catch (IllegalAccessException e) {
          // TODO Auto-generated catch block
         
      } catch (NoSuchMethodException e) {
          // TODO Auto-generated catch block
         
      } catch (InvocationTargetException e) {
          // TODO Auto-generated catch block
         
     
      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 {   
     
      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);
20.4.2 JNI读取方式

riptFromString方法之外,采用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 native String stringByEvaluatingJavaScriptFromString(String script);
    
 }
 bridge.c:
 #include <string.h>
 #include <jni.h>
 #include <dlfcn.h>


 jstring
 Java_com_example_hellojni_MyWebView_stringByEvaluatingJavaScriptFromString( JNIEnv* env,jobject thiz,jstring script )
 {
  
  
   //由webview获取webviewcore
  
   jfieldID fid_WebViewCore = (*env)->GetFieldID(env, class_webview, "mWebViewCore", "Landroid/webkit/WebViewCore;");
  
   //由webviewcore获取webframe
   jclass class_webviewcore = (*env)->FindClass(env, "android/webkit/WebViewCore");
   jfieldID fid_frame = (*env)->GetFieldID(env, class_webviewcore, "mBrowserFrame", "Landroid/webkit/BrowserFrame;");
  
   //获取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方法
     
  
  
   return str;//(*env)->NewStringUTF(env, str);
  
 }

Android.mk添加如下代码:

#-----------------------定义bridge共享库的编译(c)------------------

LOCAL_CPP_EXTENSION := .c

include $(CLEAR_VARS)

  

LOCAL_SRC_FILES := bridge.c

include $(BUILD_SHARED_LIBRARY)

*****************************************************************

riptFromString方法。

剩下的就是如何使用mywebview定义布局了(静态或动态),具体实现和20.4.1一样。

注意:jni的实现,可以借助NDK框架来简化开发,具体实现参看22节《JNI和NDK》,本例中采用的就是NDK框架,如果不采用NDK也可实现,但原理不变。

在bridge.c中,注意方法的命名,必须是包名+类名+方法名,类名和包名分别对应定义native方法的类和所在的包名称。

*****************************************************************

20.4.3扩展webkit方式

researching

直接扩展WebCore,扩展JSBridge,实现JS的数据类型到JAVA数据类型的转换,确切的说是相互转换,这里面JAVA部分可以用反射机制来做到。

20.4.4 Plug方式

Researching

采用插件的方式实现。