Paging3で写真ギャラリーを表示する #Jetpack #Paging

Paging 3

言わずと知れた、Jetpackのページングライブラリがかなり使いやすくなっていました。

developer.android.com

medium.com

時間が経ち、余計なものもたくさん入ってしまっていますが、関連リポジトリはこちら↓↓↓

github.com

依存関係

def paging_version = "3.0.0-alpha02"
implementation "androidx.paging:paging-runtime:$paging_version"

Repository(PagingSource)

PagingSourceを継承し、独自のDataSourceを作成します。引数はアプリに合わせて設定してください。今回は日付(timestamp)をキーにしたいのでLongです。

class GalleryPagingSource(private val context: Context) : PagingSource<Long, Media>()

MediaStoreの設定は以下のようによしなに...以下は最低限のUri取得用のIDとページングのキーにする日付をprojectionに指定しています。

const val PHOTO_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
const val PHOTO_ID = MediaStore.Images.ImageColumns._ID
const val PHOTO_DATE_ADDED = MediaStore.Images.ImageColumns.DATE_ADDED
val PHOTO_PROJECTION = arrayOf(
    PHOTO_ID,
    PHOTO_DATE_ADDED
)
const val PHOTO_SORT_ORDER = "$PHOTO_DATE_ADDED DESC, $PHOTO_ID ASC"
const val PHOTO_FROM_DATE_ADDED = "$PHOTO_DATE_ADDED <=?"

PagingSource.load

ここがポイントです。 以前だとここはloadInitial, loadBefore, loadAfter 3つのコールバックに分かれていましたが、1つに集約されています。 LoadResult.Pageに次のページのキーを渡してあげます。(初回はnullなので現在時刻)

pagedKeyDate == lastItemDateの部分は、終了時にキーが繰り返す事への対応です。キーの繰り返しを許可するためには、以下 keyReuseSupported を指定すればいいのですが、当然キーがループするので暫定でこの処理を入れています。もっといい対応あるかもなのであれば知りたい...

override val keyReuseSupported :Boolean = true
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, Photo> {
    return try {
        val pagedKeyDate = params.key ?: System.currentTimeMillis()
        val result = fetch(pagedKeyDate)
        val lastItemDate = result.last().detail.date
        if (pagedKeyDate == lastItemDate) {
            return LoadResult.Error(/**例外**/)
        }
        LoadResult.Page(
            data = result,
            prevKey = null,
            nextKey = lastItemDate
        )
    } catch (e: Exception) {
        LoadResult.Error(e)
    }
}

データの取得はContentrsolverでよしなに。上記で、nextkeyを最後の写真の日付にしているのでここからPageを繰り返していくだけです。

suspend fun fetch(pageKey: Long) = withContext(Dispatchers.IO) {
    val photosList = mutableListOf<Photo>()
    context.contentResolver.query(
        PHOTO_URI,
        PHOTO_PROJECTION,
        PHOTO_FROM_DATE_ADDED,
        arrayOf(pageKey.toString()),
        PHOTO_SORT_ORDER
    )?.use { cursor ->
        val idIndex = cursor.getColumnIndexOrThrow(PHOTO_ID)
        val dateAddedIndex = cursor.getColumnIndexOrThrow(PHOTO_DATE_ADDED)
        while (cursor.moveToNext()) {
            val id = cursor.getLong(idIndex)
            val dateAdded = cursor.getLong(dateAddedIndex)
            val uri = ContentUris.withAppendedId(PHOTO_URI, id)
            photosList.add(
                Photo(uri,dateAdded)
            )
        }
        cursor.close()
    }
    photosList
}

ViewModel

ViewModelでは取ったデータをFlowとして受け取ります。 LiveDataObservableにも変換できますし、cachedInでコルーチンスコープやLifecycleの間でキャッシュしてくれるのでかなり柔軟性もあります。

class GalleryViewModel(app: Application) : AndroidViewModel(app) {
    companion object {
        const val PAGE_SIZE = 20
    }
    val galleryDataFlow = Pager(
        PagingConfig(pageSize = PAGE_SIZE, initialLoadSize = PAGE_SIZE)
    ) {
        GalleryPagingSource(getApplication())
    }.flow.cachedIn(viewModelScope)
}

UI(Adapter, Activity)

collectLatestは、FlowのAPIで最新以外をキャンセルしながら受け取ってくれる感じのやつです。PagingDataAdapterを継承したAdapterにsubmitしてあげましょう。ここは今までのPagingと同じくReccyclerView.Adapterのシンプルな拡張になっていて、DiffUtillを渡してあげるだけです。

viewModel.galleryDataFlow.collectLatest {
        galleryAdapter.submitData(it)
}

まとめ

とても簡単になっていました。

Android 11 Meetups メモ (機械学習)

https://developersonair.withgoogle.com/events/a11meetups-jp?talk=ml

TensorFlowやML Kit 等、端末上で実行可能な機械学習についてのセッションを行います。 主催:GDG Osaka, GDG Tokyo, Google

Session 1: Android機械学習を活用 - 入門講座: Khanh LeViet(約 40分)

Edge ML Explosion (デバイス上での機械学習体験)

  • サーバー上でなく、デバイス上で機械学習を動かす体験はより重要になってきた
    • レイテンシ
    • ネットワーク不要
    • プライバシーデータを送らなくて良い
  • AR/ カメラを介すものなどは特にこの体験が必要(on-device出ないと難しい)
  • 歩く->走る->自転車に乗るは従来のプログラミングであればスピードを元に判定できるが、ゴルフをしているなどは全く別の話になる。だが、機械学習のアプローチだとこれが可能になる

Tensor flowを組み込む

  • Tensor flow -> DeepLearningのフレームワーク
  • 花の種別を判別する
  • Google ColabからブラウザからPythonを動かして、モデルを作れる
  • TFLite Model Maker
  • Android Studio 4.1から、Tensor Flow Liteのプラグインが使える
  • .tfliteモデルをapp下に置く
  • TensorFlowImage#Bitmapをモデルに渡す。-> processImage() ( [MEMO] : 使い方AutoMLとほぼ一緒な所感)

Leverage TF Lite pre-trained models

  • レーニング済みモデル
    • どこで落とせる?
    • 画像分類( Image Classification )
    • 物体検出( ObjectDetection ) model_object_localizer
      • 認識対象は真ん中にないといけない
      • どこにあるかまで認識できる
    • 画像セグメント(ImageSegmentation)
      • オブジェクトの切り取りなどができる(Zoomのバーチャル背景など)
    • スタイル転換(Style Transfer)
      • あるコンテンツにあるコンテンツの特徴をくっつける
      • 画像のフィルター機能など
      • Sample : Google Arts & Culture

Session 2: ML Kit's on-device APIs : yanzm(約 40分)

ML Kitのリブランディング

  • on deviceのAPIが全てFirebaseから独立した
  • cloud -> Firebase
  • ondevice -> MLKit

Bundled vs Thin

  • Bundled
    • アプリサイズ大きい
    • すぐ使える
    • Imege LabelingとObject Detectionはこちらのみ
    • Image LabelingはTensor flow Hubなどから持ってきて使える
  • Thin
    • 小さくて済む
    • ダウンロードするまで使えない
    • Text Recognitionはこちらのみ

Android Jetpack Lifecycle Support

  • Closableを実装
  • LifeCycleObserverで管理可能

Custom Modeling

  • ByteBufferなど入出力はお任せ
  • 推論結果トラベルのマッピングもお任せ
  • ML Kit compatible modelsを使える

自分でのモデル学習方法

  • AutoML Vision Edge
  • TensorFlow Lite Model Maker

Early Access Program

  • Entity Extraction
  • Pose Detection

Firebase ML

質疑応答(約 30分)

  • 端末上でモデルの学習は可能か?
    • 可能だが限界はある
  • MLKitのText RecognitionとCloudVisionAPIとの差は?
    • 本格的に、精度高く使う場合は後者
  • モデルのデータ収集を兼ねて、アプリ上で学習できるライブラリはないか?
    • データ収集は動画にすると効率が良かったりする
    • 今のところそう言った便利なものはない
  • モデル作成には膨大な画像が必要ですが、少数でできないでしょうか?
    • 動画で学習データ作ると楽だよ
    • また、100枚くらいあれば正直十分
  • MLKitとmediapipeの関係は?
    • 関係ない、目的が違う
  • Translateの精度はどの程度上がっていますか?(以前は固有名詞が苦手だったイメージ)
    • 変わってない
  • Image labelingでラベル付けされるのは英語?
    • 英語です。自動翻訳はないので翻訳を使ってがんばろう
  • アプリからサーバーにデータを送って、サーバーで学習して、アプリにモデルをダウンロードするのを効率的にやる方法はありますか?独自でシステムを構築してうまくやる感じになりますか?
    • 独自にやるのがベター

Android11 Meetups メモ

Android 11 の概要 (2020/6/23)

https://developersonair.withgoogle.com/events/a11meetups-jp/watch?talk=a11overview

Session 1: Android 11 の概要 : 荒木佑一(約 30分)

People & Identity


People API
  • 会話通知
    • DynamicShortcut
    • 会話(人間同士で進行中のコミュニケーション)
    • 会話ショートカットのインスタンスをシステムにキャッシュさせる
    • 会話通知 (Notification#setShortCutIdで渡せる)
    • 優先順位 (会話) MessagingStyle > 他の通知
  • バブル
    • アプリを切り替えずに会話を続けられる
    • Facebookメッセンジャーみたいなやつ
    • Notification#setBubbleMetaData
    • ユーザの許可、指定が必須
    • 表示する画面の単位はActivity(条件 : resizable && allowEmbedded)

https://github.com/android/user-interface-samples/tree/yaraki-People

Identity

Session 2: Developer Goodies : takahirom(約 30分)

Controls & Developer friendliness


Device Control
Behavior Changes
  • DeveloperMenuからIdentifer指定で、新機能を有効にできる
Wi-Fi Debugging
Crash Reasons Reporting
  • より信頼性の高いクラッシュ情報
ADB Incremental
  • 読んで字のゴトク (10倍以上早くなることも)
Window Inset
  • 貰える情報量が増える
  • IME Animations (キーボードの変更購読できる)
Animated HEIF
  • HEIFアニメーションロードできるよ

Session 3: Android 11 アプリ開発向けプライバシー概要 : Wasabeef (約 30分)


Privacy Changes


一回限りの権限
  • Activty表示中
  • バックグラウンド1分くらい
  • フォアグラウンドサービス起動中
    • 常に権限を確認するしかない
バックグラウンド位置情報
  • foregroundとbackgroundで分けなければいけない
パッケージの公開設定
  • packageManagerいくつか使ってるので要チェック
  • IntentFilterは対応必要かも? (Manifest 記述レベル)
Scoped Storage
  • 対応期日は迫っている
  • MediaStore APIに新しいAPI 書き込み/削除/お気に入り/ゴミ箱

Android10でMediaStore#DATE_TAKENがnull になる

Android Q から、Exifがない画像は、MediaStore.MediaColumns.DATE_TAKEN(撮影日)がnullになってしまうようです。以前はExifがない場合は追加日 or 変更日が設定されていたようです。

事象

https://issuetracker.google.com/issues/140250785

Status: Won't Fix (Intended Behavior)

DATE_TAKENは「メディアアイテムが撮影された時間」であると文書化されており、そのAPI記述を実現するために、"DateTimeOriginal" Exifメタデータフィールド、または.DATE_TAKENに基づいてこれを入力します。 スキャンされるファイルにこのメタデータがない場合、ファイルが撮影された時間を正確に判断できないため、誤解を招くようなデータを避けるためにDATE_TAKENはNULLに設定されます。

MediaStoreから写真/動画を取得し、DATE_TAKENをソートのキーや、ユーザに伝わる写真の日付に使っているアプリは、対応が必要そうです。言いたいことはわかるけど...辛み...

developer.android.com

対応

後述の課題が許容/無関係できるのであれば、DATE_TAKENがnullの場合、DATE_ADDEDDATE_MODIFIEDヘフォールバックするのが良さそう。

課題

  • DATE_TAKENがミリ秒(milliSeconds)で、DATE_ADDEDDATE_MODIFIEDは秒(seconds)
  • DATE_TAKENDATE_ADDEDDATE_MODIFIEDは当然だが桁を揃えても数秒違う

Android Interview Questions (随時更新)

github.com

Core Android

Base

すべてのAndroidアプリケーションのコンポーネントを教えてください。

  • Activty
  • Service
  • BroadcastReceiver
  • ContentProvider

コンポーネントは、システムやユーザーがアプリに入るエントリポイント

Application Fundamentals  |  Android Developers

Androidアプリのプロジェクト構成を教えてください。

モジュール

  • Android アプリ モジュール
  • ダイナミック機能モジュール(ストア上のInstant版)
  • ライブラリ モジュール
  • Google Cloud モジュール(GCPバックエンドコード)

Projects overview  |  Android Developers

Contextとは何ですか?どのように使われていますか?

アプリケーションの現在の状態。アクティビティやアプリケーションに関する情報を得るために使用する。 ApplicationContext, ActivityContextは場所によって使い分けるが吉。ContentProviderなど、アプリのライフサイクルに紐づくもの(Singleton - ライフサイクルがアプリケーションのライフサイクルに付随している)はApplicationContext。反対にアクティビティ内でUI操作などを行う際には、ActivityContextを使用する。

AndroidManifest.xmlとは?

アプリに関する重要な情報を Android ビルドツール、Android オペレーティングシステムGoogle Playに対して説明するもの。アプリの各コンポーネントはここで宣言しなければならない。

Applicationクラスとは?

ActivityやServiceなどの他のすべてのコンポーネントを含む Android アプリ内のBase classです。アプリケーションクラスまたはアプリケーションクラスのサブクラスは、アプリケーション/パッケージのプロセスが作成されたときに、他のクラスよりも先にインスタンス化されます

透明なActivity #Android

よく忘れる (status barもnavigation barも透明)

<item name="android:windowEnterAnimation">@null</item>
<item name="android:windowExitAnimation">@null</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>