【问题标题】:in-app billing doesn't work: "IAB Helper is not set up"应用内计费不起作用:“未设置 IAB 助手”
【发布时间】:2012-12-19 00:51:40
【问题描述】:

我尝试在我的应用中包含应用内计费并出于测试目的,基于应用内计费版本 3 的“TrivialDrive”示例的整个过程(并将 IAB 文件的未修改版本实现为在演示的“util”子目录中提供),但它对我不起作用 - 在 LogCat 上,就在应用程序因错误终止之前,它给出消息“应用程序内计费错误:非法状态operation (launchPurchaseFlow): IAB Helper is not setup.”(在 startRegistered() 函数被触发并给我 LOG 消息“Register button clicked; launch purchase flow for upgrade.”之后)...

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

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

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

}

这里有更多来自 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)

【问题讨论】:

  • 这可能是因为设置尚未完成或不成功,因此在 IabHelper 中,连接服务已检查是否支持计费,但不支持或服务根本没有连接。你的整个 logcat 是什么(或至少几行)
  • 谢谢,我刚刚在上面加了。
  • 您是在 4.0 设备上进行测试吗?我遇到了同样的问题,但它在较低的 API 上运行良好。
  • @richey 你能从 iabhelper 设置的地方添加你的日志猫吗
  • 我做到了,感谢您的帮助。

标签: android in-app-purchase google-play in-app-billing


【解决方案1】:

根本问题是 startRegistered() 被调用以直接响应 UI 用户单击,而 IabHelper 对象的设置是异步触发的,因此直到完成才知道通过 onIabSetupFinished() 接收到异步响应。

您的 startRegistered() 方法由用户点击触发,并调用 launchPurchaseFlow(),这反过来要求 IabHelper 对象已经完成设置,但如果用户在收到确认之前点击触发购买 (要么是因为设置失败,要么是因为用户在抽奖时异常快速),那么设置将不会完成,并且 launchPurchaseFlow() 将报告您看到的错误。对于您的 logcat,延迟为 14 秒,这通常是足够的时间,但是......在这种情况下可能不是。或者,也许出了点问题,无论您等了多长时间,您都永远无法连接。

在您的 logcat 中,没有指示“计费服务已连接”的消息,如果要完成设置,这是必须发生的第一件事。由于这不会发生,您也不会看到来自 onIabSetupFinished() 的任何消息(成功或失败)。

由于需要异步响应,这很棘手。一种方法是禁用用于触发购买的按钮,直到您的 onIabSetupFinished() 成功返回。这将阻止触发购买,直到 IabHelper 对象已成功设置。当然,如果设置失败,您将拥有一个不起作用的按钮,但至少您可以告诉用户发生了什么(通过发出一条消息,表明您正在等待设置完成 - 例如,作为按钮文本)。

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

这应该让您了解您正在处理的内容。由于这些原因,IAB 很困难 - 异步内容的多个线程(例如,设置与购买与 Android 操作系统操作阻止您的应用程序获取内存以供您的应用程序使用的 Google Play 应用程序购买操作使用正在等待获取购买结果)。很多实现的东西(包括 TrivialDrive 示例)都是不稳定的,因为它隐含地依赖于你的应用程序留在内存中,而实际上它可能会被回收,或者因为它依赖于竞争条件的一个分支(例如设置)在之前完成另一条腿(例如,购买启动)会,等等。

【讨论】:

  • 我面临同样的问题,但在不同的上下文中,我的购买流程是通过单击按钮启动的,并且它完美地工作。但只有在 onCreate() mHelper.queryInventoryAsync(mGotInventoryListener); 中的注释行之后失败是因为 IabHelper 类中的 var mSetupDone 仍然为假。但是 result.isSuccess() 正在返回 true。
  • @Sanjay Singh:看到这个答案:stackoverflow.com/questions/17944522/…
  • 嗨,卡尔,我也遇到了同样的问题。我收到了很多关于这个的应用报告。尽管如此,我测试过的设备运行良好。如果用户没有互联网连接,它会以同样的方式崩溃吗?我怀疑互联网是真正的问题,因为设置完成后,用户需要大约 30 秒才能启动购买流程。设置不会在 30 秒内完成是没有意义的吧?
  • 嗨,卡尔,我也遇到了同样的问题。我收到了很多关于这个的应用报告。尽管如此,我测试过的设备运行良好。如果用户没有互联网连接,它会以同样的方式崩溃吗?如果是这样,我怀疑互联网可能是真正的问题,因为设置发生后,用户将在大约 30 秒后单击一个按钮启动购买流程。设置不会在 30 秒内完成是没有意义的吧?
  • @idish:由于您无法重现错误,建议您尝试在具有开发人员选项“不要保留活动”的设备上进行测试,这会导致您的应用程序被销毁每次用户离开它时,模拟操作系统在需要回收内存时会做什么,这样您就可以确保您的应用程序在这些条件下工作。如果您有一个缺少该选项的旧 Android 版本,您可以通过在此处从 PandaDev(无连接)安装开发者工具应用程序来获得类似的功能:play.google.com/store/apps/details?id=com.ggb.development
【解决方案2】:

在执行 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.");
    }
}

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

【讨论】:

  • 不,如果你看我上面的代码sn-p,实际上我实现了它。谢谢。
  • 哦,我没注意到。尽管如此,从您附加的堆栈调用看来,当 startSetup 仍在运行(未完成)或该函数中的某些内容出错时,您似乎正在尝试触发 purchaseFlow 方法。我建议在触发购买之前检查 startSetup 函数中发生的情况(调试它)
  • 我没有看到 Log.d(TAG, "Setup finished.");在你的 onIabSetupListener 中声明。所以看起来你没有连接。我在一些较旧的测试设备上看到了类似的问题。它适用于较新的。您在哪个设备/版本的 Android 上运行它?
  • 当我检查提到的 startSetup() 函数时,我遇到了同样的问题,它显示 mSetupDone 它具有错误值,但在平板电脑上它工作正常。我不知道为什么
  • 如果没有添加谷歌账户,我也会遇到同样的问题。就我而言,我在设备中添加了 google 帐户并解决了问题。
【解决方案3】:

我刚刚解决了完全相同的问题。 IabHelper-Setup 启动,但在那之后,什么也没有发生。单击应用内购买会返回与您遇到的完全相同的错误。

这是我的发现:我只使用了 eclipse 中的模拟器。当我读到需要某个 Google Play 版本时,我注意到我的测试仿真驱动器上完全没有 Google Play。

当我使用真正的手机时,它可以完美运行!因此,如果您碰巧仍然遇到该问题,请尝试使用真实设备(如果您有可用的设备)。这应该够了吧。

【讨论】:

  • 文档 (developer.android.com/google/play/billing/billing_testing.html) 明确指出“您不能使用模拟器测试应用内结算;您必须在设备上安装应用程序才能测试应用内结算。”您的手机上还必须安装以下版本的 Google Play(适用于 API 版本 3): 购买和查询托管应用内商品需要 Google Play 客户端版本 3.9.16 或更高版本。购买和查询订阅项目需要 Google Play 客户端版本 3.10.10 或更高版本。
  • 真不好意思,我显然没有读过。但至于最初的问题,我有相同的错误日志,所以也许 OP 犯了同样的错误。
【解决方案4】:

我遇到的另一件事;虽然您的设备上可能有支持最新版本的应用内计费的最新版本的 google play,但其他用户可能没有。虽然理论上由此引起的崩溃应该出现在开发人员控制台中,但在我实施 firebase 之前我看不到这些崩溃......然后我看到了很多。我最终做的是使用 try catch 并将没有最新版本的 google play 或在 google play store 上遇到问题的用户链接到此页面https://support.google.com/googleplay/answer/1050566?hl=en

try {
        mHelper.launchPurchaseFlow(this, SKU_PRO_LT, RC_REQUEST,
                mPurchaseFinishedListener, payload);
    } catch (Exception e) { //with IabHelper.IabAsyncInProgressException the code still fatally crashes for some reason
        //complain("Error launching purchase flow. Another async operation in progress.");
        alert2("[error msg]");
        setWaitScreen(false);
    }

alert2 只是一个对话框,其中包含指向上述网页的链接。

但首先我建议在几部朋友的手机上测试应用购买,以确保这是 Play 商店更新问题而不是代码问题。

【讨论】:

    【解决方案5】:

    如果您安装了幸运补丁程序并且修改了游戏商店,您将收到消息“iab helper is not setup”。 解决方案:在您的 PC 或幸运补丁程序上安装一个 android 模拟器,并且修改后的 playstore 将不会安装。您将不再有错误消息并且能够进行付款,它对我来说非常适合:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-20
      • 2013-11-01
      相关资源
      最近更新 更多