AndroidでのWidget実装における落とし穴(画像編)


はじめに

おはこんにちは!
2020年度XTechグループアドベントカレンダーの5日目を担当します、エキサイト株式会社の21卒内定者、奥田です🙇‍♂️
5日目はAndroidでWidgetを実装する際の画像の扱い方について書いていきます。
(今回の記事では画像の表示に絞り記事を書いていきます。Widgetの実装方法などには触れないため他の記事と併用してください🙇‍♂️)

そもそもWidgetってなんだ?という人のために公式のドキュメントを貼っておきます!👀
Widget公式ドキュメント

問題点

WidgetではレイアウトはRemoteViewsをベースとしています!
今回の作業内容ではリモートの画像を取得し、Widget(RemoteViews)に表示する必要がありました。

画像の取得も確認でき、後は表示するだけで楽勝じゃん!っと思い下記を実装しました💪

Widget.kt
val views = RemoteViews(context?.packageName, R.layout.sample_widget)

views.setImageViewUri(R.id.imageView, item.imageURL)

しかし結果は。。。
なにも表示されませんでした😭

解決策

setImageViewUriで画像が表示されない原因の特定のためにAndroidのソースコードを見ることにしました。

setImageViewUri公式ドキュメントを載せておきますので詳しく見たい方はどうぞ!

RemoteViews.java
/**
 * Equivalent to calling {@link ImageView#setImageURI(Uri)}
 *
 * @param viewId The id of the view whose drawable should change
 * @param uri The Uri for the image
 */
public void setImageViewUri(int viewId, Uri uri) {
    setUri(viewId, "setImageURI", uri);
}

Equivalent to calling {@link ImageView#setImageURI(Uri)

→ImageViewのsetImageURI(Uri)まで飛んでみる✈️

ImageView.java
/**
 * Sets the content of this ImageView to the specified Uri.
 * Note that you use this method to load images from a local Uri only.
 * <p/>
 * To learn how to display images from a remote Uri see: <a href="https://developer.android.com/topic/performance/graphics/index.html">Handling Bitmaps</a>
 * <p/>

 * <p class="note">This does Bitmap reading and decoding on the UI
 * thread, which can cause a latency hiccup.  If that's a concern,
 * consider using {@link #setImageDrawable(Drawable)} or
 * {@link #setImageBitmap(android.graphics.Bitmap)} and
 * {@link android.graphics.BitmapFactory} instead.</p>

 * <p class="note">On devices running SDK < 24, this method will fail to
 * apply correct density scaling to images loaded from
 * {@link ContentResolver#SCHEME_CONTENT content} and
 * {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
 * on devices with SDK >= 24 <strong>MUST</strong> specify the
 * {@code targetSdkVersion} in their manifest as 24 or above for density
 * scaling to be applied to images loaded from these schemes.</p>
 *
 * @param uri the Uri of an image, or {@code null} to clear the content
 */
@android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
public void setImageURI(@Nullable Uri uri) {
    if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
        updateDrawable(null);
        mResource = 0;
        mUri = uri;

        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;

        resolveUri();

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}

Note that you use this method to load images from a local Uri only.

→リモートの画像はできないことを確認!!

On devices running SDK < 24, this method will fail to
apply correct density scaling to images loaded from
{@link ContentResolver#SCHEME_CONTENT content} and
{@link ContentResolver#SCHEME_FILE file} schemes.

→代案としてsetImageDrawableかsetImageBitmapを使うことができるようです👀

参考ドキュメント(setImageViewUri)
https://developer.android.com/reference/android/widget/ImageView#setImageURI(android.net.Uri)

Widget(RemoteViews)ではImageViewを扱うメソッドとして、

  • setImageViewUri
  • setImageViewResource
  • setImageViewBitmap
  • setImageViewIcon

上記の4点があります。

代案としてWidget(RemoteViews)で使用できるのはsetImageViewBitmapのためこのメソッドで実装します!
つまりはRemoteViewsにおいてリモートの画像を表示するにはBitmapに変換しsetImageBitmapにセットしなければいけないことが分かりました☺️

実装

ここまでの調査結果を踏まえて下記のように書き直しました!

Widget.kt
val views = RemoteViews(context?.packageName, R.layout.sample_widget)

// 画像URLからBitmapに変換する処理
val bitmap = getBitmapFromUrl(item.imageURL)
views.setImageViewBitmap(R.id.imageView, bitmap)

結果は無事に画像の表示に成功しました✨

最後に

今回はWidgetでの画像表示について書かせていただきました🙇‍♂️
稚拙な文章でしたが最後まで読んでいただき幸いです!
ここが間違ってるぞ!?や画像の真髄を伝授してやろう!っという方がいましたらコメントしていただけると幸いです!!!

また弊社では採用もバシバシ実施しているので興味のあるかたがいましたらご応募ください🙇‍♂️
https://www.wantedly.com/companies/excite

XTechグループ Advent Calendar 2020 6日目の執筆担当はkiwamunet_newさんです。
引き続きお楽しみください✨