投稿

ラベル(Android アプリ開発のメモ)が付いた投稿を表示しています

Androidアプリ「こえめと-声で操作するメトロノーム」を公開しました

「こえめと」は、楽器を両手で持ったまま、声で操作できるメトロノームです。  現在、GooglePlayでオープンテストという形で公開しています。アプリ(Androidのみ)は次からダウンロードできるので、興味のあるかたは使ってご意見等をお寄せください。 https://play.google.com/store/apps/details?id=mashro.com.wixsite.mashro.km03  趣味でアプリを作っているアマチュアなので、最小限の機能のみ搭載したシンプルな作りですが、それがかえって使いやすいように思います。  (その後、全面公開としました。また紹介動画をYouTubeにアップしました。) https://www.youtube.com/watch?v=cYpEThvyyZI 【特徴】 ・30~300の数字を言うとそのテンポでメトロノームが始動し、「止まれ」というと止まる。 ・図形が回転し、視覚的にもテンポをつかみやすい。 ・市販電子メトロノーム並みの精度(時間間隔の誤差の標準偏差±0.15%)。 ・無料で広告もなし。データの保存も外部送信もないのでプライバシーの問題もなし。 ・私自身、チェロの練習に使っているが、結構役立つと思う。 ・メトロノーム自体の音で音声認識が邪魔されることがあるので、外部スピーカーかイヤホン等を推奨するが、それらがなくても使える。 -------------------- 開発の経緯  1年半ほど前からチェロを習い始め、練習のたびにメトロノームアプリを使うのだが、スタートやストップ、テンポの変更をするたびに弓を置いて操作しなければならないのが煩わしかった。楽器を持ったままで音声で操作できるメトロノームくらいあるだろうと探してみたが、見当たらない。そこで、自分で作ることにした。 音声認識  まず、音声認識である。Androidには、SpeechRecognizerというクラスがあって、これを使うと簡単に音声認識できるらしい。 https://qiita.com/kaleidot725/items/8fe6b6f137a9a8710d21  こういったサイトを参考にして自分でやってみると、確かに簡単に、音声を文字にすることができた。ただし「ボタンをタップして音声入力する」という使い方しかできず、「ずっと待機状態にしていて...

KeyguardManager によるメモリリークはグーグルのバグらしい

イメージ
 KeygurardManagerによるメモリリークが確認された。どうやってこれを解消すればいいか検討したが、結論からいうと、どうやらこれはグーグルのバグで、解消できない可能性が高そうだ。(もし詳しい方がおられたら教えてください) ----------------------------  アプリを作ったら、メモリリークがないか確認しなければならない。  メモリリークとは、アプリが確保したメモリが不要になっても解放されないままになることで、使えるメモリが減ってしまい不具合の原因となる。その確認のためにリークキャナリー(LeakCanary)を入れて確認することが広く行われている。  リークキャナリーの入れ方についてはこちら。非常に簡単である。 https://zenn.dev/yass97/articles/e25960d5511c30  ただ、その読み方はちょっと難しい。今回確認されたリークは次のようなもの。  これを見ていくと、上から1/3くらいのところにKeyguardManager とあり、また、下から1/3くらいのところにAlarmActivityとある。さらにそのすぐ下にLeakinng:YES とあるので、結局、AlarmActivityというアクティビティの中にある、KeyguardManagerのインスタンスにリークがあるということなのだ。  ちなみに、KeyguardManager とは、スマホのロックを管理するクラスで、私は目覚まし時計を作る際に、スマホがロックされていてもアラームの画面だけは表示できるようにするために使ったのだ。  さて、ではどうやってこのメモリリークを解消するか。  メモリリークは多くの場合、アクティビティ等を閉じる段階(onPauseやonDestroy)において、リークの原因となっているオブジェクトを解放し、nullにしてやることで解消できる。このブログでも次のような例について、メモリリークが解消できたことを報告した。 https://marsh-mashro.blogspot.com/2023/08/broadcastreceiver.html https://marsh-mashro.blogspot.com/2023/08/handler.html  しかしKeyguardManagerのメモリリークはこのような方...

ボタンの重なりとelevation

elevationで画面要素のz軸方向の高さを決めるが、そのときの発見。 ボタンが重なった時、どう反応するかということ。 結論からいくと、重なったボタンがあるとき、上(elevationの値が大きいほう)のボタンが反応するときと、そうでないときがある。 両方のボタン下のボタンのelevationを0dp、上のボタンのelevationを2dpとすれば意図どおり上が反応するが、それ以外のときはよくわからない。 なぜこういうことを考えたかというと、「画面のどこでもタップすればヘルプに飛ぶ」というようなアプリを作ろうと考えて、画面全体を覆うような透明なボタンを作った。そのうえで、一時的に画面に普通のボタンを表示して、それはまた別の機能を持つようにしようと思ったところ、後者のボタンを押すと前者の透明なボタンが反応してしまう。 そこで、elevagionを操作すればこの問題が解決できるのでは、と考え、透明なボタンを10dp、一時的な普通のボタンを20dpに設定し、これならより上にある後者のボタンが反応するだろう、と思ったのだが、うまくいかず、やっぱり前者の透明なボタンが先に反応してしまう。で、いつものように悩んでいた。 この問題は、たまたま、透明なボタンのelevationを0dp、一時的なボタンのelevationを2dpとすることで解決できた。0dpと1dpではうまくいかないときがあり、また、3dp以上でもうまくいかないことがあった。 なお、ImageViewやTextViewはこれとは関係なく、それらが上にあってsetOnClickListenerが設定されていても、下にあるボタンのほうが優先的に機能するみたいだ。 「ようだ」とか「みたいだ」が多い記事ですみません。今度時間のあるときにちゃんと検証しようと思います。

SDK34におけるForegroundServiceの問題

 これまで動いていたアプリのTargetSDKを33から34に変えたところ、ForegroundServiceを立ち上げるところでいきなりエラー。その理由と対処法がわかった。  今回のバージョンアップで、ForegroundServiceには「サービスタイプ」を指定しなければならなくなった。  サービスタイプとは、そのフォアグラウンドサービスが、例えばメディア再生を行うものなのか、外部との通信を行うものなのか、といった種別のこと。次を参照。 https://developer.android.com/about/versions/14/changes/fgs-types-required?hl=ja 例えば、サービスがメディア再生を行うものであった場合は、マニフェストで、 パーミッションのところに、これまでは次の1文があれば良かった。 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 今後はそれに加えて、次の文を入れる必要がある。   <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> さらに、<service・・・></service>の・・・の部分に  android:foregroundServiceType="mediaPlayback" を追記することも必要。 この2点を追記すると、以前と同様に動くようになった。

ビルドエラー:Attempt to create a global synthetic for 'Record desugaring' without a global-synthetics consumer の解決

このエラーで苦労している人は他にはいないでしょうか。他の原因もあるかもしれないけど、私の場合はこれで解決できたので報告します。   【問題発生の経緯】 Aというアプリを複製してBというアプリを作った(複製の方法は、以前このブログで書いた次の方法)。 https://marsh-mashro.blogspot.com/2023/08/androidstudio.html ところが、次のエラーが出てビルドができない。 Attempt to create a global synthetic for 'Record desugaring' without a global-synthetics consumer 直し方はおろか、エラーの意味すらわからず途方に暮れた。そもそも、複製前のアプリと複製後のアプリはアプリ名を除いて全く同じなのに、なぜこんなことが起きるのか・・・。 検索すると次のようなページが出てくるが、読んでもよくわからない。ただ、どうやらJavaのバージョンに関係ありそうだということがわかった。 https://www.mongodb.com/community/forums/t/how-to-properly-connect-my-kotlin-android-app-with-mongodb-atlas-cluster/196264 https://stackoverflow.com/questions/75043114/android-build-produces-error-attempt-to-create-a-global-synthetic-for-record-d そこで、build.gradleの中のJavaのバージョンを表していそうなところだとか、Project Structureだとか、今まで触ったこともないようなところを闇雲に変更したり古いバージョンを使ってみたりしたが、もちろんエラーはなくならない。 【何とか解決】 結構絶望的な気分になっていたのだが、Bのもとになった空っぽのアプリはビルドができることに気づいた。ということはGradleの問題ではない。そこで、部分的にAアプリのコピーをしてBアプリにペーストし、そのたびにビルドを試す、ということをやって...

画面回転で苦労した話(原因はわかった)

画面を回転させると、なぜかレイアウトが崩れてしまう。 https://qiita.com/QiitaD/items/7d405e93f39a4a3c9d95 などを参考にさせていただいて、ちゃんと縦用の画面と横用の画面を作って、それぞれ layout と layout-land のフォルダに入れているのに、縦で立ち上げた画面を回転させても横用の画面にならず、縦用のレイアウトを無理やり横に合わせた形になってしまう。その逆も同じ。横で立ち上げた画面を縦にするときもうまくいかない。つまり、レイアウトの切り替えをやってくれないのである。その理由がわからず、ずいぶん悩んだ。 今度やっとその原因がわかったのだが、画面回転を検知するための設定に問題があった。具体的には、マニフェストの当該Activityに "android:configChanges="orientation|screenSize" を追記したとき、この問題が起きるようだ。 原因はわかったがまだ問題は残る。上記の追記は画面回転を検知するためには必要。 今回は、アプリの立ち上げ時にメッセージを出すにあたって、画面回転するたびに同じメッセージが出てはうるさいので、回転の後すぐはそれが出ないようにするために、この機能を使っていたのだった。 しかしこれをやると、画面のレイアウトが崩れてしまう・・・この両立をする方法はないのか。 (ちなみに、無理やり縦用と横用のレイアウトに違う名前を付けて、アクティビティ上で画面の向きを検知して 、 縦の時はsetContentView(縦用レイアウト)、横の時はsetContentView(横用レイアウト)としてみたが、 やはりレイアウトは切りかわらなかった。) 参考までに、画面回転の検知には、上記の追加を行ったうえで、以下のようなコードを AppCompatActivityの中に書く。この場合、画面が回転したらsharedPreferenceの"Rotated"がtrueになるので検知できる。 override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(new...

エミュレーターのバグ?

イメージ
 現在作っている時計アプリについて、自分の実機以外の機種でも動くのか試しにエミュレーターを使って試してみた。新しい機種、新しいAPIでも動くかやってみようと、Pixel 6 Pro API 31を使った。  そしたら何と!変な時間が出るではないか。今までこんな経験はしたことがなかった。これまでも時差で9時間ずれていて一瞬驚いたことはあったが、今度はそうではない。半端にずれた時刻が表示されている。一方、エミュレーターのデフォルトの時計アプリは正しい時刻を指している。もしかして、今作っているアプリは最新機種には適合してないのか・・・。そこで、非常に簡単な、時間だけを表示するアプリを作ってみた。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout. activity_main ) findViewById<Button>(R.id. button ).setOnClickListener { val now = Calendar.getInstance() val nowYear = now.get(Calendar. YEAR ) val nowMonth = now.get(Calendar. MONTH )+ 1 val nowDay = now.get(Calendar. DAY_OF_MONTH ) val nowHour=now.get(Calendar. HOUR_OF_DAY ) val nowMinute = now.get(Calendar. MINUTE ) findViewById<TextView>(R.id. tvDate ). text = " $ nowYear 年  $ nowMonth 月  $ nowDay 日 " ...

AndroidStudioのプロジェクトをコピーする

 コーディングがある程度進んだところで、プロジェクトをコピーする必要が出てきました。今までのプログラムは一応動いているのでとっておきたいが、新しいアイディアを加えることも試してみたいので。  AndroidStudioにはプロジェクトをコピーする機能がないので、自分でやらなければなりません。ネットを検索すると、プロジェクトフォルダを複製してそれに手を加える方法がいくつか出てきましたが、私は次のような方法でこれをやりました。これでも、今のところ問題なく動いています。 ----------------------  まず、新しいプロジェクトを新しい名前で作ります。今までのプロジェクトを「myApp」、新しいのを「myNewApp」としておきましょう。  まず、コピーです。  myApp の AndroidManifest を開いてそのテキストを全てコピーします。そしてmyNewApp のマニフェストファイルを開き、そこにペーストします。すなわち、myNewAppのマニフェストの内容を全て、myAppの内容に置き換えます。  同様に、MainActivityの内容を全て置き換えます。  MainActivity以外のActivityやServiceを、myAppのプロジェクトツールウィンドウからコピーして、myNewAppの同じフォルダにペーストします。  myAppのプロジェクトツールウィンドウから、resの配下にあるフォルダを全てコピーして、myNewAppのresフォルダにペーストします。  次に置換です。  myNewAppのマニフェストを開いて、ctrl+Rを使って「myApp」を「myNewApp」に置換します。  myNewAppのアクティビティやサービスを一つずつ開いて、「myApp」を「myNewApp」に置換します。通常は各アクティビティの最初の行に一つあるだけだと思います。  myNewAppのresの中のthemesを開いて(複数ある場合は順に開いて)、「myApp」を「myNewApp」に置換します。  【後で付け加え。時期によってはこれをやらないとうまくいかないことがあります】myAppとmyNewAppのbuild.Gradle(Project・・・)を開いて比較し、アプリ名以外の違いがあったら、myNewAppのほうをmyAppと同じ内容に...

BroadcastReceiverによるメモリリークが修正できた件。

  時計アプリを作っていて、アラーム時刻をサービスに送ろうとしてBroadcastReceiverを使ったら、メモリリークが発生。しかしそれを何とか修正した件。 最初に作った送信側のActivityがこれ。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_zoperation) //MyReceiverの登録 val filter1 = IntentFilter() filter1.addAction("this_is_alarm_time") registerReceiver(MyReceiver(), filter1) //ボタンを押すと findViewById(R.id.button).setOnClickListener{ //目覚まし時刻の入力(4桁の数字を入力) val alarmTime:String = findViewById(R.id.inputAlarmTime) .text.toString() val intent = Intent("this_is_alarm_time").apply { putExtra("alarmTime", alarmTime) } sendBroadcast(intent) } } } 受け側はこんな感じ。 var alarmTime2: String = "" class MyService : Service() { //MyReceiverをここに置く。 class MyReceiv...

Handlerによるメモリリークが修正できた件

  時計アプリを作っていた際に遭遇したメモリリーク。1秒ごとに、現在時刻をtvTimeNowというTextViewに表示するような簡単なプログラムだが、LeakCanaryで調べるとメモリリークがあることが判明した。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_zmain) val handler = Handler(Looper.getMainLooper()) val refresh = object : Runnable { override fun run() { //時刻を表示 val now = Calendar.getInstance() tvTimeNow.text= "${now.get(Calendar.HOUR_OF_DAY)}:${String.format("%02d", now.get(Calendar.MINUTE))}" handler.postDelayed(this, 1000) } } handler.post(refresh) } }  メモリリークについてよくわかっているとは言えないのだが、LeakCanaryによるとこのrefreshのところでリークがあるらしい。それで、これを解放するコードを書けばいいのか・・・?と試行錯誤してつぎのようになった。 class MainActivity : AppCompatActivity() { latein...

変数の名前を変える

 変数の名前を変えるのに、今まで、いちいち検索・置換をする方法しか知らなかった。グローバル変数や画面要素のidは、別画面でも検索・置換をしないといけないし、ミスしないように気をつけないといけないし、面倒だなあと思っていた。それが、ちょっと調べてみると次のようなサイトがあり、「変数を選んでshift+F6」をするだけで、Refactorできることがわかった。 sugoi_wada(Hikaru Wada) さん、ありがとうございます。  それで、作っているコードで、変数の名前をかなり大規模に変更。  Activityに出てくる変数は 「int〇〇」とか「str△△」とか、最初に型名をつけて、その変数が表示されるTextViewは「tv〇〇」「tv△△」にする、sharedPreferencesで値を保管するときはそのタグを「"int〇〇"」「"str△△"」など変数名にクオテーションマークをつけたものにする。変数名は長くなったけどずいぶん頭が整理された。どれか一つの名前がわかっていたら、残り2つの名前は見なくてもわかるし。  ちょっとの手間をかけたくなくて適当な名前をつけていたけど、やっぱりいい名前をつけたほうがいい。まあ、後でRefactorすればいいことなんだけど。  

カッコつけすぎに注意

ときどき setOnClickListener の省略しない使い方がわからなくなるので復習。 こういうのを時々書いて、忘れないようにしなければならない。 class Listener: View.OnClickListener { override fun onClick(p0: View?) { findViewById(R.id.text1).text = "ボタンが押された" } } val listener = Listener() findViewById(R.id.button1).setOnClickListener(listener) ところで、これをやりながら気づいたのだが、次のものは機能しない。 findViewById(R.id.button1).setOnClickListener({view,View -> { findViewById(R.id.text2).text = "ボタンを押した"}}) ボタンは押せるけど、テキストが変わることはない。 なぜかというと、波カッコ({})が二重になっているから。つまり次が正解。 findViewById(R.id.button1).setOnClickListener({view,View -> findViewById(R.id.text2).text = "ボタンを押した"}) 従って、これなんかも動かない。 findViewById(R.id.button1).setOnClickListener {{ findViewById(R.id.text2).text = "ボタンを押した"}} 例によって理由はよくわからないけど、波カッコはただまとめるだけじゃなくて、その中に あるものをちょっと違うものにしてしまう働きもあるのだろう。 では、...

周囲の明るさに応じた画面の輝度の調整

現在、時計アプリの製作中なのですが、周囲の明るさに応じて画面の明るさを変えられたらいいなと思い、その部分をテストするためのアプリを作りました。 今までは入力も結果表示もスマホ画面か音声の出力しかやっていなかったので、周囲の明るさを取得する照度センサーや輝度を調整するバックライトの操作は「難しそう」の先入観がありました。 しかしやってみると輝度の調整は簡単でした。onCreate(・・・) の下、setContentView(・・・) の後に次の3行を付け加えるだけ。 (kotlin) val lp = window.attributes //ウインドウの属性を取得 lp.screenBrightness = 0.1f //属性の中で輝度を0.5に設定(最大値は1.0) window.attributes = lp なんなら、次の1行でもよい。(ただし後述の問題点あり) (kotlin) window.attributes.screenBrightness = 0.1f いっぽう照度の取得は少し面倒。SensorEventListenerというインターフェースが必要で、sensorManagerというクラスも使う必要があります。 次のコードで、スマホ付属の照度センサーの有無と、センサーがある場合にはその測定値が画面表示されます。 (kotlin) class MainActivity : AppCompatActivity(), SensorEventListener { //SensorEventListenerというインターフェースをつけておくことが必要。 private lateinit var sensorManager: SensorManager //SensorManagerを遅延初期化 private var lightSensor: Sensor? = null //SensorクラスのlightSensorをメンバ変数として作っておく(onCreate、onResumeの両方で使うので) private lateinit var te...