2021/05/30

[android] WorkManager

今回、Androidの Activity以外で動作するコンポーネントについて集中して調査をしている。Activity は iOSなどと同時開発できるプラットフォームを使う前提だからだ。

主に Serviceを見ていたのだが、バックグラウンドで動作するものという調べ方をしていると WorkManagerなるものが出てきた。

WorkManager でタスクのスケジュールを設定する  |  Android デベロッパー  |  Android Developers
https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja

Android Jetpackの一部、と書かれているので、似たような素の機能があるならこれを使った方がバージョンアップなどに対応しやすいということだろう。

WorkManager は、アプリが終了した場合やデバイスが再起動した場合でも実行される、延期可能な非同期タスクのスケジュールを簡単に設定するための API です。

あとから1回や繰り返しとかの実行をすると決めたら、アプリの終了や再起動が行われても実行するというもののようだ。義理堅いというか執念深いというか。

実装するのは、doWork() が処理本体、 WorkRequest がスケジュールと思えば良いか。

 

Service とはちょっと違うが、後始末とか、必ず実行しないといけないとか、そういうのがあれば思い出すことにしよう。

[android] バックグラウンドサービスの停止がよくわからん

長生きしてほしい、けれども寿命があることも分かっている。スマホのプロセス管理はそんな感じじゃなかろうか。
ちなみに小さい組み込み環境の場合は、全部動作したとしても動くだけのメモリを確保することを考えることが多いので、アプローチが違う。火災警報器とかで「いま煙検知しててRAMが足りないのでアラームが鳴動できません」とかあったらダメだしね。

それはともかく、前回のアプリを強制終了しないといけない場合の候補優先度表があるので、その候補から下げてもらうためには何かしないといけない。

Google Developers Japan: 複数の JobService を活用する
https://developers-jp.googleblog.com/2017/10/working-with-multiple-jobservices.html

基本的に、アプリがフォアグラウンドで実行されている場合を除き、アプリのバックグラウンド サービスはシステムによって数分以内に停止されます。

API26 からそうなったらしい。
試してみよう。前回のサンプルは Activityの onStop()で Sericeを止めていたので、タイミングを onDestroy() にずらそう。

2021-05-30 12:42:42.990 D/MainActivity: onCreate
2021-05-30 12:42:43.417 D/MyService: onCreate
2021-05-30 12:42:43.417 D/MyService: onBind
2021-05-30 12:42:43.770 D/MainActivity: onServiceConnected

(Home画面)

2021-05-30 12:44:30.272 D/MainActivity: onStop

として 1時間程度放置したのだが、特に変化がない。Home画面にするだけでは甘くて、別のアプリがフォアグラウンドで動いていないといかんのだろうかと思ったのだが、別アプリを起動して4時間くらい経ったが変わらず。何か別のルールもあるのだろうか。

2021/05/29

[android] Activityの一生

前回、Activityが非表示になるとすぐ onStop()が呼ばれていた。まあ、何も実装していない Activityだったからかもしれないが、Activity がどう動くのか確認しよう。

アクティビティのライフサイクルに関するコンセプト
https://developer.android.com/guide/components/activities/activity-lifecycle#alc

手元に昔購入した Androidの本があるが、ライフサイクルはそのときから変わっていない。この本は APIレベル3 くらいの時代だから、最初から変わっていない、といってしまってもよいのかもしれない。

 

まずは、ライフサイクルに出てくる各メソッド+onRestartにログを埋め込んで見てみる。

2021-05-29 16:28:17.438 D/MainActivity: onCreate
2021-05-29 16:28:17.980 D/MainActivity: onStart
2021-05-29 16:28:17.988 D/MainActivity: onResume

(アプリ一覧)

2021-05-29 16:29:52.977 D/MainActivity: onPause
2021-05-29 16:29:53.811 D/MainActivity: onStop

(自アプリ選択)

2021-05-29 16:30:50.871 D/MainActivity: onRestart
2021-05-29 16:30:50.874 D/MainActivity: onStart
2021-05-29 16:30:50.879 D/MainActivity: onResume

(Home画面)

2021-05-29 16:31:45.120 D/MainActivity: onPause
2021-05-29 16:31:46.072 D/MainActivity: onStop

Home画面に戻してからいろいろアプリを立ち上げたり、1時間ほど放置したりしたのだが onDestroyは呼ばれなかった。アプリ一覧にして上にスワイプして終了させると onDestropyが呼ばれた。

 

Serviceのサンプルアプリでは onStopのタイミングで unbindService()したからそうなったが、別に onDestroyでもよいはずだ。どうせ同じプロセスだし。メモリの残りが少なくなったら、きっと onStopされたアプリは終了させられる候補に入るのだろう。

などということがライフサイクルのところに書いてあるんじゃないのかな?
読むとしよう。


Activityのライフサイクルについては、もう至る所で語り尽くされているに違いない。
だからここではアプリか終了させられるパターンだけに注目しよう。

onDestroy()
https://developer.android.com/guide/components/activities/activity-lifecycle#ondestroy

あれ、onDestroyされるのは 「アクティビティの終了」か「構成の変更によるアクティビティの破棄」しかない。メモリが少なくなったら、という条件がここに書かれていないということは、もっと面倒な話ということか。

 

アクティビティの状態とメモリからの退避
https://developer.android.com/guide/components/activities/activity-lifecycle#asem

ライフサイクルとは別に「プロセスの強制終了」があるのか。うん、あるよな。

表1によると、フォアグラウンドになっていても強制終了される可能性はあることになっている。が、たぶんそれは立ち上げられないほどメモリが無い状態だろうから考慮から外そう。
表の一番下の「破棄」も、まあ忘れていいだろう。プロセスが空だし。

残るは、優先度高の「バックグラウンド(非表示)」と優先度中の「バックグラウンド(フォーカスがない)」だ。 onStop と onPause の違いだろう。
ちなみに英語版だとこういう表になる。 Activity state がメソッド名と同じようになるので、多少わかりやすいか。あんまり変わらんか。

image

いくら翻訳が良くても、やっぱり原文でがしがし読めるようにならないと情報に追いつけないよなぁ、とは思うんだけどね.....

 

ライフサイクルがあるのはわかったとして、動作確認が大変すぎないか? 通常の遷移くらいであればやれば済むのだが、onPauseのときに強制終了、とかやりたかったとしても簡単にできるとは思えん。 onStopならアプリ一覧から終了させればよさそうだけど。

 

アプリのアクティビティをテストする  |  Android デベロッパー  |  Android Developers
https://developer.android.com/guide/components/activities/testing

うーん、ちょっと違うか。
しかしAndroidのテスト方法は知らないので、それはそれで知っておきたいものだ。

[android] Service (7) - bindしてみよう (4)

今回で同一プロセスでの bindシリーズを終わらせたい。

 

まず、動かす。
これはサンプルコードそのままやればよい。
ファイルを追加しながらやるよりも、AndroidStudioの Service追加を使う方がよいだろう。AndroidManifest.xml に <service> を追加してくれるからだ。まあ、これは初心者ならではか。exported設定は、今回は同一プロセスのみにするのでfalseでよいだろう。

 

AndroidManifest.xml (追加行)

01: ...
02:         <service
03:             android:name=".MyService"
04:             android:enabled="true"
05:             android:exported="false"></service>
06: ...

 

MyActivity.kt (importなどは省略)

01: class MainActivity : AppCompatActivity() {
02:     private val logTag: String = "MainActivity"
03:     private lateinit var mService: MyService
04:     private var mBound: Boolean = false
05: 
06:     private val mServiceConnection = object : ServiceConnection {
07:         override fun onServiceConnected(className: ComponentName, service: IBinder) {
08:             Log.d(logTag, "onServiceConnected")
09:             val binder = service as MyService.MyBinder
10:             mService = binder.getService()
11:             mBound = true
12:         }
13: 
14:         override fun onServiceDisconnected(p0: ComponentName?) {
15:             Log.d(logTag, "onServiceDisconnected")
16:             mBound = false
17:         }
18:     }
19: 
20:     override fun onCreate(savedInstanceState: Bundle?) {
21:         Log.d(logTag, "onCreate")
22:         super.onCreate(savedInstanceState)
23:         setContentView(R.layout.activity_main)
24: 
25:         intent = Intent(this, MyService::class.java)
26: //        startService(intent)
27:         bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
28:     }
29: 
30:     override fun onStop() {
31:         Log.d(logTag, "onStop")
32:         super.onStop()
33:         if (mBound) {
34:             unbindService(mServiceConnection)
35:             mBound = false
36:         }
37:     }
38: }

 

MyService.kt (importなどは省略)

01: class MyService : Service() {
02:     private val logTag: String = "MyService"
03: 
04:     private val mBinder = MyBinder()
05: 
06:     inner class MyBinder: Binder() {
07:         fun getService(): MyService = this@MyService
08:     }
09: 
10:     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
11:         Log.d(logTag, "onStartCommand")
12:         return super.onStartCommand(intent, flags, startId)
13:     }
14: 
15:     override fun onBind(intent: Intent): IBinder {
16:         Log.d(logTag, "onBind")
17:         return mBinder
18:     }
19: 
20:     override fun onCreate() {
21:         Log.d(logTag, "onCreate")
22:         super.onCreate()
23:     }
24: 
25:     override fun onDestroy() {
26:         Log.d(logTag, "onDestroy")
27:         super.onDestroy()
28:     }
29: }

これでビルドできたのでエミュレータにインストールして起動させると logcatが出た。

D/MainActivity: onCreate
D/MyService: onCreate
D/MyService: onBind
D/MainActivity: onServiceConnected

bindService()を呼んで Serviceが作られて、 onBindでインスタンスを返すという順番だ。

 

アプリを閉じる(Home画面を表示)とすぐに onDestroyのログも出た。時間も載せよう。

2021-05-29 15:59:35.365 D/MainActivity: onStop
2021-05-29 15:59:35.400 D/MyService: onDestroy

onSuspend()など実装していないので細かいのは分からんが、onStop()ってこんなにすぐ呼ばれるものなの? bindせずに startService()だけした場合には1分で onDestroyのログが出ていたので、bindしている=アプリとの結びつきが強くなる=アプリの onDestroy()に引っ張られる、ということかしらね。

startService()に変更して試してみよう。

D/MainActivity: onCreate
D/MyService: onCreate
D/MyService: onStartCommand

Home画面にする。

2021-05-29 16:00:41.858 D/MainActivity: onStop
2021-05-29 16:01:41.854 D/MyService: onDestroy

ほら、約1分。
不思議だねぇ。

2021/05/28

[android] Service (6) - bindしてみよう (3)

前回の続きで、Serviceをbindする方の入口となる IBinderの派生クラスを見ているところである。

inner classとして IBinderの派生クラスである LocalService.LocalBinder クラスを作っているのだが、そこで1つだけ実装されている関数が分からん。

01: // Return this instance of LocalService so clients can call public methods
02: fun getService(): LocalService = this@LocalService

関数名は getService。
戻り値の型は LocalService、つまり今回のサンプル実装である Service。
Kotlinでは戻り値しかない関数は括弧を書かずにイコールだけで表現できる。

this@LocalService が気になるのだが、おそらくここで this とだけ書くとどの classの thisなのかわからないので @で指定しているのだろう。どの言語でもそうだけど、記号関係は検索しづらくて大変だ。
ああ、ここだ。 superについても同様だ。

Service は intentを経由?して startService()で起動するので、インスタンスは OSが持っているようなものだろう。だからこの thisも OSが持っていると思われるインスタンスだろう。今回はプロセスが1つしかないのでどっかのメモリを指しているだけと思って良いか。

で、この getService()だが、overrideもなにもついていないので、勝手に作った関数である。これをどうするかというと、Binderのインスタンスを Activityなどに渡してあげて、そのインスタンスを LocalService.LocalBinder にキャストし、そうしてようやく getService()を呼び出してインスタンスを得るのだ。

同じメモリなんだから Binderとか経由せずに Serviceのインスタンスを渡せばいいんじゃないの?という気もするのだが、まあ何か理由があるんだろう。リモートオブジェクトのためのベースクラスらしいので、扱いを共通化するためにそうしているのかもしれん。


図にしたら何か分かるやすくなるかと思ったが、全然だ。

image

今回は現在の Serviceのインスタンスをそのまま渡したが、Binder自体に呼び出したい関数があっても良いし、Serviceが持っている他のクラスのインスタンスを返しても良いらしい。自由だ。同じメモリ空間だから、結局どうでもよいってことなんだろうか。他のクラスについても「サービスでホストされた」というのがよくわからんが、たぶん Serviceが直接呼び出すことができるインスタンスなんだろう。

 

Activityサンプルは、ボタンを押したら LocalServiceの randomNumber()を呼び出している。bind しているかどうかをフラグで持つのは分かるが、排他処理はいらないんだろうか? 乱数取得なんて排他とか気にしなくてよいとは思うのだけど気になるな。

というわけで、今日はここまで。

2021/05/27

[android] Service (5) - bindしてみよう (2)

なかなか進まないAndroidのServiceだが、技術日記だからのんびりいきます。

前回は、bindService()を呼べば良いけど、その前に ServiceConnectionなんかがいるということがわかるところまでだった。

 

bindしてもらう Serviceは、ただ onBind()を実装して IBinderを返せばよいというわけではなく、クライアントからの要求を受け付けられるようにしないといけない。インターフェースを提供する、と表現されているので、それに倣おう。

インターフェースの定義には3つの方法がある。

  • Binderクラスの拡張
  • Messengerの使用
  • AIDLの使用

説明を読む限りでは、こうか。

  • アプリ専用に Serviceを用意してアプリと同じプロセスで実行する場合は、Binderクラスを拡張する
  • 異なるプロセスで実行されるなら、Messengerを使用する
  • AIDLはおすすめしない

おすすめしない理由もしっかり調べるべきだろうが、今回はスルーだ。 Messenger を使うやり方は AIDLを内部で使っていそうなことを書いてある。 AIDL は昔からあったと思うのだが、使い方が危ういから Messengerでラップしたとかだろうか?

まあいい。ただでさえ進まないので、AIDL については私は調べないということにして、出てきたら考えよう。


まず、Binderクラスを拡張するやり方から見ていく。

同一プロセスで動作するのだから、メモリ空間も同じだ。
となると、マルチスレッドで動作して、オブジェクトは Mutexで排他しながら使うのだろうか。共有メモリのように大きな排他システムを使わなくてよいので、パフォーマンスが良くなるとかじゃなかろうか。

なにより、アプリから Serviceのメソッドを直接呼び出せるというのがよい。直感的だ。 Activityで全部実装した後で、やっぱりここは Serviceにしよう、という場合にも移動させやすいのだと思う。

 

この節で説明に使っている LocalServiceは、 randomNumberの getterだけが publicになったクラスだ。 getterが呼ばれるたびに乱数を返す。

そして onBind()では IBinderを返すのだが、これは constな LocalBinderクラスのインスタンスを返すだけ。この LocalBinderクラスが Binderクラスを拡張したものである。
何をやっているかというと、getService()関数だけを定義していて(Binderを継承しているのでオーバーライドしているのかな?)、それは自分自身を返している・・・?

ちょっと Kotlinの読み方が分からないので、調べる。

01:     inner class LocalBinder : Binder() {
02:         // Return this instance of LocalService so clients can call public methods
03:         fun getService(): LocalService = this@LocalService
04:     }

まず、 Binderクラスを継承している。 Kotlinは class内に classが定義できる(nested class)し、内部classも定義できる(inner class)。 あと、無名内部class(anonymous inner class)もある。 Javaもそんな感じだったか。

このページだけ読むと、inner classにしておくと外側の classが持つ変数もアクセスできるように見える。それだけなのか??

 

と、中途半端なところで今日は終わり。

2021/05/26

[android] Service (4) - bindしてみよう

前回はサービスのバインドというものについて眺めただけなので、今回は動かしてみよう。

バインドされたサービスの概要  |  Android デベロッパー  |  Android Developers
https://developer.android.com/guide/components/bound-services?hl=ja

サービスを開始させるだけならActivityなどからintentを作ってstartService()すれば動いたのだが、bindするにはいろいろやってやらんといかんようだ。

 

上から順番に。

onBind()を実装してIBinderオブジェクトを返すと、それを通じてServiceに指示ができるようだ。サーバとクライアントの関係だったと思うので、socketみたいなイメージでよいのではなかろうか。

あれ?
今までもデフォルトで作ったサービスにonBind()があったけど、特に何もしていない。

01:     override fun onBind(intent: Intent): IBinder {
02:         Log.d(logTag, "onBind")
03:         TODO("Return the communication channel to the service.")
04:     }

確かに戻り値はIBinderなのだが、これ、戻り値をしていしていないのにコンパイルエラーになってないのだが、どういうことだろうか?

戻り値を指定しない場合はその型のデフォルト値になる、というタイプかと思ったのだが、Stringを返す関数を作ってみたのだが「returnがない」とエラーになった。

コンパイルエラーになっていないのは、コメントと勘違いしていた TODO だった。

TODO - Kotlin Programming Language
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-t-o-d-o.html

常に例外を投げるので、コンパイラもそれより後ろは評価しないと言うことなのだろう。まあ、手っ取り早くサンプルを動かしたいときは役に立つな。
できればwarningくらい出してほしかったが、コンパイラには何も出ない。出す理由がないのか。
AndroidStudioだと色が違って表示されるので、何かKotlinで指定があるのかと思ったが、単に「TODO」という文字列に対して反応しているだけのようだ。


さて、Kotlinの話はここまでにしてbindできるようにしていこう。

 

ActivityなどからbindService()を呼び出せば良いのだが、その引数にIntentとServiceConnectionがいる。IntentはstartService()したものと同じでよさそうな気がするが、ServiceConnectionはなんだろうか?

bindService()を実行した側からすると、

  • bindしようとしているServiceが存在する
  • bindしようとしているServiceに対するアクセスが許可されている

という条件が満たされているかチェックされて、ようやくbindできることになる。

 

bindされるServiceはどうするかというと、bindService()を呼ぶと Serviceの onBind()が呼び出される。 onBind()は IBinderオブジェクトを返して、これを Serviceと通信するのに使ってもらう。
そう書いてあるのだが、bindService()の戻り値はbooleanだ。さっき書いた2条件を満たしたかどうかという戻り値だ。

なので、IBinderオブジェクトは ServiceConnection の方に出てくる。onServiceConnected()の引数で IBinderが渡されてくるのだ。それを getSerivce()するとサービスのインスタンスが取れるようだ。

とはいっても、bindService を呼び出した方と Serviceが同じプロセスであるという保証はないはずだ。だから間に Androidが入って bindService() を経由してうまいこと扱えるようにしてくれているのだろう。

 

はあ、眠たいので今日もここまで。
全然進まない・・・。

2021/05/25

[android] Service (3) - bindって何よ2

びっくりしたことに、昨日はbindのことを記事にするつもりだったのに、一言も触れずに終わっていた。

hiro99ma blog: [android] Service (2) - bindって何よ
https://blog.hirokuma.work/2021/05/android-service-2-bind.html

かろうじてonBind()があるからセーフか?
・・・いや、そんなことはない。


「バインドされたサービスは、クライアント サーバー インターフェースにあるサーバーです」

バインドされたサービスの概要  |  Android デベロッパー  |  Android Developers
https://developer.android.com/guide/components/bound-services?hl=ja

いきなり何を言っているかわからんな。ちなみに英語だと"bound services"と、bindの過去分詞になっている。bindedじゃないんだね。

bindされたServiceがサーバ、bindしたコンポーネントがクライアントのような立ち位置になるということだろう。

 

 

Serviceが実装されていて、ActivityなどからstartSerivce()でそのServiceを起動したとする。この状態が「開始された」サービスなのだろう。開始しているサービスがあって、それに対してbindService()すると「バインドされたサービス」になるようだ。
英語の方を見ると、別にわざわざ「開始された」を強調したりしてはいない。

 

前回のlogcatではonCreate, onStartCommandだけしか出ていないが、これにバインドまでするとonBindのログが出るんだろう。

 

眠いので、今回はここまで。

2021/05/23

[android] Service (2) - bindって何よ

前回に引き続き、Android OSのServiceについてだ。

うちのサイトは「技術日記」なので、学習としては向いていないと思う。
その代わり、正しくなかったことも書いているはず。
まあ、ネットで調べながらだとそういうことも少なくなってしまうが。


で、Serviceだ。

AndroidStudioでMyServiceというサービスを組み込んでみたのだが、その状態でアプリを起動させてもサービスが立ち上がった気配がない。気配というのは、overrideできる関数を全部オーバーライドしてLogを埋め込んだもののlogcatに何も出力されなかったからだ。

というわけで、ServiceはActivityからの起動が必要となる。
「別のアプリ コンポーネントがサービスを開始でき」と書いてあるので、別にActivityから起動させる必要はないのだが、知らないサービスを起動させることはできないだろうから、まあだいたいはServiceとセットになったActivityから起動させるのが妥当だろう。

01: package com.example.sample0
02: 
03: import android.content.Intent
04: import android.os.Bundle
05: import androidx.appcompat.app.AppCompatActivity
06: 
07: class MainActivity : AppCompatActivity() {
08:     override fun onCreate(savedInstanceState: Bundle?) {
09:         super.onCreate(savedInstanceState)
10:         setContentView(R.layout.activity_main)
11: 
12:         intent = Intent(this, MyService::class.java)
13:         startService(intent)
14:     }
15: }

Kotlinだとnewは不要なのは知っていたのだが、intentの書き方が分からんかった。
インテントとインテントフィルタ」に書いてあったしエラーにならなかったので、これでよいのだろう。

 

MyService.ktはオーバーライドできそうな関数にログを埋め込んだだけ。

01: class MyService : Service() {
02:     private val logTag: String = "MyService"
03: 
04:     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
05:         Log.d(logTag, "onStartCommand")
06:         return super.onStartCommand(intent, flags, startId)
07:     }
08: 
09:     override fun onBind(intent: Intent): IBinder {
10:         Log.d(logTag, "onBind")
11:         TODO("Return the communication channel to the service.")
12:     }
13: 
14:     override fun onCreate() {
15:         Log.d(logTag, "onCreate")
16:         super.onCreate()
17:     }
18: 
19:     override fun onDestroy() {
20:         Log.d(logTag, "onDestroy")
21:         super.onDestroy()
22:     }
23: }

これでActivityを立ち上げると、onCreate, onStartCommandの順でlogcatが出てきた(エミュレータで起動)。

そのまま放置していたが何も変わらない。
そしてアプリを閉じる(Androidのホーム表示)にすると、1分後にonDestroyがlogcatに出てきた。

たった1分かー。

[android] Service (1)

では、AndroidStudioを使ってServiceを追加してみよう。
特に何かしたいわけでもないので、毎分logcatで何か出し続けるようにしてみる。そうすればいつアプリが終了させられたのかもわかりやすいんじゃなかろうか。

 

デフォルトをちょっと変更したプロジェクトの設定はこんな感じ。

image

image

image

image

家にあるAndroid端末で一番古いのがNougatなので、Min SDK Versionはこの辺にしている。

 

プロジェクトを新規作成して取りあえずビルドしてみたのだが、トップディレクトリにあるbuild.gradleのjcenter()でwarningが出ていた。

JCenter サービスの更新  |  Android デベロッパー  |  Android Developers
https://developer.android.com/studio/build/jcenter-migration

既存のものは無期限にダウンロードできます、といっても「閉鎖されるまでは」という条件が付くだろう。メンテナンスしないとしても電気代はかかるし、自社サーバだったらストレージが故障したりするし、クラウドだったらストレージの費用も馬鹿にならんし。


Serviceを追加する!と気合いを入れずとも、AndroidStudioならメニュー操作で基本的なところはやってくれそうだ。

image

2つある・・・?

"Service"の場合

image

"Service(IntentService)"の場合

image

ちなみにActivityのあるディレクトリ?以外で実行するとどこに組み込むかのコンボボックスが増えていた。
main, debug, releaseの3択だったのだが、特定のVariantにだけ追加する(debug, release)か、全部に追加する(main)かを選んでいるのだろうか。

image

Build Variantsを追加すると増えていたので、たぶんそうなんだろう。

image


まずは、ただのServiceを追加した場合。

 

ファイルの追加

app\src\main\java\com\example\sample0\MyService.kt

01: package com.example.sample0
02: 
03: import android.app.Service
04: import android.content.Intent
05: import android.os.IBinder
06: 
07: class MyService : Service() {
08: 
09:     override fun onBind(intent: Intent): IBinder {
10:         TODO("Return the communication channel to the service.")
11:     }
12: }

 

AndroidManifest.xmlに追記。

01:         <service
02:             android:name=".MyService"
03:             android:enabled="true"
04:             android:exported="true"></service>


こちらはIntentServiceを追加した場合。

 

ファイルの追加

app/src/main/java/com/example/sample0/MyIntentService.kt

01: package com.example.sample0
02: 
03: import android.app.IntentService
04: import android.content.Intent
05: import android.content.Context
06: 
07: // TODO: Rename actions, choose action names that describe tasks that this
08: // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
09: private const val ACTION_FOO = "com.example.sample0.action.FOO"
10: private const val ACTION_BAZ = "com.example.sample0.action.BAZ"
11: 
12: // TODO: Rename parameters
13: private const val EXTRA_PARAM1 = "com.example.sample0.extra.PARAM1"
14: private const val EXTRA_PARAM2 = "com.example.sample0.extra.PARAM2"
15: 
16: /**
17:  * An [IntentService] subclass for handling asynchronous task requests in
18:  * a service on a separate handler thread.
19: 
20:  * TODO: Customize class - update intent actions, extra parameters and static
21:  * helper methods.
22: 
23:  */
24: class MyIntentService : IntentService("MyIntentService") {
25: 
26:     override fun onHandleIntent(intent: Intent?) {
27:         when (intent?.action) {
28:             ACTION_FOO -> {
29:                 val param1 = intent.getStringExtra(EXTRA_PARAM1)
30:                 val param2 = intent.getStringExtra(EXTRA_PARAM2)
31:                 handleActionFoo(param1, param2)
32:             }
33:             ACTION_BAZ -> {
34:                 val param1 = intent.getStringExtra(EXTRA_PARAM1)
35:                 val param2 = intent.getStringExtra(EXTRA_PARAM2)
36:                 handleActionBaz(param1, param2)
37:             }
38:         }
39:     }
40: 
41:     /**
42:      * Handle action Foo in the provided background thread with the provided
43:      * parameters.
44:      */
45:     private fun handleActionFoo(param1: String?, param2: String?) {
46:         TODO("Handle action Foo")
47:     }
48: 
49:     /**
50:      * Handle action Baz in the provided background thread with the provided
51:      * parameters.
52:      */
53:     private fun handleActionBaz(param1: String?, param2: String?) {
54:         TODO("Handle action Baz")
55:     }
56: 
57:     companion object {
58:         /**
59:          * Starts this service to perform action Foo with the given parameters. If
60:          * the service is already performing a task this action will be queued.
61:          *
62:          * @see IntentService
63:          */
64:         // TODO: Customize helper method
65:         @JvmStatic
66:         fun startActionFoo(context: Context, param1: String, param2: String) {
67:             val intent = Intent(context, MyIntentService::class.java).apply {
68:                 action = ACTION_FOO
69:                 putExtra(EXTRA_PARAM1, param1)
70:                 putExtra(EXTRA_PARAM2, param2)
71:             }
72:             context.startService(intent)
73:         }
74: 
75:         /**
76:          * Starts this service to perform action Baz with the given parameters. If
77:          * the service is already performing a task this action will be queued.
78:          *
79:          * @see IntentService
80:          */
81:         // TODO: Customize helper method
82:         @JvmStatic
83:         fun startActionBaz(context: Context, param1: String, param2: String) {
84:             val intent = Intent(context, MyIntentService::class.java).apply {
85:                 action = ACTION_BAZ
86:                 putExtra(EXTRA_PARAM1, param1)
87:                 putExtra(EXTRA_PARAM2, param2)
88:             }
89:             context.startService(intent)
90:         }
91:     }
92: }

AndroidManifest.xmlに追記。

01:         <service
02:             android:name=".MyIntentService"
03:             android:exported="false"></service>

なぜか知らんが、AndroidManifest.xmlはさっき追記した <service ...></service> が <service ... /> に置き換えられた。


名前に同じ「Service」が付いているものの、全然似たところがない。

だがIntentServiceServiceの派生クラスだ。
と思ったら! IntentServiceはAPI 30からdeprecatedとのことだ。代わりに androidx.work.WorkManager とか androidx.core.app.JobIntentService を使うことをおすすめされている。

WorkManagerバックグラウンドタスクのところに出ていた。 JobIntentService は「作業ステータスを報告する」に出てきていた。

 

一つ分かったのは、初学者にとってはclassから調べていくのは効率が悪そうだ、ということだ。「人間とは何か」を調べる命題があったとして、細胞とか動植物から調べ始めるようなもので、間違っているわけではないけど後で調べた方が効率がよさそう、というやつだ。

 

そして・・・私はIntentServiceは忘れることにした。
私のバッファはそんなに大きくないのだ。

[android]サービス

前回、画面を作るのはFlutterとかに任せるとして、OSに依存した部分を覚えようということになった。

まずは基本要素のサービスだろう。
と思ったのだが、ドキュメントのメニューを見て迷いが生じた。

image

サービスはバックグラウンドで動くものだったはずだが...

バックグラウンドタスクというのは、バックグラウンドで動くタスクの総称のようで、サービスがバックグラウンドで動き続けるのと比べると、ワンショットで終わったり、定期的に実行したり、指定した時間に実行したりと、ちょっと意味合いが違うようだ。

意味合いが違うのは、単位が違うからでもあろう。
サービスは「アプリコンポーネント」で、バックグランドタスクは「タスク」。
アプリコンポーネントが指すのは4つしかない。

  • アクティビティ
  • サービス
  • ブロードキャストレシーバ
  • コンテンツプロバイダ

四天王というところか。

サービスを使うかどうかは作るアプリの性質に因るだろう。ユーザに見えない形で処理をしたいことはしばしばあるだろうが、例えばボタンをタップして処理が終わるまで待ち状態のアニメーションを表示して、処理が終わったら画面を更新する、みたいなことをするとしよう。たぶんそれだけであればサービスは使わず、バックグラウンドタスクでよいだろうし、たぶんそれも意識せずにFlutterなどがやってくれるんじゃないだろうか。

サービスを使うのはバックグラウンドで長期的に動いていないといけないような、音楽の再生や通知待ちだとかになるのだろう。通知待ちにして待つもの次第で変わって、場合によってはブロードキャストレシーバとかでよいのかもしれない。


サービスはユーザインターフェースを持たない、と書いてある。私としては音楽再生もユーザインターフェースの一つ(何か操作して音楽が鳴り始めるなら、音楽は操作した結果をユーザに分からせるというUI)と考えてもよいんじゃないかと思うのだが、そういうのは専門じゃないのでよくわからん。組み込みの世界だと、LEDの点滅のさせ方とかビープ音をUIとして扱うからね。

それはともかく。
サービスとしては画面がないということだろう。そして種類として「サービスが動いていることをユーザが直接分かる(音楽が鳴っているとか)」「サービスが動いていることをユーザが分からない」の2種類があると書かれている。

ユーザが直接分かるタイプの場合は、サービスが強制終了させられるとユーザに不便が生じるので、実行が維持されるよう「最善を尽くす」ようだ。善処します、ということかな。

そうじゃないタイプの方は、システム側が好き勝手に終了させてもいいよね、という扱いなのだろう。電力消費や他のアプリを行かすことを考えるとこちらの方が望ましい。だってスマホのアプリを起動するとき、今使っているアプリを終了させてから、とかやる人はあんまりいないだろうからだ。

しかし困ったことに、「一度起動したんだから動いてるよね」と思ってしまうのも確かだ。なので、アプリを作る人はどちらのタイプにせよシステムから終了させられてもよいようにする必要がある。

 

と書くのは簡単なのだが、サービスを使う以上は裏で動き続けたいという気持ちがあるからなので、終わってもらっても困る。アプリ・・・アクティビティが前面に出ているときだけ動けばよいなら終わってもよいのだが(アクティビティの終了以降にサービスが終了するならば)、待ち受けるタイプのアプリで、しかもいつやってくるのかわからないようなタイプだと、待っている間に別のアプリを立ち上げるというのは十分に考えられる。

そのときの捌き方を覚えないと行かんな。

2021/05/22

[android]久々のAndroid(2021/05/22)

このブログでAndroidのことを記事にしたのは2019/6/10であった。コーディングに関する記事だともっと前だ。

そのくらいAndroidのことをやってなかったのだが、必要に駆られて調べることにした。


2021/05/22時点で、最新のOSバージョンは11。アルファベットはR。12のベータが進められている状況だ。

Android Studioのバージョンは4.2.1が最新。

image

image

image

 

新規プロジェクトを作ろうとするとこうなった。
Nameは私が書き換えたが、デフォルトは"My Application"だったはず。
時代はKotlinなのね......

ちなみに一番下のチェックボックスは、Minimum SDKがAPI 29以上になるとチェックできなくなる。

image


さて、基本的な環境はこれくらいとして、実際に世の人はどうやってアプリを作っているのだろうか?

昔はともかく、最近はAndroidもiOSも両方ともアプリをリリースしたいという要望は多いだろう。特にデバイスと通信して生かすようなアプリであればスマホの種類がどうのこうの関係なく使えるようにしたいはずだ。
といっても、そういうネイティブな機能はOSごとにやるだろうから、画面と遷移

 

よく聞くのはFlutterだ。Dart言語で書くらしい。

Unoというのがスマホだけでなくデスクトップ用のOSもサポートしていそうな気配がする。Xamarinという文字も出てきていたので、MicrosoftのXamarinも元気なのかもしれない。

Unityも聞くね。ゲームエンジンのようだが、UIも全部自前で描画するならばOSの違いも吸収しやすいのだろうか。

Unreal Engineもゲーム向けのようだ。

React Nativeというのもあるな。

それ以外はよくわからない...
個人的には、デスクトップ向けは別に作った方がよいと思うので、ゲームゲームしたものを作るならUnityかUnreal Engine、そうでなければFlutterかReact Nativeがよいような気がする。
どれも使ってないので雰囲気だけだがね。


取りあえず、画面は何か使って作るものとしよう。

そう割り切ると、OSで実装がいるのはOS独自の部分だ。
そして画面に関する部分は提供されるものをそのまま使えばよいと思うので、画面に出てこない部分を知っておけば良かろう。

 

まずはライブラリ

Jetpackの一部としてAndroidXというライブラリが導入されることになったらしいのだが、そもそもJetpackがわからん。
それにこの話もAPIレベル28以降と書いてあるので、minimim APIがそれより下だったら関係ない・・・?

いや「APIレベル28のリリース以降」という表現が怪しい。
それに「新しいプロジェクトではAndroidXライブラリの使用を推奨」かつ「既存もAndroidXライブラリへの移行を推奨」みたいな書き方をしているところを見ると、少なくとも新規プロジェクトの場合はあまり考えずに使えばよいのか。

そもそもサポートライブラリって何よ?なのだが......将来にAndroid OSのバージョンが上がって該当するclassなどがなくなったとしてもサポートライブラリがうまいことやってくれる、ということだろうか。違う書き方をすれば、今のAndroid OSではサポートされるようになったけれどminimum SDKでサポート外のバージョンを含めたい場合にサポートライブラリが対応していれば使える、というところか。

じゃあJetpackは何なのか?

Android Jetpackってなにもの? - Qiita
https://qiita.com/k_masa777/items/c01c1de6ac763ce5c075

サポートライブラリ(更新停止)+α=Jetpack、Jetpack+β=AndroidX、というところだろうか。
章のタイトルが「サポートライブラリ」だから、今後はAndroid開発をサポートするライブラリというそのままの意味にしてしまいたいのかもしれんね。汎用的な名前は検索しづらいので歓迎しよう。

 

それとサービスか。
表の部分はやってくれるから、バックグラウンドの部分を気にするべきだろう。

iOSもだけど、バックグラウンドで動くことについてはどんどん制限が厳しくなってきてると思う。まあ、動かしたいアプリがたくさんあって、電池の消費がネックになって、OSの機能は強化したくて、となってくるとインストールするアプリについて制限を掛けていくしかないのだろう。

といっても、サービスやらインテントやらは昔からあるので、きっと情報はたくさんあるだろう。


情報を持ってない内容をざっと調べて記事にすると、単なる日記にしかならないことが分かった。
まあ、私の記事は大半が技術日記だから良いのだけどね。

2021/05/02

[js]はてな、はてなはてな、はてなドット

C言語の人なので、JavaScriptのコードもなんとなく読める。

と思っていたのだが、さっぱりわからんものも存在する。


??, ??=

Null合体、Null合体代入、などと呼ぶらしい。

??

??=

 

まず代入しない方の??から。

A ?? Bという書き方で、Aがnullでもundefinedでもない場合はAを返し、そうでない場合はBを返す。主な使い方なのかわからんが、初期値の設定として使う例が書かれている。

C = A ?? 固定値

みたいな書き方で、Aが初期化済みだったらその値を使い、そうじゃなかったら固定値を割り当てる。

??=は、その代入版のようなものだが、代入されるのは自分が未初期化の場合のみだそうな。


||=, &&=

上の??=に似ていて、条件が合ったときだけ代入される。

||=は自分が偽の場合、&&=は自分が真の場合。


?.

Optional chaining、というそうだ。

接続されたオブジェクトチェーンの深くに位置するプロパティの値を、チェーン内の各参照が正しいかどうかを明示的に確認せずに読み込むことを可能にします

わからんね。

 

 

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog?.name;
console.log(dogName);

これだと、adventurerのメンバにdogはいないのだが、参照の正しさを確認しないので、dogNameに代入するときに「adventurer.dog.nameはundefinedだ」ということでundefinedになる。

 

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog.name;
console.log(dogName);

この場合は「adventuere.dog自体が存在しない」と代入時に判断するので、console.log()までたどり着かずにエラーとなる。

 

const dogName = adventurer?.dog;
console.log(dogName);

こんな感じでルート項目というか親というかがundefinedの場合は?.であってもエラーになる。参照をチェックしないのはチェーンなので、親はチェックするということだろう。

…などと思ったが、?.の対象となる要素は右側にある名前だ。A?.BだったらBの方に意味があるので、ここだとadventurerの中にdogがあってもなくてもエラーにしないというだけのことだ。

 

const adventurer = {};
adventurer.dog = {name: 'Pochi'};
const dogName = adventurer.dog?.name;
adventurer.dog = {name: 'Tama'};
console.log(dogName);

これは?.とは関係ないのだけど、出力は”Pochi”になった。adventurer.dog.nameはプリミティブな文字列型なのでdogNameに直接代入されたためだろう。adventurer.dogはオブジェクトなので、そっちを代入したなら出力は変更される。

const adventurer = {};
adventurer.dog = {name: 'Pochi'};
const dogName = adventurer.dog;
adventurer.dog.name = 'Tama';
console.log(dogName.name);

 


他にもあるのだろうが、気付いたのはこんなところだ。