Unity接入GooglePlay内购V4 V5 V6(源生Android方式)

2023-10-25 05:30

本文主要是介绍Unity接入GooglePlay内购V4 V5 V6(源生Android方式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

20230825更新

因为谷歌新政策要求,目前需要支持V5 V6,更新也很简单,将下面的改成5.0或6.0的aar即可

implementation("com.android.billingclient:billing:4.0.0")

改成

implementation("com.android.billingclient:billing:5.0.0")

implementation("com.android.billingclient:billing:6.0.0")

代码都是兼容的,亲测有效

-------------------------------------------------------------------

Unity接GooglePlay In-App Billing坑还是蛮多的,各种坑。

接的方式目前来看有三种:

  1. 采用Unity IAP插件,开启Unity的IAP Service
  2. 采用Android源生接入,在Android Studio接入,然后打包出jar或aar放到Unity项目,使用Unity调用
  3. 打包出Android工程,在Android工程中接入

这次介绍的是第二种,使用安卓源生方式接入,因为该方式一劳永逸,新项目可以很快就完成接入。

为什么不用第一种呢?直接导入IAP插件,然后设置参数,就可以很快实现了,该方式可以直接参考google文档。

第一种IAP插件的缺点:

虽然该方式只需要导入插件,然后进行一些参数的设置,但是此方式特别麻烦的一点是需要在Unity中开启Service,但是开启后又得填一大堆信息,巨麻烦无比,而且Unity的网络简直不能看,要么是打不开,要么是卡半死,而且网站老变,以及没有完整的文档。这些都是很恶心人的事情,甚至还要创建组织啥的,反正谁用谁恶心。

----------------------------------分割线------------------------------------------------------------------------

正式接入

接入之前需要的储备知识是:Unity如何与Android交互

准备jar和aar

我们的目标是导出自己封装的jar(里面封装了接口供Unity调用,也就是桥接层),以及找到谷歌官方提供的Billing V4插件(aar)。

因为从2021.8.2起,谷歌要求必须接入V3版以上的插件。所以我们这次干脆接了最新的V4.

接入文档可以参考指南:从 AIDL 迁移到 Google Play 结算库的迁移指南

获取aar文件

  • 打开Android Studio,创建一个工程
  • 创建一个Module,创建完如下(Module要选择Library)
  • 打开build.gradle,修改导出为Library,在dependencies中添加依赖,并同步。
    implementation("com.android.billingclient:billing:4.0.0")
    注意:plugins这边要改为'com.android.library',导出才会是aar,否则是apk.
    
    plugins {id 'com.android.library'
    }android {compileSdkVersion 30defaultConfig {//applicationId "com.egogame.iapforgoogleplay"minSdkVersion 19targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
    }dependencies {implementation 'androidx.appcompat:appcompat:1.3.0'implementation 'com.google.android.material:material:1.3.0'implementation 'org.jetbrains:annotations:15.0'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'implementation("com.android.billingclient:billing:4.0.0")
    }
  • 同步完成后,在gradle缓存路径下找到谷歌官方提供的.aar文件,复制到Unity工程Assets/Plugins/Android目录下,待用
    文件路径:C:\Users\xxx\.gradle\caches\modules-2\files-2.1\com.android.billingclient\billing\4.0.0\31aa58e2d4286f6b96480764e7a84d5de9935f02

打包jar 

  • 创建BaseMainActivity.java类,该类创建了一些供Unity调用的接口格式。
    当前类还未开始接入GP插件接口。
    package com.egogame;import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Looper;
    import android.widget.Toast;public class BaseMainActivity{public static String UNITY_GO_NAME="IAP";public static final String LOG_TAG = "EgoGameLog";protected Handler uiHandler = new Handler(Looper.getMainLooper());//unity项目启动时的上下文private Activity unityActivity;private Context context;public void Init(final String goName,final String googlePlayPublicKey){PrintLog("Init:"+goName+"===="+googlePlayPublicKey);uiHandler.post(new Runnable() {@Overridepublic void run() {UNITY_GO_NAME=goName;OnInitHandle(googlePlayPublicKey);}});}public void PrintLog(final String message,final Boolean toast){android.util.Log.d(LOG_TAG, message);uiHandler.post(new Runnable() {@Overridepublic void run() {if(toast) Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();}});}public boolean IsIAPSupported(){return false;}final public void RequstProduct(final String idsJson){uiHandler.post(new Runnable() {@Overridepublic void run() {String[] realProducts=null;try {JSONObject jObject=new JSONObject(idsJson);JSONArray jArray=jObject.getJSONArray("productIds");realProducts=new String[jArray.length()];for (int i = 0; i < jArray.length(); i++) {realProducts[i]=jArray.getString(i);}} catch (Exception e) {PrintLog("RequstProduct数据传输错误:"+e.getMessage());}if(realProducts!=null){OnRequstProduct(realProducts);}else{RequestProductsFail("数据解析错误:"+idsJson);}}});}final public void BuyProduct(final String productJson){uiHandler.post(new Runnable() {@Overridepublic void run() {try {JSONObject jObject=new JSONObject(productJson);String productId=jObject.getString("productId");boolean isConsumable=jObject.getBoolean("isConsumable");OnBuyProduct(productId,isConsumable);} catch (Exception e) {PrintLog("BuyProduct数据传输错误:"+e.getMessage());}}});}protected void OnInitHandle(String googlePlayPublicKey){}protected void OnRequstProduct(String[] productId){}protected void OnBuyProduct(String productId,boolean isConsumable){}protected void BuyComplete(String productId){PrintLog("购买成功:"+productId);SendUnityMessage("ProductBuyComplete", productId);}protected void BuyCancle(String productId){PrintLog("购买取消:"+productId);SendUnityMessage("ProductBuyCancled", productId);}protected void BuyFail(String productId,String error){PrintLog("购买失败:"+productId+"原因:"+error);try {JSONObject jObject=new JSONObject();jObject.put("productId", productId);jObject.put("error", error);SendUnityMessage("ProductBuyFailed", jObject.toString());} catch (JSONException e) {PrintLog("BuyFail数据错误:"+e.getMessage());}}protected void RequestProductsFail(String message){SendUnityMessage("ProductRequestFail", message);}protected void RecieveProductInfo(ArrayList<SkuItem> skuItems,ArrayList<String> invalidProductIds){JSONObject jsonObject=new JSONObject();try {JSONArray skuArray=new JSONArray();JSONObject tmpObj = null;for (int i = 0; i < skuItems.size(); i++) {SkuItem skuItem=skuItems.get(i);tmpObj = new JSONObject();tmpObj.put("productId" , skuItem.productId);tmpObj.put("title" , skuItem.title);tmpObj.put("desc" , skuItem.desc);tmpObj.put("price" , skuItem.price);tmpObj.put("formatPrice" , skuItem.formatPrice);tmpObj.put("priceCurrencyCode" , skuItem.priceCurrencyCode);tmpObj.put("skuType" , skuItem.skuType);skuArray.put(tmpObj);}JSONArray invalidArray=new JSONArray();for (int i = 0; i < invalidProductIds.size(); i++) {invalidArray.put(invalidProductIds.get(i));}jsonObject.put("skuItems", skuArray);jsonObject.put("invalidIds", invalidArray);} catch (JSONException e) {PrintLog("Json数据错误:"+e.getMessage());}String info=jsonObject.toString();PrintLog("当前产品信息:"+info);SendUnityMessage("RecieveProductInfos", info);}public void PrintLog(String message){PrintLog(message,false);}public void SendUnityMessage(String func,String value){CallUnity(UNITY_GO_NAME, func, value);}public Activity CurrentActivity(){return getActivity();}/*** Android调用Unity的方法* @param gameObjectName    调用的GameObject的名称* @param functionName      方法名* @param args              参数* @return                  调用是否成功*/boolean CallUnity(String gameObjectName, String functionName, String args){try {Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");Method method =classtype.getMethod("UnitySendMessage", String.class,String.class,String.class);method.invoke(classtype,gameObjectName,functionName,args);return true;} catch (ClassNotFoundException e) {System.out.println(e.getMessage());} catch (NoSuchMethodException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());} catch (InvocationTargetException e) {}return false;}/*** 利用反射机制获取unity项目的上下文* @return*/Activity getActivity(){if(null == unityActivity) {try {Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");Activity activity = (Activity) classtype.getDeclaredField("currentActivity").get(classtype);unityActivity = activity;context = activity;} catch (ClassNotFoundException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());} catch (NoSuchFieldException e) {System.out.println(e.getMessage());}}return unityActivity;}
    }
    package com.egogame;public class SkuItem {public String productId;public String title;public String desc;public String price;public String formatPrice;//格式化价格,包括其货币符号public String priceCurrencyCode;//货币代码public String skuType;//内购还是订阅
    }
    
  • 创建MainActivity类,开始接入billing插件(该命名为Activity其实没有继承Activity,因为继承Activity后,需要Unity那边AndroidManfiest文件指定为Activity才能生效,所以这里不采用继承Activity)
    package com.egogame;import android.os.Bundle;
    import android.os.Handler;
    import android.os.Looper;
    import android.text.TextUtils;import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;import com.android.billingclient.api.BillingClient;
    import com.android.billingclient.api.BillingClientStateListener;
    import com.android.billingclient.api.BillingFlowParams;
    import com.android.billingclient.api.BillingResult;
    import com.android.billingclient.api.ConsumeParams;
    import com.android.billingclient.api.Purchase;
    import com.android.billingclient.api.PurchasesUpdatedListener;
    import com.android.billingclient.api.SkuDetails;
    import com.android.billingclient.api.SkuDetailsParams;
    import com.android.billingclient.api.SkuDetailsResponseListener;import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;public class MainActivity extends BaseMainActivity implements PurchasesUpdatedListener,BillingClientStateListener {private static final long RECONNECT_TIMER_START_MILLISECONDS = 1L * 1000L;private static final long RECONNECT_TIMER_MAX_TIME_MILLISECONDS = 1000L * 60L * 15L; // 15 minsprivate static final Handler handler = new Handler(Looper.getMainLooper());private BillingClient billingClient;private String[] cacheRequestList;private Map<String, SkuDetails> skuDetailsLiveDataMap=new HashMap<>();private boolean isConsumable;private String buyProductId;private boolean billingSetupComplete = false;// how long before the data source tries to reconnect to Google playprivate long reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS;@Overrideprotected void OnInitHandle(String googlePlayPublicKey) {super.OnInitHandle(googlePlayPublicKey);if (googlePlayPublicKey.contains("CONSTRUCT_YOUR")) {throw new RuntimeException("Please put your app's public key in MainActivity.java. See README.");}billingClient = BillingClient.newBuilder(CurrentActivity()).setListener(this).enablePendingPurchases().build();billingClient.startConnection(this);}private void retryBillingServiceConnectionWithExponentialBackoff() {handler.postDelayed(() ->billingClient.startConnection(this),reconnectMilliseconds);reconnectMilliseconds = Math.min(reconnectMilliseconds * 2,RECONNECT_TIMER_MAX_TIME_MILLISECONDS);}@Overrideprotected void OnRequstProduct(String[] productId) {super.OnRequstProduct(productId);List<String> skuList = new ArrayList<>();skuList.addAll(Arrays.asList(productId));cacheRequestList=productId;SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);billingClient.querySkuDetailsAsync(params.build(),new SkuDetailsResponseListener() {@Overridepublic void onSkuDetailsResponse(BillingResult billingResult,List<SkuDetails> skuDetailsList) {int responseCode = billingResult.getResponseCode();PrintLog("onSkuDetailsResponse:"+billingResult+" code:"+GetResponseText(responseCode));switch (responseCode){case BillingClient.BillingResponseCode.OK:RecieveProducts(skuDetailsList);break;default:RequestProductsFail("Failed to query inventory: " + billingResult.getDebugMessage());PrintLog("Failed to query inventory: "+billingResult.getDebugMessage());break;}}});}private String GetResponseText(int responseCode){switch (responseCode){case BillingClient.BillingResponseCode.OK:return "OK";case BillingClient.BillingResponseCode.SERVICE_TIMEOUT:return "SERVICE_TIMEOUT";case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED:return "FEATURE_NOT_SUPPORTED";case BillingClient.BillingResponseCode.USER_CANCELED:return "USER_CANCELED";case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED:return "SERVICE_DISCONNECTED";case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE:return "SERVICE_UNAVAILABLE";case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE:return "BILLING_UNAVAILABLE";case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE:return "ITEM_UNAVAILABLE";case BillingClient.BillingResponseCode.DEVELOPER_ERROR:return "DEVELOPER_ERROR";case BillingClient.BillingResponseCode.ERROR:return "ERROR";case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:return "ITEM_ALREADY_OWNED";case BillingClient.BillingResponseCode.ITEM_NOT_OWNED:return "ITEM_NOT_OWNED";default:return "UnKnown";}}private void RecieveProducts(List<SkuDetails> skuDetailsList){ArrayList<SkuItem> skuItems=new ArrayList<SkuItem>();ArrayList<String> invaildIds=new ArrayList<String>();PrintLog("cacheRequestList:"+cacheRequestList);int length=cacheRequestList.length;if(cacheRequestList!=null && length>0){for(int i=0;i<length;i++){String productId=cacheRequestList[i];if(!TextUtils.isEmpty(productId)){SkuDetails detail=null;for (SkuDetails skuDetails : skuDetailsList) {if(skuDetails.getSku().equals(productId)){detail=skuDetails;break;}}if(detail==null){PrintLog("未找到该产品信息:"+productId);invaildIds.add(productId);continue;}skuDetailsLiveDataMap.put(productId,detail);String price=detail.getPrice();String formatPrice=price;SkuItem skuItem=new SkuItem();skuItem.productId=productId;skuItem.title=detail.getTitle();skuItem.desc=detail.getDescription();skuItem.price=price;skuItem.formatPrice=formatPrice;skuItem.priceCurrencyCode=detail.getPriceCurrencyCode();skuItem.skuType=detail.getType();skuItems.add(skuItem);}}}RecieveProductInfo(skuItems,invaildIds);}@Overridepublic boolean IsIAPSupported() {return true;}@Overrideprotected void OnBuyProduct(String productId, boolean isConsumable) {super.OnBuyProduct(productId, isConsumable);SkuDetails skuDetails=skuDetailsLiveDataMap.get(productId);if(null!=skuDetails){buyProductId=productId;this.isConsumable=isConsumable;BillingFlowParams purchaseParams =BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();billingClient.launchBillingFlow(CurrentActivity(), purchaseParams);}else{BuyFail(productId,"Can not find SkuDetails:"+productId);PrintLog("未请求商品数据,请先请求:"+productId);}}@Overridepublic void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {int responseCode = billingResult.getResponseCode();PrintLog("BillingResult [" + GetResponseText(responseCode) + "]: "+ billingResult.getDebugMessage());switch (responseCode) {case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:case BillingClient.BillingResponseCode.OK:FlowFinish(true,null,list);break;case BillingClient.BillingResponseCode.USER_CANCELED:String productId=buyProductId;buyProductId=null;BuyCancle(productId);break;default:FlowFinish(false,billingResult.getDebugMessage(),list);break;}}private void FlowFinish(Boolean isSuccess,String message,List<Purchase> purchases){if(isSuccess){if(buyProductId!=null){String productId=buyProductId;buyProductId=null;String purchaseToken=null;for (Purchase purchase : purchases) {for (String skus : purchase.getSkus()) {//需要校验付款状态if(skus.contains(productId) &&purchase.getPurchaseState()==Purchase.PurchaseState.PURCHASED){purchaseToken=purchase.getPurchaseToken();break;}if(purchaseToken!=null) break;}}if(isConsumable){if(purchaseToken==null){CallBackBuyFail("unknown purchaseToken:"+productId);}else {ConsumeParams consumeParams =ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build();billingClient.consumeAsync(consumeParams,(billingResult, token) -> {if(billingResult.getResponseCode()==BillingClient.BillingResponseCode.OK){BuyComplete(productId);}else{CallBackBuyFail(billingResult.getDebugMessage());}});}}else{BuyComplete(productId);}}}else{if(buyProductId!=null){CallBackBuyFail(message);}}}private void CallBackBuyFail(String message){String productId=buyProductId;buyProductId=null;BuyFail(productId, message);PrintLog("Error purchasing: " + message);}@Overridepublic void onBillingServiceDisconnected() {PrintLog("onBillingServiceDisconnected");billingSetupComplete = false;retryBillingServiceConnectionWithExponentialBackoff();}@Overridepublic void onBillingSetupFinished(@NonNull BillingResult billingResult) {int responseCode = billingResult.getResponseCode();String debugMessage = billingResult.getDebugMessage();PrintLog("onBillingSetupFinished: " + debugMessage+"("+ GetResponseText(responseCode)+")",true);switch (responseCode) {case BillingClient.BillingResponseCode.OK:// The billing client is ready. You can query purchases here.// This doesn't mean that your app is set up correctly in the console -- it just// means that you have a connection to the Billing service.reconnectMilliseconds = RECONNECT_TIMER_START_MILLISECONDS;billingSetupComplete = true;break;case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE:case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE:PrintLog("Billing Service Unavailable:"+debugMessage,true);break;default:retryBillingServiceConnectionWithExponentialBackoff();break;}}
    }
    
  • 选择Module,并且点击Build Module,将会导出aar文件
  • 我们将aar改为rar格式,然后用压缩软件打开,取出里面的classes.jar,我们只需要该jar即可,该jar是我们封装好的接口,后续有用,为了方便认,我将名字改为IAPForGooglePlay.jar
  • 将jar放置到Unity工程下,Assets/Plugins/Android目录下,该目录下文件如下:
    注意:jar要放在Android目录下,而不是Android/libs目录下,经试验放libs下会识别不到
  • Android这边的工作就结束了,后面我们来写Unity这边的代码

 编写Unity接口

  • 创建类GooglePlay_IAPBridge,编写调用Android我们封装好的jar里的接口的桥接类
    #if UNITY_ANDROID
    using System;
    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using Newtonsoft.Json;public class GooglePlay_IAPBridge{class BuyProductData{public string productId;public bool isConsumable;}private AndroidJavaObject javaObject;private GooglePlay_IAPBridge() {if (Application.platform != RuntimePlatform.Android)return;javaObject = new AndroidJavaObject("com.egogame.MainActivity");}private volatile static GooglePlay_IAPBridge _instance = null;private static readonly object lockHelper = new object();public static GooglePlay_IAPBridge getInstance(){if(_instance == null){lock(lockHelper){if(_instance == null)_instance = new GooglePlay_IAPBridge();}}return _instance;}public void Init(string goName,string publicKey){Debug.Log ("[GooglePlay_IAPBridge]Init:" + goName + "=====" + publicKey);if (Application.platform != RuntimePlatform.Android)return;javaObject.Call ("Init", goName, publicKey);}public bool IsIAPSupported(){if (Application.platform != RuntimePlatform.Android)return false;return javaObject.Call<bool> ("IsIAPSupported");}public void RequestProducts(string jsonData){Debug.Log ("[GooglePlay_IAPBridge]RequstProduct:" + jsonData);if (Application.platform != RuntimePlatform.Android)return;javaObject.Call ("RequstProduct", jsonData);}public void BuyProduct(string productId,bool isConsumable){BuyProductData buyProductData=new BuyProductData();buyProductData.productId = productId;buyProductData.isConsumable = isConsumable;string jsonData = JsonConvert.SerializeObject(buyProductData);Debug.Log ("[GooglePlay_IAPBridge]BuyProduct:" + jsonData);if (Application.platform != RuntimePlatform.Android)return;javaObject.Call ("BuyProduct", jsonData);}
    }
    #endif
    
    这里面我们直接 javaObject = new AndroidJavaObject("com.egogame.MainActivity");实例化我们jar封装好的类,即可直接调用public方法。请注意:因为Android和Unity线程不一样,所以jar处理时需要规避线程的同步性。
  • 再封装一个IAPBridge类,用来分流不同渠道,转接不同接口文件
    using UnityEngine;
    using System.Collections.Generic;
    using Newtonsoft.Json;public class IAPBridge{class RequestSkuData{public string[] productIds;}public static void InitIAp(string goName,string publicKey=""){Debug.Log("[IAPBridge]InitIAp:" + goName + "==" + publicKey);
    #if UNITY_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){iOS_IAPBridge.InitIAPManager(goName);}
    #elif UNITY_ANDROIDif(Application.platform==RuntimePlatform.Android){GooglePlay_IAPBridge.getInstance().Init(goName,publicKey);}
    #endif}public static bool IAPEnabeld(){
    #if UNITY_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){return iOS_IAPBridge.IsProductAvailable();}
    #elif UNITY_ANDROIDif(Application.platform==RuntimePlatform.Android){return GooglePlay_IAPBridge.getInstance().IsIAPSupported();}
    #endifreturn false;}public static void RequstProducts(List<string> productIds){RequestSkuData data=new RequestSkuData();data.productIds = productIds.ToArray();string jsonData = JsonConvert.SerializeObject(data);Debug.Log("[IAPBridge]RequstProducts:"+jsonData);
    #if UNITY_IPHONEiOS_IAPBridge.RequstProductInfo(jsonData);
    #elif UNITY_ANDROIDif (Application.platform == RuntimePlatform.Android){GooglePlay_IAPBridge.getInstance().RequestProducts(jsonData);}
    #endif}public static void SendBuyProduct(string productId,bool isConsumable){Debug.Log(string.Format("[IAPBridge]SendBuyProduct:{0} isConsumable:{1}",productId,isConsumable));
    #if UNITY_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){iOS_IAPBridge.BuyProduct(productId);}
    #elif UNITY_ANDROIDif (Application.platform == RuntimePlatform.Android){GooglePlay_IAPBridge.getInstance().BuyProduct(productId, isConsumable);}
    #endif}public static void RestoreProduct(){Debug.Log("[IAPBridge]Restore!");
    #if UNITY_IPHONEif(Application.platform==RuntimePlatform.IPhonePlayer){iOS_IAPBridge.Restore();}
    #endif}
    }
    

  • 然后Unity这边要接收下Android那边传过来的接口,将这个类挂载到某个GameObject下,如GameObject名为IAPObject,则上面初始化时调用IAPBridge.InitIAP将该gameObject名作为goName参数传过去即可。
    using Newtonsoft.Json;
    using UnityEngine;
    #pragma warning disable 0649/// <summary>
    /// 该类主要用于接收iOS和Android回调,做一个桥接用途
    /// </summary>
    public class IAPMessage : MonoBehaviour {class BuyFailData{public string productId;public string error;}#region callback from Objective-c/JAR//获取到产品列表回调void RecieveProductInfos(string jsonData){if(string.IsNullOrEmpty(jsonData)) return;var infoData = JsonConvert.DeserializeObject<IAPProductInfoData>(jsonData);OnProductInfoReceived (infoData);}//产品列表请求失败void ProductRequestFail(string message){OnProductInfoFail(message);}//购买成功回调void ProductBuyComplete(string productId){OnProductBuyComplete(productId);}//购买失败回调void ProductBuyFailed(string jsonData){var infoData = JsonConvert.DeserializeObject<BuyFailData>(jsonData);OnBuyProductFail (infoData.productId, infoData.error);}//获取商品回执回调void ProvideContent(string msg){}//购买取消回调void ProductBuyCancled(string productId){OnBuyProductCancled(productId);}/// <summary>/// 恢复购买成功/// </summary>/// <param name="productId"></param>void RestoreComplete(string productId){OnRestoreCompleted (productId);}#endregion//接收到产品信息void OnProductInfoReceived(IAPProductInfoData info){Debug.Log("[IAPMessage]Unity接收到商品信息:" + info.ToString());IAPManager.internal_CallBySDK_ProductInfosReceive(info);}//接收到产品信息void OnProductInfoFail(string error){Debug.Log("[IAPMessage]Unity商品信息请求失败:" + error);IAPManager.internal_RequestProductInfoFail(error);}//购买完成void OnProductBuyComplete(string productId){Debug.Log ("[IAPMessage]购买完成" + productId);IAPManager.internal_CallBySDK_BuyComplete(productId);}//购买失败void OnBuyProductFail(string productId,string error){Debug.Log(string.Format("[IAPMessage]购买失败:{0} 错误信息{1}", productId, error));IAPManager.internal_CallBySDK_BuyFail(productId, error);}//购买取消void OnBuyProductCancled(string productId){Debug.Log ("[IAPMessage]购买取消" + productId);IAPManager.internal_CallBySDK_BuyCanceled(productId);}//恢复完成void OnRestoreCompleted(string productId){Debug.Log ("[IAPMessage]恢复购买完成:"+productId);IAPManager.internal_CallBySDK_RestoreCompleted(productId);}
    }#pragma warning restore 0649
  • using System.Collections.Generic;public class IAPProductInfoData
    {public List<IAPSkuItem> skuItems;//请求到的产品列表public string[] invalidIds;//无效产品id
    }public struct IAPSkuItem{public string productId;//后台产品idpublic string title;//后台标题public string desc;//后台描述public string price;//格式化价格,显示请用formatPrircepublic string formatPrice;//格式化价格,包括其货币符号public string priceCurrencyCode;//货币代码public string skuType;//内购还是订阅 subscription/inapppublic override string ToString (){return string.Format("[productId]:{0} [title]:{1} [desc]:{2} [price]:{3} [formatPrice]:{4} [priceCurrencyCode]:{5} [skuType:]{6}",productId, title, desc, price, formatPrice, priceCurrencyCode, skuType);}
    }public struct IAPProvideData
    {public string cfgId;public string title;public string desc;public string formatPrice;
    }

  • 到这里就接入完成了,调用对应接口即可实现IAP的接入。

 请注意:

*打包的apk不要对安卓代码进行混淆,否则代码会被裁剪导致调用不到java代码,如下

Minify和Custom Proguard File不要勾选

 如果接入有问题,可以加Q群进行提问

Android部分源码可以在这里下载到:https://download.csdn.net/download/egostudio/20463417 

这篇关于Unity接入GooglePlay内购V4 V5 V6(源生Android方式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/280320

相关文章

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

虚拟机与物理机的文件共享方式

《虚拟机与物理机的文件共享方式》文章介绍了如何在KaliLinux虚拟机中实现物理机文件夹的直接挂载,以便在虚拟机中方便地读取和使用物理机上的文件,通过设置和配置,可以实现临时挂载和永久挂载,并提供... 目录虚拟机与物理机的文件共享1 虚拟机设置2 验证Kali下分享文件夹功能是否启用3 创建挂载目录4

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Mybatis官方生成器的使用方式

《Mybatis官方生成器的使用方式》本文详细介绍了MyBatisGenerator(MBG)的使用方法,通过实际代码示例展示了如何配置Maven插件来自动化生成MyBatis项目所需的实体类、Map... 目录1. MyBATis Generator 简介2. MyBatis Generator 的功能3

Python数据处理之导入导出Excel数据方式

《Python数据处理之导入导出Excel数据方式》Python是Excel数据处理的绝佳工具,通过Pandas和Openpyxl等库可以实现数据的导入、导出和自动化处理,从基础的数据读取和清洗到复杂... 目录python导入导出Excel数据开启数据之旅:为什么Python是Excel数据处理的最佳拍档

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

MYSQL行列转置方式

《MYSQL行列转置方式》本文介绍了如何使用MySQL和Navicat进行列转行操作,首先,创建了一个名为`grade`的表,并插入多条数据,然后,通过修改查询SQL语句,使用`CASE`和`IF`函... 目录mysql行列转置开始列转行之前的准备下面开始步入正题总结MYSQL行列转置环境准备:mysq

Linux(Centos7)安装Mysql/Redis/MinIO方式

《Linux(Centos7)安装Mysql/Redis/MinIO方式》文章总结:介绍了如何安装MySQL和Redis,以及如何配置它们为开机自启,还详细讲解了如何安装MinIO,包括配置Syste... 目录安装mysql安装Redis安装MinIO总结安装Mysql安装Redis搜索Red