Внедрение моста Javascript в WebView

Я хочу вытащить кое-что с веб-страницы в Android. Я знаю, что есть библиотеки для синтаксического анализа HTML, но я подумал, что, может быть, смогу немного схитрить.

Вот что я делаю ..

  1. Программно создайте WebView, используя контекст приложения, чтобы его не нужно было отображать в пользовательском интерфейсе.
  2. Загрузите веб-страницу
  3. Прикрепите интерфейс JS
  4. Внедрить Javascript для взаимодействия с основным приложением

Вот код ...

    public void getLatestVersion(){
        Log.e("Testing", "getLatestVersion called...");
        WebView webview = new WebView(context.getApplicationContext());
        webview.loadUrl("https://example.com");
        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();
        }
    }

Поскольку WebView не отображается в пользовательском интерфейсе, трудно сказать, какая часть неисправна. Все, что я знаю, это то, что вызывается первый вызываемый журнал, но журнал и тост из JavascriptInterface никогда не отображаются.

Возможно ли то, что я пытаюсь сделать? Если да, то что я делаю не так? Если нет, то почему?

ИЗМЕНИТЬ

Застрял представление в UI для тестирования, видимо второй вызов loadUrl не работает. Независимо от того, какой Javascript я пытаюсь ввести, это не работает.

ИЗМЕНИТЬ 2

Я чувствую себя глупо из-за того, что забыл включить Javascript, но он все еще не работает .. Я добавил следующие строки ..

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

    webview.loadUrl("https://example.com");
    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);
    }

ИЗМЕНИТЬ 3

Спасибо за все предложения, вы были полезны, но пока он все еще не работает, поэтому, если кто-то не предоставит рабочий код в течение следующего часа, Наинал получит половину награды. Если это так, я не уверен, что мне разрешат назначить за него еще одну награду, поскольку проблема все еще не решена.

Вот мой полный код после того, как я учел предложения на этой странице и попробовал несколько настроек из руководства, которые я действительно не понимаю.

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.com");
    }
    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);

        }}



}

Изменить 4

Началась новая награда и увеличена награда до 100 очков. Наинал получил последнюю награду за то, что был самым полезным, а не за решение проблемы.


person I wrestled a bear once.    schedule 05.08.2016    source источник
comment
Сообщение редактировалось слишком много раз, уследить за проблемой сложно. Какая проблема у вас возникла после всех этих улучшений?   -  person Andrei Mărcuţ    schedule 03.01.2017
comment
Еще 7 человек прекрасно поняли вопрос .. см. Принятый ответ для получения дополнительной информации.   -  person I wrestled a bear once.    schedule 03.01.2017
comment
Я только хотел наверстать упущенное и помочь, так как на момент написания у вас не было принятого ответа, а информация была разбросана повсюду. Надеюсь, вы также понимаете решение и его первопричину.   -  person Andrei Mărcuţ    schedule 03.01.2017
comment
Спасибо. Решение было простым. Я не добавил разрешение на доступ в Интернет. Android - не моя главная сумка. Ошибка нуба :)   -  person I wrestled a bear once.    schedule 03.01.2017


Ответы (5)


Вот очищенная версия, минимизирующая ненужный код. Это работает на эмуляторах API уровня 18 и 23 (и на моем телефоне 6.0.1). WebView никогда не добавляется в иерархию представлений. В тосте отображается HTML-код, который все равно был извлечен с сайта. Скомпилировано с использованием API 25 с использованием 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.com");
    }

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

Вот макет.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/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="com.foo.jsinjectiontest.MainActivity">

</RelativeLayout>

И, наконец, манифест.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.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>
person Hod    schedule 29.12.2016
comment
Я очень рад попробовать это, когда вернусь домой. Спасибо огромное. сообщим к концу дня. - person I wrestled a bear once.; 30.12.2016
comment
вы знаете, что .... я не думаю, что когда-либо включал разрешения в Интернете в манифест. это могло быть проблемой все время. мне, возможно, придется увеличить награду, прежде чем я дам ее вам, если это сработает .. - person I wrestled a bear once.; 30.12.2016
comment
Оказывается, это не имеет отношения к этому использованию, но я нашел это увлекательным. Иногда вам нужно добавить вызов void(0);, чтобы отобразить результаты внедренного JavaScript. См. Эти две ссылки: stackoverflow.com/questions/27523040 / stackoverflow.com/questions/1291942/ - person Hod; 30.12.2016
comment
Это сработало? Рад, что нашел этот вопрос. Я сам хочу попробовать это в проекте. - person Hod; 31.12.2016
comment
извините, у меня еще не было возможности попробовать это, в отпуске на новый год, но попробую завтра, если я вернусь домой достаточно рано, иначе это будет хороший способ вернуться к кодированию, когда я вернусь к работе утро вторника. - person I wrestled a bear once.; 02.01.2017
comment
Ты мужчина, @Hod! Это прекрасно сработало! Большое вам спасибо за вашу помощь. - person I wrestled a bear once.; 03.01.2017

Пожалуйста, попробуйте это, он вызывает функцию javascript и также показывает всплывающее сообщение.

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.com");
    }
    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);

        }}
}

Он не будет отображать webView в пользовательском интерфейсе, поскольку webview не определен в макете xml.

person Nainal    schedule 22.12.2016
comment
ах, есть смысл поместить это в новую ветку. с нетерпением жду возможности попробовать это. Благодарность! - person I wrestled a bear once.; 22.12.2016
comment
похоже, что он работает, потому что вызывается интерфейс JS, но он, похоже, не может взаимодействовать с DOM ... Я пробовал это сделать, но получил пустую строку: webView.loadUrl("javascript: window.CallToAnAndroidFunction.setVisible(document.getElementsByTagName('body')[0].innerHTML);"); - person I wrestled a bear once.; 27.12.2016
comment
вы должны включить хранилище DOM как - webView.getSettings (). setDomStorageEnabled (true); - person Nainal; 28.12.2016
comment
проблема все еще не решена, но я уже потерял свои 50 репутации, и вы получите половину из них, если я не награжу вас полной наградой, поэтому я просто дал ее вам. Насколько я понимаю, setDomStorageEnabled имеет отношение к indexeddb и websql, но я все равно попробовал, и он все еще не работал. плюс все остальные как бы дали те же предложения, что и вы, и вы единственный, кто предоставил полный пример. Спасибо за помощь. - person I wrestled a bear once.; 29.12.2016
comment
Я добавил дополнительную награду, у вас есть шанс выиграть еще 100 очков помимо 50, полученных от последней награды, если вы можете предоставить какие-либо дополнительные предложения. - person I wrestled a bear once.; 29.12.2016
comment
То есть вы говорите, что не получаете html от getElementsByTagName? Что вы тестируете? Тост показывает мне html. - person Hod; 29.12.2016
comment
Ваш код работает нормально, я получаю HTML-код в виде тоста. Конечно, вы получаете доступ к веб-странице, поэтому необходимо включить разрешение в Интернет, так что вы правы, что забыли включить его. Думаю, проблема была в этом. - person Nainal; 30.12.2016

Пара вещей, которые мне всплывают.

  1. Интерфейс JavaScript должен быть прикреплен ПЕРЕД загрузкой любых URL-адресов.

  2. Второй loadURL window.onload может быть назначен ПОСЛЕ загрузки исходного URL. Было бы разумнее вызывать setWebViewClient() и вызывать Droid.showToast('testing!'); изнутри метода onPageFinished.

  3. @JavascriptInterface не запускается в основном потоке пользовательского интерфейса, что останавливает запуск ваших тостов.

  4. Проблема с неработающим innerHTML кодом вашего второго редактирования связана с пунктом 2. Вы выполняете свои вызовы в синхронном единичном блоке, тогда как это должно быть ПОСЛЕ загрузки страницы dom AKA onPageFinished()

person Passer by    schedule 25.12.2016
comment
Я пробовал несколько разных способов, но не похоже, что javascript может получить доступ к dom на странице, на которую вводится. HTML-код страницы отображается в JavaScript пустым. это почему? - person I wrestled a bear once.; 27.12.2016
comment
Не возражаете ли вы отправить на github суть вашей текущей деятельности (вместе с другими классами, которые, по вашему мнению, могут быть актуальными)? Я смогу решить эту проблему довольно легко, как только увижу полную картину того, что вы делаете. Вам также следует использовать chrome://inspect/#devices инструмент на своем рабочем столе для отладки проблемы и даже Droid.showToast('test') вызовов из него !. - person Passer by; 28.12.2016
comment
Я добавил свою полную активность к вопросу (SO обычно недоволен предоставлением кода из внешних источников, таких как github). Я также увеличил и расширил награду до 100 очков, если у вас есть дополнительные предложения, они будут полезны. - person I wrestled a bear once.; 29.12.2016
comment
Я использовал ваш код в новом проекте, и, похоже, он мне подходит. Я немного оптимизировал его и дал пару полезных комментариев. Обнаружена пара незначительных проблем: журнал и тост были задержаны на 2 секунды, произошла небольшая утечка памяти с веб-просмотрами и пара дублирующих исполняемых файлов в основном потоке. Я также добавил способ визуально проверить веб-просмотр, чтобы убедиться, что он работает должным образом. Вот обновленный класс - gist. github.com/anonymous/13d7cbe3d318d4c7d64a24e139026be5 - person Passer by; 31.12.2016
comment
Разрешение android.permission.INTERNET не требуется для работы WebView. Это просто выдаст общее сообщение об ошибке, в котором говорится, что он не может подключиться к example.com. - person Passer by; 31.12.2016

webview.addJavascriptInterface(new jsInterface(), "Droid"); должен прийти до webview.loadUrl("https://example.com");

Затем используйте прослушиватель Webview. onFinish () .. затем введите свой webview.loadUrl("javascript: window.onload=function(){ Droid.showToast('testing!'); }"); в метод onFinish

Я уже делаю тонны инъекций веб-просмотра, изменяющих сеть .. я думаю, что это должно работать ..

ИЗМЕНИТЬ
используйте chrome://inspect/#devices для проверки вашего приложения при загрузке веб-просмотра

person ZeroOne    schedule 27.12.2016
comment
Я пробовал несколько разных способов, но не похоже, что javascript может получить доступ к dom на странице, на которую вводится. HTML-код страницы отображается в JavaScript пустым. это почему? - person I wrestled a bear once.; 27.12.2016
comment
Спасибо за chrome://inspect/#devices, это удобный инструмент. Я увеличил награду до 100 баллов, если у вас есть дальнейшие предложения. Спасибо. - person I wrestled a bear once.; 29.12.2016

Как насчет использования onProgressChanged() в WebChromeClient?

Я изменил код с Edit 3 на этот,

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

Изменение заключается в том, что вы вызываете javascript, когда progress==100 вместо onPageFinished()

person Choim    schedule 02.01.2017