Unity3d接入googleplay内购详细说明(二)

来源:互联网 发布:wineskin for mac 编辑:程序博客网 时间:2024/05/01 17:51

因为本文内容比较多,整理花费时间比较长,故分几篇完成,以下为本文目录结构,方便查阅:


Unity3d接入googleplay内购详细说明(一)

引言

一、准备条件:

二、谷歌开发者后台应用创建说明:


Unity3d接入googleplay内购详细说明(二)

三、Unity3d向安卓通信以及接受通信

四、Unity导出安卓Apk正式签名说明

五、使用Eclipse运行unity导出的工程

六、Java代码接入谷歌内购:

七、谷歌内购Java代码


Unity3d接入googleplay内购详细说明(三)

八、Apk上传谷歌商店测试版以及添加测试者

九、Zipalign处理APK文件

十、添加google+群组并邀请其成为测试者

十一、测试机googleplay安装以及配置:


Unity3d接入googleplay内购详细说明(四)

十二、真机测试中出现的常见错误以及解决方式:

十三、成功测试购买以及正式版发布



————————————————————————————————————————————————————————————


三、Unity3d向安卓通信以及接受通信

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模式签名,否则上传失败。签名制作方式如下:



四、Unity导出安卓Apk正式签名说明

一、签名的意义

为了保证每个应用程序开发商合法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安装到真机上测试,能够启动即可,按钮什么的当然现在还没有作用:

 

 


六、Java代码接入谷歌内购:

以下是开发者中心对内购的详细解释,英文文档,可以谷歌翻译一下。链接地址:

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进行测试。



0 0
原创粉丝点击