2012-03-16 17 views
5

मैं ऐप बनाने के लिए वेबकिट के साथ मोनोड्रॉइड का उपयोग करने की कोशिश कर रहा हूं। मुझे एचटीएमएल पेज को जावास्क्रिप्ट विधि कॉल करने में समस्या आ रही है, जो मेरे ऐप में एक विधि के लिए एक इंटरफ़ेस होगा। जावा पर इसे कैसे करें, http://developer.android.com/guide/webapps/webview.html पर इस पर एक ट्यूटोरियल है, लेकिन एक ही कोड सी # पर काम नहीं करता है।मोनोड्रॉइड जावास्क्रिप्ट कॉल-बैक

call monodroid method from javascript example पर यह एक्सचेंज जेएनआई का उपयोग करने के बारे में कुछ धागे से जुड़ा हुआ है ताकि मोनोड्रॉइड और जावास्क्रिप्ट इंटरफ़ेस विधि के साथ समस्या हो सके, लेकिन मैं इसे काम करने में सक्षम नहीं हूं।

अब, मैं कुछ कोड निर्देशों का उपयोग करने के लिए कोशिश कर रहा हूँ, लेकिन कोई सफलता के लिए:

// 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 पृष्ठ पर किसी एक बटन पर क्लिक कर सकते हैं , जावा का आह्वान करें, जो तब सी # का आह्वान करेगा। यह काम नहीं किया है।

मैं सोच रहा था कि किसी को क्या समस्या है, या कोई अन्य विचार है, इसलिए मैं मोनोड्रॉइड का उपयोग कर सकता हूं ताकि वेबकिट में एचटीएमएल बटन को एसी # विधि कॉल करने के लिए लोड किया जा सके, या मेरे सी # कोड कॉल करने में सक्षम हो एक जावास्क्रिप्ट विधि।

उत्तर

20

चलो एक कदम पीछे ले जाएं। आप जावास्क्रिप्ट से सी # कोड का आह्वान करना चाहते हैं। यदि आपको बस इतना झुकाव नहीं लगता है, तो यह काफी सरल है।

सबसे पहले, चलो हमारे लेआउट 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 विशेषता प्रदान करते हैं जो जावास्क्रिप्ट खंड Foo.run() को आमंत्रित करता है। विधि का नाम नोट करें ("रन") और यह लोअरकेस 'आर' से शुरू होता है; हम बाद में इस पर वापस आ जाएंगे।

  1. हम view.Settings.JavaScriptEnabled=true साथ जावास्क्रिप्ट सक्षम:

    वहाँ नोट के तीन अन्य चीजें हैं। इसके बिना, हम जावास्क्रिप्ट का उपयोग नहीं कर सकते हैं।

  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. हम के उदाहरण के साथ जावास्क्रिप्ट नाम "Foo" को संबद्ध करने के लिए view.AddJavascriptInterface() पर कॉल करते हैं।

अब हम MyWebChromeClient वर्ग की जरूरत है:

class MyWebChromeClient : WebChromeClient { 
} 

ध्यान दें कि यह वास्तव में कुछ भी नहीं है, इसलिए यह सभी को और अधिक दिलचस्प है कि सिर्फ एक WebChromeClient उदाहरण का उपयोग करते हुए विफल जाने का कारण बनता है।: -:

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(); 
    } 
} 

यह सिर्फ एक संक्षिप्त संदेश से पता चलता है जब Run() विधि शुरू हो जाती है/

अंत में, हम "रोचक" बिट, Foo वर्ग जो "Foo" JavaScript वैरिएबल के साथ ऊपर जुड़े थे करने के लिए मिल ।

इस कैसे काम करता है

मोनो के दौरान एंड्रॉयड निर्माण प्रक्रिया के लिए, Android Callable Wrappers हर Java.Lang.Object उपवर्ग है, जो सभी ओवरराइड तरीकों और सभी कार्यान्वित जावा इंटरफेस वाणी के लिए बनाई गई हैं। यह, ऊपर 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") लागू किया गया था, इस जोड़ नहीं किया गया था सी # प्रकार के साथ जावास्क्रिप्ट "Foo" चर। यह जावास्क्रिप्ट "Foo" वैरिएबल को सी # प्रकार के उदाहरण से जुड़े एक एंड्रॉइड कॉलबल रैपर उदाहरण के साथ जोड़ रहा था। (आह, संकेत ...)

अब हम उपरोक्त "squinting" प्राप्त करते हैं। सी # Foo वर्ग ने Java.Lang.IRunnable इंटरफ़ेस को कार्यान्वित किया, जो java.lang.Runnable इंटरफ़ेस के लिए सी # बाध्यकारी है। इस प्रकार एंड्रॉइड कॉलबल रैपर इस प्रकार घोषित करता है कि यह java.lang.Runnable इंटरफ़ेस लागू करता है, और Runnable.run विधि घोषित करता है। एंड्रॉइड, और इस प्रकार जावास्क्रिप्ट-इन-एंड्रॉइड, आपके सी # प्रकारों को "देख" नहीं देता है। वे इसके बजाय एंड्रॉइड कॉल करने योग्य लपेटें देखते हैं। नतीजतन, जावा स्क्रिप्ट कोड बुला रहा है नहीं Foo.Run() (राजधानी 'आर'), यह Foo.run() (लोअरकेस 'आर') कॉल कर रहा है, क्योंकि प्रकार है कि एंड्रॉयड/जावास्क्रिप्ट की पहुंच है एक run() विधि वाणी, नहीं एक Run() विधि।

जब जावास्क्रिप्ट Foo.run() invokes और फिर Android प्रतिदेय आवरण scratch.webview.Foo.run() विधि शुरू हो जाती है जो, खुशी JNI है कि के माध्यम से, Foo.Run() सी # विधि के निष्पादन, जो वास्तव में है में परिणाम सभी आप पहली जगह में करना चाहता था।

लेकिन मुझे रन() पसंद नहीं है!

यदि आपको run() नामक जावास्क्रिप्ट विधि पसंद नहीं है, या आप पैरामीटर, या अन्य चीजों की किसी अन्य संख्या को पसंद नहीं करते हैं, तो आपकी दुनिया अधिक जटिल हो जाती है (कम से कम एंड्रॉइड 4.2 और [Export] समर्थन के लिए मोनो तक)। आपको दो चीजों में से एक करना होगा:

  1. मौजूदा बाध्य इंटरफ़ेस या वर्चुअल क्लास विधि खोजें जो आपके इच्छित नाम और हस्ताक्षर प्रदान करता है। फिर विधि को ओवरराइड करें/इंटरफ़ेस को कार्यान्वित करें, और चीजें ऊपर दिए गए उदाहरण के समान दिखती हैं।
  2. अपनी खुद की जावा क्लास रोल करें। अधिक जानकारी के लिए monodroid mailing list पर पूछें। यह जवाब लंबे समय तक हो रहा है।
+1

जो निश्चित रूप से एक अच्छा कठोर पेय के योग्य है! –

+0

उत्तर के लिए धन्यवाद! बहुत विस्तृत – d10sfan

+0

क्या इरनेबल आवश्यक है या केवल मूल प्रश्न के रूप में शामिल है? api 17 में @JavascriptInterface की नई आवश्यकता समाधान को कैसे प्रभावित करती है? – hultqvist

1

मैंने पाया कि AddJavascriptInterface के बाद लोडडाटा को कॉल करना आवश्यक था। साथ ही, इस परिवर्तन के साथ, WebChromeClient को असाइन करने की आवश्यकता नहीं थी। उदाहरण के लिए, संशोधित संस्करण नीचे ठीक चलाता है:

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

क्या कॉलबैक के माध्यम से सी # से जावास्क्रिप्ट में डेटा वापस करने का कोई तरीका है। जैसा कि आपने उल्लेख किया है, मैं जावास्क्रिप्ट से सी # फ़ंक्शन को कॉल कर सकता हूं। लेकिन, मूल कोड संसाधित करने के बाद मैं कुछ डेटा जावास्क्रिप्ट पर वापस भेजना चाहता हूं और इसे वेब पेज पर दिखा सकता हूं। क्या यह करने योग्य है? – 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

क्या कुछ मोड में कोई वापसी विवरण नहीं होना चाहिए? –

+2

अच्छा जवाब।ध्यान दें कि यदि आप एंड्रॉइड एपीआई 17+ को लक्षित करना चाहते हैं, तो आपको '[जावास्क्रिप्ट इंटरफेस]' के साथ-साथ '[निर्यात] 'के साथ अपनी' कुछ विधि (स्ट्रिंग) 'विधि को एनोटेट करना होगा। और वैकल्पिक रूप से आप '' निर्यात] 'एनोटेशन को '[निर्यात (" कुछ अन्य विधि ") में बदलकर निर्यात की गई विधि को देने के लिए नाम निर्दिष्ट कर सकते हैं। –

2

@ kogr का जवाब मेरे लिए काम किया। आपको इरनेबल से उत्तराधिकारी की आवश्यकता नहीं है।आप do को Mono.Android.Export.dll का संदर्भ जोड़ने और [निर्यात] के साथ उजागर सार्वजनिक वर्ग विधि को टैग करने की आवश्यकता है जैसा कि वह सुझाता है।

मेरे संपर्क में विधि शून्य, नहीं स्ट्रिंग, लेकिन अन्य कि तुलना में यह समान था लौट आए।

0

यह ठीक काम करता है। जावा में ऐसा करने के समान ही प्रक्रियाएं।

1

इस उत्तर को दो विकल्पों के पूरक के लिए जोड़ना "लेकिन मुझे रन() पसंद नहीं है!" jonp's asnwer में (क्योंकि मुझे रन पसंद नहीं है!)।

[Export] विशेषता, जो अब उपलब्ध है, को ज़ैमरिन का भुगतान संस्करण आवश्यक है।
यदि आप मुफ्त संस्करण का उपयोग कर रहे हैं, तो निम्न कार्यवाही आपके लिए काम कर सकती है।

Android.Webkit.WebChromeClient से विरासत, OnJsAlert विधि ओवरराइड, और अपने वेब पेज "कॉल तरीकों" alert() समारोह का उपयोग कर, संदेश में धारावाहिक डेटा पारित करके की है।

यह कोई समस्या नहीं होनी चाहिए क्योंकि हमें WebChromeClient की आवश्यकता है, और आपको वेब पेज स्रोत कोड पर भी पूर्ण नियंत्रण होना चाहिए।

उदाहरण के लिए:

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")) में स्थापित किया जा सकता पेज यह बताने के लिए यह अलर्ट-सक्षम ब्राउज़र के तहत चलता है।

2

what jonp said के अलावा, यदि आप उदाहरण JSON तार के लिए, आगे और पीछे करने के लिए एंड्रॉयड/Xamarin में जावास्क्रिप्ट और सी # तार भेजना चाहते हैं, और आप [निर्यात] विशेषता का उपयोग नहीं कर सकते हैं, तो आप

का उपयोग कर की कोशिश कर सकते
Android.Net.UrlQuerySanitizer.IValueSanitizer 

यह केवल एक ही विधि

string Sanitize(string value); 

जो जे एस और सी # के बीच तार (JSON) पारित करने के लिए आसान है। अपने जावास्क्रिप्ट कोड में कम केस sanitize का उपयोग करने के लिए मत भूलना।

संबंधित मुद्दे