Платеж для Android в приложении: не удается запустить асинхронную операцию, поскольку выполняется другая операция async (выполняется)

Я использую классы утилиты IabHelper, как рекомендовано в учебнике Google, и я сильно пострадал от этой ошибки. По-видимому, IabHelper не может одновременно запускать несколько асинхронных операций. Мне даже удалось ударить его, пытаясь начать покупку, пока инвентаризация еще не началась.

Я уже пытался реализовать onActivityResult в своем основном классе, как предлагалось здесь, но я даже не получаю вызов этого метода до того, как ошибка попадает, Затем я нашел этот, но я не знаю, где найти этот метод flagEndAsync - это не в классе IabHelper.

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

Любые решения для этой проблемы?

Ответ 1

Простое сложное решение

перед вызовом метода purchaseItem просто добавьте эту строку

  if (billingHelper != null) billingHelper.flagEndAsync();

чтобы ваш код выглядел так.

 if (billingHelper != null) billingHelper.flagEndAsync();
 purchaseItem("android.test.purchased");

Примечание. не забудьте сделать общедоступный флагEndAsync() в IabHelper, если вы вызываете его из другого пакета.

Ответ 2

Убедитесь, что вы вызываете IabHelper handleActivityResult в Activity onActivityResult и NOT в фрагменте onActivityResult.

Следующий фрагмент кода из TrivialDrive MainActivity:

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

    // Pass on the activity result to the helper for handling
    if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
        // not handled, so handle it ourselves (here 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.");
    }
}

Обновление:

  • В настоящее время существует In-app Billing Version 3 API (какова была версия в 2013 году?)
  • Образец кода переместился на Github. Фрагмент выше отредактирован, чтобы отразить текущий образец, но логически тот же, что и раньше.

Ответ 3

Это было непросто взломать, но я нашел необходимые обходные пути. В последнее время в Google разочарованы, их веб-сайты в Android стали беспорядком (очень сложно найти полезную информацию), а их примерный код плохой. Когда я несколько лет назад занимался разработкой Android, все стало намного проще! Это еще один пример этого...

Действительно, IabUtil является ошибкой, он неправильно отменяет свои собственные задачи async. Полный набор необходимых обходных решений для стабилизации этой вещи:

1) сделать метод flagEndAsync общедоступным. Это там, просто не видно.

2) имеют каждый вызов слушателя iabHelper.flagEndAsync, чтобы удостовериться, что процедура отмечена правильно; это кажется необходимым во всех слушателях.

3) объединить вызовы с помощью try/catch, чтобы поймать IllegalStateException, который может произойти, и обрабатывать его таким образом.

Ответ 4

В итоге я сделал что-то похожее на Кинтаро. Но добавлен mHelper.flagEndAsync() в конец catch. Пользователь по-прежнему получает тост, но в следующий раз, когда они нажимают кнопку покупки, операция async была убита, и кнопка покупки готова вернуться снова.

if (mHelper != null) {
    try {
    mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
    }       
    catch(IllegalStateException ex){
        Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
        mHelper.flagEndAsync();
    }
}

Ответ 5

У меня была такая же проблема, пока я не наткнулся на другой поток SO. Я включаю в себя затронутую версию кода, найденного в другом потоке, который необходимо включить в свою активность, которая инициализирует покупку.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // Pass on the activity result to the helper for handling
    // NOTE: handleActivityResult() will update the state of the helper,
    // allowing you to make further calls without having it exception on you
    if (billingHelper.handleActivityResult(requestCode, resultCode, data)) {
        Log.d(TAG, "onActivityResult handled by IABUtil.");
        handlePurchaseResult(requestCode, resultCode, data);
        return;
    }

    // What you would normally do
    // ...
}

Ответ 6

Простым трюком, который сделал это для меня, было создание метода в IabHelper:

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

а затем в вашем коде просто проверьте:

if (!mHelper.getAsyncInProgress())
    //launch purchase
else
    Log.d(TAG, "Async in progress already..)

Ответ 7

Найти flagEndAsync() внутри IabHelper.java файла и сделать его общедоступной.

Прежде чем попробовать покупку flagEndAsync() для своего IabHelper.

вы должны сделать somthig, как этот код:

mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(AboutActivity.this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, "payload-string");

Ответ 8

Действительно раздражающая проблема. Вот быстрое и грязное решение, которое не является совершенным кодовым мудрым, но оно удобнее для пользователя и избегает плохих оценок и сбоев:

if (mHelper != null) {
        try {
        mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
        }       
        catch(IllegalStateException ex){
            Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
        }
    }

Таким образом, пользователю просто нужно нажать другое время (в худшем случае - 2 раза) и получить всплывающее окно для расчета.

Надеюсь, что это поможет

Ответ 9

Просто проверьте код запроса onActivityResult на активность и, если он совпадает с PURCHASE_REQUEST_CODE, который вы использовали при покупке, просто передайте его фрагменту.

Когда вы добавляете или заменяете фрагмент в FragmentTransaction, просто устанавливайте тег:

fTransaction.replace(R.id.content_fragment, fragment, fragment.getClass().getName());

Затем по вашей активности onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == PurchaseFragment.PURCHASE_REQUEST_CODE) {
        PurchaseFragment fragment = getSuportFragmentManager().findFragmentByTag(PurchaseFragment.class.getNAme());
        if(fragment != null) {
            fragment.onActivityResult(requestCode, resultCode, data);
        }
    }
}

Ответ 10

Или вы можете получить последний файл IabHelper.java здесь: https://code.google.com/p/marketbilling/source/browse/

Версия 15 марта исправила это для меня. (Обратите внимание, что другие файлы без изменений были зафиксированы на 15-м)

Мне все же пришлось исправить один краш, который произошел во время тестирования, вызванного ненужными намерениями, когда "android.test.canceled" был отправленным SKU. Я изменил:

int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras().get(RESPONSE_CODE);

в

int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras() != null ? i.getExtras().get(RESPONSE_CODE) : null;

Ответ 11

У меня была эта проблема иногда, и в моем случае я отследил ее до того, что если метод onServiceConnected в IabHelper можно вызвать более одного раза, если базовая услуга отключается и повторно соединяется (например, из-за прерывистой сети соединение).

Конкретными операциями в моем случае были: "Не удается запустить асинхронную операцию (обновить инвентарь), потому что выполняется другая операция async (launchPurchaseFlow)".

Как написано мое приложение, я не могу вызвать launchpurchaseFlow до тех пор, пока у меня не будет завершен queryInventory, и я вызываю queryInventory только из моей функции onIabSetupFinished обработчика.

Код IabHelper вызовет эту функцию обработчика всякий раз, когда вызывается его onServiceConnected, что может случиться более одного раза.

Документация для Android для onServiceDisconnected говорит:

Вызывается, когда соединение с Сервисом потеряно. Обычно это происходит, когда процесс хостинга службы разбился или был убит. Это не устраняет ServiceConnection - это привязка к сервису будет оставаться активной, и вы будете получить вызов onServiceConnected (ComponentName, IBinder), когда Служба следующая работает.

что объясняет проблему.

Возможно, IabHelper не должен вызывать функцию onIabSetupFinished слушателя более одного раза, но, с другой стороны, было тривиально исправить проблему в моем приложении, просто не вызывая queryInventory из этой функции, если я уже сделал это и получили результаты.

Ответ 12

Еще одна серьезная проблема с классом IabHelpr - это плохой выбор метаданных RuntimeExcptions (IllegalStateException) несколькими способами. Выбрасывание RuntimeExceptions из вашего собственного кода в большинстве случаев нежелательно из-за того, что они являются неконтролируемыми исключениями. Это похоже на саботаж собственного приложения - если не поймано, эти исключения будут пузыриться и разбивать ваше приложение.

Решением этого является реализация собственного проверенного исключения и изменение класса IabHelper для его использования вместо исключения IllegalStateException. Это заставит вас обрабатывать это исключение везде, где оно может быть выброшено в ваш код во время компиляции.

Вот мое обычное исключение:

public class MyIllegalStateException extends Exception {

    private static final long serialVersionUID = 1L;

    //Parameterless Constructor
    public MyIllegalStateException() {}

    //Constructor that accepts a message
    public MyIllegalStateException(String message)
    {
       super(message);
    }
}

Как только мы внесем изменения в класс IabHelper, мы можем обработать наше проверенное исключение в нашем коде, где мы называем методы класса. Например:

try {
   setUpBilling(targetActivityInstance.allData.getAll());
} catch (MyIllegalStateException ex) {
    ex.printStackTrace();
}

Ответ 13

если вы код в фрагменте, то вы этот код в IabHelper.java

void flagStartAsync(String operation) {
    if (mAsyncInProgress) {
        flagEndAsync();
    }
    if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
            operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
    mAsyncOperation = operation;
    mAsyncInProgress = true;
    logDebug("Starting async operation: " + operation);
}

Ответ 14

У меня была такая же проблема, и проблема заключалась в том, что я не реализовал метод onActivityResult.

@Override
protected void onActivityResult(int requestCode,
            int resultCode,
            Intent data)
{
    try
    {
        if (billingHelper == null)
        {
            return;
        } else if (!billingHelper.handleActivityResult(requestCode, resultCode, data))
        {
            super.onActivityResult(requestCode, resultCode, data);
        }
    } catch (Exception exception)
    {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

Ответ 15

Да, я тоже сталкиваюсь с этой проблемой, но я решил это, но я решил использовать

IabHelper mHelpermHelper = new IabHelper(inappActivity, base64EncodedPublicKey);
  mHelper.flagEndAsync();

Вышеуказанный метод останавливает все флаги. Его работа для меня должна проверять

Ответ 16

В этом ответе прямо рассматривается проблема, которую @Wouter видел...

Верно, что onActivityResult() должен быть запущен, как говорили многие люди. Однако ошибка заключается в том, что в некоторых случаях код Google не запускает onActivityResult(), т.е. Когда вы дважды нажимаете кнопку [BUY] при запуске отладочной сборки вашего приложения.

Кроме того, одна из основных проблем заключается в том, что пользователь может находиться в неустойчивой среде (например, в автобусе или метро) и дважды нажимает кнопку [BUY]... внезапно у вас есть исключение!

По крайней мере, Google исправил это неловкое исключение https://github.com/googlesamples/android-play-billing/commit/07b085b32a62c7981e5f3581fd743e30b9adb4ed#diff-b43848e47f8a93bca77e5ce95b1c2d66

Ниже приведено то, что я реализовал в том же классе, где создается IabHelper (для меня это относится к классу Application):

/**
 * invokes the startIntentSenderForResult - which will call your activity onActivityResult() when it finished
 * NOTE: you need to override onActivityResult() in your activity.
 * NOTE2: check IAB code updates at https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive/app/src/main/java/com/example/android/trivialdrivesample/util
 * @param activity
 * @param sku
 */
protected boolean launchPurchaseWorkflow(Activity activity, String sku)
{
    if (mIabIsInitialized)
    {
        try
        {
            mHelper.launchPurchaseFlow(
                    activity,
                    sku,
                    Constants.PURCHASE_REQUEST_ID++,// just needs to be a positive number and unique
                    mPurchaseFinishedListener,
                    Constants.DEVELOPER_PAYLOAD);
            return true;//success
        }
        catch (IllegalStateException e)
        {
            mHelper.flagEndAsync();
            return launchPurchaseWorkflow(activity, sku);//recursive call
        }
    }
    else
    {
        return false;//failure - not initialized
    }
}

Моя кнопка [BUY] вызывает это launchPurchaseWorkflow() и передает SKU и активность, в которой находится кнопка (или если вы находитесь в фрагменте, входящем в действие)

ПРИМЕЧАНИЕ: обязательно сделайте IabHelper.flagEndAsync() общедоступным.

Надеюсь, Google улучшит этот код в ближайшем будущем; эта проблема составляет около 3 лет, и это все еще проблема: (

Ответ 17

У меня такая же проблема, но она решена! Я думаю, что вы не должны запускаться "launchPurchaseFlow" в потоке пользовательского интерфейса, попробуйте запустить launchPurchaseFlow в потоке пользовательского интерфейса, он будет работать нормально!

mActivity.runOnUiThread(new Runnable(){
            public void run(){
                mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
            }
        });