「RemoteViews for widget update exceeds maximum bitmap memory usage...」について

ウィジェットを作っていると、

```
IllegalArgumentException: RemoteViews for widget update exceeds maximum bitmap memory usage (used: xxxxx, max: xxxxx)
```

というExceptionが発生し、ウィジェットがエラー表示になる場合があります。
このExceptionが発生している箇所は以下です。

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java;l=2203-2226

```java
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
        boolean isPartialUpdate) {
    if (widget != null && widget.provider != null
            && !widget.provider.zombie && !widget.host.zombie) {

        if (isPartialUpdate && widget.views != null) {
            // For a partial update, we merge the new RemoteViews with the old.
            widget.views.mergeRemoteViews(views);
        } else {
            // For a full update we replace the RemoteViews completely.
            widget.views = views;
        }
        int memoryUsage;
        if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) &&
                (widget.views != null) &&
                ((memoryUsage = widget.views.estimateMemoryUsage()) > mMaxWidgetBitmapMemory)) {
            widget.views = null;
            throw new IllegalArgumentException("RemoteViews for widget update exceeds"
                    + " maximum bitmap memory usage (used: " + memoryUsage
                    + ", max: " + mMaxWidgetBitmapMemory + ")");
        }
        scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
    }
}
```

`mMaxWidgetBitmapMemory`の計算箇所は以下で、ディスプレイの画素数の1.5倍に設定されています。

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java;l=351-358

```java
private void computeMaximumWidgetBitmapMemory() {
    Display display = mContext.getDisplayNoVerify();
    Point size = new Point();
    display.getRealSize(size);
    // Cap memory usage at 1.5 times the size of the display
    // 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h
    mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
```

これは、AppWidgetManagerのドキュメントでも説明されています。

https://developer.android.com/reference/android/appwidget/AppWidgetManager

> The total Bitmap memory used by the RemoteViews object cannot exceed that required to fill the screen 1.5 times, ie. (screen width x screen height x 4 x 1.5) bytes.

RemoteViewsで使われるBitmapのサイズは画面サイズの1.5倍を超えてはならない、ということです。

ウィジェットで複雑な描画が必要な場合、Canvasなどを使って描画を行い、Bitmap化して、それをRemoteViewsに貼り付けることで実現するというテクニックがあります。Bitmapサイズが大きくなり、さらに重ね合わせなどを使っていると抵触する可能性はありますね。

ただ、ウィジェットは頑張っても画面いっぱいになることはまずないですし、画面サイズの1.5倍ものBitmapはなかなか使わなさそうにも思います。

## SizedRemoteViewsの注意点

RemoteViewsで一つのレイアウトだけを表現している場合は、滅多に抵触することはないのですが、SizedRemoteViewsの場合は別です。
API 31から、ウィジェットのサイズ情報がArrayList<SizeF>でも通知されるようになり、それに対応してたレイアウトを詰め込んだRemoteViews返せるようになっています。Jetpack Glanceを使っている場合も内部でこちらが使われています。

この場合、また事情が変わってきて、サイズごとのRemoteViewsの中で使われるBitmapの合計で判定されてしまいます。つまり、シンプルな縦横サイズの場合でも2つのレイアウトになるので、一つあたり平均0.75倍まで、Foldableデバイスの場合は4つ以上あるので、4つの場合で一つあたり平気0.375倍まで、バリエーションが増えるとさらに一つあたりに使えるサイズが小さくなっていきます。

こうなってくると全面をBitmapで覆うようなウィジェットは容易に制約に引っかかってしまいます。
そのようなウィジェットを作る場合は、サイズ制限を計算し、必要に応じて解像度を下げる対応が必要です。

## 複数解像度端末の注意点

高解像度デバイスの一部には、省電力のため画面の解像度を下げることができるデバイスがあります。
この場合、画面サイズの計算もとが、下げた解像度の方で計算される場合があるようです。
（実際Pixcel 8 Proの場合、そのような挙動をしていました）
そのため、単純に画面サイズを取得して上限を計算し、解像度を下げる。だけでは不十分で、低い方の解像度を取得して計算する必要があります。

そういった解像度の取得方法は以下になります。

https://qiita.com/ryo_mm2d/items/1709c88831dc9ef7873e

---

以上です。
Foldableなど複数のレイアウトが必要になった段階で、これらの制限も緩めておいてくれればとは思いますが、引っかかってしまうとウィジェットが全く機能しなくなってしまうため、注意して実装する必要があります。