Android: загрузка вывода WebView в App Widget

Хотя я понимаю, что App Widget не поддерживает напрямую WebView, возможно ли вообще использовать ImageView (который поддерживается) и метод, описанный здесь для создания изображения для ImageView? WebView не будет использоваться напрямую, а используется только в фоновом режиме, чтобы обеспечить изображение для ImageView.

Ответы

Ответ 1

Это возможно, по-видимому, по крайней мере на моем устройстве, хотя ваш пробег может измениться.

В манифесте мы регистрируем Widget <receiver> как обычно, а также регистрируем наш Service, который обрабатывает WebView и его захват изображения.

<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 секунд, если есть активный виджет. Вероятно, вы не хотите делать это за пределами тестирования, поскольку обновления виджетов могут быть довольно дорогостоящими.

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 мы создаем экземпляр a WebView и добавляем его к FrameLayout, который, в свою очередь, добавляется к WindowManager с нулем для его параметров ширины и высоты. Затем мы загружаем тестовый URL-адрес в WebView, и когда страница заканчивается, мы привязываем макет WebView к соответствующему размеру и рисуем его в нашу цель Bitmap через объект Canvas. Это делается после небольшой задержки, чтобы позволить 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.

Снимок экрана WebWidget

Ответ 2

Добавляя к вышеуказанному ответу,

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

Просто замените

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.

Также не забудьте изменить высоту и ширину веб-представления после того, как страница закончила загрузку на onPageFinished() для людей, которые ищут синтаксический анализ данных с помощью Javascript.

Я полностью упустил из виду важность этого в приведенном выше ответе и закончил тратить много времени.

Первоначально мое предположение было, поскольку мое требование заключалось в том, чтобы не отображать содержимое в веб-представлении виджета через изображение, это не требовалось.

Как выясняется, javascript не может анализировать данные с веб-представления, если ширина и высота представления не равны нулю.