ロック画面と認証の改善点について詳しく説明する前に、いくつかの背景情報をお伝えします。この情報は、今回の改善点における相互の関連性を把握するために役立ちます。今回の変更は、階層化認証モデルのフレームワークに当てはめてみるとイメージしやすいはずです。階層化認証モデルとは、Android に搭載されているすべての認証方式を概念的に分類し、相互の関連性や、この分類に基づく制約をまとめたものです。
信頼できる場所や信頼できるデバイス(そして一般的な第 3 の方式)を使うと、便利な方法でデバイスの内容にアクセスできます。しかし、これらに共通する根本的な問題は、最終的にユーザーの身元確認機能の精度の低さに集約されます。たとえば、信頼できる場所を使っているスマートフォンのロックは、ユーザーの自宅のそばを通り過ぎるだけで解除できるかもしれません。また、既製のソフトウェア無線と多少のスクリプトを使えば、比較的簡単に GPS シグナルを偽装することもできます。信頼できるデバイスも同様で、許可リストに登録された Bluetooth デバイスにアクセスできれば、ユーザーのスマートフォン内のすべてのデータにアクセスできることになります。
そこで、Android 10 の環境階層に大幅な改善が加えられ、第 3 階層は積極的にロックを解除する仕組みからロック解除を延長する仕組みに変わっています。この新しいモードでは、第 3 階層でデバイスのロックを解除できなくなりました。その代わり、最初に第 1 の方式か第 2 の方式を使ってデバイスのロックを解除している場合、最大 4 時間にわたってロック解除状態を継続します。
生体認証の実装には、幅広いセキュリティ特性があります。そこで、次にあげる 2 つの重要な要素を利用して特定の実装のセキュリティを判定しています。
各クラスには、一連の制約が紐付いています。この制約は、それぞれの使いやすさとセキュリティのレベルのバランスをとることを目指したものです。
制約は、生体認証が第 1 階層の認証にフォールバックするまでの時間と、許可されているアプリ統合で表されています。たとえば、クラス 3 の生体認証はタイムアウトまでの時間が最も長く、すべてのアプリ統合オプションを利用できます。一方、クラス 1 の生体認証はタイムアウトまでの時間が最も短く、アプリ統合オプションは提供されません。概要を下の表に示します。完全版を確認したい方は Android Compatibility Definition Document(CDD)をご覧ください。
1 App Integration(アプリ統合)とは、API をアプリに公開すること(例: BiometricPrompt/BiometricManager、androidx.biometric、FIDO2 API などの統合によって)を意味します。
2 Keystore Ingegration(キーストア統合)とは、キーストアを組み込むこと(例: アプリの認証バインドキーをリリースするなど)を意味します。
生体認証を利用すると、高レベルのセキュリティを維持しつつ、ユーザーに利便性を提供できます。ユーザーは生体認証を利用するために第 1 の認証方式を設定する必要があるので、ロック画面の採用が促進されます(生体認証を提供しているデバイスは、そうでないデバイスに比べてロック画面の採用が平均 20% 高くなっています)。これにより、ロック画面が提供するセキュリティ機能のメリットを活用できるユーザーが増えます。具体的には、ユーザーの機密データへの認証されていないアクセスを防ぐことができ、暗号化バックアップなどの第 1 の認証方式によるその他のメリットも受けることができます。さらに、生体認証は、肩越しにのぞき見るショルダー サーフィン攻撃によって PIN やパターン、パスワードを入力するのを見た攻撃者が認証情報を再現しようとする試みを減らすうえでも役立ちます。
ただし、生体認証を使うことによるトレードオフ(発生し得るリスク)をユーザーが理解することが重要です。特に大切なのは、完璧な生体認証システムはないということです。これは Android だけでなく、すべてのオペレーティング システム、フォームファクタ、テクノロジーに対して言えることです。たとえば顔を使った生体認証の実装なら、ユーザーに似た家族やユーザーの 3D マスクでロックが解除できてしまうかもしれません。指紋を使った生体認証の実装なら、ユーザーの潜在指紋を使ってなりすませるかもしれません。このようななりすまし攻撃を緩和するため、なりすまし対策や Presentation Attack Detection(PAD)テクノロジーの開発が進められていますが、これらは緩和策であり、予防策ではありません。
生体認証の利用による潜在的なリスクを緩和するための Android の取り組みとして、Android P で導入されたロックダウン モードがあります。必要な場合、Android ユーザーはこの機能を使って一時的に生体認証や Smart Lock(信頼できる場所や信頼できるデバイスなど)、ロック画面の通知を無効化できます。
ロックダウン モードを使うには、まず第 1 の認証方式を設定し、その後に設定でロックダウン モードを有効化する必要があります。ロックダウン モードを有効化するための厳密な設定はデバイスのモデルによって異なりますが、Google Pixel 4 デバイスでは [Settings] > [Display] > [Lock screen] > [Show lockdown option] にあります。これを有効化すると、ユーザーは電源ボタンを押して電源ボタン メニューのロックダウン アイコンをクリックすることで、ロックダウン モードを起動できます。ロックダウン モードのデバイスは、第 1 の認証方式(PIN、パターン、パスワードなど)を使ってデバイスのロックを解除すると、ロックダウン解除状態に戻ります。
BiometricPrompt API を使用すると、さまざまなメリットが得られます。最も重要なことは、この API を使うと、アプリのデベロッパーが方式を意識せずに、さまざまな Android デバイスで生体認証を利用できることです(つまり、BiometricPrompt は、デバイスでサポートされているさまざまな生体認証方式の単一統合ポイントとして利用できます)。一方で、認証が提供する必要があるセキュリティ保証(フォールバックとしてのデバイス認証情報と合わせてクラス 3 またはクラス 2 の生体認証を必須とするなど)を制御することもできます。これにより、(ロック画面の他に)防御の第 2 階層でアプリデータを保護できることに加え、ユーザーの機密データを尊重することにもつながります。さらに、さまざまな Android デバイスのさまざまな方式の生体認証で一貫したユーザー エクスペリエンスを提供するため、BiometricPrompt は永続的な UI を提供しています。一部の情報(タイトルや説明など)をカスタマイズするオプションも存在します。
次のアーキテクチャ図に示すように、フレームワーク API やサポート ライブラリ(下位互換性を確保するための androidx.bitmetric)のどちらかを通して、Android デバイスのアプリに生体認証を組み込むことができます。注意すべき点は、FingerprintManager が非推奨になっていることです。デベロッパーには、方式を意識しない認証としてBiometricPrompt に移行することが推奨されています。
Android 11 では、BiometricManager.Authenticators インターフェースなどの新機能が導入されています。これを使うと、デベロッパーはアプリで受け入れる認証タイプを指定したり、BiometricPrompt クラスで利用ごとの認証キーをサポートしたりできます。
詳しくは、Android 11 プレビューと Android 生体認証システムのドキュメントをご覧ください。BiometricPrompt API の詳しい使用方法については、ブログ記事 Using BiometricPrompt with CryptoObject: how and why や、Codelab Login with Biometrics on Android をご覧ください。
この記事は David Winer による Android Developers Blog の記事 "The future of Kotlin Android Extensions" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android Kotlin Extensions Gradle プラグイン(Android KTX と混同しないでください)は、2017 年にリリースされ、Kotlin による Android 開発に次の 2 つの便利な新機能をもたらしました。
findViewById
kotlinx.android.synthetic
@Parcelize
しかしその後、Android ビルド ツールチェーンと密接に統合された公式サポート ライブラリ View Binding for Android がリリースされ、Kotlin synthetics と同等の機能が提供されるようになりました。Parcelize は現在も使用が推奨されていますが、Kotlin synthetics の使用についてはたとえば以下のようにさまざまな欠点が明らかになりました。
Android Kotlin Extensions プラグインは、もともと JetBrains が開発したものです。そして私たちは、JetBrains とともに、synthetics のメンテナンスを続けることの賛否について議論してきました。その中では、可能な限り API を長期にわたってサポートできるよう努力すべきという視点もありましたが、その一方でユーザーにとって使いやすいアプリを作れるよう、デベロッパーの皆さんにベスト プラクティスを提供してコードベースを健全化することの重要性も指摘されました。
そして結論として、私たちのチームは共同で、推奨オプションである View Binding のサポートを続けることを優先し、来年 1 年をかけて synthetics のサポートを終了することを決めました。
kotlin-parcelize
android-kotlin-extensions
サポートの終了期間は、2020 年 11 月 23 日(日本時間 11 月 24 日)にリリースされた Kotlin 1.4.20 から始まります。android-kotlin-extensions は少なくとも 1 年間は残されますが、2021 年 9 月またはそれ以降の Kotlin リリースでは削除されます。kotlin-parcelize プラグインのメンテナンスは長期的に続ける予定です。Parcelize で発生する問題についてのご連絡は、今後も Android Studio Issue Tracker にお寄せください。
Reviewed by Takeshi Hagikura - Developer Relations Team and Hidenori Fujii - Google Play Developer Marketing APAC
この記事は Krish Vitaldevara による Android Developers Blog の記事 "Tips for getting your app approved for background location access" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
私たちは、ユーザーのプライバシーを守るため、データアクセスにおけるユーザーコントロールと透明性を向上させる努力を重ねています。ユーザーからは一貫して、位置情報データに対する制御を強化して欲しいという声が寄せられています。そこで今年は、いくつかのプライバシーの改善策についてお知らせしました。たとえば、Google Play の位置情報アクセス許可ポリシーを改定し、Android 11 で位置情報のアクセス許可制御を強化しました。
バックグラウンドでの位置情報への不必要なアクセスを避けるため、改定したポリシーでは、アプリのコア機能に不可欠で、ユーザーに明らかなメリットを提供する場合に限り、アクセスが許可されます。バックグラウンド位置情報をリクエストするアプリの多くは、実際にはその情報を必要としないことがわかっています。この機能を削除するか、フォアグラウンドに変更すれば、アプリの電池効率向上に繋がりますし、位置情報を共有したくないユーザーから低評価を受けてアプリの評価が低くなることも回避できます。
バックグラウンド位置情報データを使っているアプリを Google Play に公開し続ける、または新規に公開するためには、必要情報をフォームに入力して審査を受け、2021 年 1 月 18 日までに承認を得る必要があります。ただし、2020 年 4 月 16 日より前に初公開されたアプリの手続きの期限は、2021 年 3 月 29 日となっています。
詳しい内容をご説明している動画(英語)と Google Play Academy の無料のトレーニング コース(英語)を作成しました。アプリで必要なアップデートを行う際にぜひご確認ください。プライバシーに関するベスト プラクティスと技術情報もご覧ください。コードでバックグラウンド位置情報を使っている可能性がある部分を特定する方法を確認できます。
Google Play をユーザーのプライバシーを尊重するアプリとプラットフォームを構築するため、ご理解とご協力をよろしくお願いします。
この記事は Chet Haase による Android Developers - Medium の記事 "Now in Android #29" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android 開発の最新ニュースやトピックをご紹介する Now in Android。今回は連載シリーズ MAD Skills の「App Bundle」、App Bundle、AndroidX 安定版リリース、連載シリーズ Kotlin Vocabulary に関する新しい記事と動画、最近公開されたブログ記事・動画・関連ドキュメント、ポッドキャスト エピソードなどをご紹介します。
最先端の Android 開発に関する技術コンテンツをミニシリーズごとにテーマを決めて扱う連載シリーズ MAD Skills。前回のミニシリーズ「ナビゲーション」につづいて、App Bundle を取り上げます。Wojtek Kaliciński と Ben Weiss が Google Play のアプリ署名、初めての App Bundle のビルド、Play Feature Delivery についてのコンテンツを公開しました。詳しくは、以下の動画と記事をご覧ください。
Android App Bundle を取り上げるミニシリーズですから、アプリの署名についてお話しするのは当然です。Google Play はユーザーのデバイスにダウンロードする APK を生成しますが、その APK をインストールするには署名が必要だからです。この動画では、Google で鍵を生成するオプションや自分の鍵をアップロードするオプションなど、Google Play Console でアプリの署名を有効化する方法について順を追って説明しています。
こちらの関連記事も忘れずにご確認ください。Play のアプリ署名についてのよくある質問を取り上げています。
こちらの動画では、Ben が App Bundle の作成手順をご説明しています。App Bundle は Android Studio またはコマンドラインで作成し、Play Console にアップロードします。さらに、Play Console でツールを使い、アップロードしたバンドルの情報を確認する方法もお話しています。
このエピソードでは、Android Studio を使ってアプリをモジュール化する方法のほか、インストール時(インストールするかどうかを決定するオプションの条件も指定可能)や、オンデマンドでダウンロードするモジュールを選択する方法をお話しています。さらに Ben は、API を使ってオンデマンドでモジュールのインストールをリクエストする方法も詳しく説明しています。
本エピソードは、ブログ記事(英語)でもご覧いただけます。
App Bundle を取り上げるミニシリーズ最後のエピソードでは、Wojtek がローカルでテストを行う bundletool や、Play Console を使ってアップロードしたものをテストする方法など、利用可能なツールを使ってバンドルと生成された APK をテストする方法を説明しています。
この動画でお話している内容は、いくつかの記事やドキュメントとも関連しています。ぜひ、詳しい情報をご確認ください。
App Bundle を取り上げるミニシーズ最後のコンテンツとして、2020 年 11 月 19 日(日本時間 11 月 20 日)に YouTube でリアルタイム Q&A を開催します。こちらのプレイリストに YouTube ライブへのリンクが表示されますのでご確認ください。さらに、質問を募集するツイートも行います(編集部注:セッションはすべて英語で、この日本語記事投稿時には実施済み)。
本シリーズのコンテンツアーカイブは、YouTube の MAD Skills プレイリスト、Medium の記事、またはすべてのリンクが掲載されているランディング ページからご覧いただけます。来週は次のミニシリーズが始まります。お楽しみに!
多くのアルファ版、ベータ版、RC 版の AndroidX ライブラリがリリースされました。その中には、いくつかの重要な安定版リリースもありました。最近リリースされた以下のライブラリにご注目ください。
前回の Now in Android #28 以降、Kotlin についていくつかの新しい記事と動画が公開されています。
Florina Muntenescu が、連載シリーズ Kotlin Vocabulary に新しいエピソードを追加しました。今回のテーマは、Kotlin のデータクラスです。データクラスを使うと、ボイラープレート コードをほとんど使わずに、データを保持する構造を簡単に作成できます。しかも、equals() 関数と hashCode() 関数は、Kotlin が自動的に適切なものを生成してくれます。また、何もしなくてもクラスのプロパティの分割代入や copy() を利用できます。いつもの Kotlin Vocabulary エピソードのように、Florina がデータクラスを逆コンパイルしたバイトコードを確認し、内部の仕組みを細かくご説明しています。
Murat Yener が、以前に掲載した Kotlin の委譲機能についての記事の続編を投稿しました(英語)。今回は、Kotlin 標準ライブラリが提供するデリゲートである lazy、observable、vetoable、notNull についてご説明しています。
Florina がこちらの記事を投稿(英語)し、Kotlin の教育や開発に力を入れることに関して、デベロッパーの皆さんからよく寄せられるいくつかの質問にご回答しています。さらに、重要な学習関連情報へのリンクも掲載しています。
この記事では、Kotlin アプリのほうが Kotlin 以外で書かれたアプリよりもエラーが起こりにくい理由について、Florina がご説明しています。その証拠となる具体的なアプリやユースケースを取り上げますが、それだけでなく、null 可能性や hashCode() != equals() など、この言語を使ったコードが安定して動作する理由についても解説しています。詳しくは、こちらのブログ記事(日本語)をご覧ください。
前回の Now in Android 以降、Android Developers Backstage に新しいエピソードが投稿されています。以下のリンクまたはお気に入りのポッドキャスト クライアントでご確認ください。
Romain Guy と Tor Norbye、そして私が、Instacart の Colin White 氏から、オープンソースのイメージ読み込みライブラリである Coil について話を聞きました。イメージの読み込み、パフォーマンス、オープンソース、そしてこの Kotlin ファーストなライブラリを Kotlin やコルーチンを使ってどう作るのかについて語り合います。
今回は以上です。次回も Android デベロッパーの世界の最新アップデートをお届けします。お楽しみに。
Reviewed by Yuichi Araki - Developer Relations Team and Hidenori Fujii - Google Play Developer Marketing APAC
この記事は Murat Yener による Android Developers Blog の記事 "Meet Google Play Billing Library Version 3" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
※2021年 8 月 2 日より新規リリースのアプリでは最新の Billing Library バージョン 3 への対応が必要となり、11 月 1 日までにすべてのアプリにその対象が広がるため、再度掲載します。
Google Play は、ユーザーに愛される高品質のアプリを開発するための、健全なエコシステムの実現に取り組んでいます。多くのデベロッパーは Google Play の 1 回限りの購入と定期購入 サービスによって、そのようなアプリの開発が実現しています。この 10 年間、キャリア決済を利用した支払い(現在対応キャリア数 180)などの機能や、費用の割り当てと定期購入の管理を簡単に行うツールにより、Android ユーザーの購入体験を改善してきました。
こういった取り組みをさらに進めたのが Billing Library バージョン 3 です。今回提供する新バージョンでは、新しい支払い方法への対応、定期購入のプロモーション機能の追加、ゲーム購入のアトリビューション、購入時の信頼性とセキュリティの向上などが行われました。2021 年 8 月 2 日以降、新しいアプリはすべて Billing Library バージョン 3 以降を使用する必要があり、2021 年 11 月 1 日までに、すべてのアプリのアップデートで Billing Library バージョン 3 以降を使用する必要があります。
Google は世界中のユーザーが 都度購入1 回限りの購入と定期購入の支払いを簡潔かつ便利にできるように継続して取り組んでいます。
現金は現在も世界で最も広く使われている支払い方法であり、2018 年時点では商品とサービス全体で 2.7 兆件の取引に使われています(出典: ユーロモニター)。Google は昨年、コンビニエンス ストアの現金払いのように、取引がデバイス外で完結する新しい支払い方法をプレビューしました。世界銀行によると、全世界で 20 億人が銀行口座を保有していません。そうした状況で、この新しい支払い方法は、特に現金払いが一般的な新興市場で新規の購入者を開拓するきっかけになり得ます。
今回のリリースにより、インドネシアとマレーシアでは、セブン-イレブンや Alfamart などの有名小売店を含む 50,000 を超える店舗で、現金による 都度購入1 回限りの購入が可能になります。この新しい支払方法は、まもなくすべてのデベロッパーにご利用いただけるようになります。
Billing Library バージョン 3 では、ユーザーがアプリ以外(Play ストア全体など)で商品を見つけて購入できるようになります。1 つの例として、新しくなったスムーズなプロモーション コードの利用があります。例えば、定期購入の無料試用のプロモーション コードを提供すると、ユーザーは簡単に Play ストアでの購入に利用することができます。しかも、これはインストール前であっても使うことができます。この機能により、に、シンプルな購入、登録、インストールの体験が実現され、ユーザーの負担を減らすことが可能になります。
ゲームやアプリではアプリ内の購入がゲーム内のどのキャラクターやアバター、プロフィールにアトリビュートされる(アトリビュートできる)ようにする必要があります。Billing Library を利用することで、購入フローの立ち上げ時にそうした情報を特定することができます。購入の完了時に、情報が取得されその購入を正しくアトリビュートすることができます。それにより、非推奨の AIDL デベロッパー ペイロードを利用した独自のソリューションを構築する必要がなくなります。
Google Play のターゲット SDK 要件と同じように、新バージョンの Billing Library ですべてのユーザーがセキュリティ、パフォーマンス、ユーザー体験の改善を体感できることが重要です。2019 年の Google I/O において、Google は Billing Library バージョン 2 をリリースし、各メジャー リリースに 2 年のサポート期間を設けるなどの変更をアナウンスしました。
そのため、2021 年 8 月 2 日以降、新しいアプリでは Billing Library バージョン 3 以降を使用する必要があり、2021 年 11 月 1 日までに、すべてのアプリのアップデートで Billing Library バージョン 3 以降を使用する必要があります。
上記の日以降、古いバージョンの AIDL、Billing Library バージョン 1、Billing Library バージョン 2 との統合を利用したアプリは公開できなくなります。すでに Play ストアにあるアプリは引き続きダウンロードが可能で、アプリ内の購入を処理できます。ただしその後のアプリのアップグレードには、Billing Library バージョン 3 以降が必要です。
Billing Library バージョン 3 は現在(2020年6月11日以降)、Java と Kotlin で開発されているすべてのゲームとアプリの デベロッパーが利用できます。Unity を利用するゲーム デベロッパー向けに、Billing Library バージョン 3 ベースの Unity IAP プラグインもリリースしており、はこれにより、Unityを利用したアプリ開発においてもBilling Library バージョン要件を満たし、Play Billingの機能すべてにアクセスできるようになります。
請求関連の SDK とライブラリを Billing Library バージョン 3 に対応したバージョンにアップグレードしてください。SDK またはライブラリが利用できない場合は所有者に問い合わせてください。Google は主要なプロバイダとともに、Billing Library バージョン 3 との互換性について取り組みを進めています。
Google は年 1 回のアップグレードをおすすめしていますが、各メジャー リリースに対しては 2 年間のサポートを提供します。デベロッパーは、2020 年 6 月 11 日(日本時間 6 月 12 日)からすべての新規アプリに Billing Library バージョン 3 を利用し、2021 年の期限より前に可能な限り早期に既存の請求の統合を移行することをおすすめします。
Billing Library に移行していないデベロッパーにとって、既存のアプリを AIDL から移行することは簡単ではありませんが、できるだけスムーズに移行できるようサポートいたします。現在 AIDL を使用しているアプリ向けには、移行ガイドと動画チュートリアルが用意されています。
また、購入のアトリビューション、プロモーション コードの利用、不正対策に関するガイドとなる、ドキュメントもアップデートされています。実装に関する問題については、こちらまでご連絡ください。
Play コマース プラットフォームに関する改善全体についての詳細は、動画 What's new in Play Commerce をご覧ください。
皆様のアプリとゲームへ素晴らしい購入体験を実現するために、今後ともご協力をお願いいたします。
Reviewed by Tetsuo Midorikawa - Google Play Product Operations
ユーザーは皆さんのアプリにシームレスな体験を期待しています。アプリがクラッシュすれば、低評価のレビューやアンインストールが増え、ブランドにダメージを与えてしまうでしょう。その一方で、コミュニティの皆さんとの対話の中で、Kotlin を採用する主な理由の 1 つはコードの高い安全性であるということをよく耳にします。実際に何社かのパートナーは、Kotlin を使ってコードの安定性を改善しています。この投稿では、その方法をいくつか紹介するとともに、Google Play ストアの統計結果にも注目し、Kotlin とクラッシュ数との間に相関関係があるかどうかを確認してみたいと思います。
アプリの品質が影響するのは、ユーザー エクスペリエンスだけではありません。クラッシュ多いと、他のいくつかの要素にも悪影響が生じます。
Kotlin で構築したアプリはクラッシュする可能性が 20% 低い
Google Play のトップ 1,000 アプリを調査してみたところ、Kotlin を使っているアプリは、それ以外のアプリよりもユーザー 1 人あたりのクラッシュ発生率が 20% 低いことがわかりました。
その具体例として、コードの 74% が Kotlin である Swiggy のエンジニアリング チームは、新機能の開発を Kotlin に移行して以来、クラッシュを 50% 減らしました。こういった結果を Kotlin はどのように実現しているのか見ていきましょう。
Google Play でのクラッシュの原因ナンバーワンは NullPointerException です。Google Home チームは、2018 年よりすべての新機能を Kotlin で書いています。その結果、1 年前と比べて null ポインタによるクラッシュが 33% 減少しました。
Google Home が NullPointerException を 33% 削減
NullPointerException を避けるには、メソッドを呼び出したりメンバーにアクセスしたりする前に、扱っているオブジェクトの参照が null でないことを確認しなければなりません。Kotlin では、null 可能性が型システムの一部として組み込まれています。たとえば、変数は最初から null 可能か null 不可能かを宣言する必要があります。null 可能性を型システムの一部として組み込むことで、コードベースについての自分の記憶や知識、またコンパイル時警告(フィールドやパラメータに @Nullable アノテーションを付けた場合)に頼る必要はなくなります。null 可能性を強制することで、単なる警告ではなく、コンパイル時にエラーが発生します。null 可能性を扱う方法については、こちらのページをご覧ください。
NullPointerException
@Nullable
私たちデベロッパーは、気づかないうちにアプリにたくさんの問題を紛れ込ませています。そのほとんどはあまりに軽微で、調査するのは難しいかもしれません。そのような問題のうち、Kotlin を使うことで回避できるものをいくつか紹介しましょう。
hashCode()
2 つのオブジェクトが等しい場合、それらのハッシュコードも同じである必要があります。しかし、どちらかのメソッドを実装し忘れたり、クラスに新しいプロパティを追加したときに更新し忘れてしまったりすることがよくあります。データを保持するためだけのクラスを扱う場合は、Kotlin のデータクラスを使うようにしましょう。データクラスを使うと、コンパイラが hashCode() と equals() を生成してくれるので、クラスのプロパティを変更すると自動的に更新されます。
equals()
2 つのオブジェクトは構造が等しい(中身が同じ)のでしょうか。それとも、参照が等しい(ポインタが同じ)のでしょうか。Java プログラミング言語では、プリミティブには常に == を使います。そのため、実際には構造が等しいかどうかを確認(equals() を呼び出してチェック)したいのに、オブジェクトにも ==(参照が等しい)を使ってしまうというのがよくある誤りです。まず、Kotlin にはプリミティブ型はなく、Int や String などのクラスを使います。つまり、すべてがオブジェクトなので、オブジェクトとプリミティブ型の区別を気にする必要はありません。次に、Kotlin では == は構造が等しい、=== は参照が等しいと定義されています。したがって、誤って参照が等しいかどうかを確認してしまうことはありません。
==
Int
String
===
enum を扱う場合、通常はすべての可能なケースを記述しなければなりません。そのため、switch や if else チェーンを使うことになります。enum を修正して新しい値を追加する場合、enum を使っている各コード スニペットを手動でチェックし、新しいケースに対応しているかどうかを確認する必要があります。しかし、これはエラーにつながりがちです。Kotlin では、when を式として使うと、この確認をコンパイラに任せることができます。すべての可能な分岐に対応していない場合、コンパイラ エラーが発生します。
switch
if else
ユーザーやブランドにとって、アプリの安定性は重要です。Kotlin を使い始めてクラッシュ率を減らし、ユーザーの満足度を高めてアプリの高評価を守り、ユーザーの維持や獲得につなげましょう。
詳しくは、Kotlin でより優れたアプリを作成するためにできることをお読みください。また、ケーススタディをご覧いただき、Kotlin のメリットをご確認ください。世界でデベロッパーに広く使われている言語の 1 つである Kotlin を使ってみたい方は、入門ページをご覧ください。
この記事は Chet Haase による Android Developers - Medium の記事 "Now in Android #28" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android 開発の最新ニュースやトピックをご紹介する Now in Android。今回は新連載シリーズ MAD Skills の「ナビゲーション」、Android X ライブラリ、連載シリーズ Kotlin Vocabulary、最近公開されたブログ記事・動画・関連ドキュメント、ポッドキャスト エピソードなどをご紹介します。
新連載シリーズ MAD Skills は、Modern Android Development(最先端の Android 開発)テクノロジーの使い方をデベロッパーの皆さんにお伝えするコンテンツです。このテクノロジーを使えば、優れたアプリを簡単に作ることができます。このシリーズは、前回の Now in Android を投稿した時期に始まりました。それから数週間が経ち、Navigation コンポーネントを扱った最初のミニシリーズが完結し、リアルタイム Q&A もご用意しました。
MAD Skills の目的の 1 つは、最先端の Android 開発のさまざまな機能の使い方をお伝えするだけでなく、皆さんの問題や質問に耳を傾けて解決のお手伝いをすることでした。そのため、各ミニシリーズの終わりには、YouTube でリアルタイム Q&A を開催する予定です(編集部注:セッションはすべて英語で実施されます)。
それに向けて、Twitter で質問を募集しました。また、Twitter と YouTube でもライブで質問に回答し、さらに、プロダクトに関わっている何名かのエキスパートに、おすすめする内容について話を聞きました。
ナビゲーションについては、10 月 29 日木曜日の午前 10 時(日本時間 10 月 30 日午前 3 時)に私と Ian Lake がリアルタイム Q&A を開催し、現在はアーカイブを公開しています。寄せられたご質問は、こちらの Twitter スレッドと、#AskAndroid がついた投稿から確認できます。
ナビゲーションのエピソードを見逃してしまった方は、動画や記事で確認できます。
続きのコンテンツは、YouTube の MAD Skills プレイリスト、Medium の記事、またはすべてのリンクが掲載されているランディング ページからご覧いただけます。今後の MAD コンテンツにもご期待ください。11 月 1 日 からは次のシリーズが始まっています!
まず、AndroidX は、 GitHub を通して多くのライブラリを提供しています。
多くのデベロッパーの皆さんは、Android でずっと使われている AOSP+Gerrit システムよりも、使い慣れた GitHub を好まれていることは認識しています。
これはすばらしいアイデアです。ただししかし、私たちが利用しているインフラストラクチャを考慮すると、そのアイデアの実現はそう簡単ではありません。それでも、皆さんの意見を踏まえて模索し始め、数か月前から Paging、Room、WorkManager という積極的に開発が行われている一部のライブラリを GitHub で利用できるようにしました。先日、Activity、Fragment、Navigation の各ライブラリも追加しましたので、ぜひご確認ください。またご自分でも貢献したいという方は、CONTRIBUTING ドキュメントに記載されている詳細をご覧ください。
いつものように、たくさんのアルファ版、ベータ版、RC 版とマイナー安定版リリースがありました。主な安定版リリースについて紹介します。
MediaRouter 1.2.0: これは注目に値するバージョンです。というのも、Android 11 の新しいメディア機能のいくつかと同期する機能が追加されているからです。
Android 11 には、メディア プレーヤー用のたくさんの細かな UI が追加され、メディア コントロールが通知パネルの新しい専用の領域にまとまっているため、他の通知に紛れることなく 1 か所から簡単にメディアを制御できます。これは、新しいデベロッパー機能というより、皆さんが既に作っているかもしれない通知を別の形で表現したものです。Lollipop リリース以降で利用できる MediaSession や MediaStyle API はそのまま使うことができます。
ただし、Android 11 には、「シームレスなメディア移行」を目的としていくつかの新機能が追加されています。これにより、ユーザーは出力スイッチャー(下記)で再生デバイスを変更できます。今回の MediaRouter の新リリースを使うと、この新しいプラットフォーム機能を操作できます。
この MediaRouter の新リリースを使うと、出力スイッチャーにどの再生デバイスを表示するかを制御できるので、ユーザーはシームレスにオーディオを移動できます。
Android 11 のメディアに関する変更点を確認したい方は、Don Turner の動画 What's new in Media(メディアの新機能)をご覧ください。
いくつかのアルファ版リリースも紹介したいと思います。アルファ版はアルファ版であり、チームの開発が進むとともに変更が繰り返されるので、通常はアルファ版に注目することはありません。しかし、Paging と Navigation のアルファ版のリリースは、近未来のアプリ開発という点で注目に値するでしょう。
Jetpack Compose のアルファ版の開発が進んでいますが、「Jetpack Compose の世界で、[ここにお気に入りの Jetpack ライブラリを挿入] は今後どうなりますか?」と、質問を受けることがあります。
お答えします。アーキテクチャ コンポーネントの多くは、個々のビューや既存の UI ツールキットを扱うものではありません。そのため、それぞれのライブラリは、Jetpack Compose の新しい世界でどれも等しく必要かつ有用です。それだけでなく、現在私たちは、異なるコンポーネントを簡単に連携させることができるように、統合機能の開発を進めています。Compose は既に ViewModels および LiveData と組み合わせて使うことができますが、本日からは Paging と Navigation の両方も Jetpack Compose のサポート対象になりました。
先日、Kotlin の言語機能に関するシリーズで 記事を 3 件公開しました。(英語記事一覧・日本語記事一覧)
Florina Muntenescu が Kotlin の分割代入機能についての新しい記事(英語)と動画を投稿しました。分割代入は、オブジェクトの異なるフィールドの値を複数の変数に割り当てる際に便利です。たとえば、次のデータクラスがあったとします。
data class Donut( dough: String, topping: String)
すると、変数を Donut インスタンスのフィールドにすばやく割り当てることができます。
val (dough, topping) = someDonut
データクラスでは何もしなくても分割代入が動作しますが、他のクラスの関数に分割代入機能を持たせることもできます。
Meghan Mehta が Kotlin 言語機能のエクステンションについて説明した新しい記事(英語)を公開しました。エクステンションとは、既存のクラスに新しいメソッドやプロパティを追加できるような機能です。実際には、エクステンションが既存のクラスに何かを挿入することはありません。しかし、メソッドの呼び出し元からはそのように見えます。なお、
内部的には、クラスのインスタンスを受け取る静的メソッドとして実装されています。
この Kotlin の機能は、私もよく使うことになりそうです。API デベロッパーとして、後から API を改善できるという考え方が気に入っています。プラットフォームやライブラリのコアの外側に API を追加するにもかかわらず、コードから使うときは、まるでそこにあるように見えるのです。たとえば、String クラスに String.isAGoodDonutName() というエクステンション メソッドを作ったとしましょう。すると、呼び出し元はこのエクステンション メソッドを使い、“Sprinkle”.isAGoodDonutName() というように String からこのメソッドを直接呼び出すことができます。他の言語のアプローチのように、他のパッケージやクラスを使って Utils.StringMethods.isAGoodDonutName(“Sprinkle”) などと記述する必要はありません。
String.isAGoodDonutName()
“Sprinkle”.isAGoodDonutName()
Utils.StringMethods.isAGoodDonutName(“Sprinkle”)
最後に、Manuel Vivo が新しい動画「コルーチンの ABC」を公開し、CoroutineScope、CoroutineContext、Dispatcher、Job などのトピックについて説明しています。コルーチンの ABC というより、CDJ のように見えるかもしれません。
CoroutineScope
CoroutineContext
Dispatcher
Job
アプリを公開する際に厄介なことの 1 つは、そのアプリを確実にストアのガイドラインに準拠させることです。このガイドラインは、ユーザーにとって優れたアプリのエコシステムを実現するために存在しています。しかし、正しい対処方法を厳密に伝えるのは難しい場合もあります。そこで、問題につながる可能性があるいくつかのよくある違反と、それを避ける方法についてより理解を深めていただくため、この記事を公開しました。
よくあるポリシー違反の中には、アプリの UI から Play Store へのリンク、アプリの明確な概要よりも検索キーワードを意識した説明、ウェブサイトのコンテンツを WebView で見せるだけのアプリなどがあります。
多くのカンファレンスがオンラインでの開催を強いられているこの時期に、Droidcon は興味深いアプローチをとりました。たくさんのイベントを組み合わせて、広いタイムゾーンをカバーする大きなイベントを実施したのです。10 月に行われたこのカンファレンスのヨーロッパ、中東、アフリカ バージョンと、そのすべての動画がオンラインで公開されています(私たちのチームが作ったものもあれば、広大なコミュニティのとてもたくさんのデベロッパーの皆さんが作ったものもあります)。
スクリーンキャストの MotionTags シリーズには、前回から 2 つのエピソードが投稿されました。
MotionTags プレイリストの他のシリーズと合わせてご覧ください。
先日、Udacity Android Kotlin Developer Nanodegree が新しくリリースされました。このコースは、ベスト プラクティスに従って Kotlin で Android アプリを構築する方法を学びたい方が対象です。前提条件や詳しい内容は、プログラムの概要をご覧ください。
なお、この Nanodegree は有償プログラムで、フィードバック付きのプロジェクト、テクニカル メンター サポート、キャリア サービスが Udacity から提供されます。ただし、ベースとなるコースのコンテンツは Google が Udacity と共同で作成したもので、誰でも無料でアクセスできます。このコンテンツは、Kotlin による Android アプリ開発や Kotlin による高度な Android で確認できます。
Romain Guy と私が、新しい 3.0 バージョンの Paging ライブラリ(「Paging 3」)について、Android ツールキット チームの Dustin Lam と Chris Craik に話を聞きました。
このバージョン(現在アルファ版)は、コルーチンと Flow を使って Kotlin で完全に書き直されています。それにはさまざまな理由がありますが、このポッドキャストではその点に迫っています。ぜひお聴きください。
この記事は Andrew Ahn による Android Developers Blog の記事 "Developer tips and guides: Common policy violations and how you can avoid them" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Google Play では、安全でエンゲージメントが高く、便利で楽しいアプリのエコシステムを培いたいと考えています。そのようなアプリが、世界中の数十億人の Android ユーザーに愛され、使われることを期待しています。そのため、Google Play のデベロッパー ポリシーとデベロッパー販売/配布契約を定期的に改定し、アプリのコンテンツや機能の境界について詳しく記述したり、デベロッパーの皆さんがアプリの宣伝や収益化を行うための最新ガイドを提供しています。
Google Play Store で公開されているアプリがデベロッパーポリシーに準拠しているかどうかを分析したところ、デベロッパーの皆さんが犯しがちないくつかの誤りや違反があることがわかりました。この記事では、デベロッパー コミュニティの皆さんにそれらの点をお伝えするとともに、ポリシー違反によってアプリやデベロッパー アカウントが停止されるリスクを軽減できるように、違反を避けるためのヒントとガイドをご紹介します。
よくある誤りの 1 つは、Play Store にリンクしているボタンやメニューに多く見受けられます。リンク先は同じデベロッパーによるアプリだったり、連携している他社のアプリだったりしますが、それが広告や宣伝のリンクであるかを明らかにしていないことがよくあります。その場合、詐欺や偽装広告と見做されてしまう可能性があります。このような誤りを避ける方法の 1 つは、ボタンやリンクを明示的な名前にすることです。たとえば、「その他のアプリ」「その他のゲーム」「試してみる」「他のアプリをチェック」などです。
よく見られるもう 1 つの誤りは、あるキーワードやフレーズのランクを上げてアプリを見つかりやすくしようと、アプリの説明にキーワードを詰め込むことです。キーワードの繰り返しや、関係のないキーワードや参照を含むテキスト ブロックやリストは、ストアの掲載情報とプロモーションのポリシーに違反します。この違反を避ける最善の方法の 1 つは、ユーザーにとって読みやすくわかりやすいアプリの説明を書くことです。
不適切なストアの掲載情報を避ける方法や、人工的にアプリの視認性を上げる方法については、こちらの動画をご覧ください。
デベロッパーがかなり以前に公開し、既にメンテナンスされなくなったアプリも存在します。メンテナンスされていないアプリは、機能が動作しないなど、ユーザー エクスペリエンスの問題を生み出します。このようなアプリは、少ない星の数や否定的なユーザー レビューで評価されるリスクがあるだけでなく、最小限の機能を提供するというポリシーに違反しているとも認識される可能性があります。デベロッパーやアプリの評判が下がるのを防ぐためにも、そのようなアプリは Play Store で非公開にすることを検討してください。なお、非公開にしても、既にアプリをインストールしている既存ユーザーには影響しません。また、デベロッパーはいつでも問題を修正してアプリを再公開することができます。
また、既存ウェブサイトを Webview で見せているだけに過ぎないアプリもよく見かけます。こういったアプリのほとんどは、Android ユーザーにエンゲージメントの高いアプリのエクスペリエンスを提供するというよりは、単にトラフィックを上げるという目的で登録されています。このようなアプリは、Webview スパムと見なされ、Play から削除されます。アプリではウェブ以上の機能を提供することを検討し、ユーザー エクスペリエンスを高める関連機能を実装してください。
Play アカデミーの ‘Webview Spam’ コースを受講
ここで説明したのはよくある誤りの一部だけですが、ポリシーの最新情報については、Google Play デベロッパー ポリシー センターをご確認ください。また、最新のポリシー アップデートの詳細については、Comply with Google Play's Spam and Minimum Functionality policiesなどの Google Play アカデミーのポリシー トレーニングコースや Play PolicyBytes 動画をご覧ください。
Reviewed by Konosuke Ogura - Trust & Safety - Play & Android, Global Policy & Operations Lead and Hidenori Fujii - Google Play Developer Marketing APAC
この記事は Florina Muntenescu による Android Developers - Medium の記事 "Don’t argue with default arguments" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
短くて使いやすいデフォルト引数を利用すると、ボイラープレートを書くことなく関数のオーバーロードを実現できます。多くの Kotlin の機能と同じように、この機能も魔法のように便利です。その秘密を知りたいと思いませんか?この記事では、デフォルト引数の内部の仕組みを紹介します。
関数のオーバーロードが必要な場合、同じ関数を複数回実装する代わりに、デフォルト引数を使うことができます。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 -->// instead of:fun play(toy: Toy){ ... }fun play(){ play(SqueakyToy)} // use default arguments: fun play(toy: Toy = SqueakyToy)fun startPlaying() { play(toy = Stick) play() // toy = SqueakyToy}
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// instead of:
fun play(toy: Toy){ ... }
fun play(){
play(SqueakyToy)
}
// use default arguments:
fun play(toy: Toy = SqueakyToy)
fun startPlaying() {
play(toy = Stick)
play() // toy = SqueakyToy
デフォルト引数はコンストラクタにも適用できます。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 -->class Doggo( val name: String, val rating: Int = 11)val goodDoggo = Doggo(name = "Tofu")val veryGoodDoggo = Doggo(name = "Tofu", rating = 12)
class Doggo(
val name: String,
val rating: Int = 11
)
val goodDoggo = Doggo(name = "Tofu")
val veryGoodDoggo = Doggo(name = "Tofu", rating = 12)
デフォルトでは、Java はデフォルト値のオーバーロードを認識しません。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> // kotlinfun play(toy: Toy = SqueakyToy) {... }// javaDoggoKt.play(DoggoKt.getSqueakyToy());DoggoKt.play(); // error: Cannot resolve method 'play()'
// kotlin
fun play(toy: Toy = SqueakyToy) {... }
// java
DoggoKt.play(DoggoKt.getSqueakyToy());
DoggoKt.play(); // error: Cannot resolve method 'play()'
コンパイラにオーバーロード メソッドを生成するよう指示するには、Kotlin 関数に @JvmOverloads アノテーションを追加します。
@JvmOverloads
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */@JvmOverloadsfun play(toy: Toy = SqueakyToy) {… }
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
fun play(toy: Toy = SqueakyToy) {… }
コンパイラが生成した内容を確認するため、逆コンパイルした Java コードを見てみましょう。[Tools] -> [Kotlin] -> [Show Kotlin Bytecode] を選択し、[Decompile] ボタンを押します。
[Tools] -> [Kotlin] -> [Show Kotlin Bytecode]
[Decompile]
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */fun play(toy: Toy = SqueakyToy)...fun startPlaying() { play(toy = Stick) play() // toy = SqueakyToy}// decompiled Java codepublic static final void play(@NotNull Toy toy) { Intrinsics.checkNotNullParameter(toy, "toy");}// $FF: synthetic methodpublic static void play$default(Toy var0, int var1, Object var2) { if ((var1 & 1) != 0) { var0 = SqueakyToy; } play(var0);}public static final void startPlaying() { play(Stick); play$default((Toy)null, 1, (Object)null);}
...
// decompiled Java code
public static final void play(@NotNull Toy toy) {
Intrinsics.checkNotNullParameter(toy, "toy");
// $FF: synthetic method
public static void play$default(Toy var0, int var1, Object var2) {
if ((var1 & 1) != 0) {
var0 = SqueakyToy;
play(var0);
public static final void startPlaying() {
play(Stick);
play$default((Toy)null, 1, (Object)null);
コンパイラが 2 つの関数を生成していることがわかります。
play
Toy
play$default
int
Object
null
play$default の int パラメータの値は、デフォルト引数が渡された引数の数と、そのインデックスに基づいて計算されます。Kotlin コンパイラは、どのパラメータを使って play 関数を呼び出すかを、このパラメータの値に基づいて判断します。
この例の play() の呼び出しでは、インデックス 0 の引数がデフォルト引数を使っています。そのため、int var1 = 2⁰ を使って play$default を呼び出します。
play()
int var1 = 2⁰
これで、play$default の実装は、var0 の値をデフォルト値で置き換えなければならないことを認識できます。
var0
この int パラメータの動作を確認するため、もう少し複雑な例を見てみましょう。play 関数を拡張し、この関数を呼び出す際に doggo と toy をデフォルト引数として使うようにします。
doggo
toy
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ fun play(doggo: Doggo = goodDoggo, doggo2: Doggo = veryGoodDoggo, toy: Toy = SqueakyToy) {...}fun startPlaying() { play2(doggo2 = myDoggo)}
fun play(doggo: Doggo = goodDoggo, doggo2: Doggo = veryGoodDoggo, toy: Toy = SqueakyToy) {...}
play2(doggo2 = myDoggo)
逆コンパイルしたコードはどうなったでしょうか。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ public static final void play(@NotNull Doggo doggo, @NotNull Doggo doggo2, @NotNull Toy toy) {... }// $FF: synthetic methodpublic static void play$default(Doggo var0, Doggo var1, Toy var2, int var3, Object var4) { if ((var3 & 1) != 0) { var0 = goodDoggo; } if ((var3 & 2) != 0) { var1 = veryGoodDoggo; } if ((var3 & 4) != 0) { var2 = SqueakyToy; } play(var0, var1, var2);}public static final void startPlaying() { play2$default((Doggo)null, myDoggo, (Toy)null, 5, (Object)null); }
public static final void play(@NotNull Doggo doggo, @NotNull Doggo doggo2, @NotNull Toy toy) {
public static void play$default(Doggo var0, Doggo var1, Toy var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var0 = goodDoggo;
if ((var3 & 2) != 0) {
var1 = veryGoodDoggo;
if ((var3 & 4) != 0) {
var2 = SqueakyToy;
play(var0, var1, var2);
play2$default((Doggo)null, myDoggo, (Toy)null, 5, (Object)null);
int パラメータが 5 になりました。この計算の仕組みは次のようになります。0 番目と 2 番目のパラメータでデフォルト引数が使われているので、var3 = 2⁰ + 2² = 5 となります。パラメータは、ビット単位の & 演算を使って次のように評価されます。
var3 = 2⁰ + 2² = 5
var3 & 1 != 0
true
var0 = goodDoggo
var3 & 2 != 0
false
var1
var3 & 4 != 0
var2 = SqueakyToy
コンパイラは、var3 に適用されたビットマスクから、どのパラメータをデフォルト値と置き換えるかを計算できます。
var3
上の例で、Object パラメータの値が常に null になっていたことに気づいたかもしれません。実際に、play$default 関数ではこの値が使われることはありません。このパラメータは、オーバーライドする関数でデフォルト値をサポートするために使用されています。
デフォルト引数がある関数をオーバーライドすると、何が起きるでしょうか。
先ほどの例を次のように変更してみましょう。
Doggo
Open
PlayfulDoggo
PlayfulDoggo.play にデフォルト値を設定しようとしても、次のように表示され、許可されません。An overriding function is not allowed to specify default values for its parameters(オーバーライド関数では、パラメータへのデフォルト値の指定は許可されていません)
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */open class Doggo( val name: String, val rating: Int = 11) { open fun play(toy: Toy = SqueakyToy) {...}}class PlayfulDoggo(val playfulness: Int, name: String, rating: Int) : Doggo(name, rating) { // error: An overriding function is not allowed to specify default values for its parameters override fun play(toy: Toy = Stick) { }}
open class Doggo(
) {
open fun play(toy: Toy = SqueakyToy) {...}
class PlayfulDoggo(val playfulness: Int, name: String, rating: Int) : Doggo(name, rating) {
// error: An overriding function is not allowed to specify default values for its parameters
override fun play(toy: Toy = Stick) { }
override を削除して逆コンパイルしたコードを確認すると、PlayfulDoggo.play() は次のようになっています。
override
PlayfulDoggo.play()
public void play(@NotNull Toy toy) {... }// $FF: synthetic methodpublic static void play$default(Doggo var0, Toy var1, int var2, Object var3) { if (var3 != null) { throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: play"); } else { if ((var2 & 1) != 0) { var1 = DoggoKt.getSqueakyToy(); } var0.play(var1); }}
public void play(@NotNull Toy toy) {... }
public static void play$default(Doggo var0, Toy var1, int var2, Object var3) {
if (var3 != null) {
throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: play");
} else {
if ((var2 & 1) != 0) {
var1 = DoggoKt.getSqueakyToy();
var0.play(var1);
これは、デフォルト引数を使った super の呼び出しが将来的にサポートされるという意味なのでしょうか。この点については、成り行きを見守るしかありません。
コンストラクタでは、逆コンパイルした Java コードに 1 つだけ違う点があります。以下をご覧ください。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ // kotlin declarationclass Doggo( val name: String, val rating: Int = 11)// decompiled Java codepublic final class Doggo { ... public Doggo(@NotNull String name, int rating) { Intrinsics.checkNotNullParameter(name, "name"); super(); this.name = name; this.rating = rating; } // $FF: synthetic method public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) { if ((var3 & 2) != 0) { var2 = 11; } this(var1, var2); }
// kotlin declaration
public final class Doggo {
public Doggo(@NotNull String name, int rating) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.rating = rating;
public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) {
var2 = 11;
this(var1, var2);
コンストラクタでも合成メソッドが作成されていますが、関数で使われていた Object ではなく、DefaultConstructorMarker が null で呼び出されています。
DefaultConstructorMarker
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */// kotlinval goodDoggo = Doggo("Tofu")// decompiled Java codeDoggo goodDoggo = new Doggo("Tofu", 0, 2, (DefaultConstructorMarker)null);
val goodDoggo = Doggo("Tofu")
Doggo goodDoggo = new Doggo("Tofu", 0, 2, (DefaultConstructorMarker)null);
デフォルト引数があるセカンダリ コンストラクタでも、プライマリ コンストラクタと同じように DefaultConstructorMarker を使った別の合成メソッドが生成されます。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */// kotlinclass Doggo( val name: String, val rating: Int = 11) { constructor(name: String, rating: Int, lazy: Boolean = true) }// decompiled Java codepublic final class Doggo { ... public Doggo(@NotNull String name, int rating) { ... } // $FF: synthetic method public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) { if ((var3 & 2) != 0) { var2 = 11; } this(var1, var2); } public Doggo(@NotNull String name, int rating, boolean lazy) { ... } // $FF: synthetic method public Doggo(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) { if ((var4 & 4) != 0) { var3 = true; } this(var1, var2, var3); }}
constructor(name: String, rating: Int, lazy: Boolean = true)
public Doggo(@NotNull String name, int rating, boolean lazy) {
public Doggo(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) {
if ((var4 & 4) != 0) {
var3 = true;
this(var1, var2, var3);
シンプルで美しいデフォルト引数を利用すると、パラメータにデフォルト値を設定できるようになるので、メソッドをオーバーロードする際に書かなければならないボイラープレート コードの量が減ります。多くの Kotlin キーワードに言えることですが、生成されるコードを調べてみれば、そこでどのような魔法が使われているかを理解できます。詳しくは、他の Kotlin Vocabulary の投稿を確認してみてください。
仕事を完了する方法の 1 つは、その仕事を他者に委譲することです。皆さんの仕事を友だちに委譲することを言っているわけではありません。今回のテーマは、あるオブジェクトから別のオブジェクトに委譲することです。
ソフトウェアの世界では、委譲という考え方は新しいものではありません。委譲はデザイン パターンの 1 つで、あるオブジェクトが デリゲートと呼ばれるヘルパー オブジェクトに委譲することでリクエストを処理することを指します。デリゲートの役割は、元のオブジェクトに代わってリクエストを処理し、その結果を元のオブジェクトが利用できるようにすることです。
Kotlin はクラス委譲とプロパティ委譲をサポートしているので、委譲を簡単に扱えます。さらに、いくつかの独自の組み込みデリゲートも提供しています。
最後に削除された項目を復元できる ArrayList を使うとしましょう。基本的に、必要なのは同じ ArrayList の機能だけですが、最後に削除された項目への参照が必要です。
ArrayList
これを実現する方法の 1 つは、ArrayList クラスを拡張することです。この新しいクラスは、MutableList インターフェースの実装ではなく ArrayList の具象クラスを拡張したものなので、ArrayList の具象クラスの実装と強く結合されることになります。
MutableList
MutableList の実装で remove() 関数をオーバーライドし、削除した項目の参照を保持できるようにしたうえで、その他の空の実装を他のオブジェクトに委譲したいと思ったことはありませんか?Kotlin では、これを実現する方法が提供されています。具体的には、内部 ArrayList インスタンスに作業の大半を委譲し、その動作をカスタマイズできます。これを行うため、Kotlin には新しいキーワード by が導入されています。
remove()
by
では、クラス委譲の仕組みを確認してみましょう。by キーワードを使うと、Kotlin は innerList インスタンスをデリゲートとして使用するコードを自動的に生成します。
innerList
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class ListWithTrash <T>( private val innerList: MutableList<T> = ArrayList<T>()) : MutableCollection<T> by innerList { var deletedItem : T? = null override fun remove(element: T): Boolean { deletedItem = element return innerList.remove(element) } fun recover(): T? { return deletedItem }}
class ListWithTrash <T>(
private val innerList: MutableList<T> = ArrayList<T>()
) : MutableCollection<T> by innerList {
var deletedItem : T? = null
override fun remove(element: T): Boolean {
deletedItem = element
return innerList.remove(element)
fun recover(): T? {
return deletedItem
by キーワードは、MutableList インターフェースの機能を innerList という名前の内部 ArrayList インスタンスに委譲するよう Kotlin に伝えます。内部 ArrayList オブジェクトに直接橋渡しするメソッドが提供されるので、ListWithTrash は MutableList インターフェースのすべての機能をサポートします。さらに、独自の動作を追加することもできるようになります。
ListWithTrash
動作の仕組みを確認してみましょう。ListWithTrash のバイトコードを逆コンパイルした Java コードを見ると、Kotlin コンパイラが実際にラッパー関数を作成していることを確認できます。このラッパー関数が、内部 ArrayList オブジェクトの対応する関数を呼び出していることもわかります。
public final class ListWithTrash implements Collection, KMutableCollection { @Nullable private Object deletedItem; private final List innerList; @Nullable public final Object getDeletedItem() { return this.deletedItem; } public final void setDeletedItem(@Nullable Object var1) { this.deletedItem = var1; } public boolean remove(Object element) { this.deletedItem = element; return this.innerList.remove(element); } @Nullable public final Object recover() { return this.deletedItem; } public ListWithTrash() { this((List)null, 1, (DefaultConstructorMarker)null); } public int getSize() { return this.innerList.size(); } // $FF: bridge method public final int size() { return this.getSize(); } //...and so on}
private Object deletedItem;
private final List innerList;
public final Object getDeletedItem() {
return this.deletedItem;
public final void setDeletedItem(@Nullable Object var1) {
this.deletedItem = var1;
public boolean remove(Object element) {
this.deletedItem = element;
return this.innerList.remove(element);
public final Object recover() {
public ListWithTrash() {
this((List)null, 1, (DefaultConstructorMarker)null);
public int getSize() {
return this.innerList.size();
// $FF: bridge method
public final int size() {
return this.getSize();
//...and so on
注: 生成されたコードで、Kotlin コンパイラは Decorator パターンという別のデザイン パターンを使ってクラス委譲をサポートしています。Decorator パターンでは、デコレータ クラスがデコレートされるクラスと同じインターフェースを共有します。デコレータ クラスは、ターゲット クラスの内部参照を保持し、そのインターフェースで提供されるすべてのパブリック メソッドをラップ(デコレート)します。
委譲は、特定のクラスを継承できない場合に特に便利です。クラス委譲を使うと、クラスが他のクラスの階層に含まれることはなくなります。その代わり、同じインターフェースを共有し、元の型の内部オブジェクトをデコレートします。つまり、パブリック API を維持したまま、実装を簡単に入れ替えることができます。
by キーワードを使うと、クラス委譲だけでなく、プロパティを委譲することもできます。プロパティ委譲では、デリゲートはプロパティの get 関数と set 関数の呼び出しを担当します。他のオブジェクトで getter/setter ロジックを再利用しなければならない場合、対応するフィールドだけでなく機能を簡単に拡張することができるので、この機能が非常に便利です。
get
set
次のような定義の Person クラスがあったとしましょう。
Person
class Person(var name:String, var lastname:String)
このクラスの name プロパティには、いくつかのフォーマット要件があります。name を設定するとき、先頭の文字が大文字、他の文字が小文字になるようにします。さらに、 name を更新する場合、updateCount プロパティを自動的にインクリメントします。
name
updateCount
この機能は、次のように実装してもいいかもしれません 。
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, var lastname: String) { var name: String = name set(value) { field = value.toLowerCase().capitalize() updateCount++ } var updateCount = 0}
class Person(name: String, var lastname: String) {
var name: String = name
set(value) {
field = value.toLowerCase().capitalize()
updateCount++
var updateCount = 0
これは動作しますが、要件が変わって lastname が変更されたときも updateCount をインクリメントすることになるとどうでしょうか。ロジックをコピーして貼り付け、カスタムの setter を書いてもいいかもしれませんが、両方のプロパティにまったく同じ setter を書いていることに気づくでしょう。
lastname
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, lastname: String) { var name: String = name set(value) { field = value.toLowerCase().capitalize() updateCount++ } var lastname: String = lastname set(value) { field = value.toLowerCase().capitalize() updateCount++ } var updateCount = 0}
class Person(name: String, lastname: String) {
var lastname: String = lastname
どちらの setter メソッドもほぼ同じということは、どちらかは不要ということです。プロパティ委譲を使うと、getter と setter をプロパティに委譲してコードを再利用できます。
クラス委譲と同じように、by を使ってプロパティを委譲します。すると、プロパティ構文を使ったときに、Kotlin はデリゲートを使うコードを生成します。
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, lastname: String) { var name: String by FormatDelegate() var lastname: String by FormatDelegate() var updateCount = 0}
var name: String by FormatDelegate()
var lastname: String by FormatDelegate()
この変更を行うと、name プロパティと lastname プロパティが FormatDelegate クラスに委譲されます。FormatDelegate のコードを確認してみましょう。デリゲート クラスは、getter だけを委譲する場合は ReadProperty<Any?, String> を、getter と setter の両方を委譲する場合は ReadWriteProperty<Any?, String> を実装する必要があります。この例の FormatDelegate は、setter が呼び出された場合にフォーマット処理を行うので、ReadWriteProperty<Any?, String> を実装しなければなりません。
FormatDelegate
ReadProperty<Any?, String>
ReadWriteProperty<Any?, String>
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class FormatDelegate : ReadWriteProperty<Any?, String> { private var formattedString: String = "" override fun getValue( thisRef: Any?, property: KProperty<*> ): String { return formattedString } override fun setValue( thisRef: Any?, property: KProperty<*>, value: String ) { formattedString = value.toLowerCase().capitalize() }}
class FormatDelegate : ReadWriteProperty<Any?, String> {
private var formattedString: String = ""
override fun getValue(
thisRef: Any?,
property: KProperty<*>
): String {
return formattedString
override fun setValue(
property: KProperty<*>,
value: String
formattedString = value.toLowerCase().capitalize()
getter 関数と setter 関数に 2 つの追加パラメータがあることに気づいた方もいらっしゃるでしょう。最初のパラメータ thisRef は、プロパティを含むオブジェクトを表します。これを使うと、オブジェクト自体にアクセスし、他のプロパティを確認したり、他のクラス関数を呼び出したりできます。2 つ目のパラメータは KProperty<*> です。これは、委譲されたプロパティについてのメタデータにアクセスするために使うことができます。
thisRef
KProperty<*>
先ほどの要件を思い出してみてください。thisRef を使って updateCount プロパティにアクセスし、インクリメントしてみましょう。
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->override fun setValue( thisRef: Any?, property: KProperty<*>, value: String) { if (thisRef is Person) { thisRef.updateCount++ } formattedString = value.toLowerCase().capitalize()}
if (thisRef is Person) {
thisRef.updateCount++
この仕組みを理解するため、逆コンパイルした Java コードを見てみます。Kotlin コンパイラは、name プロパティと lastname プロパティについての FormatDelegate オブジェクトへのプライベートな参照を保持するためのコードと、追加したロジックを含む getter/setter の両方を生成します。
さらに、委譲されるプロパティを保持する KProperty[] も作成しています。name プロパティに対して生成された getter と setter を見てみると、インスタンスはインデックス 0 に保存されています。一方、lastname プロパティはインデックス 1 に保存されています。
KProperty[]
public final class Person { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "lastname", "getlastname()Ljava/lang/String;"))}; @NotNull private final FormatDelegate name$delegate; @NotNull private final FormatDelegate lastname$delegate; private int updateCount; @NotNull public final String getName() { return this.name$delegate.getValue(this, $$delegatedProperties[0]); } public final void setName(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.name$delegate.setValue(this, $$delegatedProperties[0], var1); } //...}
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "lastname", "getlastname()Ljava/lang/String;"))};
@NotNull
private final FormatDelegate name$delegate;
private final FormatDelegate lastname$delegate;
private int updateCount;
public final String getName() {
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
//...
この仕組みによって、通常のプロパティ構文を使って任意の呼び出し元が委譲されるプロパティにアクセスできるようになっています。
person.lastname = “Smith” //
println(“Update count is $person.count”)
Kotlin は単に委譲をサポートしているだけではありません。Kotlin 標準ライブラリで組み込みの委譲も提供していますが、詳しくは別の記事で説明したいと思います。
委譲は他のオブジェクトにタスクを委譲する際に役立ち、コードの再利用性を高めます。Kotlin コンパイラは、委譲をシームレスに使えるようにコードを作成します。Kotlin は、by キーワードを使ったシンプルな構文でプロパティやクラスの委譲を行います。Kotlin コンパイラは、パブリック API を一切変更せず、委譲をサポートするために必要なすべてのコードを内部的に生成します。簡単に言えば、Kotlin は委譲に必要なボイラープレート コードをすべて生成して維持してくれます。つまり、委譲を Kotlin に委譲することができるのです。
Reviewed by Yuichi Araki - Developer Relations Team