Paging3で写真ギャラリーを表示する #Jetpack #Paging
Paging 3
言わずと知れた、Jetpackのページングライブラリがかなり使いやすくなっていました。
時間が経ち、余計なものもたくさん入ってしまっていますが、関連リポジトリはこちら↓↓↓
依存関係
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
として受け取ります。
LiveData
やObservable
にも変換できますし、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 > 他の通知
- バブル
https://github.com/android/user-interface-samples/tree/yaraki-People
Identity
- 外部ログインの課題 / 迷い
- OneTap (ログインの統合/Googleオンリー)
- BlockStore(機種変更時の引き継ぎ)
- 認証トークン <-> サーバー
- GISライブラリに認証周りが統合される
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
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
をソートのキーや、ユーザに伝わる写真の日付に使っているアプリは、対応が必要そうです。言いたいことはわかるけど...辛み...
対応
後述の課題が許容/無関係できるのであれば、DATE_TAKEN
がnullの場合、DATE_ADDED
、DATE_MODIFIED
ヘフォールバックするのが良さそう。
課題
DATE_TAKEN
がミリ秒(milliSeconds)で、DATE_ADDED
、DATE_MODIFIED
は秒(seconds)DATE_TAKEN
とDATE_ADDED
、DATE_MODIFIED
は当然だが桁を揃えても数秒違う
ImageViewをグレーアウトさせる
グレーアウトさせる
image.colorFilter = LightingColorFilter(Color.GRAY, 0)
クリアは
image.clearColorFilter()
Android Interview Questions (随時更新)
Core Android
Base
すべてのAndroidアプリケーションのコンポーネントを教えてください。
- Activty
- Service
- BroadcastReceiver
- ContentProvider
各コンポーネントは、システムやユーザーがアプリに入るエントリポイント
Application Fundamentals | Android Developers
Androidアプリのプロジェクト構成を教えてください。
モジュール
Projects overview | Android Developers
Contextとは何ですか?どのように使われていますか?
アプリケーションの現在の状態。アクティビティやアプリケーションに関する情報を得るために使用する。 ApplicationContext, ActivityContextは場所によって使い分けるが吉。ContentProviderなど、アプリのライフサイクルに紐づくもの(Singleton - ライフサイクルがアプリケーションのライフサイクルに付随している)はApplicationContext。反対にアクティビティ内でUI操作などを行う際には、ActivityContextを使用する。
AndroidManifest.xmlとは?
アプリに関する重要な情報を Android ビルドツール、Android オペレーティングシステム、Google Playに対して説明するもの。アプリの各コンポーネントはここで宣言しなければならない。
Applicationクラスとは?
ActivityやServiceなどの他のすべてのコンポーネントを含む Android アプリ内のBase classです。アプリケーションクラスまたはアプリケーションクラスのサブクラスは、アプリケーション/パッケージのプロセスが作成されたときに、他のクラスよりも先にインスタンス化されます。