最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Injecting Javascript bridge in WebView - Stack Overflow

programmeradmin6浏览0评论

I want to pull some stuff from a webpage in Android. I know there are libraries to parse HTML, but I thought maybe I could cheat a little bit.

Here's what I'm doing..

  1. Programmatically create a WebView using the application context so it doesn't have to be displayed in the UI.
  2. Load the web page
  3. Attach the JS Interface
  4. Inject some Javascript to interact with the host application

Here's some code...

    public void getLatestVersion(){
        Log.e("Testing", "getLatestVersion called...");
        WebView webview = new WebView(context.getApplicationContext());
        webview.loadUrl("");
        webview.addJavascriptInterface(new jsInterface(), "Droid");
        webview.loadUrl("javascript: window.onload=function(){ Droid.showToast('testing!'); }");
    }

    class jsInterface{
        @JavascriptInterface
        public void showToast(String message){
            Log.e("Testing", message);
            Toast.makeText(context, message, Toast.LENGTH_LONG).show();
        }
    }

Since the WebView is not visible in the UI, it's hard to tell which part is breaking. All I know is that the first Log called is called, but the Log and Toast from the JavascriptInterface are never shown.

Is what I'm trying to do even possible? If so, what am I doing wrong? If not, why not?

EDIT

Stuck the view in the UI for testing, apparently the second call to loadUrl is not working. No matter what Javascript I try to inject, it doesn't work.

EDIT 2

I feel dumb for forgetting to enable Javascript, but it's still not working.. I've added the following lines..

    WebSettings webSettings = webview.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webview.loadUrl("javascript: alert('farts0');");

    webview.loadUrl("");
    setContentView(webview);

    String js = "document.body.innerHTML = '<p>test<p>';";
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        webview.evaluateJavascript(js, null);
    }else{
        webview.loadUrl("javascript: "+js);
    }

EDIT 3

Thanks for everyone's suggestions, you've been helpful but so far it's still not working so unless someone provides working code in the next hour Nainal will get half the bounty. If so I'm not sure if I'll be allowed to place another bounty on it as the problem is still unresolved.

Here's my plete code so far after taking into account suggestions on this page and trying several settings from the manual that I don't really understand.

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    WebView webView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = new WebView(getApplicationContext());


        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
            webView.getSettings().setAllowFileAccessFromFileURLs(true);

        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
            webView.getSettings().setAllowUniversalAccessFromFileURLs(true);

        webView.getSettings().setDomStorageEnabled(true);
        webView.getSettings().setJavaScriptEnabled(true);
        try {
            webView.setWebContentsDebuggingEnabled(true);
        }catch(Exception e){}
        webView.setWebChromeClient(new WebChromeClient());
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                webView.setVisibility(View.GONE);

            }
            @Override
            public void onPageFinished(final WebView view, String url) {
                Log.e("checking", "MYmsg");
                Log.e("content-url", webView.getSettings().getAllowContentAccess()?"y":"n");
                webView.loadUrl("javascript: void window.CallToAnAndroidFunction.setVisible(document.getElementsByTagName('body')[0].innerHTML);");



            }
        });
        webView.setVisibility(View.INVISIBLE);
        webView.addJavascriptInterface(new myJavaScriptInterface(), "CallToAnAndroidFunction");
        webView.loadUrl("");
    }
    public class myJavaScriptInterface {
        @JavascriptInterface
        public void setVisible(final String aThing) {
            Handler handler = new Handler();
            Runnable runnable = new Runnable() {
                @Override
                public void run() {

                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            webView.setVisibility(View.VISIBLE);
                            Toast.makeText(MainActivity.this, "Reached JS: "+aThing, Toast.LENGTH_LONG).show();

                        }
                    });


                }
            };handler.postDelayed(runnable,2000);

        }}



}

Edit 4

Started a new bounty and increased the reward to 100pts. Nainal got the last bounty for being the most helpful, not for solving the problem.

I want to pull some stuff from a webpage in Android. I know there are libraries to parse HTML, but I thought maybe I could cheat a little bit.

Here's what I'm doing..

  1. Programmatically create a WebView using the application context so it doesn't have to be displayed in the UI.
  2. Load the web page
  3. Attach the JS Interface
  4. Inject some Javascript to interact with the host application

Here's some code...

    public void getLatestVersion(){
        Log.e("Testing", "getLatestVersion called...");
        WebView webview = new WebView(context.getApplicationContext());
        webview.loadUrl("https://example.");
        webview.addJavascriptInterface(new jsInterface(), "Droid");
        webview.loadUrl("javascript: window.onload=function(){ Droid.showToast('testing!'); }");
    }

    class jsInterface{
        @JavascriptInterface
        public void showToast(String message){
            Log.e("Testing", message);
            Toast.makeText(context, message, Toast.LENGTH_LONG).show();
        }
    }

Since the WebView is not visible in the UI, it's hard to tell which part is breaking. All I know is that the first Log called is called, but the Log and Toast from the JavascriptInterface are never shown.

Is what I'm trying to do even possible? If so, what am I doing wrong? If not, why not?

EDIT

Stuck the view in the UI for testing, apparently the second call to loadUrl is not working. No matter what Javascript I try to inject, it doesn't work.

EDIT 2

I feel dumb for forgetting to enable Javascript, but it's still not working.. I've added the following lines..

    WebSettings webSettings = webview.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webview.loadUrl("javascript: alert('farts0');");

    webview.loadUrl("https://example.");
    setContentView(webview);

    String js = "document.body.innerHTML = '<p>test<p>';";
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        webview.evaluateJavascript(js, null);
    }else{
        webview.loadUrl("javascript: "+js);
    }

EDIT 3

Thanks for everyone's suggestions, you've been helpful but so far it's still not working so unless someone provides working code in the next hour Nainal will get half the bounty. If so I'm not sure if I'll be allowed to place another bounty on it as the problem is still unresolved.

Here's my plete code so far after taking into account suggestions on this page and trying several settings from the manual that I don't really understand.

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    WebView webView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = new WebView(getApplicationContext());


        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
            webView.getSettings().setAllowFileAccessFromFileURLs(true);

        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
            webView.getSettings().setAllowUniversalAccessFromFileURLs(true);

        webView.getSettings().setDomStorageEnabled(true);
        webView.getSettings().setJavaScriptEnabled(true);
        try {
            webView.setWebContentsDebuggingEnabled(true);
        }catch(Exception e){}
        webView.setWebChromeClient(new WebChromeClient());
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                webView.setVisibility(View.GONE);

            }
            @Override
            public void onPageFinished(final WebView view, String url) {
                Log.e("checking", "MYmsg");
                Log.e("content-url", webView.getSettings().getAllowContentAccess()?"y":"n");
                webView.loadUrl("javascript: void window.CallToAnAndroidFunction.setVisible(document.getElementsByTagName('body')[0].innerHTML);");



            }
        });
        webView.setVisibility(View.INVISIBLE);
        webView.addJavascriptInterface(new myJavaScriptInterface(), "CallToAnAndroidFunction");
        webView.loadUrl("http://example.");
    }
    public class myJavaScriptInterface {
        @JavascriptInterface
        public void setVisible(final String aThing) {
            Handler handler = new Handler();
            Runnable runnable = new Runnable() {
                @Override
                public void run() {

                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            webView.setVisibility(View.VISIBLE);
                            Toast.makeText(MainActivity.this, "Reached JS: "+aThing, Toast.LENGTH_LONG).show();

                        }
                    });


                }
            };handler.postDelayed(runnable,2000);

        }}



}

Edit 4

Started a new bounty and increased the reward to 100pts. Nainal got the last bounty for being the most helpful, not for solving the problem.

Share Improve this question edited Dec 29, 2016 at 16:14 I wrestled a bear once. asked Aug 5, 2016 at 19:13 I wrestled a bear once.I wrestled a bear once. 23.4k20 gold badges72 silver badges121 bronze badges 4
  • The post was edited too many times, it's hard to follow the "problem". What is the current problem that you have after all these improvements? – Andrei Mărcuţ Commented Jan 3, 2017 at 13:39
  • 7 other people understood the question just fine.. see the accepted answer for more info. – I wrestled a bear once. Commented Jan 3, 2017 at 14:28
  • I only wanted to catch-up and help, since you didn't have an accepted answer at the time of my writing and the information was scrambled all over the place. Hope you also understand the solution and what was the root cause. – Andrei Mărcuţ Commented Jan 3, 2017 at 14:35
  • Thank you. The solution was simple. I didnt add internet permission. Android isnt my main bag. Noob mistake :) – I wrestled a bear once. Commented Jan 3, 2017 at 15:01
Add a ment  | 

5 Answers 5

Reset to default 5 +50

Please try this, it is calling the javascript function and showing toast message also.

public class Main3Activity extends AppCompatActivity {
     WebView webView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        webView = new WebView(getApplicationContext());
        webView.getSettings().setJavaScriptEnabled(true);

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                webView.setVisibility(View.GONE);

            }
            @Override
            public void onPageFinished(final WebView view, String url) {
                Log.e("checking", "MYmsg");
                webView.loadUrl("javascript:(function() { " +
                        "document.body.innerHTML = '<p>test<p>';" + "})()");
                webView.loadUrl("javascript: window.CallToAnAndroidFunction.setVisible()");



            }
        });
        webView.setVisibility(View.INVISIBLE);
        webView.addJavascriptInterface(new myJavaScriptInterface(), "CallToAnAndroidFunction");
        webView.loadUrl("https://example.");
    }
    public class myJavaScriptInterface {
        @JavascriptInterface
        public void setVisible() {

            Handler handler = new Handler();
            Runnable runnable = new Runnable() {
                @Override
                public void run() {

                    Main3Activity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            webView.setVisibility(View.VISIBLE);
                            Log.e("Testing", "no");
                            Toast.makeText(Main3Activity.this, "Reached JS", Toast.LENGTH_LONG).show();

                        }
                    });


                }
            };handler.postDelayed(runnable,2000);

        }}
}

It will not show webView in the UI, as webview is not defined in xml layout.

Here is a cleaned up version, minimizing unneeded code. This runs on API level 18 and 23 emulators (and my 6.0.1 phone). The webView is never added to the view hierarchy. The toast shows the HTML pulled from the site anyway. Compiled against API 25 using Java 8.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = new WebView(getApplicationContext());

        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebChromeClient(new WebChromeClient());

        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(final WebView view, String url) {
                webView.loadUrl("javascript: void AndroidHook.showToast(document.getElementsByTagName('body')[0].innerHTML);");
            }
        });

        webView.addJavascriptInterface(new JSInterface(), "AndroidHook");
        webView.loadUrl("http://example.");
    }

    public class JSInterface {
        @JavascriptInterface
        public void showToast(final String html) {

            MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, "Reached JS: " + html, Toast.LENGTH_LONG).show();
                }
            });
        }
    }
}

Here's the layout.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android./apk/res/android"
    xmlns:tools="http://schemas.android./tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="16dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp"
    tools:context=".foo.jsinjectiontest.MainActivity">

</RelativeLayout>

And finally the manifest.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android./apk/res/android"
    package=".foo.jsinjectiontest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-permission android:name="android.permission.INTERNET"/>

</manifest>

A couple of things that pop out to me.

  1. The JavaScript interface should be attached BEFORE loading any URLs.

  2. The second loadURL window.onload might be assigned AFTER the original URL has loaded. It would make more sense to call setWebViewClient() and call Droid.showToast('testing!'); from inside the onPageFinished method.

  3. The @JavascriptInterface doesn't run on the main UI thread which will stop your toasts from running.

  4. The issue with your second edit's innerHTML code not working is related to point 2. You're making your calls in a synchronous single block, whereas it should be AFTER the page dom has loaded AKA onPageFinished()

webview.addJavascriptInterface(new jsInterface(), "Droid"); have to e before webview.loadUrl("https://example.");

Then use Webview Listener. onFinish() method.. then inject your webview.loadUrl("javascript: window.onload=function(){ Droid.showToast('testing!'); }"); in onFinish method

i already do tons of webview injection modifying web.. i thinks its should work..

EDIT
use chrome://inspect/#devices to inspect your app when webview is load

How about use onProgressChanged() in WebChromeClient?

I've changed some code from Edit 3 to like this,

webView.setWebChromeClient(new WebChromeClient(){
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
            if(newProgress == 100){
                webView.loadUrl("javascript: void window.CallToAnAndroidFunction.setVisible(document.getElementsByTagName('body')[0].innerHTML);");
            }
        }
    });

The change is that you invoke javascript when progress==100 instead of onPageFinished()

发布评论

评论列表(0)

  1. 暂无评论