65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin.Forms Android 使用 J2V8 在没有 WebView 的情况下执行 JavaScript

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017年6月3日

CPOL

3分钟阅读

viewsIcon

18369

downloadIcon

225

这篇文章演示了如何使用 Xamarin.Forms 在 Android 上执行 JavaScript。

  • 下载 J2V8BL.zip - 4.2 MB  [这包含 Android 绑定库,您可以直接用于 Xamarin.Android。因此,您不必按照本文构建另一个。]

引言

最近,我需要在 Xamarin.Forms 项目中执行 JavaScript 代码。由于 WebView 占用移动设备更多的资源,我正在寻找一种实现需求的方法。 我发现 J2V8 非常适合这样做。 当我构建代码并使用 J2V8 时,我遇到了一些问题。 但最后,我解决了这些问题,您在使用它时可能会遇到错误。

Go

在 Xamrin.Forms 解决方案中,构建一个 Android 绑定库 (BL)。在 BL 项目中,将 "j2v8_android-3.0.5.aar" 文件添加到 "Jars" 文件夹中。 将 j2v8_android-3.0.5.aar 的构建操作设置为 LibraryProjectZip。现在 构建 BL 项目,您可能会遇到 3 个错误和其他警告。显然,我们现在不必理会这些警告。

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(10,99,10,121): error CS0738: 'V8Map' does not implement interface member 'IMap.EntrySet()'. 'V8Map.EntrySet()' cannot implement 'IMap.EntrySet()' because it does not have the matching return type of 'ICollection'.

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(10,99,10,121): error CS0738: 'V8Map' does not implement interface member 'IMap.KeySet()'. 'V8Map.KeySet()' cannot implement 'IMap.KeySet()' because it does not have the matching return type of 'ICollection'.

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(10,99,10,121): error CS0535: 'V8Map' does not implement interface member 'IMap.Put(Object, Object)'

这些错误是:问题:类未实现接口方法。 要解决这些问题,我遵循了 此处 的文档。 但可能需要一段时间才能弄清楚,因为没有演示项目。

让我们修复这些错误。

1).

在 Visual Studio 的输出窗口中,找到第一个错误并双击它。 现在它应该打开生成的 cs 文件 "..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs"。找到 "EntrySet()" 和 "KeySet ()" 方法。

static IntPtr id_entrySet;
// Metadata.xml XPath method reference: path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='entrySet' and count(parameter)=0]"
[Register ("entrySet", "()Ljava/util/Set;", "GetEntrySetHandler")]
public virtual unsafe global::System.Collections.Generic.ICollection<global::Java.Util.IMapEntry> EntrySet ()
{
    if (id_entrySet == IntPtr.Zero)
        id_entrySet = JNIEnv.GetMethodID (class_ref, "entrySet", "()Ljava/util/Set;");
    try {

        if (((object) this).GetType () == ThresholdType)
             return global::Android.Runtime.JavaSet<global::Java.Util.IMapEntry>.FromJniHandle (JNIEnv.CallObjectMethod (((global::Java.Lang.Object) this).Handle, id_entrySet), JniHandleOwnership.TransferLocalRef);
        else
             return global::Android.Runtime.JavaSet<global::Java.Util.IMapEntry>.FromJniHandle (JNIEnv.CallNonvirtualObjectMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, "entrySet", "()Ljava/util/Set;")), JniHandleOwnership.TransferLocalRef);
    } finally {
    }
}

//...............................................................................//

static IntPtr id_keySet;
// Metadata.xml XPath method reference: path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='keySet' and count(parameter)=0]"
[Register ("keySet", "()Ljava/util/Set;", "GetKeySetHandler")]
public virtual unsafe global::System.Collections.ICollection<global::Java.Util.IMapEntry> KeySet ()
{
    if (id_keySet == IntPtr.Zero)
        id_keySet = JNIEnv.GetMethodID (class_ref, "keySet", "()Ljava/util/Set;");
    try {

        if (((object) this).GetType () == ThresholdType)
            return global::Android.Runtime.JavaSet<global::Com.Eclipsesource.V8.V8Value>.FromJniHandle (JNIEnv.CallObjectMethod (((global::Java.Lang.Object) this).Handle, id_keySet), JniHandleOwnership.TransferLocalRef);
        else
            return global::Android.Runtime.JavaSet<global::Com.Eclipsesource.V8.V8Value>.FromJniHandle (JNIEnv.CallNonvirtualObjectMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, "keySet", "()Ljava/util/Set;")), JniHandleOwnership.TransferLocalRef);
        } finally {
    }
}

2). 

在 BL 项目中,展开 "Transforms" 文件夹并打开 Metadata.xml 文件。 在 xml 文件中添加 attr 节点。 XPaths 来自上述方法。

<metadata>
  <attr
    path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='entrySet' and count(parameter)=0]"
    name="managedReturn">
    System.Collections.ICollection
  </attr>

  <attr
    path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='keySet' and count(parameter)=0]"
    name="managedReturn">
    System.Collections.ICollection
  </attr>
</metadata>

现在 构建 BL 项目。您将获得最后一个错误。

3).

在生成的 Com.Eclipsesource.V8.Utils.V8Map.cs 文件中,将 V8Map 部分类添加到 Com.Eclipsesource.V8.Utils 命名空间中。并实现 IMap.Put 接口。

public partial class V8Map[...]


public partial class V8Map : global::Java.Lang.Object, global::Com.Eclipsesource.V8.IReleasable, global::Java.Util.IMap
{
    //Metadata.xml XPath method reference: path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='put' and count(parameter)=2 and parameter[1][@type='com.eclipsesource.v8.V8Value'] and parameter[2][@type='V']]"
    [Register("put", "(Lcom/eclipsesource/v8/V8Value;Ljava/lang/Object;)Ljava/lang/Object;", "GetPut_Lcom_eclipsesource_v8_V8Value_Ljava_lang_Object_Handler")]
    Java.Lang.Object global::Java.Util.IMap.Put(Java.Lang.Object key, Java.Lang.Object value)
    {
        return Put((global::Com.Eclipsesource.V8.V8Value)key, value);
    }
}

现在 构建 BL 项目。在 Visual Studio 的输出窗口中,您将获得 4 个错误。

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(228,13,228,206): error CS0266: Cannot implicitly convert type 'System.Collections.Generic.ICollection<Java.Util.IMapEntry>' to 'System.Collections.ICollection'. An explicit conversion exists (are you missing a cast?)

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(230,13,230,289): error CS0266: Cannot implicitly convert type 'System.Collections.Generic.ICollection<Java.Util.IMapEntry>' to 'System.Collections.ICollection'. An explicit conversion exists (are you missing a cast?)

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(300,13,300,213): error CS0266: Cannot implicitly convert type 'System.Collections.Generic.ICollection<Com.Eclipsesource.V8.V8Value>' to 'System.Collections.ICollection'. An explicit conversion exists (are you missing a cast?)

1>..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs(302,13,302,296): error CS0266: Cannot implicitly convert type 'System.Collections.Generic.ICollection<Com.Eclipsesource.V8.V8Value>' to 'System.Collections.ICollection'. An explicit conversion exists (are you missing a cast?)

现在我们需要将强制转换添加到代码中。双击错误并打开生成的代码。在 "EntrySet()" 和 "KeySet ()" 方法中添加 "as global::System.Collections.ICollection" 以进行强制转换。

static IntPtr id_entrySet;
// Metadata.xml XPath method reference: path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='entrySet' and count(parameter)=0]"
[Register ("entrySet", "()Ljava/util/Set;", "GetEntrySetHandler")]
public virtual unsafe global::System.Collections.ICollection EntrySet ()
{
    if (id_entrySet == IntPtr.Zero)
        id_entrySet = JNIEnv.GetMethodID (class_ref, "entrySet", "()Ljava/util/Set;");
    try {

    if (((object) this).GetType () == ThresholdType)
        return global::Android.Runtime.JavaSet<global::Java.Util.IMapEntry>.FromJniHandle (JNIEnv.CallObjectMethod (((global::Java.Lang.Object) this).Handle, id_entrySet), JniHandleOwnership.TransferLocalRef) as global::System.Collections.ICollection;
    else
        return global::Android.Runtime.JavaSet<global::Java.Util.IMapEntry>.FromJniHandle (JNIEnv.CallNonvirtualObjectMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, "entrySet", "()Ljava/util/Set;")), JniHandleOwnership.TransferLocalRef) as global::System.Collections.ICollection;
    } finally {
    }
}


//........................................................................//

static IntPtr id_keySet;
// Metadata.xml XPath method reference: path="/api/package[@name='com.eclipsesource.v8.utils']/class[@name='V8Map']/method[@name='keySet' and count(parameter)=0]"
[Register ("keySet", "()Ljava/util/Set;", "GetKeySetHandler")]
public virtual unsafe global::System.Collections.ICollection<global::Java.Util.IMapEntry> KeySet ()
{
    if (id_keySet == IntPtr.Zero)
        id_keySet = JNIEnv.GetMethodID (class_ref, "keySet", "()Ljava/util/Set;");
    try {

        if (((object) this).GetType () == ThresholdType)
            return global::Android.Runtime.JavaSet<global::Com.Eclipsesource.V8.V8Value>.FromJniHandle (JNIEnv.CallObjectMethod (((global::Java.Lang.Object) this).Handle, id_keySet), JniHandleOwnership.TransferLocalRef) as global::System.Collections.ICollection;
        else
            return global::Android.Runtime.JavaSet<global::Com.Eclipsesource.V8.V8Value>.FromJniHandle (JNIEnv.CallNonvirtualObjectMethod (((global::Java.Lang.Object) this).Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, "keySet", "()Ljava/util/Set;")), JniHandleOwnership.TransferLocalRef) as global::System.Collections.ICollection;
        } finally {
    }
}

现在 构建 BL 项目。它应该成功构建。请记住备份生成的 cs 文件,因为如果您清理项目,它将重新生成 cs 文件。

从 Android 项目添加引用 BL 项目。在 MainActivity.cs 中添加以下代码以演示执行 JS。

       
       ......

       LoadApplication(new App());

       JS.ExecuteJS();   
    }
}
    
    //..................
    public class JS
    {
        public static void ExecuteJS()
        {
            Com.Eclipsesource.V8.V8 runtime = Com.Eclipsesource.V8.V8.CreateV8Runtime();
            int result = runtime.ExecuteIntegerScript(""
                  + "var hello = 'hello, ';\n"
                  + "var world = 'world!';\n"
                  + "hello.concat(world).length;\n");

            runtime.Release();
        }
    }

有关如何执行 JS 的更多信息,请参见 此链接。您可以将 IScriptEngine 添加到 PCL 中,并从 Android 项目中实现它作为依赖项。然后您可以从 PCL 中使用它。

关注点

对于 UWP 项目,您可以使用 Jurassic ScriptEngine。 实际上,我最初在我的 Xamarin.Forms 项目中使用它,它在 UWP 上运行良好。 但它只在 Android 上运行,链接器是 None,这导致 apk 包非常大(两倍)。 我尝试了不同的方法使其工作,但失败了。 如果您能使其成功运行,请告诉我。

Xamarin.Forms Android 使用 J2V8 在没有 WebView 的情况下执行 JavaScript - CodeProject - 代码之家
© . All rights reserved.