Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
InAppUpdate実装&InAppBillingClientVer2.0実装例
Search
katsuki-nakatani
July 26, 2019
Technology
610
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
InAppUpdate実装&InAppBillingClientVer2.0実装例
katsuki-nakatani
July 26, 2019
More Decks by katsuki-nakatani
See All by katsuki-nakatani
Android 10 対応 Tips
katsukinakatani
0
120
AndroidJetpack概要 &旧AACの紹介
katsukinakatani
0
710
ViewModel使ってますか?
katsukinakatani
0
330
デフォルトアプリとパーミッション
katsukinakatani
0
230
Target SDK Version 26に上げる時につまづいたこと
katsukinakatani
3
2.9k
Whats new android wear (umeda.apk #3 LT)
katsukinakatani
1
510
Android 7.1.1 AppShortcuts setup(DevfestKansai Lightning Talk)
katsukinakatani
1
710
Firebase Authentication & Firebase Realtime Database setup
katsukinakatani
2
910
Firebase Setup for Android Studio2.2
katsukinakatani
1
640
Other Decks in Technology
See All in Technology
発表と総括 / Presentations and Summary
ks91
PRO
0
140
凡エンジニアがこの先生きのこるためには。〜TypeScript完全に理解したい〜
alchemy1115
2
310
AICoEでAIネイティブ組織への進化
yukiogawa
0
200
SRE Next 2026 何でも屋からの脱却
bto
0
1k
あなたの『Site』はどこですか? — xREという考え方
miyamu
0
1.2k
CSに"SLO"は要らない、経営層に"99.9%"は伝わらない - SREを全社に"翻訳"する3原則
cscengineer
PRO
1
5.2k
Claude Code公式skillで 自分の仕事を少しずつ手放そう!(Claude Code開発ノウハウ大公開スペシャル by クラスメソッド)
kaym
1
500
Amplify Gen2でbackend.tsにCDKを定義する/しない事によるCDKの挙動の違いとユースケース
smt7174
1
430
LLM/Agent評価:トップ営業の発言を「正解」にする 〜暗黙的正解による評価を営業資産に変える〜
takkuhiro
1
230
OpenTelemetryにおけるGoのゼロコード・コンパイル時計装について #fukuokago
quiver
0
120
世界、断片、モデル。そして理解
ardbeg1958
1
130
ヘルスケア領域における AI 活用と その安全性担保のための取り組み (Leveraging AI in Healthcare and Our Efforts to Ensure Its Safety) - Google I/O Extended Tokyo 2026, July 11, 2026
zettaittenani
0
430
Featured
See All Featured
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
180
For a Future-Friendly Web
brad_frost
183
10k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
410
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
320
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
810
Believing is Seeing
oripsolob
1
170
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
980
A Soul's Torment
seathinner
6
3.1k
Product Roadmaps are Hard
iamctodd
55
12k
Evolving SEO for Evolving Search Engines
ryanjones
0
240
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Transcript
Google I/O 2019 振り返り +In-App Update &In-App billing migration例 Osaka
Mix Leap Study #47 - Android&Flutter 勉強会
Speaker 中谷 克紀 仕事 Enterprise Server Engineer GDG 神戸スタッフ Twitter @KatsukiNakatani
Google I/O 2019
Android関連 ・CameraX ・JetPack Compose ・Kotlin first ・Android Q ・Android Studio
3.5 Beta1 ・PlayCoreLibrary stable ・PlayBillingLibrary 2.0 and more ...
さて、今日の本題 アプリ開発で問題になること アプリがアップデートされない(してくれない)
アップデートされない理由 ・アップデートに気づかないタイプ ・アップデートしたくないタイプ
みんなどうしていた? ・WebAPI機能を実装している場合 起動時にWebAPI側でVersionCodeチェック Versionが違う場合は、ダイアログを表示してPlayStore に飛ばす
私の場合 さくらのVPSにVerCode書いたファイルを置く 起動時にokhttpなどでVersionCodeチェック Versionが違う場合は、notificationを表示する
いらぬパーミッション <uses-permission android:name="android.permission.INTERNET" />
複数トラックの管理どうする? トラックごとにVerCodeをもたせて ロールアウトするごとに管理ファイルを更新するの?
段階的リリースどうする? ・段階リリースは同じトラック内にはいるが インストールユーザーのパーセンテージを指定して 徐々にユーザーにアップデートを提供する機能
ちょっと脱線 クローズドトラック使ってる人いますか?
結論 いや。もうWebAPIでは開発者がバージョン管理をするのは無 理がある
アップデートされない理由 ・アップデートに気づかないタイプ ・アップデートしたくないタイプ 少しでも改善したい
救世主登場 In-App Update API!!
In-App Updateとは? Play Core Libraryに含まれるAPI アプリケーションのアップデートを促すことができる INTERNET Permissionは不要 複数トラックも見分けすることが可能 段階的リリースにも対応
実装見ていきましょう implementation "com.google.android.play:core:1.6.1" build.gradle
初期化 lateinit var appUpdateManager: AppUpdateManager override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) appUpdateManager = AppUpdateManagerFactory.create(this) } MainActivity.kt
リスナーの追加 lateinit var appUpdateManager: AppUpdateManager override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) appUpdateManager = AppUpdateManagerFactory.create(this) appUpdateManager.appUpdateInfo.addOnSuccessListener { task -> when (task.updateAvailability()) { } } } MainActivity.kt Why Service Bind ??
ServiceBind 2019-07-05 00:12:20.248 10696-10696/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] AppUpdateService
: requestUpdateInfo(org.gdgkobe.sample.inappupdate) 2019-07-05 00:12:20.306 10696-10696/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] AppUpdateListenerRegistry : registerListener 2019-07-05 00:12:20.408 10696-10730/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] AppUpdateService : Initiate binding to the service. 2019-07-05 00:12:21.126 10696-10696/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] AppUpdateService : ServiceConnectionImpl.onServiceConnected(ComponentInfo{com.android.vending/com.google.android.finsky.installservice.DevTriggeredU pdateService}) 2019-07-05 00:12:21.135 10696-10730/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] AppUpdateService : linkToDeath 2019-07-05 00:12:21.255 10696-10718/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] OnRequestInstallCallback : onRequestInfo 2019-07-05 00:12:21.257 10696-10730/org.gdgkobe.sample.inappupdate I/PlayCore: UID: [10081] PID: [10696] AppUpdateService : Unbind from service. bind PlayStoreアプリのサービスへ バインドをしている
InternetPermissionがいらない仕組み ではなく これだからです
戻り値で更新があるかどうか判断 lateinit var appUpdateManager: AppUpdateManager override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) appUpdateManager = AppUpdateManagerFactory.create(this) appUpdateManager.appUpdateInfo.addOnSuccessListener { task -> when (task.updateAvailability()) { } } } MainActivity.kt UpdateAvailability.UNKNOWN UpdateAvailability.UPDATE_AVAILABLE UpdateAvailability.UPDATE_NOT_AVAILABLE UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
イベントフロー 初期化 Listener登録 Bind、状態返却 更新するUIを表示 Available Immediate Flexible
パターンが2つ Immediate Flexible
Immediate(即時更新) 更新ボタンを押すとダウンロードが開始 ダウンロード後自動インストール インストール後、アプリは自動再起動 ダウンロード時はアプリが利用できない
flexible 更新ボタンを押すとダウンロードが開始 ダウンロード後は開発者がインストールを促すアクションを提示 インストール後、アプリは自動再起動 ダウンロード時にアプリを利用することが可能
更新するUIを提示するコード appUpdateManager.appUpdateInfo.addOnSuccessListener { task -> when (task.updateAvailability()) { UpdateAvailability.UPDATE_AVAILABLE ->
{ appUpdateManager.startUpdateFlowForResult( task, AppUpdateType.IMMEDIATE, // or flexible this, ImmediateActivity.UPDATE_CHECK_REQUEST_CODE ) }
Immediateの場合 特に終了を意識する必要はないので、 Flowを開始すればいい。 ただし、Foregroundで処理が実行される関係上、アプリが裏側に行ってしまう場合は、 Resume処理が必要 override fun onResume() { super.onResume()
appUpdateManager.appUpdateInfo.addOnSuccessListener { task -> when (task.updateAvailability()) { UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS -> { appUpdateManager.startUpdateFlowForResult( task, AppUpdateType.IMMEDIATE, this, ImmediateActivity.UPDATE_CHECK_REQUEST_CODE ) } } } }
flexibleの場合 Flow開始後にダウンロード完了検知が必要 private fun registerInstallStateListener() { installStateUpdateListener = InstallStateUpdatedListener {
Log.d("resumeState", "State " + it) if (it.installStatus() == InstallStatus.DOWNLOADED) { showUpdateMessage() //Snackbarを表示 appUpdateManager.unregisterListener(installStateUpdateListener) installStateUpdateListener = null } } appUpdateManager.registerListener(installStateUpdateListener) } private fun showUpdateMessage() { Snackbar.make(findViewById(R.id.root_layout), "Update Available", Snackbar.LENGTH_LONG) .setAction("Update") { _ -> appUpdateManager.completeUpdate() }.show() }
Tips
Tips Immediateは強制インストールではない ☓ボタンで閉じることが出来る バックキーも有効 override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if(requestCode == UPDATE_CHECK_REQUEST_CODE && resultCode == Activity.RESULT_CANCELED){ Toast.makeText(this,"更新してくれ〜",Toast.LENGTH_LONG).show() } } onActivityResultで検知して、対応をしましょう
Tips Updateするパッケージごとに Immediate,Flexibleを分けたい appUpdateManager.appUpdateInfo.addOnSuccessListener { task -> when (task.updateAvailability()) {
UpdateAvailability.UPDATE_AVAILABLE -> { val versionCode = task.availableVersionCode() { "state": [ { "vname": "1.0.1", "vcode": 2, "type": "Flexible" }, { "vname": "1.0.2", "vcode": 3, "type": "Immediate" }, { "vname": "1.0.3", "vcode": 4, "type": "Cancel" } ] }
Tips APIを初期化した際のリクエストでは外部へストアアプリが見に行ってくれるわけではない NotAvailable ストアアプリを開いて、アプリの更新を押す Available!
Tips In-App Updateでは、Playストアアプリでアプリが公開されており一度でもインストールされていないといけ ない
Tips 署名キーはAPIに影響しない パッケージ名が違う場合は動作しない
まとめ In-App UpdateはPlay Core Libraryに含まれるAPI PlayStoreは必要 INTERNET Permissionは不要 複数トラックも見分けすることが可能 段階的リリースにも対応
アプリケーションのアップデートを簡易的に促すことができる 公開しないと使えないので公開は必須
Play Billing Library 2.0 Google Play Billing Library 1.0 リリース(2017-09-19)
Google Play Billing Library 1.1 リリース(2018-05-07) Google Play Billing Library 1.2 リリース(2018-10-18) Google Play Billing Library 1.2.1 リリース(2019-03-04) Google Play Billing Library 2.0 リリース(2019-05-07)
Play Billing Library 1.x系実装 //billing implementation "com.android.billingclient:billing:${billing_version}"
初期化とConnectionの開始 class MainActivity : AppCompatActivity(), PurchasesUpdatedListener { lateinit private var
billingClient: BillingClient ... billingClient = BillingClient.newBuilder(context).setListener(this).build() billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(@BillingClient.BillingResponse response: Int) { when (response) { OK -> { } else -> { } } } override fun onBillingServiceDisconnected() { } })
購入 val params = BillingFlowParams.newBuilder() .setType(BillingClient.SkuType.INAPP) .setSku(AppParameter.CATALOG[0]) .build() val responseCode
= activity.billingClient.launchBillingFlow(activity, params)
流れ(1.x) 初期化 Connection開始 購入Flow開始 アイテム所持
Play Billing Library 2.x系実装 //billing implementation "com.android.billingclient:billing:${billing_version}"
初期化とConnectionの開始 class MainActivity : AppCompatActivity(), PurchasesUpdatedListener { lateinit private var
billingClient: BillingClient ... billingClient = BillingClient.newBuilder(context).enablePendingPurchases().setListener(this).build() billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { when (billingResult.responseCode) { OK -> { } else -> { } } } override fun onBillingServiceDisconnected() { } })
購入 fun startPurchase() { val params = SkuDetailsParams.newBuilder() val skuList
= arrayListOf<String>().toMutableList().apply { add(“item001”) } params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP) //一度、アイテムの詳細情報を取得する activity.billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList -> if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetailsList.first()) .build() val responseCode = activity.billingClient.launchBillingFlow(activity, flowParams) } } }
とても大事なこと 承認すること override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: MutableList<Purchase>?) { when
(billingResult?.responseCode) { OK -> { if (purchases != null) { queryPurchase() } else {} } else -> { } } } fun queryPurchase() { //購入情報を取得 val result = activity.billingClient.queryPurchases(BillingClient.SkuType.INAPP) result?.purchasesList?.firstOrNull()?.run { if(purchaseState == Purchase.PurchaseState.PURCHASED){ //承認フラグが承認されていない場合 if (!isAcknowledged) { val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build() activity.billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener) } } } }
流れ(2.x) 初期化 Connection開始 購入Flow開始 アイテム所持 承認!!
承認は必要です
テストを怠るとこうなる もう一回買ってもらえばええやん? クレジットは払い戻せない
どう対応したか PlayConsoleからプロモーションコードを発行し無料で提供
まとめ In-App Billing 2.x系の対応は、承認処理が必要 それ以外は大きく変わっていないのでMigrationは比較的ラクです テストはやろう。絶対
ご清聴ありがとうございました GDG神戸のDoorKeeperにもぜひご登録ください https://gdgkobe.doorkeeper.jp/