- 投稿日:2020-04-02T20:04:54+09:00
Android JetpackのWorkManagerの使い方
Android JetpackのWorkManagerの使い方
JetpackはAndroidデベロッパーのために開発されたライブラリ、ツールなどのことを総称したものです。
今回はその中のWorkManagerという機能について説明します。https://developer.android.com/jetpack?hl=ja
WorkManagerとは
APIの一つで、アプリが終了したり再起動しても延期できるタスクをスケジューリングする機能のようです。
定期的なサーバとの同期や、ログなどの送信に便利そうです。https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja
下記が下位互換性
API 23 以上が搭載されたデバイスでは JobScheduler を使用
API 14~22 が搭載されたデバイスでは BroadcastReceiver と AlarmManager を組み合わせて使用JobSchdulerなどは確かによく使われていましたが、これをさらにパッケージ化したものでしょうか。
ライブラリ追加
WorkManagerライブラリを使用するために、下記を追加してください。
build.gradledef work_version = "2.3.4" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version"Worker
Workerでタスクを定義します。
doWorkメソッド内に実行したい処理を記述します。
そして、 Resultクラスでタスクの実行結果を受け取ることができます。MyWorkManager.ktclass MyWorkManager(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { // ここに実行したい処理を書く return Result.success() } }Resultクラスで返される状態は下記です
- Result.success(): タスクが正常に終了したかどうか
- Result.failure(): タスクが失敗したかどうか
- Result.retry(): 後でタスクを再試行する必要があるかどうか
周期的実行
タスクの実行には一回だけの実行と、周期的に実行できるオプションがあります。
WorkerRequestを使います
- OneTimeWorkRequest: 一回だけ
- PeriodicWorkRequest: 定期的
例えば10秒ごとの周期的なタスクにする場合は下記のように作ります。
val periodic: Duration = Duration.ofSeconds(10L) val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic ).build()もしこんなエラーが出たら、ここをみてください。
https://qiita.com/kph7mgb/items/28ee37957976e80e38f2
Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper ‘-jvm-target’ optionその後、タスクをシステムにぶん投げます
WorkManager.getInstance(this).enqueue(uploadWorkRequest)タスク実行タイミング
各タスクをデバイスの状態によって変えたい時に使えるのが、Constraintsクラスです。
- setRequiredNetworkType: ネットワーク状態
- setRequiresBatteryNotLow: 電池残量がたくさんある時
- setRequiresCharging: 電源に接続されてる時
- setRequiresDeviceIdle: デバイスがアイドル状態の時
- setRequiresStorageNotLow: ストレージ容量がたくさんある時
下記は一例です
var constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .setRequiresCharging(true) .setRequiresDeviceIdle(true) .setRequiresStorageNotLow(true) .build(); val periodic: Duration = Duration.ofSeconds(10L) val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic) .setConstraints(constraints) .build();タスクを10分後に実行したい時は、下記を設定します。
val periodic: Duration = Duration.ofSeconds(10L) val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic) .setInitialDelay(10, TimeUnit.MINUTES) .build()タスク再試行時間
タスクを再試行したい場合には、WorkerクラスからResult.retry()を返すようです。
また、バックオフという遅延時間を設定することで、タスクの増加時間をずらすことができるようです。val periodic: Duration = Duration.ofSeconds(10L) val uploadWorkRequest = PeriodicWorkRequestBuilder<MyWorkManager>(periodic) .setBackoffCriteria( BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build()Worker進捗・監視
Workerを進捗を設定するためにCoroutineWorkerクラスを使用します。
下記で0から100で進捗を設定することができます。class ProgressWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) { companion object { const val Progress = "Progress" private const val delayDuration = 1L } override suspend fun doWork(): Result { val firstUpdate = workDataOf(Progress to 0) val lastUpdate = workDataOf(Progress to 100) setProgress(firstUpdate) delay(delayDuration) setProgress(lastUpdate) return Result.success() } }次にWorkerを監視するためには、observe機能を使用します。
observerにはLifecycleOwnerクラスを使用してタスクの状態を作成する必要があります。
getWorkInfoByIdLiveDataにはWorkerRequestのidを入れて下さい。WorkManager.getInstance(applicationContext) .getWorkInfoByIdLiveData(uploadWorkRequest.id) .observe(observer, Observer { workInfo: WorkInfo? -> if (workInfo != null) { val progress = workInfo.progress val value = progress.getInt(Progress, 0) // 進捗状態を使った処理 } })上記でタスクを実行しながら状態をユーザーにお知らせすることが可能です。
まとめ
他にもいろいろ機能を追加することが可能ですが、基本的な使い方はここで終わりです。
これを使えば今まで非同期通信のスケジューリングなどが簡単に作成することが可能になりました。
以上です
- 投稿日:2020-04-02T19:22:47+09:00
androidのAPKを解析する。デコンパイル&デバッグ
とあることからアプリを解析することになった。
以下はその手順。参考)
https://coky-t.gitbook.io/owasp-mstg-ja/android-tesutogaido/0x05c-reverse-engineering-and-tamperingアプリを準備
当たり前なので割愛。
(ipaは取り出しにくい)apktoolのダウンロード
apk形式はzipなので、apk→zipと拡張子を変えても展開はできる。
ただ、apktoolのほうがリソースをデコードしてくれたり、dexを変換してくれたりと便利
macならbrew install apktool
で可能。コマンド入ったら、
apktool d hoge.apk
で展開される。ちなみにアプリのファイル形式がapkxという場合もあるみたい。
これは複数のapkをまとめた形式なのでunzip
などで一度解体してからでも可。解体した内容
resにリソースなどが含まれる。
javaの内容はclasses*.dex
というファイルにまとまっている。
d2j-dex2jar
とかでjarに展開可能。
apktoolだと全部展開してくれてるはず。
(ただし全部元に戻せるわけではない)リソースが一部展開されない
後にも触れるが、一部リソースは展開できないことがある。
decodeしたあとにファイルを改変して再度apkにしてもこのせいで動かないことがある。
その場合は、当該リソースを読み込んでるlayout/activity_******.xml
からそのリソースを読み込んでいる部分をすべて消す。
(大方imageviewとかなのでまぁ消しても問題なし。imageがほしいなら別だが)
こんな名前が見つかるはず→APKTOOL_DUMMY_***
res/values/drawables.xml
に参照が見つかるのでこれをまとめて変えてもいいかも。android:debuggable="true"
デバッグを有効にするため、
AndroidManifest.xml
のapplication
エレメントに設定する。
それで再度apkに変換して、adbでinstall、実機で実行して、android studioからデバッグをアタッチする。再度apkに
apktool b -f
でapkファイルにパック
zipalign -v 4 dist/hoge.apk ../hoge.apk
zipalignで処理(このへんよくわかってません。アプリ作ったことほぼないもので…)
apksigner sign --ks ~/.android/debug.keystore --ks-key-alias signkey hoge.apk
デッバグ用キーで再度署名(このへんはandroidのドキュメントを見ればOK)
adb install -r hoge.apk
で実機にインストール
これでひとまず動くはず。動かなければAndroid StudioのLogcatなどでエラーを確認する。AndroidStudioからプロセスをアタッチ
アンドロイド本体にて「デバッガを待機」などの設定をしておく。(開発者モードももちろん)
また、AndroidStudioにて新規プロジェクトを作成し、解体したソース一式をそのまま新規プロジェクトに入れてしまう。
(参考URLとやりかた同じ)
そうすることでブレークポイントが使える。
完全にブレークポイントが有効になるわけではない(バイトコードとの対応ができない部分があるみたい)
が、なんとなくあたりを付けていけば実行が追えるはず。
あとは気になる処理を根気よく探していこう。
- 投稿日:2020-04-02T16:19:07+09:00
エラー:Gradle sync failed: Cause: invalid type code: 85 の対応[AndroidStudio]
概要
2~3ヶ月ぶりにAndroidStudioを起動したら、既存、新規を含めてすべてのプロジェクトが動かなくなった件。
結論としてはJDKをAndroidStudio内蔵の物に変更したらエラーがなくなって動くようになった。
イベントログに表示されていたエラーコードは以下になります。
Gradle sync failed: Cause: invalid type code: 85
引用情報
下記のサイトの内容を参考に対応をした。
Gradle Sync Failed in Android Studio 3.6 Cause invalid type code :85エラーコードでググっただけなのでもっと詳しい情報もあるかもしれないが対応できたので深くは調べない
対応内容
特定のプジェクトだけではなく全てのプロジェクトが一律で動かなくなったので個別のエラーではなく環境の問題であると考える。
イベントログを確認するとどうもgradleの起動(?)に失敗しているらしいので「また、gradleか!」という気分になった。とりあえず下記のエラーで検索をかけるとトップにそれらしいサイトは出てきたが英語の質問サイトだったので備忘録も含めてここに日本語でも残す。
Gradle sync failed: Cause: invalid type code: 85
上記のサイトを機械翻訳をかけた結果AndroidStudio3.5からAndroidStudio3.6にアップグレードで発生している問題らしい。
対応としてはプロジェクトのJDKロケーションを外部のものではなくAndroidStudio内蔵のものに変更するといいとあるので。以下の手順で対応した。
- 上部の「ファイル」メニューから「プロジェクト構造」を選択。
2. プロジェクト構造画面が表示されるので左の一覧の「SDKロケーション」を選択
3. 中央の「JDKロケーション」から「Embedded JDK:~」を選択。「適用」もしくは「OK」を押下して閉じる。
4. gradleのアップグレードが問われるのでアップグレードもしくはインストールを選択。ここまで進めるとプロジェクトが問題なく動いた。
要はgradleの更新に失敗していると思われるが詳しいことはよくわからんかった。ちなみに、エラーが直った後にJDKロケーションを元に戻しても問題なく動いたのでやはりgradleの更新に失敗が原因だったのだろう。
(元々OpenJDKを使用していたが戻しても問題なく動いた。)後記
また、gradleか!という感想しか出てこない。
また、よく言われているようにAndroidStudiohはむやみやたらに最新化するものではないという戒めを残さないといけない
(常に最新化しないとは言っていない)今回は3.5から3.6へのアップグレード固有の問題なので多発はしないと思われる。
願わくばこの記事が不要になるような環境になるよう祈っている
- 投稿日:2020-04-02T15:59:08+09:00
貴方に向いた端末は
じゃあまず使用方法を教えてくれるかな
使用方法 Windows MacOSX(MacBookシリーズ) iPhone Android(スマホ) Android(タブレット) iPad ChromeBook 備考 ブラウジング 〇 ◎ △ △ ◎ ◎ ◎ 広い画面でUIが使いやすいとブラウジングしやすい。性能とかより機動性が大事 動画編集 ×~◎ ◎ △ ×~△ ×~〇 △~◎ ×~△ 性能が低かったり画面が小さいと厳しい。 電子書籍 △ 〇 △ △ ◎ ◎ ×~◎ ChromeBookを買うときはAndroidアプリ対応機種を買うこと。 絵を描く △ 〇 △ ×~△ △~◎ 〇~◎ ×~△ 動画編集ほどではないが性能が必要 ゲームをする △~〇 ×~△ 〇 △~◎ △~◎ 〇~◎ × もしあなたがゲームを楽しみたいのなら機動性や可用性が優れたハイエンドタブレットorハイエンドスマホのどちらかが必要。Windowsはエロゲは揃いが良いらしいがそれ以外(特にゲーム性の良いゲーム)はタブレットやスマホと比べると劣る。ChromeBookはゲームが苦手。 Office 〇 △ × × △ △ ◎ 間違いなく一番この用途ではChromeBookが使いやすい。 コスパ △ × × 〇 ◎ △ ◎ Google系がオープンソースであるためかコスパが良い 電話 × × ◎ ◎ △ △ × これは携帯電話から派生したスマホが良い 初期ソフトウェア × ◎ ◎ 〇 〇 ◎ 〇 WindowsはAndroidより要らない初期アプリが多すぎる。無駄にお金がかけられていると思うとイライラする。 セキュリティー × ◎ 〇 〇(野良アプリ(GooglePlayStore以外から入手したアプリ)を使用したときは×) 〇(野良アプリ(GooglePlayStore以外から入手したアプリ)を使用したときは×) 〇~◎ ◎ Windowsはアプリストアが貧弱すぎるため基本的にインターネット上からのダウンロードとなる プログラミング 〇 ◎ × × × × ◎ ChromeBookはプログラミングでも案外使いやすい 各種3DCGソフトウェア(Blender等) △~◎ 〇~◎ × × × △~〇 × Blender等を触りたければChromeBook以外のPCを買いましょう こちらの記事に質問させてもらいました。
【スマホVSパソコン】ハイスペックスマホにお金をかける時代は完全に終わった!
https://www.digigaze.com/pc-vs-phone人形百合姫(私):
2020年3月23日 21:21
これも用途による気がします。
ゲームをするならスマホ、動画編集等をするならPC、効率よくメモを取りたい、Office系ソフトを使いたいならChromeBook(Google製の手軽なノートPC)にそれぞれ金をかけるべきだと思う。ChromeBookならコーディング(MarkDown,HTML,CSS,PHP,JavaScript,C#等)ができるし、Google Playがあると、電子書籍を読めたりするし、ハイエンドでも安いので、PC代わりにもなり、大体の作業はこちらで代用可能です(ゲームと動画編集、3DCGソフトは無理諦めましょう)。返信
上田 龍上田 龍 より:
2020年3月31日 23:54
確かにその通りだと思います。
コメントの人形百合姫様はパソコンやスマホの知識が豊富であるようですが、当記事は最新iphoneなどを購入して機能のうちの10%も使いこなせていない人が多いという現状を知った時に執筆した記事となっています。
使いこなせない機能盛りだくさんな15万円のスマホをショップで契約させられるのであれば
スマホ5万円
パソコン10万円
という選択肢をとったほうは有利に働くことが多いと感じます。
あくまでマクロ的な視点での意見ですので、ミクロ視点だと人形百合姫様の意見が必要な方が多いのも事実です(‘◇’)ゞ
コメントありがとうございました!ゲームをする場合ハイエンドスマホまたはゲーミングスマホ(ゲームに特化した高性能アンドロイドスマホ)もしくはハイエンドタブレットPC(できればiPad pro)があれば快適。
- 投稿日:2020-04-02T12:53:26+09:00
ViewModelとLiveDataを使ったDialogFragmentのコールバック
2020年にもなっていまだにDialogFragmentのコールバック最適解がないのでGoogleは早くJetPackに作ってほしい
class MainActivity : AppCompatActivity() { private val viewModel: DialogViewModel<MainDialogResult> by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (savedInstanceState == null) { SampleDialogFragment().show(supportFragmentManager, "sample") } viewModel.result.observe(this) { when (it) { is MainDialogResult.Ok -> { Log.i("result", it.message) } is MainDialogResult.Cancel -> { Log.i("result", "cancel") } } } } } ///////////////////// CallBack sealed class MainDialogResult { data class Ok(val message: String) : MainDialogResult() object Cancel : MainDialogResult() } class DialogViewModel<T> : ViewModel() { val result: MutableLiveData<T> = MutableLiveData() } ///////////////////// DialogFragment class SampleDialogFragment : DialogFragment() { private val viewModel: DialogViewModel<MainDialogResult> by activityViewModels() override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return AlertDialog.Builder(requireContext()) .setPositiveButton(android.R.string.ok) { _, _ -> val message = "OK!" viewModel.result.postValue(MainDialogResult.Ok(message)) } .setNegativeButton(android.R.string.cancel) { _, _ -> viewModel.result.postValue(MainDialogResult.Cancel) } .create() } }これでActivity/Fragment共に画面回転のことも考慮してもちゃんと結果が受け取れるようになります。
しかしボイラープレートが多いですね...ほとんど再利用できる箇所がない
- 投稿日:2020-04-02T10:18:06+09:00
バックグラウンド音源をミュート
効果音を再生する時に裏で音楽再生アプリ等で再生していると音が重なって聞き取れない!
という時にバックグラウンドの音を一時的にミュートする方法バックグラウンド音源をミュート
AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) .setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()) .setAcceptsDelayedFocusGain(true) .setOnAudioFocusChangeListener(audio_focus_listener) .build(); audioManager.requestAudioFocus(focusRequest); } else { audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); }バックグラウンド音源のミュートを解除
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) .setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()) .setAcceptsDelayedFocusGain(true) .setOnAudioFocusChangeListener(audio_focus_listener) .build(); audioManager.abandonAudioFocusRequest(focusRequest); } else{ audioManager.abandonAudioFocus(null); }