In-App-Billing — BillingHelper не создан полностью

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


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

  • пожертвовать_маленький
  • пожертвовать_midsmall
  • пожертвовать_средний
  • пожертвовать_большой
  • пожертвовать_xlarge

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

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

Вопрос


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

Это классы, которые я использую для своей службы в приложении:

отклонить шаг вперед

  • КаналАктивность
  • SplashActivity

de.stepforward.billing

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

de.stepforward.billing.util

  • Base64.класс
  • Base64DecoderException.класс

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

Код


AppMainTest.класс

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.класс

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

Спасибо за вашу помощь заранее

Наилучшие пожелания

сафари

Приложение


Манифест

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.stepforward"
    android:versionCode="6"
    android:versionName="1.3.2" >

    <uses-sdk android:minSdkVersion="10" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/StepForward.Theme"
        android:debuggable="false"
         >
        <activity
            android:label="@string/app_name"
            android:name=".SplashActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
            <activity android:name=".ChannelActivity" ></activity>
            <activity android:name=".billing.AppMainTest"></activity>
    </application>
            <uses-permission android:name="android.permission.INTERNET" />
            <uses-permission android:name="com.android.vending.BILLING" />


 <!--  In-App-Einkäufe für Donate -->   
    <service android:name=".BillingService" />

    <receiver android:name=".BillingReceiver">
      <intent-filter>
        <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
        <action android:name="com.android.vending.billing.RESPONSE_CODE" />
        <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
      </intent-filter>
    </receiver>

</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

person safari    schedule 06.12.2011    source источник


Ответы (1)


Если вы посмотрите на этот метод:

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

Этот журнал печатается, когда ваша служба имеет значение null или ваш контекст. Aaaand ваш сервис недействителен, когда:

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

instanceiateHelper не вызывается 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));

SO:

Объявили ли вы службу в своем манифесте?

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

ИЗМЕНИТЬ

Я был прав :-) Просто ваш сервисный тег находится вне тега вашего приложения.

См. здесь: https://stackoverflow.com/a/5439157/413127

person Blundell    schedule 07.12.2011
comment
чувак, я действительно не понимаю, где ошибку ищут все утро :( - person safari; 07.12.2011
comment
Прочитай мой ответ. Тег службы должен находиться внутри тега приложения :-) Я сам был укушен этим: stackoverflow.com/a/5439157/413127 - person Blundell; 07.12.2011
comment
Работал на меня..! Спасибо @Blundell :D - person JgdGuy; 28.08.2013