2012-03-16 5 views
5

私はウェブキットでモノドイドを使ってアプリケーションを作成しようとしています。私は、htmlページにjavascriptメソッドを呼び出させることに問題があります。これは、アプリケーションのメソッドへのインタフェースになります。このチュートリアルはhttp://developer.android.com/guide/webapps/webview.htmlでjavaでやっていますが、C#では同じコードが動作しません。Monodroid Javascriptコールバック

call monodroid method from javascript exampleでのこの交換は、JNIを使​​用してモノドイドとJavaScriptインターフェイスメソッドの問題を回避する方法についていくつかのスレッドをリンクしましたが、動作させることができませんでした。

今、私はいくつかのコード命令を使用しようとしているが、ノー成功にしています:

// Java 
class RunnableInvoker { 
Runnable r; 
public RunnableInvoker (Runnable r) { 
this.r = r; 
} 
// must match the javascript name: 
public void doSomething() { 
r.run(); 
} 
} 

From C#, you'd create a class that implements Java.Lang.IRunnable: 

// C# 
class SomeAction : Java.Lang.Object, Java.Lang.IRunnable { 
Action a; 
public void SomeAction(Action a) {this.a = a;} 
public void Run() {a();} 
} 

Then to wire things up: 

// The C# action to invoke 
var action = new SomeAction(() => {/* ... */}); 

// Create the JavaScript bridge object: 
IntPtr RunnableInvoker_Class = JNIEnv.FindClass("RunnableInvoker"); 
IntPtr RunnableInvoker_ctor = JNIEnv.GetMethodID (RunnableInvoker_Class, "<init>", "(Ljava/lang/Runnable;)V"); 
IntPtr instance = JNIEnv.NewObject(RunnableInvoker_Class, RunnableInvoker_ctor, new JValue (action)); 

// Hook up WebView to JS object 
web_view.AddJavascriptInterface (new Java.Lang.Object(instance, JniHandleOwnership.TransferLocalRef), "Android"); 

このコードは、それを作ることができるようになっているので、アプリ内のhtmlページ上の誰かがボタンをクリックすることができますjavaを呼び出して、C#を呼び出します。これは働かなかった。

誰かが問題のアイデアを持っていたのか、それとも別のアイデアなので、Webkitで読み込まれたHTMLボタンを使ってac#メソッドを呼び出したり、C#コード呼び出しを行うことができますかjavascriptメソッド。

答えて

20

もう一度元気にしましょう。 JavaScriptからC#コードを呼び出したいとします。ちょうどそれほど気をつけなければ、それはかなり簡単です。

まず、私たちのレイアウトXMLから始めましょう:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
     android:orientation="vertical" 
     android:layout_width="fill_parent" 
     android:layout_height="fill_parent"> 
    <WebView 
      android:id="@+id/web" 
      android:layout_width="fill_parent" 
      android:layout_height="wrap_content" 
    /> 
</LinearLayout> 

今、私たちは自分自身をアプリケーションに取得することができます。

[Activity (Label = "Scratch.WebKit", MainLauncher = true)] 
public class Activity1 : Activity 
{ 
    const string html = @" 
<html> 
<body> 
<p>This is a paragraph.</p> 
<button type=""button"" onClick=""Foo.run()"">Click Me!</button> 
</body> 
</html>"; 

    protected override void OnCreate (Bundle bundle) 
    { 
     base.OnCreate (bundle); 

     // Set our view from the "main" layout resource 
     SetContentView (Resource.Layout.Main); 

     WebView view = FindViewById<WebView>(Resource.Id.web); 
     view.Settings.JavaScriptEnabled = true; 
     view.SetWebChromeClient (new MyWebChromeClient()); 
     view.LoadData (html, "text/html", null); 
     view.AddJavascriptInterface(new Foo(this), "Foo"); 
    } 
} 

Activity1.htmlは、我々が表示さしようとしているHTMLコンテンツです。唯一の興味深い点は、/button/@onClick属性を指定して、JavaScriptフラグメントFoo.run()を呼び出すことです。メソッド名( "run")に注意し、小文字の「r」で始まることを確認してください。私たちはこれに後で戻ります。

ノートの他の三つの点があります。

  1. 我々はview.Settings.JavaScriptEnabled=trueでJavaScriptを有効にしてください。これがなければJavaScriptを使用できません。
  2. view.SetWebChromeClient()のインスタンスをMyWebChromeClientクラス(後で定義)のインスタンスと呼びます。これは「カーゴ・カルト・プログラミング」のビットです:もしそれを提供しなければ、物事は機能しません。どうしてか分かりません。私たちが代わりにview.SetWebChromeClient(new WebChromeClient())一見同等の操作を行う場合は、実行時にエラーが発生します。

    E/Web Console(4865): Uncaught ReferenceError: Foo is not defined at data:text/html;null,%3Chtml%3E%3Cbody%3E%3Cp%3EThis%20is%20a%20paragraph.%3C/p%3E%3Cbutton%20type=%22button%22%20onClick=%22Foo.run()%22%3EClick%20Me!%3C/button%3E%3C/body%3E%3C/html%3E:1 
    

    これはどちらか私には意味がありません。

  3. 私たちはと呼んで、JavaScript名"Foo"をクラスFooのインスタンスに関連付けます。

今、私たちはMyWebChromeClientクラス必要があります。

class MyWebChromeClient : WebChromeClient { 
} 

注それは、実際には何もしませんので、それだけでWebChromeClientインスタンスを使用すると、物事が失敗する原因となることを、すべてのより多くの興味深いことを。: -/

最後に、私たちは「面白い」ビットを取得、"Foo" JavaScript変数と関連していた上Fooクラス:Run()メソッドが呼び出されたときに

class Foo : Java.Lang.Object, Java.Lang.IRunnable { 

    public Foo (Context context) 
    { 
     this.context = context; 
    } 

    Context context;   

    public void Run() 
    { 
     Console.WriteLine ("Foo.Run invoked!"); 
     Toast.MakeText (context, "This is a Toast from C#!", ToastLength.Short) 
     .Show(); 
    } 
} 

それだけで短いメッセージを示し。これはAndroidのビルドプロセスのためのモノの間に

仕組み

は、Android Callable Wrappersはすべてオーバーライドされたメソッドおよびすべての実装のJavaインタフェースを宣言し、すべてのJava.Lang.Objectサブクラスのために作成されます。これはAndroidの呼び出し可能ラッパーで、その結果、上記Fooクラスを含む:

package scratch.webkit; 

public class Foo 
    extends java.lang.Object 
    implements java.lang.Runnable 
{ 
    @Override 
    public void run() 
    { 
     n_run(); 
    } 

    private native void n_run(); 

    // details omitted for clarity 
} 

view.AddJavascriptInterface(new Foo(this), "Foo")が呼び出されたとき、これは、C#タイプではJavaScript "Foo"変数を関連付けるありませんでした。これは、JavaScript "Foo"変数を、C#タイプのインスタンスに関連付けられたAndroid Callable Wrapperインスタンスに関連付けました。 (ああ、回り道...)

今、私たちは前述の "squinting"になります。 C#FooクラスはJava.Lang.IRunnableインターフェイスを実装しました。これはjava.lang.RunnableインターフェイスのC#バインディングです。 Android Callable Wrapperはこうしてそれがjava.lang.Runnableインターフェイスを実装していると宣言し、Runnable.runメソッドを宣言します。 Android、つまりAndroidのJavaScriptは、C#の種類を「認識」しません。代わりにAndroid Callable Wrappersが表示されます。したがって、run()メソッド、ではなく、Run()メソッドを宣言するために、JavaScript/JavaScriptがアクセスするタイプであるため、JavaScriptコードはFoo.Run()(大文字の 'R')を呼び出さず、Foo.run()(小文字の「r」)を呼び出しています。

JavaScriptがFoo.run()を起動すると、その後、Androidの呼び出し可能ラッパーscratch.webview.Foo.run()メソッドはJNIで喜びを通して、本当にあなたが最初の場所でやってみたかったのすべてであるFoo.Run() C#メソッドの実行中に結果呼び出されます。

私はrun()を好きではありません!

JavaScriptメソッドがrun()であることが気に入らない場合や、パラメータなどを追加する必要がある場合、世界ははるかに複雑になります(少なくともAndroid 4.2のMonoと[Export]のサポートまで)。

  1. 必要な名前と署名を提供する既存のバインドされたインターフェイスまたは仮想クラスメソッドを検索します。その後、メソッドをオーバーライドしてインターフェイスを実装してください。上の例のように見えます。
  2. 独自のJavaクラスをロールバックします。詳細については、monodroid mailing listにお尋ねください。この回答は長くなってきています。
+1

これは確かに良い硬い飲み物に値します! –

+0

答えをありがとう!非常に詳細 – d10sfan

+0

IRunnableが必要ですか、それとも元の質問に含まれているだけですか? api 17の@JavascriptInterfaceの新しい要件はどのようにソリューションに影響しますか? – hultqvist

1

AddJavascriptInterfaceの後にLoadDataを呼び出す必要があることがわかりました。また、この変更により、WebChromeClientの割り当ては必要ありませんでした。例えば、修正されたバージョンは以下の罰金に実行されます。kogrの答え@

public class Activity1 : Activity 
{ 
    const string html = @" 
    <html> 
    <body> 
    <p>This is a paragraph.</p> 
    <button type=""button"" onClick=""Foo.run()"">Click Me!</button> 
    </body> 
    </html>"; 

    protected override void OnCreate (Bundle bundle) 
    { 
     base.OnCreate (bundle); 

     SetContentView (Resource.Layout.Main); 

     WebView view = FindViewById<WebView> (Resource.Id.web); 
     view.Settings.JavaScriptEnabled = true; 

     view.AddJavascriptInterface (new Foo (this), "Foo"); 
     view.LoadData (html, "text/html", null); 
    } 

    class Foo : Java.Lang.Object, Java.Lang.IRunnable 
    { 
     Context context; 

     public Foo (Context context) 
     { 
      this.context = context; 
     } 

     public void Run() 
     { 
      Toast.MakeText (context, "This is a Toast from C#!", ToastLength.Short).Show(); 
     } 
    } 
} 
+0

コールバックを介してC#からjavascriptにデータを戻す方法はありますか?私はあなたが言及したようにJavascriptからC#関数を呼び出すことができます。しかし、ネイティブコードを処理した後、私はいくつかのデータをJavascriptに戻してWebページに表示したいと思っています。それは可能ですか? – User382

9
// C# 
// !!! 
using Java.Interop; // add link to Mono.Android.Export 

public class Activity1 : Activity 
{ 
    const string html = @" 
    <html> 
    <body> 
    <p>This is a paragraph.</p> 
    <button type=""button"" onClick=""Foo.SomeMethod('bla-bla')"">Click Me!</button> 
    </body> 
    </html>"; 

    class Foo : Java.Lang.Object // do not need Java.Lang.IRunnable 
    { 
     Context context; 

     public Foo (Context context) 
     { 
      this.context = context; 
     } 

     [Export] // !!! do not work without Export 
     [JavascriptInterface] // This is also needed in API 17+ 
     public string SomeMethod(string param) 
     { 
      Toast.MakeText (context, "This is a Toast from C#!" + param, ToastLength.Short).Show(); 
     } 
    } 

    protected override void OnCreate (Bundle bundle) 
    { 
     base.OnCreate (bundle); 

     SetContentView (Resource.Layout.Main); 

     WebView view = FindViewById<WebView> (Resource.Id.web); 
     view.Settings.JavaScriptEnabled = true; 

     view.AddJavascriptInterface (new Foo (this), "Foo"); 
     view.LoadData (html, "text/html", null); 
    } 
} 
+0

すばらしい答えです。 – antfx

+1

SomeMethodにreturn文があるべきではありませんか? –

+2

良い答え。あなたは、Android API 17+をターゲットにする場合、あなたは '[JavascriptInterface]' 'と同様に[エクスポート]を使用して'のsomeMethod(文字列) 'メソッドを'注釈を付ける必要があることに注意してください。オプションで、 '[Export]'アノテーションを '[Export(" SomeOtherMethod ")]'に変更することで、エクスポートされたメソッドに与える名前を指定することもできます。 –

2

は私のために働きました。 IRunnableから継承する必要はありません。は、Mono.Android.Export.dllへの参照を追加し、公開されている公開クラスメソッドに[エクスポート]をタグ付けする必要があります。

私の公開方法は無効、ない文字列が、それは同じだったこと以外を返しました。

0

これで問題なく動作します。 Javaでこれを行うのと同じ手順。

1

この回答を追加すると、「しかし、私はrun()が好きではありません!」という2つのオプションを補完します。 jonp's asnwer(私は走りが好きではないので!)。

現在利用可能な[Export]属性には、有料版のXamarinが必要です。
無料版を使用している場合は、次の回避策が役立ちます。 Android.Webkit.WebChromeClientから

継承、OnJsAlertメソッドをオーバーライドし、メッセージにシリアル化されたデータを渡し、alert()機能を使って、あなたのウェブページ「コール・メソッド」を持っています。

とにかくWebChromeClientが必要なので、これは問題ありません。また、Webページのソースコードを完全に制御することになっています。

例:Webページで

private class AlertableWebChromeClient : Android.Webkit.WebChromeClient 
{ 
    private const string XAMARIN_DATA_ALERT_TAG = "XAMARIN_DATA\0"; 

    public override bool OnJsAlert(Android.Webkit.WebView view, string url, string message, Android.Webkit.JsResult result) 
    { 
     if (message.StartsWith(XAMARIN_DATA_ALERT_TAG)) 
     { 
      //Parse 'message' for data - it can be XML, JSON or whatever 

      result.Confirm(); 
      return true; 
     } 
     else 
     { 
      return base.OnJsAlert(view, url, message, result); 
     } 
    } 
} 

if (window.xamarin_alert_callback != null) { 
    alert("XAMARIN_DATA\0" + JSON.stringify(data_object)); 
} 

xamarin_alert_callbackがフラグとして使用され、ページを知ることができるように、さまざまな方法(例えばWebView.LoadURL(javascript:...)またはWebView.AddJavascriptInterface(something, "xamarin_alert_callback"))に設定することができますアラートが有効なブラウザで実行されます。あなたは、インスタンスのJSON文字列のために、アンドロイド/ XamarinでJavaScriptとC#に前後に文字列を送りたい、とあなたは[エクスポート]属性を使用できない場合

2

what jonp saidに加えて、あなたは

を使用して試みることができます
Android.Net.UrlQuerySanitizer.IValueSanitizer 

それだけJSとC#の間の文字列(JSON)を通過させるための便利である一つの方法

string Sanitize(string value); 

を有しています。 JavaScriptコードで小文字の墨塗りを使用することを忘れないでください。