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

本文背景: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相关开发文档

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值