In-App-Billing - BillingHelper no fully instantiated

Что я пытаюсь сделать


Здравствуйте ребята, я пытаюсь создать некоторые In-App-Products для моего приложения, его для использования пожертвования. Потому что я раздаю бесплатно музыку моего друга - он читает рэп. Так вот, я создал 5 In-App Products на своем аккаунте разработчика:

  • donate_small
  • donate_midsmall
  • donate_medium
  • donate_large
  • donate_xlarge

Вот ссылочные ключи, которые я создал там. Они сохранены и опубликованы. Теперь, если написать In-App-Service поверх этого руководства: In-App Easy Tutorial. Код вроде бы работает идеально, ошибок нет, и когда я компилирую демо-код, он работает. Но когда я пробую его, я постоянно получаю эту ошибку:

12-06 14:23:49.400: E/BillingService(4719): BillingHelper not fully instantiated

Вопрос


Итак, что мне нужно изменить, чтобы пользователь мог выбирать различные продукты в приложении? Нужно ли мне где-то их объявить? Также, если у вас есть отличный учебник для меня, где все это хорошо описано и полностью работает, пожалуйста, скажите мне.

Вот классы, которые я использую для моего In-app-сервиса:

de.stepforward

  • ChannelActivity
  • SplashActivity

de.stepforward.billing

  • AppMainTest.class -> Это моя активность
  • BillingHelper.class
  • BillingReceiver.class
  • BillingSecurity. class
  • BillingService.class
  • C.class

de.stepforward.billing.util

  • Base64.class
  • Base64DecoderException.class

На данный момент я предоставлю вам код моего BillingHelper и Activity, если вам нужно больше кода, просто скажите мне.

Код


AppMainTest.class

package de.stepforward.billing;


import de.stepforward.R;
import de.stepforward.R.id;
import de.stepforward.R.layout;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class AppMainTest extends Activity implements OnClickListener{

    private static final String TAG = "BillingService";

    private Context mContext;
    private ImageView purchaseableItem;
    private Button purchaseButton;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i("BillingService", "Starting");
        setContentView(R.layout.donate);

        mContext = this;

        purchaseButton = (Button) findViewById(R.id.main_purchase_yes);
        purchaseButton.setOnClickListener(this);
        purchaseableItem = (ImageView) findViewById(R.id.main_purchase_item);

        startService(new Intent(mContext, BillingService.class));
        BillingHelper.setCompletedHandler(mTransactionHandler);
    }

    public Handler mTransactionHandler = new Handler(){
            public void handleMessage(android.os.Message msg) {
                Log.i(TAG, "Transaction complete");
                Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
                Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);

                if(BillingHelper.latestPurchase.isPurchased()){
                    showItem();
                }
            };

    };

    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.main_purchase_yes:
            if(BillingHelper.isBillingSupported()){
                BillingHelper.requestPurchase(mContext, "android.test.purchased"); 
                // android.test.purchased or android.test.canceled or android.test.refunded or com.blundell.item.passport
            } else {
                Log.i(TAG,"Can't purchase on this device");
                purchaseButton.setEnabled(false); // XXX press button before service started will disable when it shouldnt
            }

            break;
        default:
            // nada
            Log.i(TAG,"default. ID: "+v.getId());
            break;
        }

    }

    private void showItem() {
        purchaseableItem.setVisibility(View.VISIBLE);
    }

    @Override
    protected void onPause() {
        Log.i(TAG, "onPause())");
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        BillingHelper.stopService();
        super.onDestroy();
    }
}

BillingHelper.class

package de.stepforward.billing;

import java.util.ArrayList;

import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;

import com.android.vending.billing.IMarketBillingService;

import de.stepforward.billing.BillingSecurity.VerifiedPurchase;
import de.stepforward.billing.C.ResponseCode;

public class BillingHelper {

    private static final String TAG = "BillingService";

    private static IMarketBillingService mService;
    private static Context mContext;
    private static Handler mCompletedHandler;

    protected static VerifiedPurchase latestPurchase;

    protected static void instantiateHelper(Context context, IMarketBillingService service) {
        mService = service;
        mContext = context;
    }

    protected static void setCompletedHandler(Handler handler){
        mCompletedHandler = handler;
    }

    protected static boolean isBillingSupported() {
        if (amIDead()) {
            return false;
        }
        Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
        if (mService != null) {
            try {
                Bundle response = mService.sendBillingRequest(request);
                ResponseCode code = ResponseCode.valueOf((Integer) response.get("RESPONSE_CODE"));
                Log.i(TAG, "isBillingSupported response was: " + code.toString());
                if (ResponseCode.RESULT_OK.equals(code)) {
                    return true;
                } else {
                    return false;
                }
            } catch (RemoteException e) {
                Log.e(TAG, "isBillingSupported response was: RemoteException", e);
                return false;
            }
        } else {
            Log.i(TAG, "isBillingSupported response was: BillingService.mService = null");
            return false;
        }
    }

    /**
     * A REQUEST_PURCHASE request also triggers two asynchronous responses (broadcast intents). 
     * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides error information about the request. (which I ignore)
     * Next, if the request was successful, the Android Market application sends an IN_APP_NOTIFY broadcast intent. 
     * This message contains a notification ID, which you can use to retrieve the transaction details for the REQUEST_PURCHASE
     * @param activityContext
     * @param itemId
     */
    protected static void requestPurchase(Context activityContext, String itemId){
        if (amIDead()) {
            return;
        }
        Log.i(TAG, "requestPurchase()");
        Bundle request = makeRequestBundle("REQUEST_PURCHASE");
        request.putString("ITEM_ID", itemId);
        try {
            Bundle response = mService.sendBillingRequest(request);

            //The RESPONSE_CODE key provides you with the status of the request
            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
            //The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI
            PendingIntent pendingIntent = (PendingIntent) response.get("PURCHASE_INTENT");
            //The REQUEST_ID key provides you with a unique request identifier for the request
            Long requestIndentifier     = (Long) response.get("REQUEST_ID");
            Log.i(TAG, "current request is:" + requestIndentifier);
            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
            Log.i(TAG, "REQUEST_PURCHASE Sync Response code: "+responseCode.toString());

            startBuyPageActivity(pendingIntent, new Intent(), activityContext);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed, internet error maybe", e);
            Log.e(TAG, "Billing supported: "+isBillingSupported());
        }
    }

    /**
     * A GET_PURCHASE_INFORMATION request also triggers two asynchronous responses (broadcast intents). 
     * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request.  (which I ignore)
     * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent. 
     * This message contains detailed transaction information. 
     * The transaction information is contained in a signed JSON string (unencrypted). 
     * The message includes the signature so you can verify the integrity of the signed string
     * @param notifyIds
     */
    protected static void getPurchaseInformation(String[] notifyIds){
        if (amIDead()) {
            return;
        }
        Log.i(TAG, "getPurchaseInformation()");
        Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
        // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate.
        // The Android Market application returns this nonce with the PURCHASE_STATE_CHANGED broadcast intent so you can verify the integrity of the transaction information.
        request.putLong("NONCE", BillingSecurity.generateNonce());
        // The NOTIFY_IDS key contains an array of notification IDs, which you received in the IN_APP_NOTIFY broadcast intent.
        request.putStringArray("NOTIFY_IDS", notifyIds);
        try {
            Bundle response = mService.sendBillingRequest(request);

            //The REQUEST_ID key provides you with a unique request identifier for the request
            Long requestIndentifier     = (Long) response.get("REQUEST_ID");
            Log.i(TAG, "current request is:" + requestIndentifier);
            //The RESPONSE_CODE key provides you with the status of the request
            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
            Log.i(TAG, "GET_PURCHASE_INFORMATION Sync Response code: "+responseCode.toString());

        } catch (RemoteException e) {
            Log.e(TAG, "Failed, internet error maybe", e);
            Log.e(TAG, "Billing supported: "+isBillingSupported());
        }
    }

    /**
     * To acknowledge that you received transaction information you send a
     * CONFIRM_NOTIFICATIONS request.
     * 
     * A CONFIRM_NOTIFICATIONS request triggers a single asynchronous response—a RESPONSE_CODE broadcast intent. 
     * This broadcast intent provides status and error information about the request.
     * 
     * Note: As a best practice, you should not send a CONFIRM_NOTIFICATIONS request for a purchased item until you have delivered the item to the user. 
     * This way, if your application crashes or something else prevents your application from delivering the product,
     * your application will still receive an IN_APP_NOTIFY broadcast intent from Android Market indicating that you need to deliver the product
     * @param notifyIds
     */
    protected static void confirmTransaction(String[] notifyIds) {
        if (amIDead()) {
            return;
        }
        Log.i(TAG, "confirmTransaction()");
        Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
        request.putStringArray("NOTIFY_IDS", notifyIds);
        try {
            Bundle response = mService.sendBillingRequest(request);

            //The REQUEST_ID key provides you with a unique request identifier for the request
            Long requestIndentifier     = (Long) response.get("REQUEST_ID");
            Log.i(TAG, "current request is:" + requestIndentifier);

            //The RESPONSE_CODE key provides you with the status of the request
            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);

            Log.i(TAG, "CONFIRM_NOTIFICATIONS Sync Response code: "+responseCode.toString());
        } catch (RemoteException e) {
            Log.e(TAG, "Failed, internet error maybe", e);
            Log.e(TAG, "Billing supported: " + isBillingSupported());
        }
    }

    /**
     * 
     * Can be used for when a user has reinstalled the app to give back prior purchases. 
     * if an item for sale's purchase type is "managed per user account" this means google will have a record ofthis transaction
     * 
     * A RESTORE_TRANSACTIONS request also triggers two asynchronous responses (broadcast intents). 
     * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request. 
     * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent. 
     * This message contains the detailed transaction information. The transaction information is contained in a signed JSON string (unencrypted).
     * The message includes the signature so you can verify the integrity of the signed string
     * @param nonce
     */
    protected static void restoreTransactionInformation(Long nonce) {
        if (amIDead()) {
            return;
        }
        Log.i(TAG, "confirmTransaction()");
        Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
        // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate
        request.putLong("NONCE", nonce);
        try {
            Bundle response = mService.sendBillingRequest(request);

            //The REQUEST_ID key provides you with a unique request identifier for the request
            Long requestIndentifier     = (Long) response.get("REQUEST_ID");
            Log.i(TAG, "current request is:" + requestIndentifier);

            //The RESPONSE_CODE key provides you with the status of the request
            Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
            C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
            Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());
        } catch (RemoteException e) {
            Log.e(TAG, "Failed, internet error maybe", e);
            Log.e(TAG, "Billing supported: " + isBillingSupported());
        }
    }

    private static boolean amIDead() {
        if (mService == null || mContext == null) {
            Log.e(TAG, "BillingHelper not fully instantiated");
            return true;
        } else {
            return false;
        }
    }

    private static Bundle makeRequestBundle(String method) {
        Bundle request = new Bundle();
        request.putString("BILLING_REQUEST", method);
        request.putInt("API_VERSION", 1);
        request.putString("PACKAGE_NAME", mContext.getPackageName());
        return request;
    }

    /**
     * 
     * 
     * You must launch the pending intent from an activity context and not an application context
     * You cannot use the singleTop launch mode to launch the pending intent
     * @param pendingIntent
     * @param intent
     * @param context
     */
    private static void startBuyPageActivity(PendingIntent pendingIntent, Intent intent, Context context){
        //TODO add above 2.0 implementation with reflection, for now just using 1.6 implem

        // This is on Android 1.6. The in-app checkout page activity will be on its
        // own separate activity stack instead of on the activity stack of
        // the application.
        try {
            pendingIntent.send(context, 0, intent);         
        } catch (CanceledException e){
            Log.e(TAG, "startBuyPageActivity CanceledException");
        }
    }

    protected static void verifyPurchase(String signedData, String signature) {
        ArrayList purchases = BillingSecurity.verifyPurchase(signedData, signature);
        latestPurchase = purchases.get(0);

        confirmTransaction(new String[]{latestPurchase.notificationId});

        if(mCompletedHandler != null){
            mCompletedHandler.sendEmptyMessage(0);
        } else {
            Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
        }
    }

    public static void stopService(){
        mContext.stopService(new Intent(mContext, BillingService.class));
        mService = null;
        mContext = null;
        mCompletedHandler = null;
        Log.i(TAG, "Stopping Service");
    }
}

Заранее спасибо за помощь

С уважением

safari

Appendix


Manifest




    

    
        
            
                

                
            
        
            
            
    
            
            


    
    

    
      
        
        
        
      
    


LogCat (немного дополнительной информации)

12-07 13:58:14.334: I/BillingService(20997): Starting
12-07 13:58:14.364: D/dalvikvm(20997): GC_EXTERNAL_ALLOC freed 15K, 58% free 2919K/6791K, external 414K/926K, paused 22ms
12-07 13:58:23.864: E/BillingService(20997): BillingHelper not fully instantiated
12-07 13:58:23.864: I/BillingService(20997): Can't purchase on this device

8
задан safari 7 December 2011 в 13:00
поделиться