Mono для Android, поле ввода файла WebView не работает - PullRequest
4 голосов
/ 21 февраля 2012

У меня есть веб-страница, которая используется для загрузки файлов.Пользователь выбирает файл с <input type="file" /> и нажимает кнопку отправки, все работает отлично.Теперь мне нужно создать приложение для Android (на C # с моно для Android), которое содержит простое веб-представление и должно работать как веб-версия.

Но я наткнулся на проблему - когда я нажимаю кнопку Choose file, тофайл не открывается.

Я погуглил эту проблему несколько дней, но не нашел решения.Похоже, что обходной путь на платформе Java , но он не работает на C #.

У кого-нибудь есть идеи, как заставить его работать?

Ответы [ 2 ]

2 голосов
/ 22 февраля 2012

У меня есть идея, как заставить это работать. Отчасти это болотный стандарт «как связать виртуальный метод», а отчасти это чистое безудержное зло.

Во-первых, нам нужен «посредник». Поскольку 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.

Я же говорил, что это было чистое безудержное зло, не так ли?

0 голосов
/ 16 ноября 2017

step1 = Загрузка файлов будет работать, нам нужно дать разрешение на чтение / запись в манифесте Android. в Main Activity.cs

step2=
private Action<int, Result, Intent> resultCallbackvalue;

public void StartActivity(Intent intent, int requestCode, Action<int, Result, Intent> resultCallback)
{
this.resultCallbackvalue = resultCallback;
StartActivityForResult(intent, requestCode);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (this.resultCallbackvalue != null)
{
this.resultCallbackvalue(requestCode, resultCode, data);
this.resultCallbackvalue = null;
}
}
step3= add ExtendedChromeClient,cs Inheriting from : WebChromeClient

private static int filechooser = 1;
private IValueCallback message;
private MainActivity activity = null;

  public ExtendedChromeClient(MainActivity context)
    {
        this.activity = context;
    }

  public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
    {
        this.message = filePathCallback;
        Intent chooserIntent = fileChooserParams.CreateIntent();
        chooserIntent.AddCategory(Intent.CategoryOpenable);
        this.activity.StartActivity(Intent.CreateChooser(chooserIntent, "File Chooser"), filechooser, this.OnActivityResult);
        return true;
    }

  private void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        if (data != null)
        {
            if (requestCode == filechooser)
            {
                if (null == this.message)
                {
                    return;
                }

                this.message.OnReceiveValue(WebChromeClient.FileChooserParams.ParseResult((int)resultCode, data));
                this.message = null;
            }
        }
    }
...