Unity3d接入googleplay内购详细说明(二)
2017-04-04
| 转藏
因为本文内容比较多,整理花费时间比较长,故分几篇完成,以下为本文目录结构,方便查阅: Unity3d接入googleplay内购详细说明(一) 引言 一、准备条件: 二、谷歌开发者后台应用创建说明: Unity3d接入googleplay内购详细说明(二) 三、Unity3d向安卓通信以及接受通信 四、Unity导出安卓Apk正式签名说明 五、使用Eclipse运行unity导出的工程 六、Java代码接入谷歌内购: 七、谷歌内购Java代码 Unity3d接入googleplay内购详细说明(三) 八、Apk上传谷歌商店测试版以及添加测试者 九、Zipalign处理APK文件 十、添加google+群组并邀请其成为测试者 十一、测试机googleplay安装以及配置: Unity3d接入googleplay内购详细说明(四) 十二、真机测试中出现的常见错误以及解决方式: 十三、成功测试购买以及正式版发布 ———————————————————————————————————————————————————————————— 1、作为测试的是临时写的unity3ddemo,只具有最基本的支付功能。首先解决unity安卓通信,这个基本上都是固定的代码。 例子里面分别添加了2种有效商品,后台中sku分别为jb_1,lb_1; 2、其中主要就几句代码,基本通用,需要改的仅是“Pay”方法,以及注意传入的string参数(用来区分不同sku): private void UnityToAndroid(string buykey) { AndroidJavaClass m_unityPlayer = newAndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject m_curActivity = m_unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); m_curActivity.Call("Pay",buykey); } 3、所有代码如下: using UnityEngine; usingSystem.Collections; public classGUIpay : MonoBehaviour { private int i = 0; private int j = 0; private string key;//suk private string MessageFromAndroid;//从安卓端接受的消息 void OnGUI(){ if(GUILayout.Button("金币1",GUILayout.Height(100),GUILayout.Width(150))){ i++; key="jb_1"; UnityToAndroid(key); } if(GUILayout.Button("礼包1",GUILayout.Height(100),GUILayout.Width(150))){ j++; key ="lb_1"; UnityToAndroid(key); } GUILayout.Button("购买:"+key+"分别:"+i.ToString()+"次/"+j.ToString()+"次",GUILayout.Height(100),GUILayout.Width(200)); GUILayout.Button("AndroidMessage:"+MessageFromAndroid,GUILayout.Height(100),GUILayout.Width(200)); } //安卓支付通信 private void UnityToAndroid(stringbuykey){ AndroidJavaClass m_unityPlayer =new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject m_curActivity =m_unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); if(m_curActivity == null) { Debug.LogError("获得不到JAVA对象"); return; } m_curActivity.Call("Pay",buykey); } //从安卓端接受消息,因为本脚本挂在一直存在的MainCamera对象上,将从其上获取消息,对应java代码要将消息发送对象指定为MainCamera void Messgae(string message) { MessageFromAndroid = message; } } 4、运行效果如下,点击会报错,所以只能在真机上测试: 5、接下来,打开设置,勾选导出安卓工程,icon,应用名字,起始页等如果仅做测试可以不必正式。就是说如果不是正式发布的apk,仅需要正式的包名,正式的签名,正式发布记得修改其他icon、起始页等信息: 6、上传到谷歌商店需要正式的签名,不能为debug模式签名,否则上传失败。签名制作方式如下: 一、签名的意义 为了保证每个应用程序开发商合法ID,防止部分开放商可能通过使用相同的Package Name来混淆替换已经安装的程序,我们需要对我们发布的APK文件进行唯一签名,保证我们每次发布的版本的一致性(如自动更新不会因为版本不一致而无法安装)。 这里详细解释一下: 安装包apk的名字,在世界范围内,有众多开发者,很可能命名重复,当命名重复的时候就需要有一个唯一的识别符加以区分。这就是安卓中的签名。这个签名不用向谷歌申请,而是自己在本机签名即可。 这个签名唯一,相当重要,务必保管好,是制作后续版本更新的重要依据。如果更换,即使是同一个应用,也不会覆盖安装,而是单独当做另外一个新的应用安装。而且当你提交应用时,如果更换签名,就不会当成之前应用的后续更新版本。 二、.签名的步骤(仅安卓需要签名) 一般来说,安卓的apk安装包都是由eclipse编译而成,而他自带了签名插件。包括正式签名和dubug测试签名。 而我们这里并不用eclipse,而是利用unity自带签名功能。 1、工程确认无bug后,需要倒出apk文件时: 2、打开导出设置选项: 3、这publishing settings下面默认是debug签名,如果仅仅自己测试,就不需要修改,上传谷歌商店,需要制作签名。 4、选择 create new keystore,下面选择保存位置, 5、点击key 6、填写详细签名信息,务必记住密码,因为在eclipes里面要使用。存储到可靠位置,丢失后无法找回: 7、证书如下,命令行工具能打开,需要安装jdk: 验证签名信息: 命令行输入 jarsigner -verify -verbose -certs XXX.apk(apk 完整路径) 可以看到 比对签名信息(需要安装了jdk) 8、签名栏默认是unsigned,不签名,debug模式 以后再次导出apk时,必须先选中该签名,还需要在编译前输入原始密码 9、如果为debug签名,那么在上传到谷歌应用商店时会遇到这样情况: 10、签名正确即可正确上传 11、当然也可以利用eclipes来签名,具体请百度。接下来我们要在eclipes里面接入谷歌内购api: 五、使用Eclipse运行unity导出的工程1、打开eclipes,创建工作空间。2、新建安卓工程: 3、填写跟unity一样的包名,其他默认即可,下一步·····下一步 5、这是刚创建的空工程 6、接下来将unity导出的工程,里面的文件夹直接拖动到hnn工程下,选择全部覆盖,我们的工程内容将被替换为unity内容: 7、为了试验一下是否能够在真机上运行,可以编译一下工程: 8、找到编译出来的apk安装到真机上测试,能够启动即可,按钮什么的当然现在还没有作用: 以下是开发者中心对内购的详细解释,英文文档,可以谷歌翻译一下。链接地址: In-appBilling Overview PreparingYour In-app Billing Application 1、首先我们需要下载内购sdk。在eclipes里面可以下载到,目前因为禁止链接外网,下载时可能需要vpn,内购demo文件并不大。 这个是他的官方demo,介绍了如何在安卓工程中接入内购,这就是为什么我们选择unity导出安卓工程,而不选择eclipes打jar文件,放到unity中编译apk: 我们需要的就是:IInAppBillingService.aidl这个文件。 2、将该文件拖入到我们的工程src目录下、路径请自己设定好。 3、此外还需要加入必备的工具类。 也就是util文件夹下面的所有代码。同样拖动到我们的工程目录下,拖进来后可能会报引入路径错,注意修改成符合你自己工程的正常路径。例如我的工程导入完毕后结构大约如此。 从上至下依次为内购必须文件、unity自带类、内购工具类。文件目录结构: 4、AndroidMainfest文件添加权限,如果你还做了其他接入功能,例如分享等,权限做合并。 <uses-permission android:name="com.android.vending.BILLING"/> 5、接着我们在java代码中写入被unity调用以及向unity回传消息的代码。 前文说道: 6、向uinty中发送消息,一般用来传送是否购买成功等等。 7、Unity中接受内容继续做处理: 8、内购方面,需要写入base64 ras公共密钥。就是前文说的再谷歌开发者后台申请的key 9、再写入sku,这个sku就是内购项目的唯一id,可以从unity中传过来,也可以写在java中,我们这里做传入。 10、其他添加内购时的各种监听,是否登录,是否符合测试条件,是否绑定银行卡等等,主要借鉴其demo中的代码。 七、谷歌内购Java代码以下为unity导出的默认类中所有代码:packagecom.taojinzhe.hnn; importcom.unity3d.player.*; importandroid.app.NativeActivity; importandroid.content.res.Configuration; importandroid.graphics.PixelFormat; importandroid.os.Bundle; importandroid.view.KeyEvent; import android.view.MotionEvent; importandroid.view.View; importandroid.view.Window; importandroid.view.WindowManager; importandroid.app.Activity; importandroid.app.AlertDialog; importandroid.content.Intent; importandroid.content.SharedPreferences; import android.util.Log; importandroid.widget.ImageView; importandroid.widget.Toast; importcom.util.IabHelper; importcom.util.IabResult; importcom.util.Inventory; importcom.util.Purchase; public classUnityPlayerNativeActivity extends NativeActivity { protected UnityPlayer mUnityPlayer; // don't change the name of thisvariable; referenced from native code //___________________________________________________________ //The helper object IabHelper mHelper; // Debug tag, for logging static final String TAG ="hongneinei"; // Does the user have the premium upgrade? boolean mIsPremium = false; // Does the user have an activesubscription to the infinite gas plan? boolean mSubscribedToInfiniteGas = false; // SKUs for our products: the premiumupgrade (non-consumable) and gas (consumable) static String SKU_consume =""; static String SKU_noconsume =""; //static final String SKU_GAS=""; //SKU for our subscription (infinite gas) //static final String SKU_INFINITE_GAS ="infinite_gas"; // (arbitrary) request code for the purchaseflow static final int RC_REQUEST = 10001; //___________________________________________________________ // Setup activity layout @Override protected void onCreate (BundlesavedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); getWindow().takeSurface(null); setTheme(android.R.style.Theme_NoTitleBar_Fullscreen); getWindow().setFormat(PixelFormat.RGB_565); mUnityPlayer = newUnityPlayer(this); if (mUnityPlayer.getSettings().getBoolean ("hide_status_bar", true)) getWindow ().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); //___________________________________________________________ /* base64EncodedPublicKey should be YOURAPPLICATION'S PUBLIC KEY * (that you got from the Google Playdeveloper console). This is not your * developer public key, it's the*app-specific* public key. * * Instead of just storing the entireliteral string here embedded in the * program, construct the key at runtime from pieces or * use bit manipulation (for example,XOR with some other string) to hide * the actual key. The key itself is not secret information, butwe don't * want to make it easy for an attackerto replace the public key with one * of their own and then fake messagesfrom the server. */ String base64EncodedPublicKey ="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtBVdoRrdD/oCWHYgzhT4TBIh0AZ80n0Sf1uXD8gWQ1H9LdpOB4MX4QG9RP9pBbS0e6W8f7E91bjKyicEa6LetTxpg1Gf+3N0L0c9E2G3RwIO9SXaRUNfzjHN2lzaspLQ52Kj5+SLpT8JD6ISVuro7OS4nxmi7xQT0lx/dPOAOs8mQ/1qmlgwsJRybqWQ+hAvu1fchMggT50TAyG1RyqKTJNErlNYTvog7ZQjjvCZXW5aDBnGeEjFoI79Lnt5XAoaUpuObmbkoCOkJeSiUTQqD+mqAQCvXnBhUso2klLDlOhTz0FUT7X19KZ68OU1Q+lXqNlJ6GurXzhFguvhdo+3DQIDAQAB"; // Create the helper, passing it our contextand the public key to verify signatures with Log.d(TAG, "Creating IABhelper."); mHelper = new IabHelper(this,base64EncodedPublicKey); // enable debug logging (for aproduction application, you should set this to false). mHelper.enableDebugLogging(true); // Start setup. This is asynchronousand the specified listener // will be called once setup completes. Log.d(TAG, "Startingsetup."); mHelper.startSetup(newIabHelper.OnIabSetupFinishedListener() { public voidonIabSetupFinished(IabResult result) { Log.d(TAG, "Setupfinished."); if (!result.isSuccess()) { // Oh noes, there was a problem. complain("Problemsetting up in-app billing: " + result); return; } // Have we been disposed of inthe meantime? If so, quit. if (mHelper == null) return; // IAB is fully set up. Now, let'sget an inventory of stuff we own. Log.d(TAG, "Setupsuccessful. Querying inventory."); mHelper.queryInventoryAsync(mGotInventoryListener); } }); //___________________________________________________________ } //___________________________________________________________ protected void Pay(final String buykey) { /* TODO: for security, generate your payloadhere for verification. See the comments on * verifyDeveloperPayload() for more info.Since this is a SAMPLE, we just use * an empty string, but on a productionapp you should carefully generate this. */ if(buykey.contains("jb_")) { SKU_consume = buykey; } if(buykey.contains("lb_")) { SKU_noconsume = buykey; } runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(),buykey,Toast.LENGTH_SHORT).show(); SendToUnityMessage(buykey); } }); String payload = ""; mHelper.launchPurchaseFlow(this,buykey, RC_REQUEST, mPurchaseFinishedListener,payload); } //Listener that's called when we finish querying the items and subscriptions weown 购买侦听器完成 IabHelper.OnIabPurchaseFinishedListenermPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener(){ public void onIabPurchaseFinished(IabResult result, Purchase purchase){ Log.d(TAG, "Purchase finished: " + result + ", purchase:" + purchase); if (result.isFailure()) { //complain("Error purchasing: " +result); //setWaitScreen(false); return; } if (!verifyDeveloperPayload(purchase)) { complain("Error purchasing.Authenticity verification failed."); // setWaitScreen(false); return; } Log.d(TAG, "Purchase successful."); if (purchase.getSku().equals(SKU_consume)) { Log.d(TAG, "Purchase is gas.Starting gas consumption."); mHelper.consumeAsync(purchase,mConsumeFinishedListener); } else if (purchase.getSku().equals(SKU_noconsume)) { Log.d(TAG, "Purchase ispremium upgrade. Congratulating user."); alert("Thank you forupgrading to premium!"); mIsPremium = true; } } }; // Listener that's called when we finishquerying the items and subscriptions we own IabHelper.QueryInventoryFinishedListener mGotInventoryListener = newIabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventoryinventory) { Log.d(TAG, "Query inventoryfinished."); // Have we been disposed of in themeantime? If so, quit. if (mHelper == null) return; // Is it a failure? if (result.isFailure()) { complain("Failed to queryinventory: " + result); return; } Log.d(TAG, "Query inventorywas successful."); /* * Check for items we own. Noticethat for each purchase, we check * the developer payload to see ifit's correct! See * verifyDeveloperPayload(). */ // Do we have the premium upgrade? Purchase premiumPurchase =inventory.getPurchase(SKU_noconsume); mIsPremium = (premiumPurchase !=null && verifyDeveloperPayload(premiumPurchase)); Log.d(TAG, "User is " +(mIsPremium ? "PREMIUM" : "NOT PREMIUM")); // Check for gas delivery -- if weown gas, we should fill up the tank immediately Purchase gasPurchase =inventory.getPurchase(SKU_consume); if (gasPurchase != null &&verifyDeveloperPayload(gasPurchase)) { Log.d(TAG, "We have gas.Consuming it."); mHelper.consumeAsync(inventory.getPurchase(SKU_consume),mConsumeFinishedListener); return; } Log.d(TAG, "Initial inventoryquery finished; enabling main UI."); } }; //Called when consumption is complete IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = newIabHelper.OnConsumeFinishedListener() { public voidonConsumeFinished(Purchase purchase, IabResult result) { Log.d(TAG, "Consumptionfinished. Purchase: " + purchase + ", result: " + result); // if we were disposed of in themeantime, quit. if (mHelper == null) return; // We know this is the"gas" sku because it's the only one we consume, // so we don't check which sku wasconsumed. If you have more than one // sku, you probably shouldcheck... if (result.isSuccess()) { // successfully consumed, so weapply the effects of the item in our // game world's logic, which inour case means filling the gas tank a bit Log.d(TAG, "Consumptionsuccessful. Provisioning."); } else { complain("Error whileconsuming: " + result); } Log.d(TAG, "End consumptionflow."); } }; /** Verifies the developer payload of apurchase. */ boolean verifyDeveloperPayload(Purchase p){ String payload =p.getDeveloperPayload(); /* * TODO: verify that the developerpayload of the purchase is correct. It will be * the same one that you sent wheninitiating the purchase. * * WARNING: Locally generating a randomstring when starting a purchase and * verifying it here might seem like agood approach, but this will fail in the * case where the user purchases anitem on one device and then uses your app on * a different device, because on theother device you will not have access to the * random string you originallygenerated. * * So a good developer payload hasthese characteristics: * * 1. If two different users purchasean item, the payload is different between them, * so that one user's purchase can't be replayed to another user. * * 2. The payload must be such that youcan verify it even when the app wasn't the * one who initiated the purchase flow (so that items purchased by the useron * one device work on other devices owned by the user). * * Using your own server to store andverify developer payloads across app * installations is recommended. */ return true; } void complain(String message) { Log.e(TAG, "**** TrivialDrive Error: " + message); alert("Error: " + message); } void alert(String message) { AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setMessage(message); bld.setNeutralButton("OK", null); Log.d(TAG, "Showing alert dialog: " + message); bld.create().show(); } //___________________________________________________________ //向unity发送消息 void SendToUnityMessage(String Sendmessage) { UnityPlayer.UnitySendMessage("MainCamera","Messgae",Sendmessage); } // Quit Unity @Override protected void onDestroy () { mUnityPlayer.quit(); super.onDestroy(); } // Pause Unity @Override protected void onPause() { super.onPause(); mUnityPlayer.pause(); } // Resume Unity @Override protected void onResume() { super.onResume(); mUnityPlayer.resume(); } // This ensures the layout will becorrect. @Override public voidonConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mUnityPlayer.configurationChanged(newConfig); } // Notify Unity of the focus change. @Override public voidonWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); mUnityPlayer.windowFocusChanged(hasFocus); } // For some reason the multiple keyeventtype is not supported by the ndk. // Force event injection by overridingdispatchKeyEvent(). @Override public booleandispatchKeyEvent(KeyEvent event) { if (event.getAction() ==KeyEvent.ACTION_MULTIPLE) return mUnityPlayer.injectEvent(event); returnsuper.dispatchKeyEvent(event); } // Pass any events not handled by(unfocused) views straight to UnityPlayer @Override public boolean onKeyUp(intkeyCode, KeyEvent event) { returnmUnityPlayer.injectEvent(event); } @Override public boolean onKeyDown(intkeyCode, KeyEvent event) { returnmUnityPlayer.injectEvent(event); } @Override public booleanonTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); } /*API12*/ public boolean onGenericMotionEvent(MotionEventevent) { returnmUnityPlayer.injectEvent(event); } } 本片结语: 至此,unity,eclipes代码部分基本上完结,接下来我们将编译出apk进行测试。 |
本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
转藏
分享
QQ空间
QQ好友
新浪微博
微信
献花(0)
+1
来自: >