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

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

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

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

Какие-нибудь решения для этой проблемы?

19 ответов

Простое хитрое решение

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

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

так ваш код выглядит так

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

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

Обязательно позвоните в IabHelper's handleActivityResult в деятельности onActivityResult, а не во фрагменте onActivityResult,

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

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

Обновить:

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

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

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

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

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

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

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

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

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

Перед попыткой покупки позвоните flagEndAsync() для тебя IabHelper,

Вы должны сделать что-то вроде этого кода:

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

У меня была та же проблема, пока я не наткнулся на другой поток 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
    // ...
}

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

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

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

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

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

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 раза) и получить всплывающее окно

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

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

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

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

Тогда по вашей активности на ActiveResult

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

Если вы кодируете фрагмент, то вы этот код в 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);
}

Или вы можете получить последнюю версию файла 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;

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

Решением этой проблемы является реализация вашего собственного проверенного исключения и изменение класса 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();
}

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

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

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

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

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

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

который объясняет проблему.

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

Мое решение простое

1.) Сделать переменную mAsyncInProgress видимой за пределами IabHelper

public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

2.) Используйте это в своей деятельности, как:

...
if (mIabHelper.AsyncInProgress()) return;
mIabHelper.queryInventoryAsync(...);
...

Немного модифицированная версия ответа NadtheVlad, которая работает как шарм

private void makePurchase() {

    if (mHelper != null) {
        try {
            mHelper.launchPurchaseFlow(getActivity(), ITEM_SKU, 10001, mPurchaseFinishedListener, "");
        }
        catch(IllegalStateException ex){
            mHelper.flagEndAsync();
            makePurchase();
        }
    }
}

Логика проста, просто поставьте launchPurchaseFlow() вещь в методе и использовать рекурсию в блоке catch. Вам все еще нужно сделать flagEndAsync() общественность из IabHelper учебный класс.

Этот ответ напрямую решает проблему, которую @Wouter видел...

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

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

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

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

/**
 * invokes the startIntentSenderForResult - which will call your activity's onActivityResult() when it's 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
    }
}

Моя кнопка [КУПИТЬ] вызывает это launchPurchaseWorkflow() и передает SKU и действие, в котором находится кнопка (или, если вы находитесь во фрагменте, включающее действие)

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

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

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

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

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

У меня была та же проблема, и проблема была в том, что я не реализовал метод 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);
    }
}

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

mActivity.runOnUiThread(new Runnable(){
            public void run(){
                mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
            }
        });
Другие вопросы по тегам