【问题标题】:Android: Using WebView outside an Activity contextAndroid:在 Activity 上下文之外使用 WebView
【发布时间】:2013-09-22 18:54:46
【问题描述】:

我正在尝试通过后台 IntentService 实现 Web Scraping,该 IntentService 会定期抓取网站,而不会在用户手机上显示视图。

  • 因为我必须在加载的页面上调用一些 javascript,所以我不能使用任何 HttpGet 等。
  • 因此,我必须使用只能在 UI 线程上运行的 WebView 实例。
  • 任何尝试启动使用 WebView 的 Activity 都会导致 View 进入手机前台(根据 Android 的 Activity 设计)
  • 任何尝试在 Activity 上下文之外使用 WebView 都会导致错误,指出您无法在非 UI 线程上使用 WebView。
  • 由于各种复杂性原因,我无法考虑使用 Rhino 等库进行无 UI 网页抓取。

有没有办法解决这个问题?

【问题讨论】:

    标签: android android-activity android-webview web-scraping intentservice


    【解决方案1】:

    您可以显示来自服务的 web 视图。下面的代码会创建一个您的服务可以访问的窗口。窗口不可见,因为大小为 0 x 0。

    public class ServiceWithWebView extends Service {
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
            params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
            params.gravity = Gravity.TOP | Gravity.LEFT;
            params.x = 0;
            params.y = 0;
            params.width = 0;
            params.height = 0;
    
            LinearLayout view = new LinearLayout(this);
            view.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
    
            WebView wv = new WebView(this);
            wv.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
            view.addView(wv);
            wv.loadUrl("http://google.com");
    
            windowManager.addView(view, params);
        }
    }
    

    这也需要android.permission.SYSTEM_ALERT_WINDOW 权限。

    【讨论】:

    • 您仍然在 ACTIVITY 类的 onCreate 方法中启动服务。我无法在活动中执行代码,因为用户可以看到活动。在不继承 Activity 部分的类中创建新的 WebView 实例将导致运行时异常。
    • 我不确定我是否理解您评论的第一部分。是的,您必须找到一种方法来启动您的服务,但它可能是一个简单地启动然后完成的活动。就 WebView 而言,我绝对知道这会起作用。这个“onCreate()”方法来自服务本身。大约一周前我自己做了这个,没有遇到过运行时异常。我的猜测是您尝试使用可能从不同线程调用的“onStartCommand()”函数。
    • 如果我在 Activity 中启动服务,那么用户至少可以在几分之一秒内看到某些屏幕,不是吗?它必须是隐形的。我已经成功地从一个 IntentService 启动了你的 ServiceWithWebView 示例,AlarmManager 每 20 秒调用一次。一切都执行得很好,除了添加任何 WebView 代码我得到异常“从 Activity 上下文之外调用 startActivity() 需要 FLAG_ACTIVITY_NEW_TASK 标志”。我相信这是因为我是从非 UI 线程启动的。我已经尝试在意图上添加 FLAG_ACTIVITY_NEW_TASK。
    • 我知道这是一个旧答案,但供将来参考:这种方法确实有效,即使在 KitKat 中使用基于 Chromium 的 WebView 也是如此。我在使用 AlarmManager 定期启动的服务中使用它。不过,您不需要将 WebView 添加到 LinearLayout。将其直接添加到 WindowManager 似乎可以正常工作。
    • 最近有人在棒棒糖上试过这个吗?我必须在加载的 html 页面中找到一些东西,但是永远不会调用 onPageFinished 方法...
    【解决方案2】:

    如果我错了,请纠正我,但这个问题的正确答案是,当用户在手机上做其他事情而不通过 Activity 中断用户时,不可能在后台使用 WebView。

    我已经应用了 Randy 和 Code_Yoga 的建议:使用带有“Theme.NoDisplay”的活动来启动带有 WebView 的后台服务来做一些工作。然而,即使没有可见的视图,在启动服务的那一秒切换到该活动也会中断用户(例如暂停正在玩的正在运行的游戏)。

    对我的应用来说完全是灾难性的消息,所以我仍然希望有人能给我一种方法来使用不需要 Activity 的 WebView(或可以完成相同任务的 WebView 的替代品)

    【讨论】:

    • 这是真的,没有正确的方法可以在 ActivityContext 之外使用 webview,Randy 的回答可能是解决这个问题的方法,但不建议在所有其他应用程序之上创建一个窗口.
    • 嗨皮埃尔。我不认为您对 code_yoda 答案的评论今天有效,因为它需要在 onResume() commonsware.com/blog/2015/11/02/… 之前执行 finish() 。但是,即使活动被销毁,活动的上下文仍然存在于与 NoDisplay 主题的活动相关联的 Web 视图客户端中(我不知道如何)。你想对此发表评论吗?我也添加了一个替代答案,但如果有一些基于 code_yoda 解决方案的可行性,我认为我们可以围绕它构建一个解决方案。
    【解决方案3】:

    您可以使用它来隐藏 Activity

             <activity android:name="MyActivity"
              android:label="@string/app_name"
              android:theme="@android:style/Theme.NoDisplay">
    

    这样做会阻止应用显示任何活动。 然后你就可以在 Activity 中做你的事情了。

    【讨论】:

    • 活动仍然“显示”,只是不可见。关闭它并引用 WebView 仍然允许以后使用 WebView 吗?
    【解决方案4】:

    解决方案是这样的,但使用 Looper.getMainLooper() :

    https://github.com/JonasCz/save-for-offline/blob/master/app/src/main/java/jonas/tool/saveForOffline/ScreenshotService.java

    @Override
    public void onCreate() {
        super.onCreate();
        //HandlerThread thread = new HandlerThread("ScreenshotService", Process.THREAD_PRIORITY_BACKGROUND);
        //thread.start();
        //mServiceHandler = new ServiceHandler(thread.getLooper()); // not working
        mServiceHandler = new ServiceHandler(Looper.getMainLooper()); // working
    }
    

    在@JonasCz 的帮助下:https://stackoverflow.com/a/28234761/466363

    【讨论】:

      【解决方案5】:

      我使用以下代码解决了这个问题:

      Handler handler = new Handler(Looper.getMainLooper());
      try
      {
          handler.post(
              new Runnable()
              {
                  @Override
                  public void run()
                  {
                      ProcessRequest(); // Where this method runs the code you're needing
                  }
              }
          );
      } catch (Exception e)
      {
          e.printStackTrace();
      }
      

      【讨论】:

        【解决方案6】:

        WebView 不能存在于 Activity 或 Fragment 之外,因为它是 UI。 但是,这意味着只需要一个 Activity 来创建 WebView,而不是处理它的所有请求。

        如果您在主 Activity 中创建不可见的 WebView 并使其可从静态上下文访问,您应该能够从任何地方在后台视图中执行任务,因为我相信所有 WebView 的 IO 都是异步完成的。

        要消除这种全局访问的麻烦,您始终可以启动一个引用 WebView 的服务来完成您需要的工作。

        【讨论】:

        • 这是否意味着您将有内存泄漏,因为您有一个引用此 Activity 的 WebView?
        【解决方案7】:

        或者替代一个可以完成相同功能的WebView

        【讨论】:

        • 部分内容只能在WebView上添加显示,因为它支持JavaScript,可以有添加内容的功能。
        【解决方案8】:

        您为什么不创建一个后端服务来为您进行抓取?

        然后您只需轮询来自 RESTful Web 服务的结果,甚至使用消息传递中间件(例如 ZeroMQ)。

        如果它适合您的用例,可能会更优雅:让抓取服务通过 GCM 发送您的应用推送消息 :)

        【讨论】:

        • 这是因为后端将被检测为从同一 IP 抓取的机器人并被阻止(除了在不同页面上进行大量抓取所需的后端资源)。
        • 因为在线时并非一切都在发生
        【解决方案9】:

        我不确定这是否是解决给定问题的灵丹妙药。 根据@Pierre 接受的答案(对我来说听起来是正确的)

        没有办法在后台使用 WebView 而 用户正在电话上做其他事情而不会打断用户 通过 Activity。

        因此,我认为必须进行一些架构/流程/策略更改才能解决此问题。

        建议的解决方案 #1: 而不是从服务器获取推送通知并运行后台作业,然后运行一些 JS 代码或 WebView。相反,每当用户启动应用程序时,都应该查询后端服务器以了解是否需要执行任何 scraping。并且在后台输入的基础上,android客户端可以运行JS代码或WebView并将结果传回服务器。

        我还没有尝试过这个解决方案。但希望是可行的。


        这也将解决 cmets 中所述的以下问题:

        这是因为后端将被检测为从同一 IP 抓取的机器人并被阻止(除了在不同页面上进行大量抓取所需的后端资源之外)。

        数据可能在一段时间内不可用(直到某些用户为您抓取它)。但我们可以肯定地使用这种策略为最终用户提供更好的用户体验。

        【讨论】:

          【解决方案10】:

          我知道已经一年半了,但我现在面临同样的问题。我最终通过在我的 Android 应用程序中运行的 Node 引擎中运行我的 Javascript 代码来解决它。它被称为JXCore。你可以看看。另外,看看这个在没有 WebView 的情况下运行 Javascript 的 sample。我真的很想知道你最后用的是什么?

          【讨论】:

          • 这两个链接都不起作用。如果你修复它们,请联系我。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-24
          • 2019-07-17
          • 1970-01-01
          • 1970-01-01
          • 2016-11-20
          相关资源
          最近更新 更多