Unity接入GooglePlay内购V4(源生Android方式)
Unity接GooglePlay In-App Billing坑還是蠻多的,各種坑。
接的方式目前來看有三種:
這次介紹的是第二種,使用安卓源生方式接入,因為該方式一勞永逸,新項目可以很快就完成接入。
為什么不用第一種呢?直接導入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那邊傳過來的接口 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的接入。
Android部分源碼可以在這里下載到:https://download.csdn.net/download/egostudio/20463417?
總結
以上是生活随笔為你收集整理的Unity接入GooglePlay内购V4(源生Android方式)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蔡氏混沌matlab,蔡氏混沌电路的MA
- 下一篇: 有参函数