【问题标题】:Android: loading WebView output into App WidgetAndroid:将 WebView 输出加载到 App Widget
【发布时间】:2015-11-25 22:35:16
【问题描述】:

虽然我很欣赏App Widget 不直接支持WebView,但是否可以使用ImageView(受支持)以及类似here 描述的技术来生成图像ImageView? WebView 不会直接使用,而只会在后台使用,以便为ImageView 提供图像。

【问题讨论】:

  • 那个webView是否会在不同的时间发生变化?
  • 是的,App Widget 会定期更新,并且每次 App Widget 的图像(使用 WebView 检索)都会相应地刷新。所以我认为WebView 将作为后台服务运行或在后台服务中运行。我在这里 (stackoverflow.com/a/18989695/4070848) 尝试过这个建议,但我无法让它按我的意愿工作。
  • 但是,如何在没有任何交互的情况下更新 webView?
  • 如果后台 Service 正在运行,您不能每隔一段时间与 Service 交互以更新 WebView 并获取生成的图像吗?
  • OK。但是我们不能在服务中使用 webview。很抱歉。

标签: android webview imageview android-appwidget appwidgetprovider


【解决方案1】:

这显然是可能的,至少在我的设备上是可能的,尽管您的里程可能会有所不同。

在清单中,我们照常注册 Widget 的 <receiver>,并注册处理 WebView 及其图像捕获的 Service

<manifest ... >
    ...

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

    <application ... >
        ...

        <receiver
            android:name=".WebWidgetProvider"
            android:label="@string/widget_name" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>

        <service android:name=".WebShotService" />

    </application>

</manifest>

在 Widget 的信息文件 widget_info.xml 中,我已将最小尺寸设置为 4 x 2。

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minWidth="292dip"
    android:minHeight="146dip" />

对于这个例子,小部件的布局,widget_layout.xml,只是一个ImageView.

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget_image"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitXY"
    android:src="@drawable/ic_launcher" />

本例中的AppWidgetProvider 仅覆盖onUpdate() 方法,因为我们使用相同的ACTION_APPWIDGET_UPDATE 来触发我们自己的AlarmManager 更新。请注意,只要有一个活动的小部件,示例警报就会每 30 秒触发一次。您可能不希望在测试之外执行此操作,因为 Widget 更新可能相当昂贵。

public class WebWidgetProvider extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // We can't trust the appWidgetIds param here, as we're using
        // ACTION_APPWIDGET_UPDATE to trigger our own updates, and
        // Widgets might've been removed/added since the alarm was last set.
        final int[] currentIds = appWidgetManager.getAppWidgetIds(
            new ComponentName(context, WebWidgetProvider.class));

        if (currentIds.length < 1) {
            return;
        }

        // We attach the current Widget IDs to the alarm Intent to ensure its
        // broadcast is correctly routed to onUpdate() when our AppWidgetProvider
        // next receives it.
        Intent iWidget = new Intent(context, WebWidgetProvider.class)
            .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
            .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, currentIds);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, iWidget, 0);

        ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE))
            .setExact(AlarmManager.RTC, System.currentTimeMillis() + 30000, pi);

        Intent iService = new Intent(context, WebShotService.class);
        context.startService(iService);
    }
}

Service 中,我们实例化一个WebView,并将其添加到FrameLayout,然后将其添加到WindowManager,其宽度和高度参数为零。然后我们在WebView 中加载一个测试URL,当页面完成时,我们强制WebView 布局为适当的大小,并通过Canvas 对象将其绘制到我们的目标Bitmap。这是在稍许延迟后完成的,以允许WebView 完全呈现自己,以免我们最终得到一个空白的白色图像。然后将Bitmap 设置在RemoteViews 对象上以更新小部件布局中的ImageView。我在代码中留下了 Toast 用于测试目的,因为我们的示例 Widget 可能比 Stack Overflow 的首页刷新更频繁地更新。

public class WebShotService extends Service {
    private WebView webView;
    private WindowManager winManager;

    public int onStartCommand(Intent intent, int flags, int startId) {
        winManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        webView = new WebView(this);
        webView.setVerticalScrollBarEnabled(false);
        webView.setWebViewClient(client);

        final WindowManager.LayoutParams 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.x = 0;
        params.y = 0;
        params.width = 0;
        params.height = 0;

        final FrameLayout frame = new FrameLayout(this);
        frame.addView(webView);
        winManager.addView(frame, params);

        webView.loadUrl("http://stackoverflow.com");

        return START_STICKY;
    }

    private final WebViewClient client = new WebViewClient() {
        public void onPageFinished(WebView view, String url) {
            final Point p = new Point();
            winManager.getDefaultDisplay().getSize(p);

            webView.measure(MeasureSpec.makeMeasureSpec((p.x < p.y ? p.y : p.x),
                                MeasureSpec.EXACTLY), 
                            MeasureSpec.makeMeasureSpec((p.x < p.y ? p.x : p.y),
                                MeasureSpec.EXACTLY));
            webView.layout(0, 0, webView.getMeasuredWidth(), webView.getMeasuredHeight());

            webView.postDelayed(capture, 1000);
        }
    };

    private final Runnable capture = new Runnable() {
        @Override
        public void run() {
            try {
                final Bitmap bmp = Bitmap.createBitmap(webView.getWidth(),
                    webView.getHeight(), Bitmap.Config.ARGB_8888);
                final Canvas c = new Canvas(bmp);
                webView.draw(c);

                updateWidgets(bmp);
            }
            catch (IllegalArgumentException e) {
                e.printStackTrace();
            }

            stopSelf();
        }
    };

    private void updateWidgets(Bitmap bmp) {
        final AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
        final int[] ids = widgetManager.getAppWidgetIds(
            new ComponentName(this, WebWidgetProvider.class));

        if (ids.length < 1) {
            return;
        }

        final RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget_layout);
        views.setImageViewBitmap(R.id.widget_image, bmp);
        widgetManager.updateAppWidget(ids, views);

        Toast.makeText(this, "WebWidget Update", 0).show();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

这是 Widgety goodness 的截图。

【讨论】:

  • 我已更新我的答案以删除不必要的广播和文件写入。我还去掉了一些其他无用的东西,并在这里和那里调整了一些东西。希望这个也适用于你。干杯!
  • @drmrbrewer 好吧,TYPE_TOAST 似乎可以在未经许可的情况下工作,至少在我能够测试的几个版本上是这样。 TYPE_APPLICATION_PANEL 在早期版本中崩溃,这并不让我感到惊讶,因为它是从 Service 添加的。不确定该限制是否在以后的版本中更改。请注意,TYPE_TOAST 现在在 Android O 中已弃用,取而代之的是TYPE_APPLICATION_OVERLAY,但我还不能使用它,所以我不确定可能需要什么,或者确切的预期的行为。我会等待编辑答案,直到我得到更好的信息
  • @drmrbrewer 是的,似乎是这样,但我想暂时不会有很多用户会使用 O。另外,我不确定他们是如何处理这种弃用的。可能TYPE_TOAST 仍然可用,他们会逐渐完全淘汰它,但考虑到他们在 O 中所做的其他一些更改,如果他们直接禁用它,我真的不会感到震惊。如前所述,我无法在自动取款机上测试任何东西,而且我并没有真正跟上所有宣布的 API 更改。如果我想到任何其他方式来做到这一点,我会告诉你。
  • @drmrbrewer 嗯,不知道。从这些链接来看,这似乎确实是罪魁祸首,但来自崩溃的更多细节将有助于确定这是具体问题。查看源代码,我真的看不到任何改变持续时间的方法。我会仔细考虑,并且,如前所述,我会尝试完全想出其他方法来做到这一点。我已经离开家一段时间了,我刚刚回到我的开发工作中,但如果我有任何工作,我会及时通知你。
  • 清除这个想法...我刚刚确认 Chrome 的无头功能在 Android 版 Chrome 中不受支持:(
【解决方案2】:

添加到上面的答案,

对于遇到此错误的人 - 无法添加窗口。此窗口类型的权限被拒绝至少在我的情况下运行棉花糖的设备需要更改类型。

简单替换

WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY 到任一

WindowManager.LayoutParams.TYPE_TOAST(或)

WindowManager.LayoutParams.TYPE_APPLICATION_PANEL

它会工作得很好。

注意:检查并确认 Nexus6p 运行最新版本的 marsmallow。

WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY需要权限android.permission.INTERNAL_SYSTEM_WINDOW,该权限只能用于android系统相关的应用程序。

对于希望使用 Javascript 解析数据的人,一旦页面在 onPageFinished() 完成加载后,不要忘记更改 Web 视图的高度和宽度。

我在上面的答案中完全忽略了这一点的重要性,最终浪费了很多时间。

最初我的假设是因为我的要求是不通过图像视图将 Web 视图中的内容显示到小部件,这不是必需的。

事实证明,除非视图的宽度和高度不为零,否则 javascript 无法解析来自 Web 视图的数据。

【讨论】:

  • 这是否意味着不再需要SYSTEM_ALERT_WINDOW 权限?即,如果您使用WindowManager.LayoutParams.TYPE_TOAST 而不是LayoutParams.TYPE_SYSTEM_OVERLAY?必须向用户请求此权限有点问题...默认情况下未启用,一些用户对此感到紧张是可以理解的。
【解决方案3】:

WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 仅在 Android M 之后有效。 并且需要配置权限Settings.canDrawOverlays(context)

【讨论】:

    猜你喜欢
    • 2011-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-23
    • 1970-01-01
    • 2013-11-07
    • 2011-08-12
    相关资源
    最近更新 更多