在 WebView 中注入 Javascript 桥接器

Posted

技术标签:

【中文标题】在 WebView 中注入 Javascript 桥接器【英文标题】:Injecting Javascript bridge in WebView 【发布时间】:2016-12-12 06:53:55 【问题描述】:

我想从 android 网页中提取一些内容。我知道有一些库可以解析 html,但我想也许我可以作弊。

这就是我正在做的事情..

    使用应用程序上下文以编程方式创建 WebView,因此它不必显示在 UI 中。 加载网页 附加 JS 接口 注入一些 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 在 UI 中不可见,因此很难判断哪个部分出现故障。我只知道调用了第一个 Log,但从未显示 JavascriptInterface 中的 Log 和 Toast。

我正在尝试做的事情是否可能?如果是这样,我做错了什么?如果没有,为什么不呢?

编辑

将视图卡在 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

感谢大家的建议,您提供了帮助,但到目前为止它仍然无法正常工作,因此除非有人在接下来的一个小时内提供工作代码,否则 Nainal 将获得一半的赏金。如果是这样,我不确定是否允许我再对其进行赏金,因为问题仍未解决。

在考虑了此页面上的建议并尝试了手册中我不太了解的几个设置之后,这是我迄今为止的完整代码。

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 分。 Nainal 获得了最后的赏金,因为他是最有帮助的,而不是为了解决问题。

【问题讨论】:

帖子编辑次数过多,“问题”很难追。在所有这些改进之后,您目前遇到的问题是什么? 其他 7 个人很好理解了这个问题。有关更多信息,请参阅接受的答案。 我只是想赶上并提供帮助,因为在我撰写本文时您还没有公认的答案,而且信息到处乱七八糟。希望您也了解解决方案以及根本原因。 谢谢。解决方案很简单。我没有添加互联网许可。 Android 不是我的主要包。菜鸟错误:) 【参考方案1】:

WebChromeClient 中使用onProgressChanged() 怎么样?

我已将一些代码从 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);");
            
        
    );

变化是你在progress==100而不是onPageFinished()时调用javascript

【讨论】:

【参考方案2】:

这是一个经过清理的版本,可最大限度地减少不需要的代码。这在 API 级别 18 和 23 模拟器(以及我的 6.0.1 手机)上运行。 webView 永远不会添加到视图层次结构中。无论如何,toast 显示了从站点中提取的 HTML。使用 Java 8 针对 API 25 编译。

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_
    android:layout_
    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>

【讨论】:

当我回到家时,我真的很想试试这个。太感谢了。将在当天结束前报告。 你知道吗......我认为我从未在清单中包含互联网权限。这可能一直是问题所在。如果这行得通,我可能必须先增加赏金再给你.. 原来它与此用途无关,但我发现这很有趣。您有时想添加对void(0); 的调用以显示注入的JavaScript 的结果。看到这两个链接:***.com/questions/27523040/…***.com/questions/1291942/… 成功了吗?很高兴我找到了这个问题。我想自己在一个项目中尝试一下。 对不起,我还没有机会尝试,新年假期,但如果我早点回家,明天会试一试,否则这将是一个放松的好方法当我周二早上回去工作时开始编码。【参考方案3】:

webview.addJavascriptInterface(new jsInterface(), "Droid"); 必须在 webview.loadUrl("https://example.com"); 之前

然后使用 Webview 监听器。 onFinish() 方法.. 然后在 onFinish 方法中注入你的webview.loadUrl("javascript: window.onload=function() Droid.showToast('testing!'); ");

我已经做了大量的 webview 注入修改 web.. 我认为它应该可以工作..

编辑 在加载 webview 时使用 chrome://inspect/#devices 检查您的应用

【讨论】:

我尝试了几种不同的方法,但似乎 javascript 无法访问被注入页面上的 dom。页面的 html 对 javascript 来说是空白的。这是为什么呢? 感谢chrome://inspect/#devices,这是一个方便的工具。如果您有任何进一步的建议,我已将赏金增加到 100 分。谢谢。【参考方案4】:

我突然想到了几件事。

    应在加载任何 URL 之前附加 JavaScript 界面。

    第二个 loadURL window.onload 可能在原始 URL 加载后分配。从onPageFinished 方法内部调用setWebViewClient() 和调用Droid.showToast('testing!'); 会更有意义。

    @JavascriptInterface 不在主 UI 线程上运行,这会阻止您的 toast 运行。

    第二次编辑的innerHTML 代码不起作用的问题与第 2 点有关。您在同步单个块中进行调用,而它应该在页面 dom 加载 AKA onPageFinished() 之后

【讨论】:

我尝试了几种不同的方法,但似乎 javascript 无法访问被注入页面上的 dom。页面的 html 在 javascript 中显示为空白。这是为什么呢? 您介意发送您当前活动的 github 要点(以及您认为可能相关的其他课程)吗?一旦我能看到你正在做的事情的全貌,我应该能够相当容易地解决这个问题。您还应该使用桌面上的chrome://inspect/#devices 工具来调试问题,甚至使用它进行Droid.showToast('test') 调用!。 我已将我的全部活动添加到问题中(所以通常不喜欢从 github 等非现场资源提供代码)。如果您有任何进一步的建议会有所帮助,我还将赏金增加并扩大到 100 分。 我在一个新项目中使用了您的代码,它似乎对我来说工作正常。我进一步优化了它,并提供了一些有用的 cmets。发现了几个小问题:日志和 toast 延迟了 2 秒,webview 存在轻微的内存泄漏,以及主线程上有几个冗余的可运行文件。我还添加了一种可视化检查 webview 的方法,以确保它按预期工作这是更新后的类 - gist.github.com/anonymous/13d7cbe3d318d4c7d64a24e139026be5 android.permission.INTERNET 权限不是 WebView 运行所必需的。它只会给出一条通用错误消息,说明它无法连接到 example.com【参考方案5】:

请试试这个,它正在调用 javascript 函数并显示 toast 消息。

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

        

它不会在 UI 中显示 webView,因为 webview 没有在 xml 布局中定义。

【讨论】:

啊,把它放在一个新线程中是有意义的。期待尝试这个。谢谢! 它似乎正在工作,因为正在调用 JS 接口,但它似乎无法与 DOM 交互...我尝试这样做,但得到一个空字符串:@987654322 @ 你必须启用 DOM 存储为-- webView.getSettings().setDomStorageEnabled(true); 问题仍未解决,但我已经失去了 50 个代表,如果我不给你全部赏金,你将获得一半,所以我把它给了你。我的理解是setDomStorageEnabled 与 indexeddb 和 websql 有关,但我还是尝试了它,但它仍然没有用。再加上其他所有人都给出了与您相同的建议,并且您是唯一提供完整示例的人。谢谢你的帮助。 我添加了额外的赏金,如果您能提供任何进一步的建议,您有机会在上次赏金中获得的 50 分的基础上再赢取 100 分。

以上是关于在 WebView 中注入 Javascript 桥接器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 React-Native Webview 的注入 JavaScript 道具中使用 RegExp?

如何在 Android Webview 中加载网页之前注入 javascript?

在 React Native WebView 中从注入的 JavaScript 接收消息

专业技术揭秘安卓浏览器如何注入javascript脚本

UIWebView 与 WebView Javascript 注入

有没有办法将JavaScript注入WebView供以后使用?