- 投稿日:2020-10-16T23:46:40+09:00
androidのアプリリンクで開く時にプロセスが2つ立ち上がったりジャンプ元のアプリ内で開く時の対処法
はじめに
アプリリンク(iOSでいうところのユニバーサルリンク)を導入すると、リンクを開く時に指定したアプリで開くことができるため、ユーザーに「どのアプリで開くのか」を選択してもらう必要がなくなりUXの向上になります。
しかし、androidではデフォルトの設定だと、アプリリンクを踏んだ時の動きがおかしくなります。
アプリリンクを踏むアプリをA、アプリリンクで開きたいアプリををアプリBとした時の理想の動きとしては
- アプリBがバックグラウンドにいる
- アプリAでアプリリンクを踏むとアプリBに遷移する
- アプリBがkill状態
- アプリAでアプリリンクを踏むとアプリBが起動して遷移する
ですが、androidのデフォルトの設定だと
- アプリBがバックグラウンドにいる
- アプリAでアプリリンクを踏むとアプリAのなかでアプリBが開いているパターン
- アプリAでアプリリンクを踏むと、アプリBがもう1つ開かれて遷移するパターン
- アプリBがkill状態
- アプリAでアプリリンクを踏むとアプリAのなかでアプリBが開いているパターン
といった現象が発生します。起動しているアプリ一覧を見ると、同じアプリが複数立ち上がっているような状況で明らかに異常な状態です。
原因
androidManifest内でlaunchModeを設定していないため、デフォルトの
standard
が適用されてしまっているから対応
AndroidManifestのactivity内にlaunchMode="singleTask"を追加する
例
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:windowSoftInputMode="adjustPan" android:screenOrientation="portrait" android:launchMode="singleTask">launchModeについて
launchModeには4つある。
standard、singleTop、singleTask、singleInstancestandard
デフォルトの設定です。システムは常に、ターゲット タスク内でアクティビティの新しいインスタンスを作成し、そのインスタンスにインテントを渡します。
つまりジャンプ元のアプリの中で、アプリリンクで開きたいアプリを展開される。(タイトルの不具合)
singleTop
アクティビティのインスタンスがターゲット タスクの一番上に既に存在する場合は、システムはアクティビティの新しいインスタンスを作成せずに、onNewIntent() メソッドを呼び出して、インテントをそのインスタンスに渡します。
アプリリンクでアプリを開く時、
- すでにアプリが開いている時は、そのままジャンプする
- アプリが開いていない時は、ジャンプ元のアプリの中で開きたいアプリが展開されるsingleTask
システムは新しいタスクのルートでアクティビティを作成し、そのアクティビティにインテントを渡します。ただし、アクティビティのインスタンスが既に存在する場合は、システムは新しいインスタンスを作成せずに、onNewIntent() メソッドを呼び出して、インテントを既存のインスタンスに渡します。
アプリリンク でアプリを開く時、
- すでにアプリが開いている時は、そのままジャンプする
- アプリが開いていない時は、新たにアプリを開いてジャンプする
singleInstance
インスタンスを保持しているタスクでシステムが他のアクティビティを起動しないことを除いて "singleTask" と同じです。アクティビティは常に、そのタスクの唯一のメンバーです。
singleTaskと同じ?要動作確認
備考
公式では
singleTask
とsingleInstance
は推奨されていません。
しかし、同じアプリリンクでジャンプできるgoogle play storeの動きを見ると明らかにsingleTask
の動きであるため、アプリリンクを使う時はこのオプションは必須と考えても良いのではないでしょうか。参考
https://developer.android.com/guide/topics/manifest/activity-element.html
- 投稿日:2020-10-16T19:18:26+09:00
雑魚競プロerがアプリ開発する(1週目)
1.はじめに
兼ねてから勉強してみたいと思っていたアプリ開発に着手し始めたので、今後の勉強の進捗について軽くまとめておきたいと思います。
2.自己紹介
1.専門は数学
2.半年前から競プロに取り組む。茶コーダー3.ここ半年で学んだ技術
1.競技プログラミングで使うレベルのC++の知識
2.何を勉強すればいいのかわからなくてなんとなく学んだhtml,css,pythonの知識
3.TCP/IPの基礎知識、Linuxの基礎知識
4.Androidアプリ開発のための基本的な知識。基本的なアプリの作例をみて作成4.今週学んだ知識
Kotlinの基本文法を公式のドキュメントを使って学んだ。
競プロのおかげで基本的な文法の流れはわかっていたので、型や繰り返し、Switchなどの知識は頭に入ってき易かったと思う。
流れとしては、基本的な文法をなんとなくドキュメントでさらった後にテキストエディタによって自分用にまとめた。
適宜ローカル開発環境を使って実際に動作しているところを確認した。
以下には今週まとめた分の一部を載せておく。matome/* Kotlinの基本文法まとめ 開発環境 →ローカル開発環境とファイル転送アプリ、VSCodeを使用。 →ローカル環境には MyCentOSによる仮想サーバを使用 →Kotlin version 1.4.10-release-411 (JRE 1.8.0_265-b01) ターミナル上でのコンパイラコマンド →kotlinc ファイル名(.kt) -include-runtime -d ファイル名(.jar) jarファイルの実行コマンド →ava -jar ファイル名 */ fun main(args: Array<String>) { //String, char var msg = "Hello World" println(msg) //Byte,Short,Int,Long Longの場合は文末にLをつるける必要がある val i: Int =100 val l: Long = 44444444444L //Float,Double Floatは文末にFをつける val d: Double =224.543 val f: Float = 12.423F //Boolean (true/false) 真理値の型 val flag: Boolean = false //演算子 足し算 引き算 掛け算 割り算 余り // + - * / % val x = 10 println(x/3) // 小数点は切り捨てられる println(x/3.0) // double型で出したい場合はどちらかをdouble println(x % 3) var y = 5 y++ //インクリメント y = y + 1 println(y) y-- //ディクリメント y = y - 1 println(y) var z = 4 z += 12 //z = z + 12 //AND OR NOT (論理演算子) //&& || ! val flag =true println(!flag) //文字列の文中での展開 println("hello" + "world") val name = "umemura" println("my name is $name") //数字の文中での展開 println("my score is ${12 +32}") // \n: 改行 // \t: タブ println("hello\n wor\tld" /*→出力 hello wor ld */ //if (条件式) {実行動作}←一行なら{}いらない //比較演算子 > >= < <= == != val score = 85 if (score > 80) { println("Great!") } else if (score > 60) { println("Good") } else { println("so so...") } //結果 Great! val result = if (score > 80) "Great" else "so so..." println (result) // Great //when val num = 5 /* when (num) { 0 -> println("zero") 1 -> println("one") 2,3 -> println("Two or Three") in 4..10 -> println("many") else -> println("other") } */ val result = when (num) { 0 -> "zero" 1 -> "one" 2,3 -> "Two or Three" in 4..10 -> "many" else -> "other" } println(result) }5.今週のまとめ
有名なアプリ開発のエンジニアさんが「言語を学ぶより作って慣れろ」とおっしゃっていたので先週までは作例を作りつつ学んでいたのですが、自分にはその学び方は合っていなかったなと思いました。
やはり自分のような優秀でない層の人間にとっては、一歩一歩積み上げていくのが重要なのかもしれません・
- 投稿日:2020-10-16T19:18:26+09:00
雑魚競プロerがアプリ開発がする(1週目)
1.はじめに
兼ねてから勉強してみたいと思っていたアプリ開発に着手し始めたので、今後の勉強の進捗について軽くまとめておきたいと思います。
2.自己紹介
1.専門は数学
2.半年前から競プロに取り組む。茶コーダー3.ここ半年で学んだ技術
1.競技プログラミングで使うレベルのC++の知識
2.何を勉強すればいいのかわからなくてなんとなくで学んだhtml,css,pythonの知識
3.TCP/IPの基礎知識、Linuxの基礎知識
4.Androidアプリ開発のための基本的な知識。基本的なアプリの作例をみて作成4.今週学んだ知識
Kotlinの基本文法を公式のドキュメントを使って学んだ。
競プロのおかげで基本的な文法の流れはわかっていたので、型や繰り返し、Switchなどの知識は頭に入ってき易かったと思う。
流れとしては、基本的な文法をなんとなくドキュメントでさらった後にテキストエディタによって自分用にまとめた。
適宜ローカル開発環境を使って実際に動作しているところを確認した。
以下には今週まとめた分の一部を載せておく。matome/* Kotlinの基本文法まとめ 開発環境 →ローカル開発環境とファイル転送アプリ、VSCodeを使用。 →ローカル環境には MyCentOSによる仮想サーバを使用 →Kotlin version 1.4.10-release-411 (JRE 1.8.0_265-b01) ターミナル上でのコンパイラコマンド →kotlinc ファイル名(.kt) -include-runtime -d ファイル名(.jar) jarファイルの実行コマンド →ava -jar ファイル名 */ fun main(args: Array<String>) { //String, char var msg = "Hello World" println(msg) //Byte,Short,Int,Long Longの場合は文末にLをつるける必要がある val i: Int =100 val l: Long = 44444444444L //Float,Double Floatは文末にFをつける val d: Double =224.543 val f: Float = 12.423F //Boolean (true/false) 真理値の型 val flag: Boolean = false //演算子 足し算 引き算 掛け算 割り算 余り // + - * / % val x = 10 println(x/3) // 小数点は切り捨てられる println(x/3.0) // double型で出したい場合はどちらかをdouble println(x % 3) var y = 5 y++ //インクリメント y = y + 1 println(y) y-- //ディクリメント y = y - 1 println(y) var z = 4 z += 12 //z = z + 12 //AND OR NOT (論理演算子) //&& || ! val flag =true println(!flag) //文字列の文中での展開 println("hello" + "world") val name = "umemura" println("my name is $name") //数字の文中での展開 println("my score is ${12 +32}") // \n: 改行 // \t: タブ println("hello\n wor\tld" /*→出力 hello wor ld */ //if (条件式) {実行動作}←一行なら{}いらない //比較演算子 > >= < <= == != val score = 85 if (score > 80) { println("Great!") } else if (score > 60) { println("Good") } else { println("so so...") } //結果 Great! val result = if (score > 80) "Great" else "so so..." println (result) // Great //when val num = 5 /* when (num) { 0 -> println("zero") 1 -> println("one") 2,3 -> println("Two or Three") in 4..10 -> println("many") else -> println("other") } */ val result = when (num) { 0 -> "zero" 1 -> "one" 2,3 -> "Two or Three" in 4..10 -> "many" else -> "other" } println(result) }5.今週のまとめ
有名なアプリ開発のエンジニアさんが「言語を学ぶより作って慣れろ」とおっしゃっていたので先週までは作例を作りつつ学んでいたのですが、自分にはその学び方は合っていなかったなと思いました。
やはり自分のような優秀でない層の人間にとっては、一歩一歩積み上げていくのが重要なのかもしれません・
- 投稿日:2020-10-16T18:13:47+09:00
Google Play Console の新しいUIでの時間指定公開とリリース手順
- 投稿日:2020-10-16T17:36:37+09:00
AndroidアプリをGooglePlayConsoleで公開を止めている状態で、リリースの上書きができるか確認
前回からの引き続きで、 https://qiita.com/backgroundcolor/items/f1941a6b08eecbe3d0a7
公開を止めている状態で、リリースの上書きができるか確認
- 投稿日:2020-10-16T17:34:59+09:00
Android SIP API : REGISTER リクエストの有効期間と再送信
概要
SIP REGISTER リクエストの有効期間
REGISTER リクエストには有効期間 (Expires) を指定することができます。
- http://www.softfront.co.jp/tech/ietfdoc/trans/rfc3261j.txt (Page 56 - 60 を参照)
REGISTER の有効期間に関する動作を簡単にまとめると以下のような感じです。
- クライアントは、REGISTER リクエストの Expires に希望する期間を指定して、送信。
- サーバーは、レスポンスにサーバー自身が希望する期間を指定して、送信。サーバーが指定した期間は、クライアントが希望した値かもしれないし、サーバーが希望する別の値に変更しているかもしれない。
- クライアントは、レスポンスに書かれた期間を過ぎた後(または期間に近づいた際に)、 REGISTER リクエストを再送信することが必要。
Android SIP API の auto registration 機能
Android SIP API には auto registration 機能があり、この機能を有効にした場合は REGISTER リクエストの送信・再送信は android 側が行ってくれます。
auto registration を使用しない場合、SipManager.register(SipProfile localProfile, int expiryTime, SipRegistrationListener listener)、SipSession.register(int expiryTime) を使用して REGISTER リクエストの送信を指示します。引数
expiryTime
に期間を指定できます。問題
Android SIP API の auto registration を使用する場合、どのくらいの期間を指定しているのか、サーバーからの応答に指示された期間をどう処理しているのか、わからなかったためコードを調べました。
コードの調査
以下のサイトを使用しました。ブランチは
android-7.0.0_r35
としました。結論
結論を先に書きます。
auto registration 機能を使用する場合には
- REGISTER リクエストには有効期間に 3600 秒を設定して送信している。
- REGISTER リクエストのレスポンスに書かれた有効期間の60秒前に、REGISTER リクエストを再送する。
- (レスポンスに書かれた有効期間が 3600 秒であれば、3600 - 60 秒後に再送信)
- レスポンスに有効期間が書かれていない場合は、有効期間を3600秒として動作する。
REGISTER リクエストの送信に関するコード
1
REGISTER リクエストを作成しているのは、SipHelper.sendRegister(SipProfile userProfile, String tag, int expiry)。
222 行 : 引数expiry
がリクエストにセットされる。2
SipHelper.sendRegister
の呼び出し元は、SipSessionImpl.readyForCall(EventObject evt) 。
引数expriy
には、変数duration
を指定している。
変数duration
は、RegisterCommand.getDuration()
の値がセットされている。3
RegisterCommand.getDuration() は、インスタンス時の引数
duration
を返す。
RegisterCommand
を作成しているのは、SipSessionGroup.register(int duration) 。
SipSessionGroup.register
の引数duration
を変更せず使用して、RegisterCommand
をインスタンス化する。4
SipSessionGroup.register
の呼び出し元はいくつかあるが、auto registration 時は以下の2つ。どちらも、定数
EXPIRY_TIME
を指定している。EXPIRY_TIME は 3600。REGISTER リクエストの応答処理に関するコード
1
レスポンスから
Expires
を取得しているのは、SipSessionImpl.getExpiryTime(Response response)。
取得できなかった場合は、EXPRIY_TIME (3600) を返している。2
SipSessionImpl.getExpiryTime(Response response)
の呼び出し元は、SipSessionImpl.registeringToReady(EventObject evt)。
getExpiryTime
で取得した値は、SipSessionImpl.onRegistrationDone に渡す。3
SipSessionImpl.onRegistrationDone
はmProxy.onRegistrationDone
を呼ぶ。
mProxy
は、setListener
でセットされる。呼び出し元をたどると以下のようにSipAutoReg
クラスがある。
SipSessionImpl.setListener
← new SipSessionImpl
← SipSessionImpl.createSession
← SipAutoReg.start
SipAutoReg.onRegistrationDone
は、SipAutoReg.restart
にduration - MIN_EXPIRY_TIME
を渡す。
MIN_EXPIRY_TIME は 60 。
(おそらく、期限が切れる60秒前に再送するため)4
SipAutoReg.restart はタイマーを実行する。タイマーの期間は
duration * 1000
ミリ秒後。
タイマーにより、SipAutoReg.run が実行され、mSession.register
を実行。
(mSession.register ... REGISTER リクエストの送信指示)参考
- http://www.softfront.co.jp/tech/ietfdoc/trans/rfc3261j.txt (page 56 - 60)
- 投稿日:2020-10-16T10:36:54+09:00
Associate Android Developer スタディガイド翻訳①
Associate Android Developer certificate
Google公式のAndroid開発者認定試験があるの、ご存知ですか。
Google Developers Certification : Associate Android Developerあんまり普及してないのか、受験した方のブログなんかも少ないですね。
自分は太古の時代にAndroidアプリ開発をしていて、最近また何かやってみようとAndroid Studioをインストールしたものの何がなんだかわからず、とりあえず初心者として基本から勉強しようかと。
試験を受けるかはさておき、Androidの基礎を理解するのによさそうと思ってスタディガイドを読み始めたところです。
ただ漫然と読んでると目が滑るので、ノート取り代わりに翻訳してみることにしました。※そもそも試験が英語のみでオンライン面接もあるそうなので、英語が全くダメだと取得は難しいと思われます
今回の翻訳はこちら
Study Guide : Android CoreAndroid Core
AndroidはLinuxベースで主にモバイル端末向けにデザインされたOSです。Anroidアプリはマルチタスクで、Java、Kotlin、またはC++で書くことができます。
AAD(訳注:アソシエイトAndroid開発者) 認定試験の準備のため、Android開発者は以下の必要があります:
- Androidシステムの構造を理解している
- Androidアプリの基礎的な構成について説明できる
- Androidアプリのビルド、実行方法を知っている
-Toast
やSnackbar
を用いたポップアップで簡単なメッセージを表示できる
-Notifications
を使用してアプリUIの外側でメッセージを表示することができる
- アプリのローカライズ方法を理解している
-JobScheduler
を使用してバックグラウンド実行タスクをスケジュールできるResources
訳注:文中で触れられたリソースについてリンクが並んでます。リンク先ページは↑のURLリンクから本家で参照してください
Android Developers -> Toasts
Android Developers -> Snackbar
Android Developers -> Localize your app
アプリのローカライズ方法
Android Developers -> Application fundamentals
アプリケーションの基礎
Android Developers -> Create a notification
Notification(通知)の作成
Android Developers -> AndroidX overview
AndroidX概要
Android Developers -> Getting started with Jetpack
JetPackを始めよう
Android Developers -> Android KTX (Kotlin)
Codelabs -> Notifications
Codelabs -> JobScheduler
Codelabs -> WorkManager (Java)
Codelabs -> WorkManager (Kotlin)
今回はここまで。
Resourcesはもう少し色々読んでから追記します。
- 投稿日:2020-10-16T00:19:12+09:00
[Delphi] 10.4 移行で TakePhotoFromLibraryAction を使ってると落ちる罠
10.4.1
みなさん!Delphi 10.4.1 は楽しんでますか!
Community Edition が出てないから楽しめないって!?
わかる~
早く出してくれることを祈りましょう。TakePhotoFromLibraryAction
さて、僕は 10.4.1 を楽しんでいるのですが、Android で、ちょっとした問題にぶつかりました。
それは、TakePhotoFromLibraryAction を使っていると、10.4 に移行したときにアプリが落ちてしまうという問題です。
TakePhotoFromLibraryAction はデバイスの写真を取ってくるアクションですが、ここで写真を選択して、アプリに戻ると直後に落ちます。
対処
10.4.1 で新規に作ったアプリでは落ちないので 10.3.3 から持ってきたファイルで 10.4.1 と何が違うのか diff を取ってみたところ原因がわかりました。
10.4 で新規に作った AndroidManifest.Template.xml には application タグの最後に
android:requestLegacyExternalStorage="true"が挿入されていました。
そこで、10.3.3 から持ってきた AndroidManifest.Template.xml の application タグの最後にAndroidManifest.Template.xml<application android:persistent="False" android:restoreAnyVersion="False" android:label="Project1" android:debuggable="True" android:largeHeap="False" android:icon="@drawable/ic_launcher" android:theme="@style/AppTheme" android:hardwareAccelerated="true" android:resizeableActivity="false" android:requestLegacyExternalStorage="true"> ←ここ!追加したところ、写真を選んでも落ちなくなりました。
対象範囲別外部ストレージアクセス
Android 10 から「対象範囲別外部ストレージアクセス」(Scoped Strage)という機能が入りました。
これによりメディアファイル(写真や動画)などの取得方法が変りました。写真を端末から選ぶ操作は普通は Scoped Strage の影響を受けないのですが、TakePhotoFromLibraryAction は受け取った写真データを一旦ファイルに保存し、後でそれを TBitmap.LoadFromFile を使って読み出す、というロジックになっています。
一旦ファイルに保存した時点で Scoped Strage の影響を受けることになり、TBitmap.LoadFromFile が例外を吐いて落ちていました。
requestLegacyExternalStorage
今回追加した requestLegacyExternalStorage は、旧式の外部ファイル管理方式を使うフラグで、これを付けると Scoped Strage は無効化されるため、上記の TakePhotoFromLibraryAction が動作します。
ですが、この救済措置が使えるのは Android 10 (API Level 29) までです。
TargetSDKVersion を Android 11 (API Level 30) にすると無視されます。Delphi 10.4.1 は正式には Android 11 に対応していないため、現状はこの解決方法でも良いですが、次に出る 10.4.2 は Android 11 に対応して欲しいものです。
See also: サポートされているターゲット プラットフォーム
最後に
ちょっとした問題と書きましたが、いくつか要因を排除するために数日かかっています。
これが皆さんの移行の手助けになれば幸いです!