本文背景:Delphi XE10.2 Tokyo
Firemonkey自带的浏览器TWebbrower控件在Android平台上只是简单继承封装了Android系统本身的WebView组件。Android WebView 由于安全等原因本身并没有实现 <input type="file"/> 标签的选择文件功能,而是预留了一个setWebChromeClient方法来设置替换默认的Chrome处理。
关于WebChromeClient,它既不是接口也不是抽象类,但声明的方法很多方法体都是空的,通过这个WebChromeClient,我们可以自定义实现Webview对话框处理。这里为了实现input标签选文件功能,我们只需重写WebChromeClient中关于文件选择的方法onShowFileChooser和openFileChooser,前者是Android5.0公布的新接口,后者是Android4.1未公布(隐藏)的一个接口。
由于涉及Java类的继承,这里直接使用Java代码实现一个自定义的FMXWebChromeClient,并将其编译成Jar包,就可以在Delphi中使用。FMXWebChromeClient定义了一个选择监听器接口来自定义选择文件的逻辑处理,并且定义了doCallback回调函数来通知Webview并回传用户的选择结果。
unit tu2.fmxhelper.webchromeclient;
interface
uses Androidapi.JNIBridge, Androidapi.JNI.JavaTypes, Androidapi.JNI.Webkit,
System.Messaging, System.Types;
type
[JavaSignature('tu2/fmx/webchromeclient/FMXWebChromeClient$OnChooserListener')]
JOnChooserListener = interface(IJavaInstance)
['{0543AA53-85A1-48A1-A7B1-133C776850D3}']
procedure onExecute(acceptType: JString; capture: JString); cdecl;
end;
[JavaSignature('tu2/fmx/webchromeclient/FMXWebChromeClient')]
JFMXWebChromeClient = interface(JWebChromeClient)
['{16615C3F-6ABC-4CA0-BA77-6B002F22F2DA}']
procedure setChooserListener(listener: JOnChooserListener); cdecl;
procedure doCallback(filename: JString); cdecl;
end;
JFMXWebChromeClientClass = interface(JWebChromeClientClass)
['{DC8D9CA9-31AD-4BDD-9A81-030C2286B16B}']
function init: JFMXWebChromeClient; cdecl;
end;
TJFMXWebChromeClient = class(TJavaGenericImport<JFMXWebChromeClientClass,JFMXWebChromeClient>) end;
TImageChooserListener = class(TJavaLocal, JOnChooserListener)
private
FSize: TSize;
[Weak]FChrome: JFMXWebChromeClient;
procedure DidReceivedImagePath(const Sender: TObject; const M: TMessage);
procedure DidCancelReceiveImage(const Sender: TObject; const M: TMessage);
public
procedure onExecute(acceptType: JString; capture: JString); cdecl;
public
constructor Create(const AChrome: JFMXWebChromeClient);
destructor Destroy; override;
end;
implementation
uses FMX.Platform.Android, FMX.Platform, FMX.Graphics, AndroidApi.Helpers;
{ TImageChooserListener }
constructor TImageChooserListener.Create;
begin
inherited Create;
if AChrome<>nil then
begin
FChrome := AChrome;
FChrome.setChooserListener(Self);
end;
FSize.cx := TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize);
FSize.cy := TCanvasManager.DefaultCanvas.GetAttribute(TCanvasAttribute.MaxBitmapSize);
TMessageManager.DefaultManager.SubscribeToMessage(TMessageCancelReceivingImage, DidCancelReceiveImage);
TMessageManager.DefaultManager.SubscribeToMessage(TMessageReceivedImagePath, DidReceivedImagePath);
end;
destructor TImageChooserListener.Destroy;
begin
TMessageManager.DefaultManager.Unsubscribe(TMessageReceivedImagePath, DidReceivedImagePath);
TMessageManager.DefaultManager.Unsubscribe(TMessageCancelReceivingImage, DidCancelReceiveImage);
inherited;
end;
procedure TImageChooserListener.DidCancelReceiveImage(const Sender: TObject;
const M: TMessage);
begin
if FChrome<>nil then
FChrome.doCallback(StringToJString(''));
end;
procedure TImageChooserListener.DidReceivedImagePath(const Sender: TObject;
const M: TMessage);
var
ImagePath: string;
begin
if (FChrome<>nil) and (M is TMessageReceivedImagePath) then
begin
ImagePath := 'file://' + (M as TMessageReceivedImagePath).Value;
FChrome.doCallback(StringToJString(ImagePath));
end;
end;
procedure TImageChooserListener.onExecute(acceptType, capture: JString);
begin
MainActivity.getFMXMediaLibrary.takeImageFromLibrary(FSize.Width, FSize.Height, False);
end;
end.
TImageChooserListener是用Delphi实现的图片选择器实现类,使用上我们只需在FMX.WebBrowser.Android单元的TAndroidWebBrowserService.InitUIThread方法中创建一个FMXWebChromeClient对象和相应的TImageChooserListener对象实例,并通过setWebChromeClient方法与Webview绑定,就可以使webview支持文件的上传功能。这里不再赘述,具体参考Demo。
完整Demo:下载。为了测试,我们用PHP设计一个简单的测试页面。
前端网页代码:
<html>
<body>
<form action="http://192.168.1.99/upload.php" method="post" enctype="multipart/form-data">
<input οnchange="document.forms[0].submit();" type="file" name="file" />
</form>
</body>
</html>
upload.php代码:
echo '<pre>';
print_r($_FILES["file"]);
echo '</pre>';
在Windows平台下浏览器测试返回如下结果:
在Android平台下测试我们的Demo界面结果截图。
关于acceptType, capture参数的补充:
TImageChooserListener在onExecute中只是简单的调用了Firemonkey的标准Action从相册选图片,并没有判断acceptType, capture参数。acceptType参数对应input标签的Accept属性,通过acceptType可以判断要选择的是图片,视频,音频等;capture代表需要开启的Capturing功能。
在android5.0及更高版本中,capture=“capture”代表支持Capturing功能,通过acceptType来判断要开启capture的设备是camera/camcorder/microphone。在android4.4及之前版本中capture的可能取值有:“”,“filesystem”,“camera”(相机),“camcorder”(摄像机),“microphone”(麦克风)。