Firemonkey增强浏览器在Android平台上与JavaScript的交互

来源:互联网 发布:淘宝网页如何用英语 编辑:程序博客网 时间:2024/05/18 01:53

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

2 0
原创粉丝点击