Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android utilize V8 without WebView

I'm exercising executing javascript from Java. Rhino works very well for this on desktop, but has to fall back to (slow) interpreted mode on Android (due to dalvik being unable to execute the Java bytecode the Rhino JIT compiles).

Android has its built-in V8 javascript engine which is accessed internally via JNI and ought to give much better performance than Rhino; however, the only way I can find to access it is indirectly through a WebView.

Unfortunately, WebView requires a Context, and crashes with NPE with a null context, so I'm unable to even instantiate a dummy WebView to merely execute the code and return the result. The nature of my exercise doesn't really allow me to provide a Context for WebView, so I'm hoping perhaps there's something I'm overlooking.

Several of these V8Threads run in parallel, so it's not really feasible (as far as I'm aware) to add a WebView to my layout and hide it, as I don't believe a single WebView can execute functions in multiple threads.

private class V8Thread extends Thread
{
    private WebView webView;
    private String source;

    private double pi;
    private int i, j;

    public V8Thread(int i, int j)
    {
        pi = 0.0;
        this.i = i;
        this.j = j;

        source = "";

        try {
            InputStreamReader isReader = new InputStreamReader(assetManager.open("pi.js"));
            int blah = isReader.read();
            while (blah != -1)
            {
                source += (char)blah;
                blah = isReader.read();
            }

            webView = new WebView(null);
            webView.loadData(source, "text/html", "utf-8");
            webView.getSettings().setJavaScriptEnabled(true);
            webView.addJavascriptInterface(this, "V8Thread");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public double getResult()
    {
        return pi;
    }

    @Override
    public void run() 
    {
        webView.loadUrl("javascript:Androidpicalc("+i+","+j+")");
    }
}

Ideally there must be some supported way to call V8 directly, or at least execute javascript without requiring an actual WebView, as it seems a rather clunky and convoluted method just to run javascript code.

UPDATE

I've rearranged my code a bit, though unseen here is that now I am instantiating the V8Threads on the AsyncTasks's onPreExecute() while keeping everything else in doInBackground(). The source code is read in earlier in the program, so it's not redundantly re-read for each thread.

Because now the V8Thread is instantiated on the UI Thread, I can pass it the current view's Context (I'm using fragments so I can't just pass it "this"), so it no longer crashes.

private class V8Thread extends Thread
{
    private WebView webView;

    private double pi;
    private int i, j;

    public V8Thread(int i, int j)
    {
        pi = 0.0;
        this.i = i;
        this.j = j;

        source = "";

        webView = new WebView(v.getContext());
    }

    @SuppressWarnings("unused")
    public void setResult(String in)
    {
        Log.d("Pi",in);
    }

    public double getResult()
    {
        return pi;
    }

    @Override
    public void run()
    {
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(this, "V8Thread");
        webView.loadData(source, "text/html", "utf-8");
        //webView.loadUrl("javascript:Androidpicalc("+i+","+j+")");
        webView.loadUrl("javascript:test()");
        Log.d("V8Thread","Here");
    }
}

However, when executing, logcat spits out one per thread of the error "Can't get the viewWidth after the first layout" and the javascript code never executes. I know the thread fires completely, because the "Here" log message is sent. Here's the relevant test() function in the js code.

function test()
{
V8Thread.setResult("blah");
}

Working correctly, "blah" should show up four times in logcat, but it never shows up. Could be my source code is read incorrectly, but I doubt that.

Scanner scan = new Scanner(assetManager.open("pi.js"));
while (scan.hasNextLine()) source += scan.nextLine();

The only other thing I can imagine is that due to these aforementioned errors, the webView never actually gets around to executing the javascript.

I'll also add that pi.js contains only javascript, no HTML whatsoever. However, even when I wrap it in just enough HTML for it to qualify as a webpage, still no luck.

like image 829
Uejji Avatar asked Jul 30 '11 02:07

Uejji


People also ask

How do you override a WebView?

If you want to override certain methods, you have to create a custom WebView class which extends WebView . Also, when you are inflating the WebView , make sure you are casting it to the correct type which is CustomWebView . CustomWebView webView = (CustomWebView) findViewById(R. id.

Can you run JavaScript on Android?

Javascript works on android by default, but if you have turned it off then you can follow these instructions to enable javascript on android phones.

What is WebView in android studio?

The WebView class is an extension of Android's View class that allows you to display web pages as a part of your activity layout. It does not include any features of a fully developed web browser, such as navigation controls or an address bar. All that WebView does, by default, is show a web page.

What is Android Webkit?

The androidx. webkit library is a static library you can add to your Android application in order to use android. webkit APIs that are not available for older platform versions. Requirements. The minimum sdk version to use this library is 14.


3 Answers

You can create a new V8 Context via its API and use that to execute your JavaScript, look into https://android.googlesource.com/platform/external/v8 include directory which contains two C++ header files. Link against the libwebcore.so (compiled from https://android.googlesource.com/platform/external/webkit) library via the NDK, nothing special.

v8::Persistent<v8::Context> context = v8::Persistent<v8::Context>::New(v8::Context::New());
context->Enter();

Refer to https://developers.google.com/v8/get_started which will work on Android. Just make sure the device actually ships with V8 (some older devices ship with JSC [JavaScript Core]).

like image 59
soulseekah Avatar answered Sep 20 '22 19:09

soulseekah


A bit of a late response but it may be useful to anyone stumbling upon this question. I used the J2V8 library which is a Java wrapper on Google's V8 engine. This library comes with pre-compiled V8 binaries for x86 and armv7l Android devices. It work seamlessly. See here for tutorials. Just keep in mid that since pure V8 is just an Ecmascript engine, there is no DOM element available.

like image 6
Angelo Avatar answered Sep 19 '22 19:09

Angelo


I found this really nifty open source ECMAScript compliant JS Engine completely written in C called duktape

Duktape is an embeddable Javascript engine, with a focus on portability and compact footprint.

You'd still have to go through the ndk-jni business, but it's pretty straight forward. Just include the duktape.c and duktape.h from the distributable source here(If you don't want to go through the build process yourself) into the jni folder, update the Android.mk and all that stuff.

Here's a sample C snippet to get you started.

#include "duktape.h"

JNIEXPORT jstring JNICALL
Java_com_ndktest_MainActivity_evalJS
(JNIEnv * env, jobject obj, jstring input){
    duk_context *ctx = duk_create_heap_default();
    const char *nativeString = (*env)->GetStringUTFChars(env, input, 0);
    duk_push_string(ctx, nativeString);
    duk_eval(ctx);
    (*env)->ReleaseStringUTFChars(env, input, nativeString);
    jstring result = (*env)->NewStringUTF(env, duk_to_string(ctx, -1));
    duk_destroy_heap(ctx);
    return result;
}

Good luck!

like image 2
Pawan Kumar Avatar answered Sep 21 '22 19:09

Pawan Kumar