{"id":1235,"date":"2021-03-18T11:54:00","date_gmt":"2021-03-18T03:54:00","guid":{"rendered":"https:\/\/badgameshow.com\/fly\/?p=1235"},"modified":"2021-03-30T22:33:59","modified_gmt":"2021-03-30T14:33:59","slug":"android-%e5%88%a9%e7%94%a8in-app-billing-%e5%85%a7%e8%b3%bc-app%e5%85%a7%e4%bb%98%e6%ac%be","status":"publish","type":"post","link":"https:\/\/badgameshow.com\/fly\/android-%e5%88%a9%e7%94%a8in-app-billing-%e5%85%a7%e8%b3%bc-app%e5%85%a7%e4%bb%98%e6%ac%be\/","title":{"rendered":"Android \u5229\u7528In-App Billing \u5167\u8cfc APP\u5167\u4ed8\u6b3e"},"content":{"rendered":"<h1>Android \u5229\u7528In-App Billing \u5167\u8cfc APP\u5167\u4ed8\u6b3e<\/h1>\n<ol>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#a\">\u5148\u5c07\u6b0a\u9650\u8207Library\u5c0e\u5165Project<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#b\">\u5305\u4e00\u7248APK\u7d66\u5167\u90e8\u6e2c\u8a66\u5f8c,\u65b0\u589e\u5546\u54c1(\u7b49\u5f85\u5be9\u6838\u901a\u904e\u624d\u6709\u8cc7\u6599)<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#c\">\u5275\u5efa\u8cfc\u8cb7\u76e3\u807d\u8207\u5ba2\u6236\u76e3\u807d<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#d\">\u8b80\u53d6\u5546\u54c1\u8cc7\u6599<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#e\">\u4ed8\u6b3eUI<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#f\">\u7d00\u9304\u6240\u6709\u5546\u54c1<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#g\">\u8abf\u7528\u4ed8\u6b3e<\/a><\/li>\n<li><a href=\"https:\/\/badgameshow.com\/fly\/android-\u5229\u7528in-app-billing-\u5167\u8cfc-app\u5167\u4ed8\u6b3e\/fly\/android\/#h\">\u6548\u679c\u5c55\u793a<\/a><\/li>\n<\/ol>\n<hr \/>\n<p><a id=\"a\"><\/a><\/p>\n<h4>1.\u5148\u5c07\u6b0a\u9650\u8207Library\u5c0e\u5165Project<\/h4>\n<p>AndroidManifest.xml<\/p>\n<pre><code class=\"language-XML line-numbers\">&lt;uses-permission android:name=\"com.android.vending.BILLING\" \/&gt;\n<\/code><\/pre>\n<p>build.gradle(Module)<\/p>\n<pre><code class=\"language-Gradle line-numbers\">def billing_version = \"3.0.3\"\nimplementation \"com.android.billingclient:billing-ktx:$billing_version\"\n<\/code><\/pre>\n<p><a id=\"b\"><\/a><\/p>\n<h4>2.\u5305\u4e00\u7248APK\u7d66\u5167\u90e8\u6e2c\u8a66\u5f8c,\u65b0\u589e\u5546\u54c1(\u7b49\u5f85\u5be9\u6838\u901a\u904e\u624d\u6709\u8cc7\u6599)<\/h4>\n<h5>\u71df\u5229>\u7522\u54c1>\u61c9\u7528\u7a0b\u5f0f\u5167\u5546\u54c1<\/h5>\n<p><img decoding=\"async\" src=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2021\/03\/1616035746108.jpg\" width=\"40%\"><\/p>\n<h5>\u8a2d\u5b9a\u81ea\u5df1\u7684\u7522\u54c1(ID\u662f\u70ba\u4e86 \u6bd4\u5c0d\u7522\u54c1)<\/h5>\n<p><img decoding=\"async\" src=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2021\/03\/1616038935433.jpg\" width=\"100%\"><\/p>\n<p><a id=\"c\"><\/a><\/p>\n<h4>3.\u5275\u5efa\u8cfc\u8cb7\u76e3\u807d\u8207\u5ba2\u6236\u76e3\u807d<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">class BillingClientLifecycle private constructor(private val app: Application) : LifecycleObserver,\n    PurchasesUpdatedListener, BillingClientStateListener {\n\n    private lateinit var billingClient: BillingClient\n    var skuDetails: MutableLiveData&lt;Map&lt;String, SkuDetails&gt;&gt; = MutableLiveData()\n\n    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)\n    fun create() {\n        billingClient = BillingClient\n            .newBuilder(app.applicationContext)\n            .setListener(this)\n            .enablePendingPurchases()\n            .build()\n\n        if (!billingClient.isReady) {\n            billingClient.startConnection(this)\n        }\n    }\n\n    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)\n    fun destroy() {\n        if (billingClient.isReady) {\n            billingClient.endConnection()\n        }\n    }\n\n    companion object {\n        @Volatile\n        private var INSTANCE: BillingClientLifecycle? = null\n\n        fun getInstance(app: Application): BillingClientLifecycle =\n            INSTANCE ?: synchronized(this) {\n                INSTANCE ?: BillingClientLifecycle(app).also { INSTANCE = it }\n            }\n    }\n\n    \/\/\u8a2d\u7f6e\u5b8c\u6210\n    override fun onBillingSetupFinished(billingResult: BillingResult) {\n        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {\n            GlobalScope.launch(Dispatchers.IO) {\n                try {\n                    querySkuDetails()\n                } catch (e: Exception) {\n                    Toast.makeText(app.applicationContext, \"\u8b80\u53d6\u8cc7\u6599\u5931\u6557\", Toast.LENGTH_SHORT).show()\n                } finally {\n                    cancel()\n                }\n            }\n        }\n    }\n\n    \/\/\u95dc\u9589\u9023\u7dda\n    override fun onBillingServiceDisconnected() {\n        billingClient.startConnection(this)\n    }\n\n    \/\/\u8cfc\u8cb7\u5f8c\n    override fun onPurchasesUpdated(\n        billingResult: BillingResult,\n        purchase: MutableList&lt;Purchase&gt;?\n    ) {\n        when (billingResult.responseCode) {\n            BillingClient.BillingResponseCode.OK -&gt; {\n                purchase?.let {\n                    Toast.makeText(app.applicationContext, \"\u8cfc\u8cb7\u6210\u529f\", Toast.LENGTH_SHORT).show()\n                }\n            }\n\n            BillingClient.BillingResponseCode.USER_CANCELED -&gt; {\n                Toast.makeText(app.applicationContext, \"\u4f7f\u7528\u8005\u53d6\u6d88\", Toast.LENGTH_SHORT).show()\n            }\n\n            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -&gt; {\n                Toast.makeText(app.applicationContext, \"ITEM_ALREADY_OWNED\", Toast.LENGTH_SHORT)\n                    .show()\n            }\n\n            BillingClient.BillingResponseCode.DEVELOPER_ERROR -&gt; {\n                Toast.makeText(app.applicationContext, \"DEVELOPER_ERROR\", Toast.LENGTH_SHORT).show()\n            }\n        }\n    }\n}\n<\/code><\/pre>\n<p><a id=\"d\"><\/a><\/p>\n<h4>4.\u8b80\u53d6\u5546\u54c1\u8cc7\u6599<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">private suspend fun querySkuDetails() {\n    val params = SkuDetailsParams.newBuilder()\n        .setType(BillingClient.SkuType.INAPP)\n        .setSkusList(listOf(SKU.ProductOne.id, SKU.ProductThree.id))\n        .build()\n\n    val skuDetailsResult = withContext(Dispatchers.IO) {\n        billingClient.querySkuDetails(params)\n    }\n\n    when (skuDetailsResult.billingResult.responseCode) {\n        BillingClient.BillingResponseCode.OK -&gt; {\n            skuDetailsResult.skuDetailsList?.also {\n                skuDetails.postValue(\n                    HashMap&lt;String, SkuDetails&gt;().apply {\n                        it.forEach {\n                            put(it.sku, it)\n                        }\n                    }\n                )\n            } ?: also {\n                skuDetails.postValue(emptyMap())\n            }\n        }\n        BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,\n        BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,\n        BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,\n        BillingClient.BillingResponseCode.ITEM_UNAVAILABLE,\n        BillingClient.BillingResponseCode.DEVELOPER_ERROR,\n        BillingClient.BillingResponseCode.ERROR -&gt; {\n            Log.e(\"error\", \"\u932f\u8aa4\u8acb\u7a0d\u5f8c\u5728\u8a66\")\n        }\n        BillingClient.BillingResponseCode.USER_CANCELED,\n        BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,\n        BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED,\n        BillingClient.BillingResponseCode.ITEM_NOT_OWNED -&gt; {\n            Log.e(\"error\", \"\u932f\u8aa4\u8acb\u7a0d\u5f8c\u5728\u8a66\")\n        }\n    }\n}\n<\/code><\/pre>\n<p><a id=\"e\"><\/a><\/p>\n<h4>5.\u4ed8\u6b3eUI<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">fun launchBillingFlow(activity: Activity, params: BillingFlowParams): Int {\n    if (!billingClient.isReady) {\n        Toast.makeText(app.applicationContext, \"\u8acb\u7a0d\u5f8c\u5728\u8a66\", Toast.LENGTH_SHORT).show()\n    }\n    val billingResult = billingClient.launchBillingFlow(activity, params)\n    return billingResult.responseCode\n}\n<\/code><\/pre>\n<p><a id=\"f\"><\/a><\/p>\n<h4>6.\u7d00\u9304\u6240\u6709\u5546\u54c1<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">enum class SKU(val id: String) {\n    ProductOne(\"1\"),\n    ProductThree(\"3\"),\n    \/\/\u7528\u65bc\u6e2c\u8a66\u7528\n    AndroidTestPurchased(\"android.test.purchased\"),\n    AndroidTestCanceled(\"android.test.canceled\"),\n    AndroidTestRefunded(\"android.test.refunded\"),\n    AndroidTestItemUnavailable(\"android.test.item_unavailable\")\n}\n<\/code><\/pre>\n<p><a id=\"g\"><\/a><\/p>\n<h4>7.\u8abf\u7528\u4ed8\u6b3e<\/h4>\n<pre><code class=\"language-Kotlin line-numbers\">override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.activity_login)\n\n    \/\/\u7d81\u5b9aLifeCycle\n    lifecycle.addObserver(BillingClientLifecycle.getInstance(application))\n}\n\nfun grass(view: View) {\n    BillingClientLifecycle.getInstance(application).skuDetails.observe(this, Observer {\n        it.forEach { skuDetail -&gt;\n            if (skuDetail.key == SKU.ProductOne.id) {\n                BillingClientLifecycle.getInstance(application).launchBillingFlow(\n                    this,\n                    BillingFlowParams.newBuilder().setSkuDetails(skuDetail.value).build()\n                )\n            }\n        }\n    })\n}\n\nfun box(view: View) {\n    BillingClientLifecycle.getInstance(application).skuDetails.observe(this, Observer {\n        it.forEach { skuDetail -&gt;\n            if (skuDetail.key == SKU.ProductThree.id) {\n                BillingClientLifecycle.getInstance(application).launchBillingFlow(\n                    this,\n                    BillingFlowParams.newBuilder().setSkuDetails(skuDetail.value).build()\n                )\n            }\n        }\n    })\n}\n<\/code><\/pre>\n<p><a id=\"h\"><\/a><\/p>\n<h4>8.\u6548\u679c\u5c55\u793a<\/h4>\n<p><img decoding=\"async\" src=\"https:\/\/badgameshow.com\/fly\/wp-content\/uploads\/2021\/03\/20210318_115916.gif\" width=\"30%\"><\/p>\n\n<div style=\"font-size: 0px; height: 0px; line-height: 0px; margin: 0; padding: 0; clear: both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>Android \u5229\u7528In-App Billing \u5167\u8cfc APP\u5167\u4ed8\u6b3e \u5148\u5c07\u6b0a\u9650\u8207Library\u5c0e\u5165Projec &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"pgc_sgb_lightbox_settings":"","footnotes":""},"categories":[5],"tags":[13,176,15],"class_list":["post-1235","post","type-post","status-publish","format-standard","hentry","category-android","tag-android","tag-in-app-billing","tag-kotlin"],"_links":{"self":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1235","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/comments?post=1235"}],"version-history":[{"count":6,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1235\/revisions"}],"predecessor-version":[{"id":1249,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/posts\/1235\/revisions\/1249"}],"wp:attachment":[{"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/media?parent=1235"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/categories?post=1235"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badgameshow.com\/fly\/wp-json\/wp\/v2\/tags?post=1235"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}