- 投稿日:2020-03-29T21:18:46+09:00
消せないAndroidアプリをadbを使って強制アンインストール
前提
adbコマンドを実行できることが前提です。
また、開発者モードをOnにして、USBデバッグモードでPCに接続してください。
スマホに接続する
adb shell
を実行して、スマホに接続します。削除したいアプリのpackage名を調べる
am monitor
を実行する。
am monitor
は、起動するアプリをリアルタイムで把握するコマンドです。
HWPOT-H
はスマホの機種によって変わると思います。HWPOT-H:/ $ am monitor Monitoring activity manager... available commands: (q)uit: finish monitoring
am monitor
を実行した後、アンインストールしたいアプリを起動することで、下のようになります。HWPOT-H:/ $ am monitor Monitoring activity manager... available commands: (q)uit: finish monitoring ** Activity starting: com.android.calculator2今回は、最初から入っている計算アプリを消そうとしています。
この、
com.android.calculator2
というpackage名をどこかに記録しておいてください。そして、
q
を押してからエンターをおすことで、am monitor
を終了し、次のコマンドを入力できるようになります。アプリをアンインストールする
pm uninstall com.android.calculator2
を実行します。(com.android.calculator2
は削除したいアプリのpackage名に置き換えてください)そうすると、そのアプリをアンインストールすることができます。
備考
DELETE_FAILED_INTERNAL_ERROR1
が発生する場合
pm uninstall -k --user 0 package名
をpm uninstall package名
の代わりに使用する。その他
hatenaでも投稿しています。
ぜひ、そちらも見てください
- 投稿日:2020-03-29T20:54:19+09:00
【Flutter】バグ解決: Bad state: Stream has already been listened to.
今回の件のGitHubはこちら
https://github.com/Tetsukick/flutter_BLoc/commit/76748e912be5896f8e998a5a50244fcb77d659b6
現象
StreamControllerを使ってBLoCパターンでstatement管理をしていたが、TabBarViewを追加して、
Tabを切り替えて、再度同じタブに復帰した際に画面の表示が崩れるようになりました。その際のerrorがこちら。
Bad state: Stream has already been listened to.解決策
StreamController
ではなく、BehaviorSubject
を使用することで解消元のソースコード
エラー解消前import 'dart:async'; class CounterBloc { // input final _actionController = StreamController<bool>(); Sink<void> get changeCountAction => _actionController.sink; //output final _countController = StreamController<int>(); Stream<int> get count => _countController.stream; int _count = 0; CounterBloc() { _actionController.stream.listen((isPlus) { if (isPlus) { _count++; } else { _count--; } _countController.sink.add(_count); }); } void dispose() { _actionController.close(); _countController.close(); } }エラー解消後import 'dart:async'; import 'package:rxdart/rxdart.dart'; class CounterBloc { // input final _actionController = BehaviorSubject<bool>(); Sink<void> get changeCountAction => _actionController.sink; //output final _countController = BehaviorSubject<int>(); Stream<int> get count => _countController.stream; int _count = 0; CounterBloc() { _actionController.stream.listen((isPlus) { if (isPlus) { _count++; } else { _count--; } _countController.sink.add(_count); }); } void dispose() { _actionController.close(); _countController.close(); } }その他
勉強不足で
StreamController
とBehaviorSubject
の違いをまだ理解できておりません。
ご存知の方いましたら、教えていただけますと幸甚です。参考記事
https://stackoverflow.com/questions/51396769/flutter-bad-state-stream-has-already-been-listened-to
- 投稿日:2020-03-29T16:58:05+09:00
DataBindingを使っているChipをChipGroupに動的に追加/削除する
要件
- アプリ全体ではDataBindingを使っているものとする
- Chipの個数は可変であり、動的に増減させることが出来る
- Chipには✕ボタンを表示し、押したら消えるようにする
実装
ライブラリの追加
Chipを使うためのライブラリを追加する。
app/build.gradledependencies { (中略) implementation 'com.google.android.material:material:1.1.0' (中略) }AppThemeの変更
Chipを使う際にAppComponentだとクラッシュするので暫定対応。
本記事ではTheme.MaterialComponents.Light.DarkActionBar
を利用している。styles.xml<resources> <!-- Base application theme. --> <!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">--> <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources>タグの定義
Tag.ktdata class Tag( val id: String, val name: String, )Chipのレイアウトを用意
view_tag_chip.xml<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="tag" type="com.masaibar.chipsample.Tag" /> </data> <com.google.android.material.chip.Chip style="@style/Widget.MaterialComponents.Chip.Entry" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checkable="false" android:text="@{tag.name}" android:textAppearance="@style/TextAppearance.AppCompat.Small" tools:text="Tag" /> </layout>Chipを追加する画面レイアウトを用意
activity_main.xml<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context=".view.main.MainActivity"> <com.google.android.material.chip.ChipGroup android:id="@+id/chip_group_tags" android:layout_width="match_parent" android:layout_height="wrap_content" app:singleSelection="false" /> </FrameLayout> </layout>動的に追加
ChipにもDataBindingを使いたいのでinflateしてタグ情報をバインドしている。
✕ボタンが押された際のイベントはsetOnCloseIconClickListener
で設定できる。
Chipが生成できたらChipGroupTagsのaddView()に渡してやると、ChipGroupの子要素に追加される。MainActivity.ktclass MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView( this, R.layout.activity_main ) for (i in 0 until 20) { DataBindingUtil.inflate<ViewTagChipBinding>( LayoutInflater.from(this), R.layout.view_tag_chip, null, false ).apply { tag = Tag( id = i, name = UUID.randomUUID().toString().substring(0, Random.nextInt(10)) ) (root as? Chip)?.setOnCloseIconClickListener { binding.chipGroupTags.removeView(root) } }.let { binding.chipGroupTags.addView(it.root) } } } }結果
ChipGroupのおかげでChipの大きさに応じて良しなにレイアウト、収まらない場合は改行してくれているのがわかる。
✕ボタンを押すとChipが消え、消えたところは良しなにレイアウトを詰めてくれる。
参考
https://stackoverflow.com/questions/40917521/android-data-binding-programmatically-instantiated-view
https://stackoverflow.com/questions/50494502/how-can-i-add-the-new-android-chips-dynamically-in-android/50823177
- 投稿日:2020-03-29T12:27:59+09:00
Google Play ConsoleでのANRの調査
Google Play Console
では、アプリのクラッシュやANRの発生ログを確認することができます。
ANRは、クラッシュに比べると発生原因や再現手順を比較的追いにくい場合が多いです。そこで、基本的なANRの調査方法についてまとめました。
ANRの基本的な知識
念のため、はじめにANRの基本についても書いておきました。
基本的な内容なので、読み飛ばしていただいでも結構です。ANRとは
ANRは、
Application Not Responding
の略です。
その名の通り、アプリが応答していないことをユーザーに伝えるための仕組みです。ANRが発生すると、「アプリケーション応答なし」のダイアログが表示されます。
「ボタンをタップしたらフリーズした…」といったときに、以下のように表示されるダイアログがANRのダイアログです。
ANRの発生条件
ANRの発生条件は以下の2つです。
- 入力イベント(キーの押下や画面タッチなどのイベント)に対する応答が 5 秒以内にない
BroadcastReceiver
の実行が 10 秒以内に終了しないANRの発生要因
ANRは、UIスレッドで実行時間の長い処理を行って処理をブロックしてしまうことが要因で起こります。
UIスレッドは、画面の描画などGUI関連の操作に使用されるスレッドですが、タッチイベントなどユーザーの操作に対してフィードバックを与えるようなイベント処理もUIスレッドで行われます。
このフィードバックが遅くなると、ユーザーから「なぜか反応しない。固まった。フリーズしたああああ」など異常事態に感じられてしまいます。
なので、素早く処理をしてあげたいところですが、UIスレッドはスレッドなので、同時には1つの処理しかできません。
つまり、このUIスレッドでユーザーのタッチ反応とは別で、長い処理(ネットワーク通信やデータベース関連の処理など)を行ってしまうと、ユーザが画面をタッチしても、タッチのイベント処理がこの長い時間のかかる処理が終わるのを待ってから処理されてしまう...ことになります。
そのために、UIスレッドで実行されるメソッドでは、実行する処理をできる限り少なくする必要があります。
ANRの対策方法
以下の方法がANRの対策方法として一般的です。
- 主なライフサイクルメソッド(
onCreate()
やonResume()
など)で設定する内容はできる限り少なくする- ネットワークやデータベースの処理や、CPUへの負荷が高い計算(ビットマップのサイズ変更など)は、ワーカースレッドで行う
※データベース処理の場合はワーカースレッドもしくは非同期リクエストを使用するでもいいと思います。
UIスレッドで行う処理はなるべく短くして、時間のかかる処理は別スレッドで行う
これがアプリのUXを向上させる&ANRを防ぐ鉄則だと思います。Google Play consoleでのANRの調査方法
基本的な見方
Google Play console上に表示される、ANRのログの基本的な見方は以下の通りです。
- 下から順番(降順)に追って見る
- 黒字で書いてあるものは自分たちのコード(グレーは内部的な処理)
- 最新のエラー発生versionを見て、最初に該当のANRが発生した
versionCode
を確認する(既存なのか新規発生なのかがわかる)まず、黒字で書いてあるものは自分たちのコード(グレーは内部的な処理)なので、
この場合だと、InstalledApp
のonCreate()
からStartNotificationWorker.kt
というクラスを呼んでいる所が問題の該当箇所になっている可能性が高いとわかります。ANRを調査するときに意識すること
問題になっていそうな該箇所がわかったら、次にその箇所のコードを見て、以下のような観点で調査をしていきます。
- 「黒字」で書かれた自分たちのコードがどういうタイミングで走るのか?
- UIスレッドで行われているか?
- 通信周りやDB操作は「非同期」で処理していない所があるか?
- ログにたくさん出ていないか?(ログにたくさん出ているところは、処理がネストしていることが原因になっていることが多い)
自分が書いたコードの場所でANRが発生している例
例として、以下のような自分が書いたコード部分でANRが発生していそうなログだった場合を考えます。
at jp.co.hoge.view.ProgressAnimationLayout.<init> at jp.co.hoge.ui.base.BaseFragment.onCreateView (BaseFragment.java:126)まず、上記のログから、
BaseFragment
のonCreateView()
でANRが発生していることがわかります。
onCreateView()
が走っている場合は、Fragmentが生成されたタイミングになるので、UIスレッドの処理の最中だとわかります。ここで、
onCreateView()
にそんなに時間がかかっていなければログには表示されないので、onCreateView()
の中の処理に絞ることができます。そして、
ProgressAnimationLayout.<init>
のinitとなっているのがポイントで、これは view の初期化タイミングを意味します。そうなると、
Fragment
のonCreateView()
の中でProgressAnimationLayout
の初期化の中で10秒以上たった = 「Fragment の view の初期化処理が重い」といった感じで仮説を考えることができます。
内部的な処理の場所でANRが発生している例
ANRを日々調査していると、以下のような黒字が全くなく、内部的なコードの中で発生しているANRもよくあります。
こういう場合も下から順に追って、怪しそうなやつを探します。
今回の場合だと、
at androidx.work.WorkManager.initialize<init>この辺りが怪しそうだなと思います。
以下のような流れで、WorkManeger
の初期化に時間がかかってANRが発生してしまったかななど考えられます。
- アプリが死んでいる状態で、
PlayService
に登録していたWorkManager
が立ち上がるApplication
関係の初期化Worker
の実行中に時間がかかってしまったパターン見分け方としては、他の
Runnable
になっているスレッドをいくつか開いて調査します。
Runnable
になっている Thread(Signal Catcher)をみるとAndroidのBootプロセス系が走っているところだったRunnable
になっている Thread(Queue)を見るとRemoteConfigManager.<init>
が走っていて、held mutexes = mutator lock(shared held)
と書いあったしたがって、
Remote Config
の初期化に時間のかかった &mutax
がロックされているのでどこかで共有プロセスがロックされて動けない状態が続いたことで10秒経過してANRが発生といった感じで、内部的な処理の場合でも仮設を考えられます。
まとめ
ANRを調査するときは、以下のような事を意識して調査すると、問題の箇所が見つけやすい
というのが自分なりの見解です。
- 「黒字」で書かれた自分たちのコードがどういうタイミングで走るのか?
- UIスレッドで行われているか?
- 通信周りやDB操作は「非同期」で処理していない所があるか?
- ログにたくさん出ていないか?(ログにたくさん出ているところは、処理がネストしていることが原因になっていることが多い)
また、ANRが発生しないようにコードを書くことがそもそも大切なので、上記の内容はコードを書く際も気をつけるポイントになってくるかなとも思います。
あくまで今回は、
Google Play Console
上のログから、原因の調査をするところまでなので、
この後は、CPU Profilerなどを使用して、自分の仮説が正しいかさらに詳細な調査をしていく流れになっていくと思います。詳細はこちらの公式ページにわかりやすくまとまっています。
CPU Profiler を使用して CPU アクティビティを検査する自分の経験からできあがった見解なので、間違っている所もあるかもしれません。
もしあればご指摘いただけると嬉しいです。
最後まで読んでいいただき、ありがとうございました。参考文献
- 投稿日:2020-03-29T01:50:44+09:00
Android Vulkan で画面回転の取り扱いのメモ
Vulkan では, いろいろ自前で処理コードをかかないといけません.
Android で, 画面が回転したり, 非アクティブになったりだと SwapChain の作り直しになります.
Android 側で回転させる
https://developer.android.com/ndk/guides/graphics/design-notes#apply
Android のコンポジターのほうで画面回転させて表示させる方法があります(筆者は試したことありません)
GPU 負荷がかかるとありますので, できれば自前で処理したほうがよさそうです.
回転を固定する
AndroidManifest.xml に screenOrientation 記述でとりあえず固定して, 回転を考えるのを止める手があります.
https://developer.android.com/guide/topics/manifest/activity-element
(orientation にもいろいろパターンあってめんどいですね..)ただ, 条件によっては回転してしまったり, OS 側の機能かなにか無理やり回転させることはできるっぽいので, Google Play できちんとしたアプリ公開するなら回転対応は必須そうです.
How to force landscape or portrait mode in apps like Instagram and others (Android)
https://www.phonearena.com/news/How-to-force-landscape-or-portrait-mode-in-apps-like-Instagram-and-others-Android_id78439how to force landscape mode with NDK using pure c++ codes
https://stackoverflow.com/questions/12702868/how-to-force-landscape-mode-with-ndk-using-pure-c-codes注意点
<activity>
のタグにandroid:screenOrientation
を記載しないといけません!筆者は上位の
<application>
タグに記載してしまい, 設定が反映されず 1/2 営業日を浪費してしまいました回転の処理をいい感じに扱う
Handling Device Orientation Efficiently in Vulkan With Pre-Rotation
https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.htmlめんどくさいですね...
画面が回転したかどうかの判定は, Android 10 だと
vkQueuePresentKHR
で取得できて楽です.
Android 9 or earlier だとめんどいです.まとめ
画面がリサイズとかしたらいずれにせよ SwapChain 作り直しになりますので, SwapChain になるべく依存しないデータ構造にしておくのがベターですね.
(UniformBufferObject とか, swapChain の枚数ぶん Descriptor 作成が必要になったりする. 画面リサイズとかで swapChain の枚数が変わるというのは基本ないですが...)