- Download J2V8BL.zip - 4.2 MB [This contains the Android Bindings Library you can directly use it for Xamarin.Android. So you don't have to follow this article to build another one.]
Introduction
Recently, I need to execute JavaScript code in an Xamarin.Forms Project. Due to the WebView is taking more resources from the mobile device. I was seeking an approach to achieve my requirement. And I found the J2V8 was good to do so. When I build my code and use the J2V8, I faced some problems. But finally, I solved these problems and you may get the errors when you are trying to use it.
Go
In the Xamrin.Forms solution, build an Android Binding Library(BL). In the BL project, add the "j2v8_android-3.0.5.aar" file to the "Jars" folder. Set the Build Action for j2v8_android-3.0.5.aar" to LibraryProjectZip. Now build the BL project, you may get 3 errors and others warnings. Obviously, we don't have to take care of the warnings at this time.
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)'
These errors are Problem: Class does not implement interface method. To fix these problems, I followed the documentation from here. But it may take sometime to figure it out since there is no demo project.
Let's fix the errors.
1).
In the output window from Visual Studio, find the first error and double click it. Now it should open the generated cs file "..\obj\Debug\generated\src\Com.Eclipsesource.V8.Utils.V8Map.cs". Find the "EntrySet()
" and the "KeySet ()
" method.
static IntPtr id_entrySet;
[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;
[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).
In the BL project, expand the "Transforms" folder and open the Metadata.xml file. Add attr
nodes inside the xml file. The XPaths are from the above methods.
<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>
Now build the BL project. You will get the last error.
3).
In the generated Com.Eclipsesource.V8.Utils.V8Map.cs file, Add the V8Map
partial class inside the Com.Eclipsesource.V8.Utils
namespace. And implemente the IMap.Put
interface.
public partial class V8Map[...]
public partial class V8Map : global::Java.Lang.Object, global::Com.Eclipsesource.V8.IReleasable, global::Java.Util.IMap
{
[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);
}
}
Now build the BL project. In the output window from Visual Studio, you will get 4 errors.
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?)
Now we need to add cast into the code. Double click the error and open the generated code. Add "as global::System.Collections.ICollection
" to do the cast inside the "EntrySet()
" and the "KeySet ()
" method.
static IntPtr id_entrySet;
[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;
[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 {
}
}
Now build the BL project. It should build successfully. Keep in mind that backup the generated cs file because if you clean the project, it will regenerate the cs files.
Add the reference BL project from Android project. Add the following code in the MainActivity.cs to execute the JS as a demo.
......
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();
}
}
For more infomation about how to execute JS, please see this. You can add IScriptEngine into the PCL, and implement it from the Android project as a Dependency. Then you can use it from the PCL.
Points of Interest
For UWP project, you may use the Jurassic ScriptEngine. Actually I used it in my Xamarin.Forms project at first, it is working good on UWP. But it is working on Android only the Linker is None, this leads that the apk package is very large(double). I tried different ways to make it work, but failed. If you can make it work successfully, please let me know.