20200402のAndroidに関する記事は6件です。

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.gradle
def 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.kt
class 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)
                    // 進捗状態を使った処理
                }
            })

上記でタスクを実行しながら状態をユーザーにお知らせすることが可能です。

まとめ

他にもいろいろ機能を追加することが可能ですが、基本的な使い方はここで終わりです。
これを使えば今まで非同期通信のスケジューリングなどが簡単に作成することが可能になりました。
以上です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.xmlapplicationエレメントに設定する。
それで再度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とやりかた同じ)
そうすることでブレークポイントが使える。
完全にブレークポイントが有効になるわけではない(バイトコードとの対応ができない部分があるみたい)
が、なんとなくあたりを付けていけば実行が追えるはず。
あとは気になる処理を根気よく探していこう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エラー: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内蔵のものに変更するといいとあるので。

以下の手順で対応した。

  1. 上部の「ファイル」メニューから「プロジェクト構造」を選択。

無題3.png
2. プロジェクト構造画面が表示されるので左の一覧の「SDKロケーション」を選択
3. 中央の「JDKロケーション」から「Embedded JDK:~」を選択。「適用」もしくは「OK」を押下して閉じる。
4. gradleのアップグレードが問われるのでアップグレードもしくはインストールを選択。

ここまで進めるとプロジェクトが問題なく動いた。
要はgradleの更新に失敗していると思われるが詳しいことはよくわからんかった。

ちなみに、エラーが直った後にJDKロケーションを元に戻しても問題なく動いたのでやはりgradleの更新に失敗が原因だったのだろう。
(元々OpenJDKを使用していたが戻しても問題なく動いた。)

後記

また、gradleか!という感想しか出てこない。
また、よく言われているようにAndroidStudiohはむやみやたらに最新化するものではないという戒めを残さないといけない
(常に最新化しないとは言っていない)

今回は3.5から3.6へのアップグレード固有の問題なので多発はしないと思われる。
願わくばこの記事が不要になるような環境になるよう祈っている

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

貴方に向いた端末は

じゃあまず使用方法を教えてくれるかな

使用方法 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)があれば快適。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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共に画面回転のことも考慮してもちゃんと結果が受け取れるようになります。
しかしボイラープレートが多いですね...ほとんど再利用できる箇所がない

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バックグラウンド音源をミュート

効果音を再生する時に裏で音楽再生アプリ等で再生していると音が重なって聞き取れない!
という時にバックグラウンドの音を一時的にミュートする方法

バックグラウンド音源をミュート

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);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む