Java 应用内计费-BillingHelper未完全实例化

Java 应用内计费-BillingHelper未完全实例化,java,android,in-app-purchase,in-app-billing,Java,Android,In App Purchase,In App Billing,我想做的事 大家好,我正在尝试为我的应用创建一些应用内产品,供捐赠使用。因为我免费赠送我朋友的音乐——他说唱。不管怎样,我在我的开发者帐户上创建了5个应用内产品: 捐小 捐赠中街 捐赠中草药 捐赠大量 捐赠 这是我在那里生成的引用键。它们被保存和发布。现在,如果通过本教程编写应用程序内服务:。代码似乎工作得很完美,没有错误,当我编译演示代码时,它工作得很好。但当我尝试时,我总是会遇到这样的错误: 12-06 14:23:49.400: E/BillingService(4719): Bil

我想做的事


大家好,我正在尝试为我的应用创建一些应用内产品,供捐赠使用。因为我免费赠送我朋友的音乐——他说唱。不管怎样,我在我的开发者帐户上创建了5个应用内产品:

  • 捐小
  • 捐赠中街
  • 捐赠中草药
  • 捐赠大量
  • 捐赠
这是我在那里生成的引用键。它们被保存和发布。现在,如果通过本教程编写应用程序内服务:。代码似乎工作得很完美,没有错误,当我编译演示代码时,它工作得很好。但当我尝试时,我总是会遇到这样的错误:

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

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

那么,我需要做什么来改变用户可以选择不同的应用内产品的标准呢?我需要在什么地方申报吗??另外,如果你有一个很好的教程给我,这些东西都是很好的描述和充分的工作,请告诉我

以下是我用于应用内服务的类:

de.stepproward

  • 渠道活动
  • 喷溅活动
de.stepproward.billing

  • AppMainTest.class->这是我的活动
  • 比林赫尔班
  • BillingReceiver.class
  • BillingSecurity.class
  • BillingService.class
  • C级
de.stepfroward.billing.util

  • Base64.class
  • Base64DecoderException.class
目前,我将为您提供我的付费助手和活动的代码,如果您需要更多代码,请告诉我

代码


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();
    }
}
比林赫尔班

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<VerifiedPurchase> 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");
    }
}

如果您查看此方法:

  private static boolean amIDead() {
    if (mService == null || mContext == null) {
        Log.e(TAG, "BillingHelper not fully instantiated");
        return true;
    } else {
        return false;
    }
}
当您的服务为null或您的上下文时,将打印该日志。aa在以下情况下,您的服务为空:

 protected static void instantiateHelper(Context context, IMarketBillingService service) {
            mService = service;
            mContext = context;
    }
实例化帮助程序不称为aaand

 @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "Market Billing Service Connected.");
            mService = IMarketBillingService.Stub.asInterface(service);
            BillingHelper.instantiateHelper(getBaseContext(), mService);
    }
当您的服务连接时调用,我可以看到您尝试如下方式连接到该服务:

 startService(new Intent(mContext, BillingService.class));
因此:

您是否在清单中声明了该服务?

 <application ...
     <service android:name=".BillingService" />
     ...
 </application>

伙计,我真的不知道整个上午在哪里搜索错误:(阅读我的答案。服务标签必须在应用程序标签中:-)我自己也被这个问题咬了一口:为我工作。。!Thankx@Blundell:D
 <application ...
     <service android:name=".BillingService" />
     ...
 </application>