应用内结算不起作用:"IAB Helper未设置"

ric*_*hey 26 android in-app-purchase in-app-billing google-play

我尝试在我的应用程序中包含应用内结算并进行测试,基于应用程序计费版本3的"TrivialDrive"示例的整个过程(以及实现IAB文件中提供的未修改版本的IAB文件)演示文稿的"util"子目录),但它对我不起作用 - 在LogCat上,就在应用程序因错误而终止之前,它会显示消息"应用程序内计费错误:操作非法状态(launchPurchaseFlow):IAB Helper未设置. "(在startRegistered()函数被触发后,给我LOG消息"点击注册按钮;启动购买流程进行升级.")...

知道这里出了什么问题吗?

以下是我的代码的相关部分:

package com.mytest;

(..)
import com.mytest.iab.IabHelper; // the originals from the demo example, unmodified
import com.mytest.iab.IabResult;
import com.mytest.iab.Inventory;
import com.mytest.iab.Purchase;

public class Result3 extends Activity implements OnClickListener {

private static final String TAG = "BillingService";

private Context mContext;

boolean mIsRegistered = false;

    // this has already been set up for my app at the publisher's console
static final String IS_REGISTERED = "myregistered";

static final int RC_REQUEST = 10001;

// The helper object
IabHelper mHelper; 

/** Call when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.result3);
    mContext = this;

    String base64EncodedPublicKey = "[my public key]"; // (from publisher's console for my app)

    // Create the helper, passing it our context and the public key to verify signatures with
    Log.d(TAG, "Creating IAB helper.");
    mHelper = new IabHelper(this, base64EncodedPublicKey);

    // enable debug logging (for a production application, you should set this to false).
    mHelper.enableDebugLogging(true);

    // Start setup. This is asynchronous and the specified listener
    // will be called once setup completes.
    Log.d(TAG, "Starting setup.");
    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
        public void onIabSetupFinished(IabResult result) {
            Log.d(TAG, "Setup finished.");

            if (!result.isSuccess()) {
                complain("Problem setting up in-app billing: " + result);
                return;
            }

            // Hooray, IAB is fully set up. Now, let's get an inventory of stuff we own.
            Log.d(TAG, "Setup successful. Querying inventory.");
            mHelper.queryInventoryAsync(mGotInventoryListener);
        }
    });

   // Set the onClick listeners
   findViewById(R.id.btnPurchase).setOnClickListener(this);
}

// Listener that's called when we finish querying the items we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
        Log.d(TAG, "Query inventory finished.");
        if (result.isFailure()) {
            complain("Failed to query inventory: " + result);
            return;
        }

        Log.d(TAG, "Query inventory was successful.");

        // Do we have the premium upgrade?
        mIsRegistered = inventory.hasPurchase(IS_REGISTERED);
        Log.d(TAG, "User is " + (mIsRegistered ? "REGISTERED" : "NOT REGISTERED"));

        setWaitScreen(false);
        Log.d(TAG, "Initial inventory query finished; enabling main UI.");
    }
};      

// User clicked the "Register" button.
private void startRegistered() {
    Log.d(TAG, "Register button clicked; launching purchase flow for upgrade.");
    setWaitScreen(true);
    mHelper.launchPurchaseFlow(this, IS_REGISTERED, RC_REQUEST, mPurchaseFinishedListener);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);

    // Pass on the activity result to the helper for handling
    if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
        // not handled, so handle it ourselves (here's where you'd
        // perform any handling of activity results not related to in-app billing..
        super.onActivityResult(requestCode, resultCode, data);
    }
    else {
        Log.d(TAG, "onActivityResult handled by IABUtil.");
    }
}

// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
        Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
        if (result.isFailure()) {
            // Oh noes!
            complain("Error purchasing: " + result);
            setWaitScreen(false);
            return;
        }

        Log.d(TAG, "Purchase successful.");

        if (purchase.getSku().equals(IS_REGISTERED)) {
            Log.d(TAG, "User has registered..");
            alert("Thank you.");
            mIsRegistered = true;
            setWaitScreen(false);
        }
    }
};

// We're being destroyed. It's important to dispose of the helper here!
@Override
public void onDestroy() {
    // very important:
    Log.d(TAG, "Destroying helper.");
    if (mHelper != null) mHelper.dispose();
    mHelper = null;
}

void complain(String message) {
    Log.e(TAG, "**** Register Error: " + message);
    alert("Error: " + message);
}

void setWaitScreen(boolean set) {
    // just a dummy for now
}

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

@Override
public void onClick(View v) {
    switch (v.getId()) {
    case R.id.btnPurchase:
        startRegistered();
        break;
    default:
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

}

这里有来自Logcat的更多行:

12-20 01:06:36.701: D/dalvikvm(299): GC_FOR_MALLOC freed 4262 objects / 308592 bytes in 84ms
12-20 01:06:36.701: D/webviewglue(299): nativeDestroy view: 0x2ea718
12-20 01:06:36.771: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:07.111: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:18.510: D/webviewglue(299): nativeDestroy view: 0x2dd458
12-20 01:07:18.510: D/dalvikvm(299): GC_FOR_MALLOC freed 6042 objects / 544504 bytes in 50ms
12-20 01:07:18.530: D/webviewglue(299): nativeDestroy view: 0x2ea8d0
12-20 01:07:18.660: D/BillingService(299): Creating IAB helper.
12-20 01:07:18.660: D/BillingService(299): Starting setup.
12-20 01:07:18.660: D/IabHelper(299): Starting in-app billing setup.
12-20 01:07:19.621: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:20.160: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:32.481: D/webviewglue(299): nativeDestroy view: 0x3f88e8
12-20 01:07:32.491: D/dalvikvm(299): GC_FOR_MALLOC freed 5798 objects / 513640 bytes in 50ms
12-20 01:07:32.511: D/BillingService(299): Register button clicked; launching purchase flow for upgrade.    
12-20 01:07:32.511: E/IabHelper(299): In-app billing error: Illegal state for operation (launchPurchaseFlow): IAB helper is not set up.
12-20 01:07:32.521: D/AndroidRuntime(299): Shutting down VM
12-20 01:07:32.521: W/dalvikvm(299): threadid=1: thread exiting with uncaught exception (group=0x4001d800)
12-20 01:07:32.541: E/AndroidRuntime(299): FATAL EXCEPTION: main
12-20 01:07:32.541: E/AndroidRuntime(299): java.lang.IllegalStateException: IAB helper is not set up. Can't perform operation: launchPurchaseFlow
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.iab.IabHelper.checkSetupDone(IabHelper.java:673)
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.iab.IabHelper.launchPurchaseFlow(IabHelper.java:315)
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.iab.IabHelper.launchPurchaseFlow(IabHelper.java:294)
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.Result3.startRegistered(Result3.java:157)
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.Result3.onClick(Result3.java:248)
12-20 01:07:32.541: E/AndroidRuntime(299):  at android.view.View.performClick(View.java:2408)
12-20 01:07:32.541: E/AndroidRuntime(299):  at android.view.View$PerformClick.run(View.java:8816)
12-20 01:07:32.541: E/AndroidRuntime(299):  at android.os.Handler.handleCallback(Handler.java:587)
12-20 01:07:32.541: E/AndroidRuntime(299):  at android.os.Handler.dispatchMessage(Handler.java:92)
12-20 01:07:32.541: E/AndroidRuntime(299):  at android.os.Looper.loop(Looper.java:123)
12-20 01:07:32.541: E/AndroidRuntime(299):  at android.app.ActivityThread.main(ActivityThread.java:4627)
12-20 01:07:32.541: E/AndroidRuntime(299):  at java.lang.reflect.Method.invokeNative(Native Method)
12-20 01:07:32.541: E/AndroidRuntime(299):  at java.lang.reflect.Method.invoke(Method.java:521)
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
12-20 01:07:32.541: E/AndroidRuntime(299):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
12-20 01:07:32.541: E/AndroidRuntime(299):  at dalvik.system.NativeStart.main(Native Method)
Run Code Online (Sandbox Code Playgroud)

Car*_*arl 31

根本问题是,startRegistered()被调用到一个UI用户点击直接回应,而你的IabHelper对象的设置是异步触发,所以直到异步响应经由onIabSetupFinished收到无法得知已经完成().

您的startRegistered()方法由用户单击触发,并且调用launchPurchaseFlow(),这反过来要求IabHelper对象已经完成设置,但是如果用户在收到确认之前单击以触发购买(因为设置失败或者因为用户在绘图时非常快,然后安装将不会完成,并且launchPurchaseFlow()将报告您正在看到的错误.对于你的logcat,延迟是14秒,这通常是足够的时间,但......在这种情况下可能不是.或者,也许出了点问题,无论你等多久,你都不会有任何关联.

在您的logcat中,没有消息指示"已连接结算服务",这是您要完成设置时必须首先发生的事情之一.由于没有发生这种情况,因此您也无法从onIabSetupFinished()中看到任何消息(成功或失败).

由于需要异步响应,这是棘手的事情.一种方法是禁用用于触发购买的按钮,直到onIabSetupFinished()成功返回.这将阻止在成功设置IabHelper对象之前触发购买.当然,如果设置失败,你将有一个无法正常工作的按钮,但至少你可以告诉用户怎么了正常的(通过发出一条消息,表明你正在等待设置完成 - 例如,作为一部分按钮文字).

即使这样,一旦您的购买开始并且用户看到付款对话框,您的应用程序可能会通过onStop()循环,在用户考虑购买时从内存中刷新您的应用程序(因为购买对话框是一部分)谷歌播放,不是你的应用程序的一部分,操作系统可能需要内存来运行它,并可以通过停止你的应用程序获得该内存).这会破坏你的IabHelper()对象,然后必须再次创建和异步设置它.同样,由于这是在onCreate()方法中异步触发的,因此在IabHelper对象的设置完成之前,Google Play服务可以调用onActivityResult()来报告用户的购买操作,因为在onActivityResult()中,您需要要使用您的IabHelper实例,这可能会导致错误.看来你必须为任何事情做好准备.

这应该会给你你正在处理的东西的味道.IAB很难正是出于这些原因 - 异步内容的多个线程(例如,设置与购买相比,Android操作系统操作会阻止您的应用获取内存,很可能是您的应用的Google Play应用购买操作等待获得购买的结果).很多实现的内容(包括TrivialDrive示例)都是片状的,因为它隐含地依赖于你的应用程序保留在内存中,实际上它可能会被回收,或者因为它依赖于竞争条件的一条腿(例如设置)之前完成另一条腿(例如,购买发射)确实如此,等等.


cpl*_*ogl 22

执行purchaseFlow功能时遇到同样的问题.请查看Google示例中的Activity类,特别是该方法protected void onActivityResult(int requestCode, int resultCode, Intent data).你可能忘了实现这一个.此功能对于整个机制在没有故障的情况下工作至关重要.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.i(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);

    // Pass on the activity result to the helper for handling
    if (!inappBillingHelper.handleActivityResult(requestCode, resultCode, data)) {
        super.onActivityResult(requestCode, resultCode, data);
    }
    else {
        Log.i(TAG, "onActivityResult handled by IABUtil.");
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:此外,当您的手机上的gmail帐户密码错误时,问题也存在(今天发生在我身上).当然,所有Inapp计费功能都应该在手机上进行测试,但我认为很明显.