- 投稿日:2021-08-29T23:16:31+09:00
Intent / intent-filter のCategoryとはなんなのか?
Androidを扱う上で基本中の基本であるIntentですが、様々なパラメータを利用します、Action / Category / Extra / Flag / Data(Uri) とありますが、Categoryってあまり意味を意識して使わないのではないでしょうか? ここでは、Categoryってなんなのかを説明してみようと思います。 intent-filterとintent Categoryがどのように扱われるのかを調べるため、サンプルアプリを作ります。 アプリA(com.android.myapplication2):単に起動されるアプリです。検証用にhogeというschemeを受け取れるintent-filterを設定しておきます。 AndroidManifest.xml <intent-filter> <action android:name="android.intent.action.VIEW" /> <data android:scheme="hoge" /> </intent-filter> アプリB(com.android.myapplication):アプリAを起動するアプリです。 単にstartActivityするだけですが、同じIntentをqueryIntentActivitiesとresolveActivityに渡した結果を表示するようにしてみます。 val intent = Intent(Intent.ACTION_VIEW).also { it.data = Uri.parse("hoge://test") } packageManager.queryIntentActivities(intent, 0).forEach { Log.e("XXXX", "queryIntentActivities: ${it.activityInfo?.packageName}/${ it.activityInfo?.name}") } packageManager.resolveActivity(intent, 0).let { Log.e("XXXX", "resolveActivity: ${it?.activityInfo?.packageName}/${ it?.activityInfo?.name}") } try { startActivity(intent) } catch (e: Exception) { Log.e("XXXX", "error", e) } 早速一発実行してみましょう。 E/XXXX: queryIntentActivities: com.android.myapplication2/com.android.myapplication2.MainActivity E/XXXX: resolveActivity: com.android.myapplication2/com.android.myapplication2.MainActivity E/XXXX: error android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=hoge://test } はい、queryIntentActivitiesとresolveActivityではアプリAが見つかっていますが、startActivityはActivityNotFoundExceptionになってしまいました。 Intent.CATEGORY_DEFAULT いきなり変な挙動を見せましたが、このような挙動になる理由は、暗黙的IntentはstartActivity時に自動的にIntent.CATEGORY_DEFAULTが付与されているため、intent-filterにandroid.intent.category.DEFAULTがついていないため反応しなかったのです。 なので、暗黙的Intentを受け取る場合は、intent-filterにandroid.intent.category.DEFAULTを付与します。 AndroidManifest.xml <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="hoge" /> </intent-filter> これで実行すると、 E/XXXX: queryIntentActivities: com.android.myapplication2/com.android.myapplication2.MainActivity E/XXXX: resolveActivity: com.android.myapplication2/com.android.myapplication2.MainActivity と、queryIntentActivitiesとresolveActivityの結果は変わらず、起動に成功します。 逆に、intent-filterを逆の状態で、intentにIntent.CATEGORY_DEFAULTを追加してみます。 val intent = Intent(Intent.ACTION_VIEW).also { it.data = Uri.parse("hoge://test") it.addCategory(Intent.CATEGORY_DEFAULT) } すると、結果は以下のようになり、queryIntentActivitiesは空、resolveActivityはnullが返ってくるようになり、startActivityの結果と一致します。 E/XXXX: resolveActivity: null/null E/XXXX: error android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW cat=[android.intent.category.DEFAULT] dat=hoge://test } ただ、Intent.CATEGORY_DEFAULTは通常追加しないと思います、queryIntentActivitiesとresolveActivityで暗黙的IntentをstartActivityに渡した場合と同じ結果が必要な場合は、queryIntentActivitiesとresolveActivityの第二引数であるflagsにPackageManager.MATCH_DEFAULT_ONLYを指定します。 val intent = Intent(Intent.ACTION_VIEW).also { it.data = Uri.parse("hoge://test") } packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).forEach { Log.e("XXXX", "queryIntentActivities: ${it.activityInfo?.packageName}/${ it.activityInfo?.name}") } packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY).let { Log.e("XXXX", "resolveActivity: ${it?.activityInfo?.packageName}/${ it?.activityInfo?.name}") } これで先ほどと同じ結果を得ることができます。 このことから IntentにCategoryがついている場合、intent-filterにもついていなければ合致しない と言えるでしょう。 Intent.CATEGORY_DEFAULTのつかないintent-filter ちょっと話が横道にそれますが、暗黙的Intentを受け取るにはIntent.CATEGORY_DEFAULTが必要です。明示的intentであればComponentNameが指定されるのでintent-filterが無くてもIntentを受け取ることができます。では、Intent.CATEGORY_DEFAULTがついていないintent-filterって意味があるのかな?と思ってしまったりするかもしれませんが、身近な例がありますね。 テンプレートから新規プロジェクトを作成すると作成されるintent-filterです。 AndroidManifest.xml <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> 起動Acitivtyについているintent-filterです。 これがどういう意味合いかは以下の記事で説明していますが、ホームアプリから起動起点となるActivityを探すために利用されます。 超シンプルなホームアプリを作る ~ホームアプリ(ランチャーアプリ)の作り方~ 簡単にいうと、Intent.CATEGORY_LAUNCHERがついたintent-filterを検索し、そのComponentNameを利用して明示的Intentを投げます。つまり、Intentを直接受け取るために使用されるわけではなく、intent-filterを検索するために利用されています。当然、この目的で検索をするときはPackageManager.MATCH_DEFAULT_ONLYは使いません。 複数のCategoryを追加してみる 何でも良いので、Intent.CATEGORY_BROWSABLEを追加してみましょう。 val intent = Intent(Intent.ACTION_VIEW).also { it.data = Uri.parse("hoge://test") it.addCategory(Intent.CATEGORY_BROWSABLE) } なお、暗黙的IntentはstartActivity時にIntent.CATEGORY_DEFAULTが自動で付与されるというのは、他のCategoryがすでに設定されている場合でも同様です。つまり、上記intentはstartActivity時にはIntent.CATEGORY_DEFAULTとIntent.CATEGORY_BROWSABLEが付与された状態になります。 それでは実行! E/XXXX: resolveActivity: null/null E/XXXX: error android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=hoge://test } はい、やはり、IntentにIntent.CATEGORY_BROWSABLEがついているのにintent-filterにないため起動できません。 AndroidManifest.xml <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="hoge" /> </intent-filter> こうすれば、成功します。 queryIntentActivities: com.android.myapplication2/com.android.myapplication2.MainActivity resolveActivity: com.android.myapplication2/com.android.myapplication2.MainActivity 逆に、Intentの方のCategoryを削除してみます val intent = Intent(Intent.ACTION_VIEW).also { it.data = Uri.parse("hoge://test") } これは、成功します。 queryIntentActivities: com.android.myapplication2/com.android.myapplication2.MainActivity resolveActivity: com.android.myapplication2/com.android.myapplication2.MainActivity このことから、 intent-filterに設定されているCategoryはIntentに付与されていなくても合致する と言えるでしょう。 intentとintent-filterの合致アルゴリズム intentとintent-filterが合致するかのロジックは、IntentFilter#matchに実装されています IntentFilter.java public final int match(String action, String type, String scheme, Uri data, Set<String> categories, String logTag, boolean supportWildcards, @Nullable Collection<String> ignoreActions) { if (action != null && !matchAction(action, supportWildcards, ignoreActions)) { if (false) Log.v( logTag, "No matching action " + action + " for " + this); return NO_MATCH_ACTION; } int dataMatch = matchData(type, scheme, data, supportWildcards); if (dataMatch < 0) { if (false) { if (dataMatch == NO_MATCH_TYPE) { Log.v(logTag, "No matching type " + type + " for " + this); } if (dataMatch == NO_MATCH_DATA) { Log.v(logTag, "No matching scheme/path " + data + " for " + this); } } return dataMatch; } String categoryMismatch = matchCategories(categories); if (categoryMismatch != null) { if (false) { Log.v(logTag, "No matching category " + categoryMismatch + " for " + this); } return NO_MATCH_CATEGORY; } // It would be nice to treat container activities as more // important than ones that can be embedded, but this is not the way... if (false) { if (categories != null) { dataMatch -= mCategories.size() - categories.size(); } } return dataMatch; } 簡単にみると、Actionを確認し、Dataを確認し、次にcategoryを確認していますね。 matchCategoriesの中身を見てみましょう。戻り値がStringで分かりにくいですが、合致しなかったCategoryが戻り、合致した場合はnullになります。 IntentFilter.java public final String matchCategories(Set<String> categories) { if (categories == null) { return null; } Iterator<String> it = categories.iterator(); if (mCategories == null) { return it.hasNext() ? it.next() : null; } while (it.hasNext()) { final String category = it.next(); if (!mCategories.contains(category)) { return category; } } return null; } ここからも、intentに付与されているCategoryがすべてintent-filterに含まれている場合、合致、intent-filter側にあるが、intent側にないものは無視されていることが分かります。 余談ですが、上記IntentFilterのソースコードは一読をお勧めします。 まとめ ということでまとめると intentに付与したCategoryがすべてintent-filterに記載されていないと、合致しない intent-filterに付与したCategoryがintentに付与されていなくても、合致する というルールになっていて、intentでintent-filterのカテゴリーを指定するために使うものであると言えるでしょう。intent-filterからintentのカテゴリーを指定するためには使用できません。 また、おまけ的にですが、 暗黙的intentを受け取るにはintent-filterにIntent.CATEGORY_DEFAULTを付与する必要がある 暗黙的intentを投げた場合と同じ結果を得るためには、queryIntentActivitiesなどのflagsにPackageManager.MATCH_DEFAULT_ONLYを指定する ということも分かりましたね。 以上です。
- 投稿日:2021-08-29T12:39:23+09:00
【Androidアプリ開発】Kotlin初心者向けの超おすすめUdemy講座6選をまとめた
アンドロイドのアプリ開発をしたい初心者向けのkotlinのUdemy講座のまとめです。 なお、基本的にJavaの経験はなくてもOKです。 ※ユーデミーは普段数万円する講座がセール時に数千円に値引きされていることがあります。 従ってセール時の購入がおすすめです。 はじめての Kotlin【Java 知らなくてOK!丁寧な解説で Android に必要な Kotlin の基本を学習】 おすすめ度:★★★★★ ・はじめての Kotlin【Java 知らなくてOK!丁寧な解説で Android に必要な Kotlin の基本を学習】 この講座で学べる内容 ・Kotlin の基本的な文法や特徴について学びます。 ・Android 開発に取り組むために必要なKotlin の基本の知識が身に付きます。 ・Java の前提知識不要でKotlin の学習ができます。 ・ラムダ式やオブジェクト宣言など、Kotlin が持つ簡潔な表現方法を学ぶことができます。 Kotlin と JUnit で学ぶ、はじめてのユニットテスト【丁寧な解説+演習問題で プログラミング 中級者になろう】 おすすめ度:★★★★ ・Kotlin と JUnit で学ぶ、はじめてのユニットテスト【丁寧な解説+演習問題で プログラミング 中級者になろう】 この講座で学べる内容 ・ユニットテストを開発に使うための知識を学びます。 ・ユニットテストとはなにか?なぜ行うのか?を理解できます。 ・JUnitをはじめとしたテスティングフレームワークについて理解できます。 ・ユニットテストの基本・本質について理解できるので、他の環境にも応用できる知識が得られます。 Kotlin プログラミング入門講座【Java 知識不要の初心者コースで基礎知識を学ぼう!】 おすすめ度:★★★ ・Kotlin プログラミング入門講座【Java 知識不要の初心者コースで基礎知識を学ぼう!】 この講座で学べる内容 ・Kotlin の基本的な文法や特徴について学習します ・Kotlin の特徴である簡潔なコード表現について学習します ・「null 安全性 」「オブジェクト指向」など Kotlin の特徴を学習します ・Java 知識は不要で Kotlin を学習することができます Java知識ゼロOK!プロのAndroid開発者になるためのマスターコース おすすめ度:★★★★ ・Java知識ゼロOK!プロのAndroid開発者になるためのマスターコース この講座で学べる内容 ・Android Studioを利用してアプリを開発できるようになります。 ・Javaの基本的なプログラミング構文を学習できます。 ・タイマーを使ったアプリを開発できるようになります。 ・ボタンナビゲーションとWebViewを使ったブラウザーアプリを開発できるようになります。 ・端末の実機を使用して加速度センサーと連動したアプリを開発できるようになります。 ・音の出るアプリやスライドショーアニメーションを使ったアプリを開発できるようになります。 ・データベースを使った本格的アプリケーションを開発できるようになります。 ・マテリアルデザインで格好いいデザインのアプリを開発できるようになります。 【3日でできる】はじめてのAndroidアプリ開発入門【Android Studio 2.3・Java 8対応】 おすすめ度:★★★★★ ・【3日でできる】はじめてのAndroidアプリ開発入門【Android Studio 2.3・Java 8対応】 この講座で学べる内容 ・Android Studio 2.1でAndroidアプリを開発できるようになります。 ・Androidアプリ開発に必要なJavaの知識を学べます。 ・データベースを使用したアプリが作れるようになります。 ・NoSQLデータベースRealmが扱えるようになります。 ・Gradleを使用したプラグイン管理ができるようになります。 はじめてのAndroidアプリケーション開発 おすすめ度:★★★★★ ・はじめてのAndroidアプリケーション開発 この講座で学べる内容 ・Androidと今までの携帯電話やパソコンとの違いがわかる ・Androidプログラミングの基本的な流れがわかる ・Android Studioの基本的な使用方法がわかる ・作成したアプリケーションをエミュレータ(Android Virtual Device)に格納し実行できる 関連記事 ・Java初心者に超オススメ出来るUdemy動画講座7選まとめ。最速マスター特化 ・Typescript入門におすすめのユーデミー講座5選。JS未経験者も可
- 投稿日:2021-08-29T12:23:59+09:00
【Android Kotlin】Calendarクラスを使用して今日の曜日を取得しよう!
1. 今日の曜日を取得する MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Calendarクラスをインスタンス化する. val calendar: Calendar = Calendar.getInstance() // 今日の曜日を取得する. val day: Int = calendar.get(Calendar.DAY_OF_WEEK) } } 2. 取得した今日の曜日をTextViewで表示する MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ~ 省略 ~ // レイアウトのtext_viewをTextViewという変数で扱う. val textView: TextView = findViewById(R.id.text_view) // dayの値によって表示する曜日を変える. if (day == Calendar.SUNDAY) { textView.text = "日曜日" } else if (day == Calendar.MONDAY) { textView.text = "月曜日" } else if (day == Calendar.TUESDAY) { textView.text = "火曜日" } else if (day == Calendar.WEDNESDAY) { textView.text = "水曜日" } else if (day == Calendar.THURSDAY) { textView.text = "木曜日" } else if (day == Calendar.FRIDAY) { textView.text = "金曜日" } else if (day == Calendar.SATURDAY) { textView.text = "土曜日" } } } 【発展】 if式の代わりにwhen式を使ってみよう! MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ~ 省略 ~ // dayの値によって表示する曜日を変える. when (day) { Calendar.SUNDAY -> textView.text = "日曜日" Calendar.MONDAY -> textView.text = "月曜日" Calendar.TUESDAY -> textView.text = "火曜日" Calendar.WEDNESDAY -> textView.text = "水曜日" Calendar.THURSDAY -> textView.text = "木曜日" Calendar.FRIDAY -> textView.text = "金曜日" Calendar.SATURDAY -> textView.text = "土曜日" } } } when式は文字通り式なので,以下のように評価結果をそのまま変数に代入できる. MainActivity.kt textView.text = when (day) { Calendar.SUNDAY -> "日曜日" Calendar.MONDAY -> "月曜日" Calendar.TUESDAY -> "火曜日" Calendar.WEDNESDAY -> "水曜日" Calendar.THURSDAY -> "木曜日" Calendar.FRIDAY -> "金曜日" Calendar.SATURDAY -> "土曜日" else -> "該当無し" }