20210909のAndroidに関する記事は7件です。

iOSとAndroidを区別するのに便利なJavaScript置いておくね

var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); これは基本的にユーザーエージェント(UA)を見て正規表現でandroidかiOSかを判別しています。UA &&の部分はnullチェックですね。UAの値を取得できなかった時にエラーで落ちないようにしています。 日本のスマホのシェアはAndroidとiOSが99%を占めているので上記以外はその他で例外処理をしてしまって良いでしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エミュレーターの画面を録画する(+gidに変換してQiitaに上げられるようにする)

AndroidStudioではLogcatのScreen Recordからエミュレーターの操作をキャプチャできます。 のですが、どういうわけかこんな風に画面がチラつきまくってまともにキャプチャできない! 困った! Win+Alt+Rで録画する というわけでショートカットキーWin+Alt+Rからキャプチャをしていきます。 録画がスタートすると次のような録画コントローラーが表示されます。 録画開始時にアクティブなウィンドウのみがキャプチャされる点にご注意ください。 (エミュレーターのキャプチャではむしろトリミングの必要がなくて便利ですね) 停止ボタンか、もう一度Win+Alt+Rを押すと録画が終了します。 録画が終了するとダイアログが表示されます。 アナウンスの通りWind+GでGame Barを開いてもよいのですが、録画されたmp4ファイルは所定のフォルダに自動で保存されます。 C:\Users\(ユーザー名)\Videos\Capturesがデフォルトかな? ちなみにこのキャプチャは本来はGame Bar(正式名称はXBox Game Bar)の機能のようです。 あんま意識したこと無いけど。 注意点 念のためいくつか。 マウスポインタが映り込む アクティブウィンドウ上のマウスが一緒にキャプチャされてしまいます。 邪魔と言えば邪魔ですが、エミュレーターを録画するならアプリでどんな操作をしているかが分かるので良し悪しでしょうか。 タップ操作が分かりにくい 本来のScreen RecordではShow Tapという機能を有効にするとどこをタップしているのか分かりやすく表示してくれます。 が、このやり方だとそれができません。代わりにマウスを見てね、って感じ。 画面サイズの変更は不可 キャプチャ対象ウィンドウのサイズを変更するとその時点で録画が終了してしまいます。 エクスプローラー、デスクトップのキャプチャは不可 前述の通りアクティブウィンドウをキャプチャする機能です。なのでエクスプローラーやデスクトップの操作を録画することはできません。 gifファイルに変換 こっからは余談ですが、キャプチャしたmp4ファイルをgifに変換していきます。 編集ソフト使っても良いのですが自分は簡単にコマンドから実行できるのでffmpegを使っています。 ダウンロードはこちらから。 (Macならターミナルでbrewを使ってインストールできます) mp4→gifへの変換は以下のコマンドを実行します。 ffmpeg -i (変換対象ファイル名).mp4 (変換後ファイル名).gif このままだと記事にアップロードするにはサイズが大きいのでリサイズします。 縦横比を自由に編集ることもできますが、いちいちエミュレーターで起動しているスマホの縦横比を調べるのも面倒なのでwidthにあわせてheightを自動で調整してくれるオプションを使います。 ffmpeg -i (変換対象ファイル名).mp4 -vf scale=320:-1 (変換後ファイル名).gif -vf scale=320:-1ってところがそうですね。自分はだいたいwidthに320pxを指定しています。 本来のやり方①(ScreenRecord) 参考までに本来のやり方もメモしておきます。 AndroidStudioでView→Tool Windows→Logcatを選択。 エミュレーターを起動するとLogcatウィンドルの左下にあるカメラのマークをクリックします。 ダイアログが表示されます。 ビットレートと解像度を指定してねって言ってます。まぁデフォルトで良いでしょう。タップを分かりやすく表示したい場合はShow Tapにチェックを入れいます。 キャプチャした内容は.webm形式で保存されます。 公式ドキュメントはこちら。 本来のやり方②(エミュレーターから) エミュレーターからキャプチャを開始することもできます。 オプションの一番下の・・・のアイコンを選択。 Extended Control画面が開くので、Record and Playbackタブを選択します。 中央にStart Recordingというボタンがあるのでここをクリックしてキャプチャを開始します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BroadcastReceiver を unregisterReceiver する際に java.lang.IllegalArgumentException: Receiver not registered が出るので修正する

概要 Android アプリ開発で、(実装がまずくて)BroadcastReceiver の unregister 時に例外が起きたので対応内容を書きます。 java.lang.IllegalArgumentException: Receiver not registered ContextImpl に register していない BroadcastReceiver インスタンス(null 含む)を unregister しようとするとこの例外が出ます。 Caused by: java.lang.IllegalArgumentException: Receiver not registered: jp.toastkid.music.MediaPlayerService at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:1555) at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:1789) at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:769) at jp.toastkid.music.MediaPlayerService.unregisterReceivers(MediaPlayerService.kt:223) if (context == null) { throw new IllegalStateException("Unbinding Receiver " + r + " from Context that is no longer in use: " + context); } else { throw new IllegalArgumentException("Receiver not registered: " + r); // <- ここ } 例えば、UI生成・表示時にはまだ register せず、 UIイベントが発生したら BroadcastReceiver のインスタンスを register し、 UIが破棄されるタイミングで unregister するようなコードを書いた際に、UIイベントを起こさないままUIを破棄すると起こります。 例:音楽プレイヤー機能の場合 曲の一覧とコントロールパネルのUIを表示 再生ボタンが押されたタイミングで BroadcastReceiver を register 画面を閉じる際に unregister こういう画面を作った際、2をやらずに3を実行すると例外が発生する可能性があります。 具体的には「音楽聞きたいと思ったけど、やっぱりいいや」と思って、開いた画面をすぐ閉じた際にアプリがクラッシュします。 IllegalArgumentException は RuntimeException のサブクラスで、 クラスの使い方が正しくないことを示す実行時例外ですので、ソフトウェアのリリース前(本来なら実装フェイズ)に直しておくのが良いでしょう。 修正 ContextImpl には BroadcastReceiver に関する問い合わせ系の API がありません。 ある BroadcastReceiver が register されているのかどうかを知る方法はないので、 この例外を回避するには BroadcastReceiver を register したかどうかのフラグを何らかの形で持っておくしかなさそうです。 try-catch で IllegalArgumentException を捕捉して > /dev/null すればクラッシュはしなくなりますし、Q&Aサイトを見るとそういう回答もありました。 override fun onDestroy() { try { unregisterReceiver(audioNoisyReceiver) } catch (e: IllegalArgumentException) { Timber.w(e) } が、それでは問題の解決になっていないので別の方法を考えます。 今回は以下の方法で修正しました。BroadcastReceiver インスタンスが null / non-null で not-registered / registered を管理します。 BroadcastReceiver を Nullable var にして自分で遅延初期化 register する前に初期化する unregisterReceiver する前に null チェック onDestroy 等のタイミングでフィールドに null を再代入 1. BroadcastReceiver を Nullable var にして自分で遅延初期化 var かつ Nullable 型で宣言します。 private var audioNoisyReceiver: BroadcastReceiver? = null private var playbackSpeedReceiver: BroadcastReceiver? = null 今回の方法では、BroadcastReceiver は lateinit ではなく普通の var で宣言する必要があります。 4のタイミングで null を再代入できるようにするためです。 また by lazy では null チェックをする際にインスタンスが生成されてしまうので、 register していないインスタンスを unregister しようとして同じ例外が起こります。 2. register する前に初期化する インスタンスが null の場合のみインスタンスを生成するコードを書きます。 private fun initializeReceiversIfNeed() { if (audioNoisyReceiver == null) { audioNoisyReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { mediaSession.controller.transportControls.pause() } } } if (playbackSpeedReceiver == null) { playbackSpeedReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val speed = intent.getFloatExtra(KEY_EXTRA_SPEED, 1f) mediaPlayer.playbackParams = PlaybackParams().setSpeed(speed) } } } } 実装ミスが起きやすくて、正直あまりいい方法ではありません。 3. unregisterReceiver する前に null チェック BroadcastReceiver を register していればインスタンスが null ではなくなっているので unregister します。 インスタンスが null ならその BroadcastReceiver は register されていない(はず)なので、unregister をスキップします。 override fun onDestroy() { if (audioNoisyReceiver != null) { unregisterReceiver(audioNoisyReceiver) } if (playbackSpeedReceiver != null) { unregisterReceiver(playbackSpeedReceiver) } 4. onDestroy 等のタイミングでフィールドに null を再代入 基本的なことですが、忘れずにやっておきます。 override fun onDestroy() { //...3 のコード audioNoisyReceiver = null playbackSpeedReceiver = null これを忘れると、 ContextImpl が再生成された時に Receiver のインスタンスが残留してしまい、3の unregister 処理が実行されてしまいます。 そして、そのタイミングで java.lang.IllegalArgumentException: Receiver not registered が発生します。 再生成された Context には register していないからです。 終わりに BroadcastReceiver を unregisterReceiver する際に java.lang.IllegalArgumentException: Receiver not registered が出ていたので、 私が修正した時の方法について書きました。よりよいやり方をご存知の方はコメント等でお教えくださいますと幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】Button押下イベントのハンドリング in Kotlin

➊ はじめに Androidで、入力イベントをハンドリングする方法を調べました。 ➋ どんな感じ? 百聞は一見にしかずということで、こんな感じ~になります。 「Button」のみのアプリです? ※結果は、Logcatで見てください。 ➌ お勉強ポイント 以下の代表的なものについて実装してみたいと思います。 クリック ロングクリック タッチ ( 押す、離す、移動する) ※詳細はこちら?「入力イベントの概要」 ➍ Button押下イベントのハンドリング 例えば「クリック」の場合、イベントを受け取るために「setOnClickListener()」メソッドをコールしておいて、「onClick()」メソッドにイベント発生時の処理を書くだけです。他も同様に実装できます。 (1) クリックイベント ユーザーがアイテムをタップしたときに呼び出されます。 snippet class MainActivity : AppCompatActivity() , View.OnClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // OnClickListenerを設定 val button : Button = findViewById(R.id.button) button.setOnClickListener(this) } // onClick override fun onClick(view: View){ when (view.id) { R.id.button -> { Log.d("app6/onClick", "button") // ここへボタンクリック時の処理を書く // ・・・ } } } } (2) ロングクリックイベント ユーザーがアイテムを長押ししたときに呼び出されます。 このメソッドのリターンを「false」にすると、onClick処理を続けて実行します。onClick処理を続けて実行しないようにする場合は、リターンを「true」にします。 snippet class MainActivity : AppCompatActivity() ,View.OnLongClickListener{ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // OnLongClickListenerを設定 val button : Button = findViewById(R.id.button) button.setOnLongClickListener(this) } // onLongClick override fun onLongClick(view: View): Boolean { when (view.id) { R.id.button -> { Log.d("app6/onLongClick", "button") // ここへボタンロングクリック時の処理を書く // ・・・ } } // trueはonClick処理しない。falseでonClick処理を続けて実行。 return(false) } (3) タッチイベント アイテムの境界内で、タッチイベントとして判断されるアクション(押す、離す、移動するなどの操作)をユーザーが行ったときに呼び出されます。 このメソッドのリターンを「false」にすると、onTouchイベントの補足を続けます。onTouchイベントの補足を続けて実行しないようにする場合は、リターンを「true」にします。 snippet class MainActivity : AppCompatActivity() ,View.OnTouchListener { @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // onTouchListenerを設定 val button : Button = findViewById(R.id.button) button.setOnTouchListener(this) } // onTouch @SuppressLint("ClickableViewAccessibility") override fun onTouch(view: View, motEvent: MotionEvent): Boolean { when (view.id) { R.id.button -> { when (motEvent.action) { MotionEvent.ACTION_DOWN -> { Log.d("app6/onTouch", "button(ACTION_DOWN)") // ここへボタンタッチ(押す)時の処理を書く // ・・・ } MotionEvent.ACTION_UP -> { Log.d("app6/onTouch", "button(ACTION_UP)") // ここへボタンタッチ(離す)時の処理を書く // ・・・ } else -> { Log.d("app6/onTouch", "button(" + motEvent.action.toString() + ")") // ここへボタンタッチ(移動など)時の処理を書く // ・・・ } } } } // trueでコールバック処理終了。falseはコールバック処理を継続。 return (false) } } ➎ 実装 仕組みが分かったところで、上記をまとめ、諸々実装していきます。 言語は、「Kotlin」で実装します。 (1) activity_main.xml app/src/main/res/layout/activity_main.xml activity_main.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> (2) MainActivity.kt app/src/main/java/com/poodlemaster/app6/MainActivity.kt MainActivity.kt package com.poodlemaster.app6 import android.annotation.SuppressLint import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.view.MotionEvent import android.view.View import android.widget.Button class MainActivity : AppCompatActivity() ,View.OnClickListener ,View.OnLongClickListener, View.OnTouchListener { //------------------------------------------------------------------------- @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // OnClickListenerを設定 val button : Button = findViewById(R.id.button) button.setOnClickListener(this) button.setOnLongClickListener(this) button.setOnTouchListener(this) } //------------------------------------------------------------------------- override fun onClick(view: View){ when (view.id) { R.id.button -> { Log.d("app6/onClick", "button") } } } //------------------------------------------------------------------------- override fun onLongClick(view: View): Boolean { when (view.id) { R.id.button -> { Log.d("app6/onLongClick", "button") } } // trueはonClick処理しない。falseでonClick処理を続けて実行。 return(false) } //------------------------------------------------------------------------- @SuppressLint("ClickableViewAccessibility") override fun onTouch(view: View, motEvent: MotionEvent): Boolean { when (view.id) { R.id.button -> { when (motEvent.action) { MotionEvent.ACTION_DOWN -> { Log.d("app6/onTouch", "button(ACTION_DOWN)") } MotionEvent.ACTION_UP -> { Log.d("app6/onTouch", "button(ACTION_UP)") } else -> { Log.d("app6/onTouch", "button(" + motEvent.action.toString() + ")") } } } } // trueでコールバック処理終了。falseはコールバック処理を継続。 return (false) } } ➏ 結果 クリックやロングクリック、タッチ(押す、離す)の結果は、Logcatで見てください。 以下は、2回クリック後に、1回ロングクリックをしたときのログです。 ※[View] -> [Tool Windows] -> [Logcat]で起動できます。 ※ログ種別を「Debug」、検索対象を「app6/」に絞ると見やすくなります。 Logcat # ボタンクリック 2021-09-09 23:18:25.316 1631-1631/com.poodlemaster.app6 D/app6/onTouch: button(ACTION_DOWN) 2021-09-09 23:18:25.423 1631-1631/com.poodlemaster.app6 D/app6/onTouch: button(ACTION_UP) 2021-09-09 23:18:25.433 1631-1631/com.poodlemaster.app6 D/app6/onClick: button # ボタンクリック 2021-09-09 23:18:28.348 1631-1631/com.poodlemaster.app6 D/app6/onTouch: button(ACTION_DOWN) 2021-09-09 23:18:28.434 1631-1631/com.poodlemaster.app6 D/app6/onTouch: button(ACTION_UP) 2021-09-09 23:18:28.437 1631-1631/com.poodlemaster.app6 D/app6/onClick: button # ボタンロングクリック 2021-09-09 23:18:29.405 1631-1631/com.poodlemaster.app6 D/app6/onTouch: button(ACTION_DOWN) 2021-09-09 23:18:29.810 1631-1631/com.poodlemaster.app6 D/app6/onLongClick: button 2021-09-09 23:18:32.699 1631-1631/com.poodlemaster.app6 D/app6/onTouch: button(ACTION_UP) 2021-09-09 23:18:32.699 1631-1631/com.poodlemaster.app6 D/app6/onClick: button ➐ 以上 「Android Studio」でkotlin言語を使って簡単なアプリを作れたら楽しいだろうなと思って少し使い始めました。まずは、機能の基礎勉強をしているので、備忘録も兼ねてここに記録を残しておきます。何番煎じなのかすら分かりませんが、どこかの誰かが役に立ったと言ってくれたら嬉しいです。 お疲れ様でした?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【MPAndroidchart】 棒グラフの横軸を動的に変更したいときに頭に入れておくとよいかも?な点

概要 Androidアプリ ツケ台帳 のツケ金額の解析画面を作る際、 MPAndroidの棒グラフ横軸を動的に変更(要素数に合わせて本数変更)したのですが、 若干詰まったのでメモ アプリの解説、過去の投稿は→こちら 起きた現象 listで設定した以上の数の軸が表示されてしまう。 (※本当はtestmen2、testの2軸のみ表示したい) おそらく、listで指定されていないインデックスの要素はリストの最後の要素(この場合は test)とするという仕様なのではないかと思われます。 解決方法 ラベルの個数を明確に指定してあげました。 HogeActivity.kt barchart.xAxis.apply { LabelCount = xLabel.size = 1 //ここで明示的にラベル個数を指定 //... } 明示って大事ですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MoshiをKotlinで使う

Kotlinで、Moshiを使う場合の方法を記載します。 app/build.gradle.kts implementation("com.squareup.moshi:moshi-kotlin:1.12.0") kapt(com.squareup.moshi:moshi-kotlin-codegen:1.12.0) Adapterの登録を行います。 DaggerなどのDIで、Module化すると楽になります。 val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() Hiltを利用した場合 @Module @InstallIn(SingletonComponent::class) class JsonModule { @Provides @Singleton fun provideMoshi(): Moshi { return Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() } } Json定義 JavaのMoshiの場合、定義出来る型が決まっていました。 それ以外を使用する場合、自前でアダプターを用意する必要があります。 Primitives (int, float, char...) and their boxed counterparts (Integer, Float, Character...). Arrays, Collections, Lists, Sets, and Maps Strings Enums Kotlinの場合、Kotlinの型が使用できるため、Anyクラスなど使用することが可能です。 @JsonClass(generateAdapter = true) data class Request( val name: String, val parameters: Any? ) 文字列からJSON val text = "..." val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val adapter = moshi.adapter(Request::class.java) val request = adapter.toJson(text) JSONから文字列 val request = Request("name", 1) val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val adapter = moshi.adapter(Request::class.java) val str = adapter.fromJson(request) List型の場合 val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val type = Types.newParameterizedType(List::class.java, String::class.java) val adapter = moshi.adapter(type) moshi-kotlin-codegenは、簡単に使用できますので、是非お試しください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

お問い合わせフォーム メール送信機能の作成

はじめに 備忘録として、自作アプリに組み入れたお問い合わせフォーをどの様にして作成したのかを記述 環境 ⚫︎OS : macOS Catalina 10.15.7 ⚫︎Androidsutdio : 4.2.1 ⚫︎Kotlin : 1.5.21 画像の送信ボタンを押すと、 gmailの方でタイトルと本文を受け継ぐ処理をしていきます ※本来、メールの送信先のアドレスも受け渡しますが、個人情報保護の為画面のTo項目からは消してます。 実装 class OptionsInquiryActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_options_inquiry) supportActionBar?.setDisplayHomeAsUpEnabled(true) } fun onInquiryClickButton(view: View){ //件名を取得 val etSubject = findViewById<EditText>(R.id.etSubject) //入力された件名を取得 val subject = etSubject.text.toString() //メール本文を取得 val etComment = findViewById<EditText>(R.id.etComment) //入力された件名を取得 val comment = etComment.text.toString() val mailerIntent = Intent() mailerIntent.action = Intent.ACTION_SEND mailerIntent.type = "message/rfc822" mailerIntent.putExtra(Intent.EXTRA_EMAIL , arrayOf(送信先のメールアドレスを記入)) mailerIntent.putExtra(Intent.EXTRA_SUBJECT , subject) mailerIntent.putExtra(Intent.EXTRA_TEXT , comment) startActivity(mailerIntent) finish() } override fun onOptionsItemSelected(item: MenuItem): Boolean { //戻り値用の変数を初期値trueで用意 var returnVal = true //選択されたメニューが[戻る]の場合、アクティビティ終了 if(item.itemId==android.R.id.home){ finish() } //それ以外 else{ //親クラスの同盟メソッドを呼び出し、その戻り値をreturnValとする returnVal = super.onOptionsItemSelected(item) } return returnVal } } Main画面から遷移しているので、バーに戻るボタンを追記しています。 さいごに メール画面に遷移するのにあれだけの記述で値を渡しながら遷移できるのは驚き、、
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む