webView js与java互调

js调用java
mWebView.addJavascriptInterface(new DemoJavaScriptInterface(), "demo");
这样就把java对象DemoJavaScriptInterface生成了js的demo对象,直接window.demo调用
被js调用的java对象方法需手动加上@JavascriptInterface。

调用很快40-50ms,返回值尽量使基本类型,对象类型多了会出错。

给js给java传参或返回值是fianl的。返回值是字符串不能对它进行一些修改操作,比如想对它 substr ,取不到。怎么解决呢?转成 locale 的。使用 toLocaleString() 函数就可以了


java调用js
mWebView.loadUrl("javascript:ttt()"); //没有办法获得返回值,并且速度慢,100-200ms尽量不调用js
ttt是js的普通函数。

线程说明:

js调用java,java代码执行在名为javaBrige子线程中。

可用runOnUiThread()使java代码执行在主线程。

A.    webview组件如何使用

1)       添加权限:AndroidManifest.xml中必须使用许可"android.permission.INTERNET",否则会出Web page not available错误。 2)       在要Activity中生成一个WebView组件:WebView webView = new WebView(this);或者可以在activitylayout文件里添加webview控件:

<WebView

android:id="@+id/wv"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:text="@string/hello"

    />

3)       设置WebView基本信息:
          如果访问的页面中有Javascript,则webview必须设置支持Javascript
          webview.getSettings().setJavaScriptEnabled(true);  
          触摸焦点起作用
          requestFocus();
          取消滚动条
          this.setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);

4)       设置WevView要显示的网页:
          互联网用:webView.loadUrl("http://www.google.com"); 
          本地文件用:webView.loadUrl("file:///android_asset/XX.html");  本地文件存放在:assets文件中
5)       如果希望点击链接由自己处理,而不是新开Android的系统browser中响应该链接。给WebView添加一个事件监听对象(WebViewClient)并重写其中的一些方法:
shouldOverrideUrlLoading:对网页中超链接按钮的响应。当按下某个连接时WebViewClient会调用这个方法,并传递参数:按下的url。比如当webview内嵌网页的某个数字被点击时,它会自动认为这是一个电话请求,会传递urltel:123,如果你不希望如此可通过重写shouldOverrideUrlLoading函数解决:

  

[java] view plaincopyprint?

  • public boolean shouldOverrideUrlLoading(WebView view,String url){  
  •   
  •         if(url.indexOf("tel:")<0){//页面上有数字会导致连接电话  
  •   
  •             view.loadUrl(url);  
  •   
  •         }  
  •   
  •            return true;            
  •   
  •        }  


public boolean shouldOverrideUrlLoading(WebView view,String url){         if(url.indexOf("tel:")<0){//页面上有数字会导致连接电话             view.loadUrl(url);         }            return true;                  }

        另外还有其他一些可重写的方法 
1,接收到Http请求的事件
onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)  

2,打开链接前的事件
public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; }
这个函数我们可以做很多操作,比如我们读取到某些特殊的URL,于是就可以不打开地址,取消这个操作,进行预先定义的其他操作,这对一个程序是非常必要的。

3,载入页面完成的事件
public void onPageFinished(WebView view, String url){ }
同样道理,我们知道一个页面载入完成,于是我们可以关闭loading条,切换程序动作。

4,载入页面开始的事件
public void onPageStarted(WebView view, String url, Bitmap favicon) { }
这个事件就是开始载入页面调用的,通常我们可以在这设定一个loading的页面,告诉用户程序在等待网络响应。

通过这几个事件,我们可以很轻松的控制程序操作,一边用着浏览器显示内容,一边监控着用户操作实现我们需要的各种显示方式,同时可以防止用户产生误操作。
      

6)       如果用webview点链接看了很多页以后,如果不做任何处理,点击系统“Back”键,整个浏览器会调用finish()而结束自身,如果希望浏览的网页回退而不是退出浏览器,需要在当前Activity中处理并消费掉该Back事件。
       覆盖Activity类的onKeyDown(int keyCoder,KeyEvent event)方法。
   [java] view plaincopyprint?

  • public boolean onKeyDown(int keyCoder,KeyEvent event){  
  •                         if(webView.canGoBack() && keyCoder == KeyEvent.KEYCODE_BACK){  
  •                                  webview.goBack();   //goBack()表示返回webView的上一页面  
  •   
  •                                 return true;  
  •                          }  
  •                          return false;  
  •                 }  


public boolean onKeyDown(int keyCoder,KeyEvent event){                         if(webView.canGoBack() && keyCoder == KeyEvent.KEYCODE_BACK){                                  webview.goBack();   //goBack()表示返回webView的上一页面                                 return true;                          }                          return false;                 }

其实webview加载资源的速度并不慢,但是如果资源多了,当然就很慢。图片、css 、js 、html这些资源每个大概需要10-200ms ,一般都是30ms就ok了。不过webview是必须等到全部资源都完成加载,才会进行渲染的,所以加载的速度很重要!从Google上我们了解到,webview加载页面的顺序是:先加载html,然后从里面解析出css、js文件和页面上的图片资源进行加载。如果webkit的缓存里面有,就不加载。加载完这些资源之后,就进行css的渲染和js的执行。Css的渲染一般不需要很长时间,几十毫秒就ok。关键是js的执行,如果用了jQuery,则执行起来需要5-6秒。而在这段时间,如果不在webview里设置背景,网页部分是白色的,很难看。这是一个很糟糕的用户体验。所以#欧#柏泰#克建议如果用网页布局程序,最好别用那些庞大的js框架。最好使用原生的js写业务脚本,以提升加载速度,改善用户体验。

在Android上怎样实现JAVA和JS交互呢?Android的webview是基于webkit内核的,webview中集成了js与java互调的接口函数,通过addJavas criptInterface方法,可以将Java的类注册进webkit,给网页上的js进行调用,而且还可以通过loadUrl方法是给webkit传递一个URL,供浏览器来进行解析,实现Java和js交互。

要想运行网页上的js脚本,webview必须设置支持Javas cript。

Java和js交互有以下一些特点:

1.Java 调用 js 里面的函数,速度并不令人满意,大概一次一两百毫秒吧,如果要做交互性很强的事情,这种速度会让人疯掉的。而反过来就不一样了, js 去调 java 的方法,速度很快,基本上 40-50 毫秒一次。所以尽量用 js 调用 java 方法,而不是 java 去调用 js 函数。

2.Java 调用 js 的函数,没有返回值,而 Js 调用 java 方法,可以有返回值。返回值可以是基本类型、字符串,也可以是对象。如果是字符串,有个很讨厌的问题,第 3 点我会讲的。如果是对象,这个对象会被转换为 js 的对象,直接可以访问里面的方法。但是我不推荐 java 返回给 js 的是对象,除非是必须。因为 js 收到 java 返回的对象,会产生一些交换对象,而如果这些对象的数量增加到了 500 或 600 以上,程序就会出问题。所以尽量返回基本数据类型或者字符串。

3.Js 调用 Java 的方法,返回值如果是字符串,你会发现这个字符串是 native 的,不能对它进行一些修改操作,比如想对它 substr ,取不到。怎么解决呢?转成 locale 的。使用 toLocaleString() 函数就可以了。不过这个函数的速度并不快,转化的字符串如果很多,将会很耗费时间。

背景:借助现有接口技术,js可以执行原生java代码中的方法,可以得到方法的返回值,可以让原生java代码在主线程中动态的操作UI;但是借助该接口,原生java代码,采用webview.loadUrl("javascript: JsFunctionName"),只能做到执行js中的方法,如果想获取js中定义的全局变量,或者获取某个js函数的返回值,这种方式无法做到,webview也没有提供别的函数来可供使用。

分析:为了实现该功能,我们分析application framework的源代码发现,从webviewloadurl()方法一路追踪,最终在WebViewCore.java中找到如下代码:

private native voidpassToJs(int frame, int node, int x, int y, int gen,

            String currentText, int keyCode, intkeyValue, boolean down,

            boolean cap, boolean fn, boolean sym);

BrowserFrame中,追踪到:

private native voidnativeAddJavascriptInterface(int nativeFramePointer,

            Object obj, String interfaceName);

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

方案:

1)反射读取方式

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

public native StringstringByEvaluatingJavaScriptFromString(String script);

这个和苹果中的类似:

Public NSStringstringByEvaluatingJavaScriptFromString(NSString script);

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

步骤:

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

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

2修改布局中的WebViewcom.appeon.test.MyWebView类型;

3  在页面load完成的情况下,编码取得JS变量或函数返回值;

2JNI读取方式

除了采用反射方式能访问到私有类BrowserFrame中的stringByEvaluatingJavaScriptFromString方法之外,采用JNI技术,也能做到;下面我们采用JNI技术来实现20.4.1中的MyWebView类。

原理:java->C->java,具体到这里就是mywebview.java调用bridge.cbridge.c再调用BrowserFrame.java

3)扩展webkit方式

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

4Plug方式

采用插件的方式实现。

5)使用android调用js后,通过js调用android本地方法,从而传入值的方式。
代码如下:
webView.loadUrl("javascript:androidGetInfo()");//android调用当前页面的androidGetInfo()方法。
页面的js

<script>

      var title = '${article.title}';

      var articleId = '${article.id}';

      var articleType = '${article.type}';

      function androidGetInfo(){

          window.tlsj.getInfo(title,articleType);//调用android 中的getInfo方法。

      }

</script>

android手机端代码如下:

webView.addJavascriptInterface(newObject() {

          public void getInfo(String _title,String _articleType) {

             title = _title;

             articleType = _articleType;

             Message msg = new Message();

             handler.sendMessage(msg);

          }

      }, "tlsj");
 
由此即可将js中的全局变量或者函数返回值传入android手机端

分析:

反射读取方式存在一定的不稳定性,如当内部不开放函数或变量名发生变化时,反射就会出现问题。

JNI读取方式需要程序员对c++有一定了解。

使用android调用js后,通过js调用android本地方法不够灵活。

Android 4.4之后使用evaluateJavascript即可。这里展示一个简单的交互示例 具有返回值的js方法

function getGreetings() {
      return 1;
}
java代码时用evaluateJavascript方法调用


private void testEvaluateJavascript(WebView webView) {
  webView.evaluateJavascript("getGreetings()", new ValueCallback<String>() {

  @Override
  public void onReceiveValue(String value) {
      Log.i(LOGTAG, "onReceiveValue value=" + value);
  }});
}
输出结果
I/MainActivity( 1432): onReceiveValue value=1
注意

上面限定了结果返回结果为String,对于简单的类型会尝试转换成字符串返回,对于复杂的数据类型,建议以字符串形式的json返回。
evaluateJavascript方法必须在UI线程(主线程)调用,因此onReceiveValue也执行在主线程。
疑问解答

Alert无法弹出

你应该是没有设置WebChromeClient,按照以下代码设置

 1
myWebView.setWebChromeClient(new WebChromeClient() {});
Uncaught ReferenceError: functionName is not defined

问题出现原因,网页的js代码没有加载完成,就调用了js方法。解决方法是在网页加载完成之后调用js方法

myWebView.setWebViewClient(new WebViewClient() {

  @Override
  public void onPageFinished(WebView view, String url) {
      super.onPageFinished(view, url);
      //在这里执行你想调用的js函数
  }
 
});
Uncaught TypeError: Object [object Object] has no method

安全限制问题

如果只在4.2版本以上的机器出问题,那么就是系统处于安全限制的问题了。Android文档这样说的

Caution: If you’ve set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available your web page code (the method must also be public). If you do not provide the annotation, then the method will not accessible by your web page when running on Android 4.2 or higher.

中文大意为

警告:如果你的程序目标平台是17或者是更高,你必须要在暴露给网页可调用的方法(这个方法必须是公开的)加上@JavascriptInterface注释。如果你不这样做的话,在4.2以以后的平台上,网页无法访问到你的方法。

两种解决方法

将targetSdkVersion设置成17或更高,引入@JavascriptInterface注释
自己创建一个注释接口名字为@JavascriptInterface,然后将其引入。注意这个接口不能混淆。
注,创建@JavascriptInterface代码


public @interface JavascriptInterface {

}
代码混淆问题

如果在没有混淆的版本运行正常,在混淆后的版本的代码运行错误,并提示Uncaught TypeError: Object [object Object] has no method,那就是你没有做混淆例外处理。 在混淆文件加入类似这样的代码


-keep class com.example.javajsinteractiondemo$JsInteration {
    *;
}
All WebView methods must be called on the same thread

过滤日志曾发现过这个问题。

E/StrictMode( 1546): java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {528712d4} called on Looper (JavaBridge, tid 121) {52b6678c}, FYI main Looper is Looper (main, tid 1) {528712d4})
E/StrictMode( 1546):   at android.webkit.WebView.checkThread(WebView.java:2063)
E/StrictMode( 1546):   at android.webkit.WebView.loadUrl(WebView.java:794)
E/StrictMode( 1546):   at com.xxx.xxxx.xxxx.xxxx.xxxxxxx$JavaScriptInterface.onCanGoBackResult(xxxx.java:96)
E/StrictMode( 1546):   at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
E/StrictMode( 1546):   at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
E/StrictMode( 1546):   at android.os.Handler.dispatchMessage(Handler.java:102)
E/StrictMode( 1546):   at android.os.Looper.loop(Looper.java:136)
E/StrictMode( 1546):   at android.os.HandlerThread.run(HandlerThread.java:61)
在js调用后的Java回调线程并不是主线程。如打印日志可验证

lineos:false
1
ThreadInfo=Thread[WebViewCoreThread,5,main]
解决上述的异常,将webview操作放在主线程中即可。

webView.post(new Runnable() {
    @Override
    public void run() {
        webView.loadUrl(YOUR_URL).
    }
});

猜你喜欢

转载自wenzongliang.iteye.com/blog/2199981