У меня есть идея, как заставить это работать. Отчасти это болотный стандарт «как связать виртуальный метод», а отчасти это чистое безудержное зло.
Во-первых, нам нужен «посредник». Поскольку WebChromeClient
не объявляет метод openFileChooser()
, нам нужно объявить версию, которая делает это, с именем OpenFileWebChromeClient
. Он объявляет метод virtual
OpenFileChooser
и предоставляет для него привязку, чтобы его можно было переопределить:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.OS;
using Android.Webkit;
namespace Scratch.FileUpload
{
[Register ("android/webkit/WebChromeClient", DoNotGenerateAcw=true)]
class OpenFileWebChromeClient : WebChromeClient {
static IntPtr id_openFileChooser;
[Register ("openFileChooser", "(Landroid/webkit/ValueCallback;)V", "GetOpenFileChooserHandler")]
public virtual void OpenFileChooser (IValueCallback uploadMsg)
{
if (id_openFileChooser == IntPtr.Zero)
id_openFileChooser = JNIEnv.GetMethodID (ThresholdClass, "openFileChooser", "(Landroid/webkit/ValueCallback;)V");
if (GetType () == ThresholdType)
JNIEnv.CallVoidMethod (Handle, id_openFileChooser, new JValue (JNIEnv.ToJniHandle (uploadMsg)));
else
JNIEnv.CallNonvirtualVoidMethod (Handle, ThresholdClass, id_openFileChooser, new JValue (JNIEnv.ToJniHandle (uploadMsg)));
}
#pragma warning disable 0169
static Delegate cb_openFileChooser;
static Delegate GetOpenFileChooserHandler ()
{
if (cb_openFileChooser == null)
cb_openFileChooser = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr>) n_OpenFileChooser);
return cb_openFileChooser;
}
static void n_OpenFileChooser (IntPtr jnienv, IntPtr native__this, IntPtr native_uploadMsg)
{
OpenFileWebChromeClient __this = Java.Lang.Object.GetObject<OpenFileWebChromeClient> (native__this, JniHandleOwnership.DoNotTransfer);
var uploadMsg = Java.Lang.Object.GetObject<IValueCallback> (native_uploadMsg, JniHandleOwnership.DoNotTransfer);
__this.OpenFileChooser (uploadMsg);
}
#pragma warning restore 0169
}
}
Далее, поскольку в C # отсутствуют анонимные внутренние классы, нам нужен явный класс с именем MyOpenFileWebChromeClient
здесь:
namespace Scratch.FileUpload {
class MyOpenFileWebChromeClient : OpenFileWebChromeClient {
Action<IValueCallback> cb;
public MyOpenFileWebChromeClient(Action<IValueCallback> cb)
{
this.cb = cb;
}
public override void OpenFileChooser (IValueCallback uploadMsg)
{
cb (uploadMsg);
}
}
Порт Activity ~ идентичен упомянутому вами сообщению в блоге, за исключением того, что он использует MyOpenFileWebChromeClient
вместо анонимного внутреннего класса. Я также обновил некоторую логику для отображения URI, который получает OnActivityResult()
:
namespace Scratch.FileUpload {
[Activity (Label = "Scratch.FileUpload", MainLauncher = true)]
public class Activity1 : Activity
{
private WebView wv;
private IValueCallback mUploadMessage;
const int FilechooserResultcode = 1;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
wv = new WebView (this);
wv.SetWebViewClient(new WebViewClient());
wv.SetWebChromeClient(new MyOpenFileWebChromeClient(uploadMsg => {
mUploadMessage = uploadMsg;
var intent = new Intent (Intent.ActionGetContent);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("image/*");
StartActivityForResult(Intent.CreateChooser(intent, "File Chooser"),
FilechooserResultcode);
}));
SetHtml(null);
SetContentView(wv);
}
void SetHtml(string filename)
{
string html = @"<html>
<body>
<h1>Hello, world!</h1>
<p>Input Box:</p>
<input type=""file"" />
<p>URI: " + filename + @"
</body>
</html>";
wv.LoadData(html, "text/html", "utf-8");
}
protected override void OnActivityResult (int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult (requestCode, resultCode, data);
if (requestCode == FilechooserResultcode) {
if (mUploadMessage == null)
return;
var result = data == null || resultCode != Result.Ok
? null
: data.Data;
SetHtml(result.ToString());
mUploadMessage.OnReceiveValue(result);
mUploadMessage = null;
}
}
}
}
К сожалению, сейчас пришло время совершить чистое безудержное зло. Проблема с приведенным выше объявлением для MyOpenFileWebChromeClient
заключается в том, что он не будет работать по той же причине, по которой блог M0S не смог использовать @Override
в объявлении анонимного внутреннего класса: android.jar
, против которого вы строите свое приложение. не объявляет метод openFileChooser()
.
В процессе сборки будет генерироваться Android Callable Wrappers , который должен содержать действительный код Java. Проблема заключается в том, что сгенерированный код использует @Override
для переопределенных методов и методов интерфейса, в результате чего Android Callable Wrapper для MyOpenFileWebChromeClient
:
package scratch.fileupload;
public class MyOpenFileWebChromeClient
extends android.webkit.WebChromeClient
{
static final String __md_methods;
static {
__md_methods =
"n_openFileChooser:(Landroid/webkit/ValueCallback;)V:GetOpenFileChooserHandler\n" +
"";
mono.android.Runtime.register ("Scratch.FileUpload.MyOpenFileWebChromeClient, Scratch.FileUpload, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", MyOpenFileWebChromeClient.class, __md_methods);
}
@Override
public void openFileChooser (android.webkit.ValueCallback p0)
{
n_openFileChooser (p0);
}
private native void n_openFileChooser (android.webkit.ValueCallback p0);
java.util.ArrayList refList;
public void monodroidAddReference (java.lang.Object obj)
{
if (refList == null)
refList = new java.util.ArrayList ();
refList.add (obj);
}
public void monodroidClearReferences ()
{
if (refList != null)
refList.clear ();
}
}
Очевидно, что @Override
на MyOpenFileWebChromeClient.openFileChooser()
вызовет ошибку компилятора, так как же нам заставить эту работу работать? Предоставляя нашу собственную @Override
аннотацию!
package scratch.fileupload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Поместите вышесказанное в файл с именем Override.java
, добавьте его в проект и установите для его действия Build значение AndroidJavaSource
.
Полученный проект работает, потому что мы предоставляем пользовательскую аннотацию @Override
в том же пакете, что и тип MyOpenFileWebChromeClient
. (Следовательно, это требует, чтобы вы знали, каким будет сгенерированное имя пакета, и чтобы вы предоставили отдельную аннотацию @Override
для каждого пакета, для которого вы делаете это.) Типы в том же пакете имеют приоритет над импортированными именами, даже имена, поступающие из java.lang
, поэтому наша пользовательская аннотация @Override
не только компилируется, но и используется MyOpenFileWebChromeClient
вызываемой программой-оберткой для Android вместо предпочтения java.lang.Override
.
Я же говорил, что это было чистое безудержное зло, не так ли?