現在JavaScriptの設定が無効になっています。このページを利用するにはJavaScriptの設定を有効にしてください。トップ全記事一覧テクノロジー2016.09.16AndroidにおけるSDK開発時に意識していることこんにちは。真也(@shikato)です。今回はAndroid向けSDK開発について、作り方や普段意識していることを紹介したいと思います。このテーマにした理由は、AndroidやiOSにおける広告などのSDKを使う際の知見は比較的充実しているように思うのですが、それらのSDKを開発する際の知見はあまりないように感じているためです。本記事は、私が最近担当しているAndroidの広告関連SDK開発時の内容となりますが、いわゆるアドテクのような広告業界ならではの話題は少なめとなっています。開発者に向けた、ライブラリのようなソフトウェアの開発全般に適用できる話題を盛り込みましたので、広告にあまり興味がない方にも読んでもらえるとうれしいです。ヤフーの広告に関わるSDKについてそもそもSDKとはYahoo!辞書でSDKという単語を調べてみると、以下の説明が見つかります。kit》ある特定のハードウエアやオペレーティングシステム上で動作するソフトウエアを開発する際に必要な各種のツールをひとまとめにしたもの。通常、サンプルプログラム、APIのライブラリー、技術文書などが含まれており、企業が自社製品のソフトウエア開発を促すために無償で配布することが多い。ソフトウエア開発キット。Yahoo!辞書それをヤフーの広告関連SDKに当てはめると「ヤフーの広告でマネタイズするための仕組みをアプリに組み込むためのSDK」と言ったところでしょうか。JAPANアプリやYahoo!天気アプリ、Yahoo!ニュースアプリなどがあります。SDKが提供されているおかげで、サードパーティデベロッパーもAndroidアプリ開発が可能となっています。私が担当している広告関連のSDKは「SDK」という大層な名前がついていますが、その作り方自体はAndroid向けライブラリの開発とさほど違いはありません。Android向けライブラリの開発に関しては、Cookpadさんがクックパッド開発者ブログにてとても参考になる記事を書かれていますので、そちらを参照していただければと思います。ライブラリづくりとライセンスについてAndroid向けSDK開発時に意識していることを分類するでは、ここからは私がAndroid向けSDK開発時に意識していることやTipsを書いていきます。私が普段意識している事を大きく分類すると以下の3つの項目になりました。してアプリ開発者がSDKを簡単に組み込めるようにするか如何にしてアプリに影響や制限を与えない設計とするかアプリ開発者の声を聞くでは1つずつ紹介していきます。してアプリ開発者がSDKを簡単に組み込めるようにするかまず1つ目の分類項目についてです。私は、Android向けSDKを評価する指標として「SDK提供者が提供したい機能を、アプリ開発者が簡単に組み込めるSDKとなっているか」があると考えています。ここからは、その指標を満たすために意識していることやTipsを紹介していきます。言わずもがなですが、ドキュメントは充実させておきたいです。アプリ開発者に対して必要なのは、導入ドキュメント、APIリファレンス、あとはFAQあたりでしょうか。頻繁に問い合わせを受ける内容はFAQ化しておきます。問い合わせという行為は、アプリ開発者とSDK開発者ともにそれなりに負担がかかるので、不要な問い合わせが発生しない状況を作っておきます。導入サンプルの充実やはり導入サンプルも充実させておきたいところです。GitHubでSDKのコードを管理しているのであれば、GitHubで公開されているプロジェクトでよく見られる構成と同様に、リポジトリ直下にsampleのようなディレクトリを作ります。そこにサンプルアプリを設置しておけば、アプリ開発者が他のGitHubで公開されているプロジェクトと同じ要領で迷わず利用できます。簡潔明瞭なAPIアプリ開発者が直接触れるクラスやメソッドは、その用途が明瞭になるように理解しやすい名前をつけます。ここは多少時間をかけてでも誰もが納得する名前を考えるべきです。コードレビューなどを活用して議論します。加えて、アプリ開発者が直接触れる必要のないクラスやメソッドは、ProGuardで難読化して隠蔽します。ProGuardの設定を配布するそのProGuardの設定ですが、AndroidのライブラリフォーマットであるAARに同梱できます。SDK内でリフレクションを使っているなどの理由で、特定のクラスやメソッドにアプリ側でProGuardを使って難読化してほしくない場合は、そのProGuardの設定をAARに同梱しましょう。しておけばアプリ開発者にSDK用のProGuard設定を追記してもらう手間が省けます。build.gradleに依存関係を記述するだけでSDKを取り込めるようにするGradleがAndroidアプリのビルドツールに採用される前は直接jarファイルを配っていましたが、今はbuild.gradleに依存関係を一行記述するだけでアプリにSDKを取り込んでもらうことが可能です。その方法についても先ほど紹介させていただいたCookpadさんの記事で詳しく説明されています。問い合わせやすいログ出力SDKが出力するログは、導入アプリで何か問題が発生した時に貴重な情報となるので、ログの内容からSDK開発者である自分たちが問題を識別できるログ出力を心がけます。頻出してしまう類の問題に関しては、SDKが出力するログから、アプリ開発者が必要な対応内容を確認できるようにFAQ化しておきます。SDK開発者用のログとアプリ開発者用のログを分けるSDK開発者用のログと、アプリ開発者用のログは明確に分けます。Variantsを使うなどして、アプリへの組込時にはSDK開発者用のログは出力されないようにします。することで、アプリ開発者を不要な情報で惑わさないようにします。NotesやChangeLogとして記録します。Requestのリンクや詳細な変更点を記述するようにしています。セマンティックバージョニング最近、OSSとして公開されているプロジェクトなどで、よく採用されているのを目にしますが、私が担当しているSDKでもセマンティックバージョニングを採用しています。セマンティックバージョニングを採用することで、SDKを更新した際に、アプリ開発者はどういう変更があったのかをバージョン名からある程度推測できます。SDK開発者としては、後方互換性のない変更を伴うメジャーバージョンアップを可能な限り避けられるように、拡張性のある設計を心がけたいところです。Mergerという機能が備わっています。こちらはその名前の通り、AARに含まれるManifestファイルをアプリのManifestファイルにマージしてくれる機能です。SDKで必要なパーミッションやActivityをSDK側のManifestファイルで宣言しておけば、アプリ側でSDKのための宣言をせずに済むということです。パーミッションの追加は、ダウンロード数に影響したり追加するパーミッションによってはセキュリティを考慮する必要が出てきたりと、かなりセンシティブな行為と言えます。ActivityやServiceの追加も同様で、アプリ開発者が気づかない形でServiceによるバックグラウンド処理が追加されてしまった、といったことも発生するかもしれません。Mergerを使う場合は、導入ドキュメントなどでマージされる項目をアプリ開発者に説明した上で使用するようにします。Mergerによってマージされる項目は以下ファイルから確認できます。SDKを使う側の視点を持つ当たり前かもしれませんが、SDKを使う側の視点に立てないと利用しづらいSDKになってしまいがちです。SDKを使う側の視点を持つには、やはりアプリを開発した経験が必要です。SDK開発者は自身でもアプリを開発する経験を積んで、SDKのユーザーであるアプリ開発者の視点に立てるようにしましょう。SDK開発者はどこまで視点を持つべきか最近のアプリ開発では普通にネイティブアプリとして開発する以外にも、以下のような環境を利用して開発をすることも多いかと思います。Engine、クロスプラットフォーム開発ならXamarinなどです。SDK開発者としての理想は、それらの環境を利用しているアプリ開発者の視点に立てる事かと思いますが、対応環境が増えれば増えるほどその道のりは険しくなっていきそうです。その際には、経験者からサポートしてもらったり自身でも利用するなどして、少しでもアプリ開発者の目線に立てるように可能な限り善処したいところです。してアプリに影響や制限を与えない設計とするかここからは、2つ目の分類項目である如何にしてアプリに影響や制限を与えない設計とするかについてです。SDK開発者は以下のような状況が可能な限り発生しないようにSDKを設計すべきだと私は考えています。SDK起因でアプリのパフォーマンスが悪化するSDK起因でアプリに何かしらの制限が生まれるSDK起因でアプリがクラッシュする(当然ですね)では、その考えを実践するために意識していることを紹介していきます。アプリに影響がありそうな実装を避けるSDK開発において、特に何も意識していないと意図せずアプリに影響を与えてしまう実装をしてしまうことが多々あります。今回はその一例として、SDK開発でやってしまいがちなAsyncTaskの実装について、Androidのコード※を交えながら紹介します。android/platform_frameworks_baseにてandroid-6.0.1_r68としてタグ打ちされているコードで確認しています。AsyncTaskの直列実行AsyncTaskで処理を開始する際にはAsyncTask#executeを使用するかと思いますが、SDKで使う場合には問題が生じる場合があります。実際にどういう問題が生じるのかをAsyncTask#executeの実装を見ながら確認していきます。以下がAsyncTask#executeの定義です。AsyncTask#executeOnExecutorにsDefaultExecutorを渡しています。params);}https://github.com/android/platform_frameworks_base/blob/android-6.0.1_r68/core/java/android/os/AsyncTask.java#L549-L552そのsDefaultExecutorはstatic変数でありSERIAL_EXECUTORが格納されています。SerialExecutor()が格納されています。SerialExecutor();https://github.com/android/platform_frameworks_base/blob/android-6.0.1_r68/core/java/android/os/AsyncTask.java#L210SerialExecutorは小さいstaticクラスです。SerialExecutor#executeは引数のr(アプリ開発者がAsyncTaskで定義した処理=タスク)を実行するためのRunnableをキュー(mTasks)の末尾に挿入しています。SerialExecutor#scheduleNextを呼び出し、キューから先頭のRunnableを取り出しTHREAD_POOL_EXECUTORで実行しています。一度SerialExecutor#scheduleNextが呼ばれれば、後はキューに挿入しているRuunable#run内でSerialExecutor#scheduleNextが再帰的に呼ばれるため、キューが空になるまで順番に処理されます。}}https://github.com/android/platform_frameworks_base/blob/android-6.0.1_r68/core/java/android/os/AsyncTask.java#L226-L250AsyncTask#executeは、このような実装となっているため、アプリとSDKがともにAsyncTask#executeを使った場合に問題が発生します。AsyncTaskを使ってSDK側で何か時間のかかる処理をすると、タイミングによってはアプリ側の処理はその分だけ待たされる可能性があり、双方が非常に気づきづらい形でアプリに悪い影響を与えてしまいます。並列実行するにはどうすれば良いでしょうか。AsyncTaskの並列実行AsyncTask#executeのドキュメントを見てみると、以下の説明が見つかります。THREAD_POOL_EXECUTOR;https://developer.android.com/reference/android/os/AsyncTask.html#execute(Params...))AsyncTask#executeOnExecutorをAsyncTask.THREAD_POOL_EXECUTORとともに使えば良さそうです。AsyncTask#executeOnExecutorの実装を見てみます。説明されていた通りにAsyncTask#executeOnExecutorの引数にAsyncTask.THREAD_POOL_EXECUTORを渡すとAsyncTask.THREAD_POOL_EXECUTORでタスクが実行されそうです。this;}https://github.com/android/platform_frameworks_base/blob/android-6.0.1_r68/core/java/android/os/AsyncTask.java#L588-L610AsyncTask.THREAD_POOL_EXECUTORも見てみると、その実体はThreadPoolExecutorとなっています。sThreadFactory);https://github.com/android/platform_frameworks_base/blob/android-6.0.1_r68/core/java/android/os/AsyncTask.java#L202-L204それらの引数は以下のように定義されているため、AsyncTask.THREAD_POOL_EXECUTORはCPUのコア数に対応したスレッド数を持つスレッドプールと言えそうです。LinkedBlockingQueue<Runnable>(128);https://github.com/android/platform_frameworks_base/blob/android-6.0.1_r68/core/java/android/os/AsyncTask.java#L196-L197以上のことからAsyncTask#executeOnExecutorの引数にAsyncTask.THREAD_POOL_EXECUTORを渡すと、CPUのコア数に対応したスレッドを持つスレッドプールでタスクが処理されるため並列実行である、ということが分かります。実はAsyncTask#executeもタスク自体はAsyncTask.THREAD_POOL_EXECUTORで実行されているのですが、タスクを直接AsyncTask.THREAD_POOL_EXECUTORに渡さず、前述したように一旦SerialExecutorを使ってキューイングしているため直列に実行されています。3.0)からで、それ以前はAsyncTask#executeがAsyncTask#executeOnExecutor相当の挙動をしていました。AsyncTask.THREAD_POOL_EXECUTORで並列実行してもアプリに影響が出る場合AsyncTask.THREAD_POOL_EXECUTORを使って並列実行してもアプリに影響を与えてしまう場合があります。それは、static変数として定義されているAsyncTask.THREAD_POOL_EXECUTORのスレッドやキューをSDKが専有してしまう場合です。そのリスクが懸念される場合は、AsyncTask#executeOnExecutorの引数にAsyncTask.THREAD_POOL_EXECUTORを渡さずに、SDK側で定義したExecutorを渡すようにしましょう。そもそもSDK開発ではAsyncTaskを使わない、という選択も有効だと思います。AsyncTask問題まとめ今回紹介したAsyncTaskの例のように、SDK開発者が意図せずアプリに影響を与えてしまう実装をしてしまうことが多々あります。SDKを開発する際には、アプリに影響がないかを常に意識しながら実装することを心がけておきたい所です。OSSなどのライブラリに可能な限り依存しないSDK開発ではOSSなどのライブラリに可能な限り依存するべきではないと私は考えています。その理由としてライブラリのバージョン競合問題があります。ライブラリのバージョン競合問題が発生するのかを説明していきます。Gradleの推移的な依存関係の管理Androidアプリのビルドツールとして採用されているGradleは推移的に依存関係を管理しています。Gradleのドキュメントに推移的な依存関係の管理についての説明があります。libraries.https://docs.gradle.org/current/userguide/dependency_management.html#sec:dependency_management_overview仮にあるアプリAが存在しているとします。そのアプリAが、あるSDK-Aに依存していて、さらにそのSDK-AがライブラリAやライブラリBに依存することを可能とするテクニックが推移的な依存関係の管理です。依存関係は以下のタスクを実行すると確認できます。:app:dependenciesでは上述したようにアプリAが依存するSDK-AがライブラリA(バージョン2.0.0)に依存していて、アプリA自身もライブラリA(バージョン1.0.0)に依存している場合、どのようにGradleは依存関係を解決するのでしょうか。以下のようなケースです。'jp.co.yahoo.android.library:library-a:2.0.0'}Gradleのドキュメントにバージョン競合を解決するための戦略についての説明があります。backwards-compatible.先程のケースで言うと、より新しいバージョンであるライブラリA(2.0.0)が使用されるということです。ライブラリA(2.0.0)には1.0.0と後方互換性のない変更が加えられており、アプリA側で、その変更が影響する1.0.0の機能を使っていたとします。この場合アプリAはライブラリA(1.0.0)を使う必要があるため以下のような手段を取ることになります。推移的に依存関係を解決しないバージョンを強制する推移的な依存関係を除外するでは1つずつ見ていきます。推移的に依存関係を解決しないtransitiveをfalseとすることで推移的に依存関係を解決する機能をOFFにできます。バージョンを強制するGradleは使用するライブラリのバージョンを強制できます。推移的な依存関係を除外するexcludeを使用することで特定の推移的な依存関係を除外できます。バージョンの指定では解決できない場合ここまで特定のバージョンを指定するための方法を見てきましたが、それだけで解決できない時はどうすべきでしょうか。アプリAとSDK-Aが、それぞれライブラリA(1.0.0)とライブラリA(2.0.0)にしかない機能を使っている場合です。この場合はバージョンを指定するだけでは解決できないので、アプリAとSDK-Aのどちらかが歩み寄る必要が出てきます。SDK-Aを導入しているアプリが少数の場合は、それで何とかなるかもしれません。大多数のアプリに組み込まれるSDKの場合だとどうなるでしょうか。アプリ側、SDK側、ともにつらい状況が頻繁に発生する未来が容易に想像できるのではないでしょうか。バージョン競合問題まとめこのようなバージョン競合問題を避けるためにも、SDK開発では依存ライブラリを可能な限りなくすべきだと私は考えています。Adsには依存しますが、主だった使用用途が実装コスト削減などのOSS(RxJava、Retrofit、Gsonなど)には依存しません。一開発者として、これらのライブラリを使いたい衝動には駆られるのですが、SDK開発者としてはデメリットの方が大きいと判断しているので我慢しています。その分テスティングフレームワークをいろいろ試したり、デバッグ時にしか使わないソフトウェアを使うことでライブラリを使いたい衝動を満たすようにしています。Pluginも、そういった衝動から生まれたものです。gradle-android-coverage-checkに関してはQiitaに投稿した記事でも紹介しているので興味がある方は読んでみてください。対応OSバージョン対応OSバージョンの選択によってアプリ側に大きな制限が生まれてしまうので、SDK開発者としては可能な限り低いバージョンから対応したい所です。対応するバージョンによっては対応コストが跳ね上がってしまうこともあるので、Googleが公開しているOSバージョン別シェア数などを見て判断します。SDK開発の場合だと、それらに加えて市場に出回っているアプリの対応バージョンにも目を通しておきたい所です。Servicesが取っている手法です。主なメリットしては以下が挙げられそうです。64K(65536)問題を回避する手段はいくつかあると思いますが、そもそもアプリが対応しなくてもすむようにSDK側でも可能な限り善処すべきです。Servicesと同様に必要な機能をアプリ開発者が取捨選択できるようにすることを考慮しましょう。Services規模のSDKを開発する事はまれだと思いますが)モジュールを分割する際の粒度ですが、広告関連のSDKの場合だと広告商品ごとに取捨選択して取り込めるようにするのも面白いかもしれないと考えています。SDKの非公開なAPIを叩けますが、可能な限り避けるべきです。(SDK開発の場合は特に)理由としては、そのAPIが非公開なだけに新しいOSバージョンで急に使えなくなるなどのリスクがあるためです。その結果、SDK起因でアプリが新しいOSに対応できない、という制限を生んでしまいます。iOSの場合はさらに深刻で、非公開APIを使用しているとリジェクトされてしまいます。変化に追随するSDK開発に限った話でもないですが、Androidの新しいバージョンが出た際には動作テストは必ずしましょう。これを怠ると、ひとつ前の項目でも述べたSDK起因でアプリが新しいOSに対応できない、という制限がアプリに発生してしまいます。想像力を働かせるどのような構成のアプリに組み込まれるかわからないので、想像力を働かせて、いろいろな構成のアプリに組み込み可能なSDKを目指します。「SDKを使う側の視点を持つ」の章でも述べたように、こればかりは経験がモノを言う領域だとも感じるので、対応環境が増えれば増える程つらくなっていきそうです。品質についてこれもSDKに限った話でもないですが、ソフトウェアとしての品質にはこだわります。SDKという類のソフトウェアは大多数のアプリに組み込まれるケースが多いかと思います。JAPANアプリや、Yahoo!天気アプリ、Yahoo!ニュースアプリがあり、それらに私が関わっているSDKが組み込まれています。SDKの軽微な問題が、大多数のユーザーが影響を受ける大きな問題に発展してしまうこともあり得るということです。広告関連のSDKは金銭が直接絡む以上、より大きな問題に発展する可能性があります。SDKの方で修正できたとしても導入先アプリへの反映には時間がかかったり、最悪反映されないケースもあり得ます。そのため、SDK開発では普通のアプリを開発する以上に品質に対して敏感になる必要があると感じています。一概に品質と言っても信頼性や安全性などさまざまな観点があり、それらを向上させるための手法もさまざまだと思います。よって具体例はこの場では挙げきれないですが、少なくとも発生しうるリスクと高い品質を維持する必要性は認識しておきたい所です。アプリ開発者の声を聞くここからは、私が意識していることの3つ目の分類項目であるアプリ開発者の声を聞くについてです。SDKのユーザーであるアプリ開発者の声に耳を傾けると、自分たちでは想像つかない問題も発掘されることがあります。アプリ開発者がフィードバックしやすい場を作るアプリ開発者がフィードバックしやすい場を提供します。私が最近担当しているSDKは主に社内向けなので、MYMと呼ばれる社内チャットでアプリ開発者からのフィードバックを受け付けています。こちらで紹介されているように、MYMは誰でも気軽にやりとりできるコミュニケーションツールなので、アプリ開発者がフィードバックをするための負担は小さいと思っています。感謝の気持ちを持つ時に厳しい内容のフィードバックをいただくこともありますが、フィードバックはプロダクトの品質を向上させるための貴重な情報なので、めげずに役立てていきましょう。Enterpriseで管理しています。Requestが送られてくることがあります。Requestを送る側が開発時に戸惑わないように、開発時のルールなどはCONTRIBUTING.mdのような形で掲示しておきます。Requestによる負担を減らす上で有効です。最後にここまでAndroid向けSDK開発時に私が意識していることを書いてきましたが、皆様のお役に立てる内容になっていたでしょうか。普段からライブラリのようなソフトウェアを開発されている方からすると当たり前な事柄が多かったかもしれませんが、皆様に何か1つでも新しい発見を与えることができましたら幸いです。所属するチームではSDK開発者の数が足りていない、という問題を抱えています。いうことで、iOS/Androidにおける広告に関わるSDK開発者を絶賛募集中なので、興味の有る方は以下の求人情報をご覧になっていただければと思います!ヤフーのアプリビジネスの基盤となるSDK開発エンジニアを募集!蛇足かもしれませんが、私は以下のモチベーションがあったため自らの意志で今のチームに異動しました。JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。JAPANのサービス開発を支える技術やデザイン、文化を発信しています。Reserved.このページの先頭へ。