[转载]Android笔记(三十六)Android中js和java的互调(二)(调用js获取返回值)

原文地址:Android笔记(三十六)Android中js和java的互调(二) 作者:潜易

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 (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);

20.4.2 JNI读取方式

除了采用反射方式能访问到私有类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(envstr);

    //------------------------------------------------------------

}

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部分可以用反射机制来做到。

20.4.4 Plug方式

Researching

采用插件的方式实现。

你可能感兴趣的:([转载]Android笔记(三十六)Android中js和java的互调(二)(调用js获取返回值))