26

我尝试在我的应用程序中包含应用程序内计费,并且出于测试目的,基于应用程序内计费版本 3 的“TrivialDrive”示例的整个过程(并实施 IAB 文件的未修改版本,如在"util" 演示的子目录),但它对我不起作用 - 在 LogCat 上,就在应用程序因错误终止之前,它给出消息“应用内计费错误:操作的非法状态(launchPurchaseFlow):IAB未设置助手。 ”(在 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;
    }
}

}

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

5 回答 5

31

根本问题是 startRegistered() 是在直接响应 UI 用户点击时被调用的,而 IabHelper 对象的设置是异步触发的,因此在通过 onIabSetupFinished() 接收到异步响应之前无法知道它已经完成。

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

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

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

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

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

于 2013-04-01T21:40:48.057 回答
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.");
    }
}

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

于 2012-12-20T22:19:44.350 回答
4

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

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

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

于 2013-02-12T11:22:45.650 回答
2

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

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 只是一个对话框,其中包含指向上述网页的链接。

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

于 2017-02-12T22:32:34.923 回答
0

如果您安装了幸运的补丁程序并且修改了 playstore,您将收到消息“iab helper is not setup”。解决方案:在您的 PC 或幸运补丁程序上安装 android 模拟器,修改后的 playstore 将不会安装。您将不再收到错误消息,并且可以付款,它对我来说非常完美 :)

于 2020-02-24T23:54:52.913 回答