たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 4194
どうもこんばんわ。最近買ったもの紹介ドラゴン。いつまで使えてるか記録します。
水筒。象印の720mlのワンタッチじゃない方です。水道水飲んでます。名古屋のお水が美味しいらしいので飲んでみたいです(買ったものと関係ない)
なんか知らんけどよく頭が痛くなるので、水不足による頭痛を回避するために持ち歩いてる。まあ寝すぎの頭痛とかあるので原因の1つを潰しただけなんですけどね、
ひねって開けるタイプの方を買ったので、容量の割に小さめなのかなとちょっと思った。
あとはリュックサックを無印のでかいやつにしました。いっぱい入るのでいい!!
今まで使ってたやつがもう風前の灯。汚れてきちゃった。
おわり。
Xperia 1 VIIのカメラ、なんとアプリを切り替えても状態が保持されてる。Android アプリ開発したことがあれば、状態を引き継ぐonSaveInstanceState()を律儀に実装していて偉いと口を揃えるかと思います。
全人類の敵であるアクティビティを保持しないを有効にしてもちゃんと動くというのは素晴らしいです。
静止画撮影モード・動画撮影モード・ズーム倍率・自撮りカメラ・・・をアプリを終了せずに切り替えた場合に(タスク一覧画面に残した状態)アプリの状態を復元する。
が、、、どうもこの挙動が慣れなくて・・・Pixelの場合だと動画撮影で倍率変えてても、アプリを一旦離れた状態でリセットされてるので、履歴画面から開いてもまた静止画モード、等倍ズームに戻ってくれてます。
こっちがいい!!
んだけど、設定を見てもそれらしき項目は見つからなかった。。私は!こっちの!挙動が良い!!
Android アプリ開発者向けに話すと、この処理を任意のアプリで使いたい。
アプリを離れたらonStop()にライフサイクルが進むので、そこでタスクから削除するfinishAndRemoveTask()を呼び出したい。
class MainActivity : ComponentActivity() {
override fun onStop() {
super.onStop()
finishAndRemoveTask()
}
}アプリを離れたら自動でタスクを終了するアプリ!!!
以下の録画はカメラアプリではないですが、好きなアプリを離れたと同時にタスク削除することが出来ます。
| 前 | 後 |
|---|---|
使い道としては、今回のように離れたときにアプリを勝手に閉じておいてほしい時に使えます。
今回のカメラアプリの件や、放ったらかしにすると使えなくなるアプリとか?に
ブラウザアプリは絶対やめたほうが良いです。シークレットタブが消えるので。
いい感じに動いてると思います。これでカメラの倍率を毎回戻す手間がなくなりました。。。
APKあります。
ソースコードもあります。後述しますがShizuku+隠しAPIのコンボなのでandroid.jarを差し替える必要があります。
Activityがタスク削除対象になるまで待つAndroidのソースコードであるAOSPを斜め読みして、今表示されているActivityが切り替わったときに呼ばれるコールバックと、タスク一覧画面からタスク(アプリ)を終了する関数を見つけました。
多分IActivityManagerにあるregisterTaskStackListener()とremoveTask()ですね。
ただ、もちろんのことこれらのAPIはAndroid内部で使われる前提なので、そもそもサードパーティーのアプリが利用できない。
いくつか問題があり、権限の件、隠しAPIの件、リフレクション対策の件。
権限の話ですが、Shizukuを使います。これは内部で使われているAPIをShizukuが代わりに叩いてくれます。詳しい話はまだ今度(前もまた今度って言った気がする・・・)ActivityManagerとかTelephonyManager等のなんとかManagerにある関数呼び出しをShizuku経由にすることができます。
まだ問題があります。隠しAPIの話です。Android StudioのSDK Managerからダウンロードできるandroid.jarは隠しAPIが削除された状態になってます。
まあ実行時には隠しAPIも存在するのでリフレクションする技もありますが・・・Android Studioへ隠しAPIが入ったandroid.jarに差し替えるのが良いかと。
最後にリフレクション対策の話。これはAndroidHiddenApiBypassライブラリを入れることで回避できます。
というのも、いくら隠しAPIとは言えAOSPには確かに存在するのに、リフレクションで関数を呼び出そうとしても存在しない例外を投げる仕組みが何故か存在します。Shizukuは関数呼び出しに成功した後の、権限を回避するために使うものであり、そもそも関数が見つからない場合はこの回避策が必要になる。
Shizukuで呼び出して、Flowでコールバックをいい感じにしてこんな感じ。ActivityManagerにある関数呼び出しをShizuku経由にするためのIActivityManager...ですね。
あとはcollect { }するたびにcallbackFlow { }の中身が起動する(コールバックがその都度登録される)のをやめるためにstateIn()でHotFlow (StateFlow)に変換しています。
val taskStackHotFlow = callbackFlow {
val listener = object : ITaskStackListener.Stub() {
override fun onTaskMovedToFront(taskInfo: ActivityManager.RunningTaskInfo?) {
trySend(taskInfo)
}
// TODO 他にもコールバックがいっぱい存在します
}
val activityManager = IActivityManager.Stub.asInterface(
ShizukuBinderWrapper(ServiceManager.getService("activity"))
)
activityManager.registerTaskStackListener(listener)
awaitClose { activityManager.unregisterTaskStackListener(listener) }
}.stateIn(
scope = scope,
started = SharingStarted.Eagerly,
initialValue = null
)first { }で削除対象が来るまで一時停止します。その後、他のアプリに切り替わるのも待って確認します。
これでもう他のアプリに切り替わったので削除の準備が整った。removeTask()を呼び出します。
純粋なwhileループで作れたのがお気に入り。suspend funなのでめっちゃ同期的なコードに原点回帰が出来る。
scope.launch {
val idList = listOf("") // ここに離れたときに削除したい applicationId
val activityManager = IActivityManager.Stub.asInterface(
ShizukuBinderWrapper(ServiceManager.getService("activity"))
)
while (isActive) {
// 削除対象が来るまで待つ
val removeTask = taskStackHotFlow.first { info -> info?.topActivity?.packageName in idList }
// 別のアプリが開かれるのを待つ
taskStackHotFlow.first { info -> info?.topActivity?.packageName !in idList }
if (removeTask != null) {
// 削除する
activityManager.removeTask(removeTask.taskId)
}
}
}これだけです。このアプリではこれをフォアグラウンドサービスで実行しています。
多分いま表示されているActivityを取得することはShizuku使わずにともAccessibilityServiceで作れた可能性がある。
ただタスクから消す方法がイマイチ存在しなさそうで。
Toastを出したかったが、何故かカメラアプリだから出せなかった。logcatには以下のように表示された。
Suppressing toast from package io.github.takusan23.onstop2finishandremovetask by user request.Shizuku経由でToastを出せば流石に表示されるやろってことで、これで表示できると思います。
val notification = INotificationManager.Stub.asInterface(
ShizukuBinderWrapper(ServiceManager.getService("notification"))
)
notification.enqueueTextToast(
"com.android.shell",
Binder(),
"Shizuku で Toast を出す",
Toast.LENGTH_SHORT,
isUiContext,
displayId,
object : ITransientNotificationCallback.Stub() {
override fun onToastShown() {
// do nothing
}
override fun onToastHidden() {
// do nothing
}
}
)まじで関係ない話ですが、B2CとかE2EとかP2Pとかの、toを2に置き換えるやつ、同じ音(?)だから、ただ短くなるってだけの意味だけなんですかね?英語わからん・・・