Firemonkey扩展增强:Android 浏览器执行JavaScript获取结果及JavaScript调用本地方法

原创 2017年01月06日 13:56:57

本文背景:delphi XE10.1

Firemonkey自带的TWebBrower对于JavaScript的交互支持一直不是很好,仅仅提供了一个本地执行JavaScript的方法EvaluateJavaScript,而且该方法不提供JS执行的返回结果。在安卓平台上,EvaluateJavaScript是通过WebView的loadUrl('javascript:' + JavaScript)实现的。

在Android 4.4之后,WebView提供了一个新的执行JS的接口:

procedure evaluateJavascript(script: JString; resultCallback: JValueCallback); cdecl;

该接口可以注册一个获取JS执行结果的回调函数以便在JS异步执行完时返回结果。

TJavaScriptCallBack = procedure(const AResult: string) of object;
TJSResultCallback = class(TJavaLocal, JValueCallback)
private
  fCallBack: TJavaScriptCallBack;
public
  procedure onReceiveValue(value: JObject); cdecl;
end;

{ TResultCallback }
procedure TJSResultCallback.onReceiveValue(value: JObject);
begin
  if Assigned(fCallBack) and (value<>nil) then
    fCallBack(JStringToString(value.toString));
end;

这样我们只需创建 一个TJSResultCallBack对象, 并作为WebView.evaluateJavascript接口的第二个参数,就可以异步获取本地执行JS代码的结果。


WebView还提供一个本地代码扩展JavaScript功能的接口:

procedure addJavascriptInterface(object_: JObject; name: JString); cdecl;

该接口注册一个本地实现类,在JS中可以使用指定的名称直接调用该类实现的本地方法。 由于目前不知道怎么直接用Delphi的JNI直接产生一个Java本地对象【注1】,而且Android在高版本中基于安全JS只支持调用本地实现类中标记了“@JavascriptInterface”属性的方法【注2】,所以这里直接使用Java编写了一个代理类:

package tu2.com.jshellper;

import android.webkit.JavascriptInterface;

/**
 * Created by tutu on 2017-01-01.
 */

public class JavaScriptHelper {
    public interface LocalCallBack {
        public String executeCustomJavaScript(String cmd, String param);
    }

    protected LocalCallBack mLocalCallBack;

    public void setLocalCallBack(LocalCallBack callBack) {
        mLocalCallBack = callBack;
    }

    @JavascriptInterface
    public String executeCmd(String cmd, String param) {
        String sRet= "";
        if (mLocalCallBack != null) {
            sRet = mLocalCallBack.executeCustomJavaScript(cmd, param);
        }
        return sRet;
    }
}

将这个Java实现类编译成Jar包,就可以在Delphi中使用。

      [JavaSignature('tu2/com/jshellper/JavaScriptHelper$LocalCallBack')]
      JJavaScriptHelper_LocalCallBack = interface(IJavaInstance)
        ['{74BA78C9-00E5-474A-8FAE-D9DE8D1219F3}']
        function executeCustomJavaScript(cmd: JString; param: JString): JString; cdecl;
      end;

      TJavaScriptHelper_LocalCallBack = class(TJavaLocal, JJavaScriptHelper_LocalCallBack)
      private
        FCallBack: TLocalCallBack;
      public
        function executeCustomJavaScript(cmd: JString; param: JString): JString; cdecl;
      end;

      [JavaSignature('tu2/com/jshellper/JavaScriptHelper')]
      JJavaScriptHelper = interface(JObject)
        ['{B70ED7E1-37E5-4F7A-B26B-478A9D36A128}']
        procedure setLocalCallBack(callBack: JJavaScriptHelper_LocalCallBack); cdecl;
        function executeCmd(cmd: JString; param: JString): JString; cdecl;
      end;

      JJavaScriptHelperClass = interface(JObjectClass)
      ['{9A522A31-9D8A-4748-9704-1E97C84E5657}']
        function init: JJavaScriptHelper; cdecl;
      end;
      TJJavaScriptHelper = class(TJavaGenericImport<JJavaScriptHelperClass, JJavaScriptHelper>) end;

基于上面的介绍,可以扩展TWebBrower在android平台上的实现,这里为了不引起其他单元代码的修改,采取外部主动注册的方式。

RegisterJavaScriptCallBack方法为浏览器控件注册本地扩展JS方法回调和本地异步执行JS的结果回调,EvaluateJavaScriptAsync方法使浏览器异步调用JS,如果JS有返回值将触发TJavaScriptCallBack方法的执行。

unit FMX.WebBrowser.Android;

interface

{$SCOPEDENUMS ON}

uses FMX.WebBrowser;

type
  TLocalCallBack = procedure(const ACmd, AParam: string; var AResult: string) of object;
  TJavaScriptCallBack = procedure(const AResult: string) of object;
  procedure RegisterJavaScriptCallBack(const AWebControl: TCustomWebBrowser;
    const ALocal: TLocalCallBack; const AJavaScript: TJavaScriptCallBack);
  procedure EvaluateJavaScriptAsync(const AWebControl: TCustomWebBrowser;
    const JavaScript: string);


在TAndroidWebBrowserService的字段部分增加:

    FBounds: TRect;
    FRealBounds: TRect;
    //[Add By Tu2
    FJSHelper: JJavaScriptHelper;
    FJSLocal: TJavaScriptHelper_LocalCallBack;
    FJSResult: TJSResultCallback;
    procedure DoEvaluateJavaScriptAsync(const JavaScript: string);
    //Add By Tu2]
    procedure InitUIThread;
    procedure CalcRealBorder;


方法InitUIThread中增加:

procedure TAndroidWebBrowserService.InitUIThread;
begin
  FJWebBrowser := TJWebBrowser.JavaClass.init(TAndroidHelper.Activity);
  FJWebBrowser.getSettings.setJavaScriptEnabled(True);
  FListener := TWebBrowserListener.Create(Self);
  FJWebBrowser.SetWebViewListener(FListener);
  FJNativeLayout := TJNativeLayout.JavaClass.init(TAndroidHelper.Activity,
    MainActivity.getWindow.getDecorView.getWindowToken);
  FJNativeLayout.setPosition(100,100);
  FJNativeLayout.setSize(300,300);
  FJNativeLayout.setControl(FJWebBrowser);
  FFocusChangeListener := TFocusChangeListener.Create(Self);
  FJNativeLayout.setOnFocusChangeListener(FFocusChangeListener);
  FJWebBrowser.getSettings.setGeolocationEnabled(True);
  FJWebBrowser.getSettings.setAppCacheEnabled(True);
  FJWebBrowser.getSettings.setDatabaseEnabled(True);
  FJWebBrowser.getSettings.setDomStorageEnabled(True);
  FJWebBrowser.getSettings.setBuiltInZoomControls(True);
  FJWebBrowser.getSettings.setDisplayZoomControls(False);
  //Add By TU2
  FJSResult := TJSResultCallback.Create;
  FJSHelper := TJJavaScriptHelper.JavaClass.init;
  FJSLocal := TJavaScriptHelper_LocalCallBack.Create;
  FJSHelper.setLocalCallBack(FJSLocal);
  FJWebBrowser.addJavascriptInterface(FJSHelper, StringToJString('TU2JSHelper'));
end;

最后实现开头定义的注册扩展方法:

procedure TAndroidWebBrowserService.DoEvaluateJavaScriptAsync(const JavaScript: string);
begin
  CallInUIThread(procedure
    begin
      FJWebBrowser.evaluateJavascript(StringToJString(JavaScript), FJSResult);
    end);
end;

{ TAndroidWebBrowserService.TResultCallback }
procedure TAndroidWebBrowserService.TJSResultCallback.onReceiveValue(value: JObject);
begin
  if Assigned(fCallBack) and (value<>nil) then
  begin
    TThread.Queue(nil, procedure begin
      fCallBack(JStringToString(value.toString));
    end);
  end;
end;

{ TAndroidWebBrowserService.TJavaScriptHelper_LocalCallBack }

function TAndroidWebBrowserService.TJavaScriptHelper_LocalCallBack.executeCustomJavaScript(
  cmd, param: JString): JString;
var
  AResult: string;
begin
  if Assigned(FCallBack) then
  begin
    AResult := '';
    FCallBack(JStringToString(cmd), JStringToString(param), AResult);
    Result := StringToJString(AResult);
  end;
end;

type
  TMyCustomWebBrowser = class(TControl)
  private
    FWeb: ICustomBrowser;
  end;

procedure RegisterJavaScriptCallBack(const AWebControl: TCustomWebBrowser;
  const ALocal: TLocalCallBack; const AJavaScript: TJavaScriptCallBack);
begin
  with (TMyCustomWebBrowser(AWebControl).FWeb as TAndroidWebBrowserService) do
  begin
    FJSLocal.FCallBack := ALocal;
    FJSResult.fCallBack := AJavaScript;
  end;
end;

procedure EvaluateJavaScriptAsync(const AWebControl: TCustomWebBrowser; const JavaScript: string);
begin
  if TOSVersion.Check(4,4) then
    (TMyCustomWebBrowser(AWebControl).FWeb as TAndroidWebBrowserService).
      DoEvaluateJavaScriptAsync(JavaScript)
  else begin
    //暂未实现
  end;
end;


至此,TWebBrower在android平台上的JS扩展交互就实现了。只需为WebBrowser控件调用一次RegisterJavaScriptCallBack,就可以在JS代码中如下使用:

function CallHost() {
document.getElementById("demoHost").innerHTML = window.TU2JSHelper.executeCmd("JSCallLocal","读取Label的Tag值:");
}

完整的FMX.WebBrowser.Android和Demo: http://download.csdn.net/detail/tht2009/9730534


注1:Delphi使用两种方式创建Java对象,一种是TJavaGenericImport导入Java中的类,一种是使用(TJavaLocal, JXXX)创建本地实例对象,两种都需要已知的签名接口,不能直接用delphi定义一个新Java Object对象。但在 turbococoa 中介绍了可以直接使用TJavaObject继承扩展Java类,只是这是一个第三方商业项目,不知道其实现原理。

注2:详情参见android相关开发文档

版权声明:本文为博主原创文章,未经博主允许不得转载。

Firemonkey扩展增强:Android 浏览器支持Input file标签上传功能

Delphi XE10.2 Tokyo Firemonkey自带的浏览器TWebbrower控件在Android平台上只是简单继承封装了Android系统本身的WebView组件。Android We...
  • tht2009
  • tht2009
  • 2017年08月07日 12:43
  • 247

Android之WebView的使用与简单浏览器

在要浏览网页的时候,第一种我们可以通过Intent来使用自带的浏览器,第二种可以通过WebView这个控件来浏览。 第一种很简单,直接看代码: Uri uri = Uri.parse("http://...

ASP程序中调用JavaScript中getYear()方法显示时间浏览器兼容显示错误问题

最近帮朋友修改网站,发现以前写的使用getYear()获取年份的代码(见下面),在IE浏览器中运行显示正常,但是到非IE浏览器(不是Trident内核)中就显示不正常了(Chrome浏览器Webkit...

JavaScript的中写了方法,没有调用之前,即使里面有错,在浏览器上也不会报错。

function a(){ q; } alert(undefined); var undefined=1...

javascript 调用浏览器的打印方法。并设置打印为横向打印

最近做一个简单的打印功能,要使用JavaScript调用浏览器的打印功能。并设置打印为横向打印: 为了适应A4纸的宽和高这里设置:body{margin:0 auto; ...

Javascript 获取浏览器窗口中文档(视口)可用尺寸的方法

摘要: 由于浏览器的差异,许多信息的获取都要考虑兼容性,窗口中文档可用尺寸是一个经常需要用到的信息,由于浏览器不同甚至版本不同,获取的方法也不一样,本文介绍的函数能够兼容各种浏览器,获取这一信息。同时...
  • judyge
  • judyge
  • 2015年12月08日 12:19
  • 202

android webview中使用Java调用JavaScript方法并获取返回值_Android

webview与js交互相关,涉及到js中调用android本地的方法,于是整理了一下android和js互相调用 的过程。如下demo,demo的主要实现过程如下:通过加载本地的html文件(里面有...

[JavaScript]JS中常遇到的浏览器兼容问题和解决方法

JS中常遇到的浏览器兼容问题和解决方法 今天整理了一下浏览器对JS的兼容问题,希望能给你们带来帮助,我没想到的地方请留言给我,我再加上; 常遇到的关于浏览器的宽高问题: ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Firemonkey扩展增强:Android 浏览器执行JavaScript获取结果及JavaScript调用本地方法
举报原因:
原因补充:

(最多只允许输入30个字)