Development Tip

Android 인앱 결제 : 다른 비동기 작업 (진행 중)으로 인해 비동기 작업을 시작할 수 없습니다.

yourdevel 2020. 11. 2. 19:52
반응형

Android 인앱 결제 : 다른 비동기 작업 (진행 중)으로 인해 비동기 작업을 시작할 수 없습니다.


IabHelperGoogle의 자습서에서 권장 하는 유틸리티 클래스를 사용하고 있는데이 오류로 인해 큰 타격을 받고 있습니다. 분명히 IabHelper동시에 여러 비동기 작업을 실행할 수 없습니다. 재고 조사가 진행되는 동안 구매를 시작하려고 시도하는 것만으로도 성공했습니다.

여기onActivityResult제안 된대로 이미 기본 클래스에서 구현하려고 시도했지만 오류가 발생하기 전에 해당 메서드에 대한 호출도 얻지 못합니다. 그럼 내가 발견 있지만이 찾을 아무 생각이없는 방법을 - 그것은에없는 클래스를.flagEndAsyncIabHelper

이제이 문제를 해결하는 방법을 찾고 있습니다 (전체 she-bang을 다시 구현하지 않음). 내가 생각할 수있는 유일한 해결책 asyncActive은 비동기 작업이 시작되기 전에 확인 되는 부울 필드를 만들고 다른 작업이 활성화 된 경우에는 수행하지 않는 것입니다. 그러나 그것은 다른 많은 문제를 가지고 있으며 활동 전반에 걸쳐 작동하지 않습니다. 또한 비동기 작업 대기열을 만들고 전혀 실행하지 않는 대신 허용되는 즉시 실행하는 것을 선호합니다.

이 문제에 대한 해결책이 있습니까?


간단하고 까다로운 솔루션

purchaseItem 메서드 를 호출하기 전에 다음 줄을 추가하십시오.

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

그래서 당신의 코드는 이렇게 보입니다.

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

참고 : 다른 패키지에서 호출하는 경우 IabHelper에서 공용 flagEndAsync () 메서드를 만드는 것을 잊지 마십시오.


반드시 당신이 IabHelper의 전화 확인 handleActivityResult활동의에 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'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.");
    }
}

최신 정보:

  • 이제 인앱 결제 버전 3 API가 있습니다 (2013 년 버전은 무엇 이었나요?).
  • 코드 샘플이 Github 로 이동되었습니다 . 위의 스 니펫은 현재 샘플을 반영하도록 편집되었지만 논리적으로 이전과 동일합니다.

이것은 크래킹하기 쉽지 않았지만 필요한 해결 방법을 찾았습니다. 최근 구글에 상당히 실망한 그들의 안드로이드 웹 사이트는 엉망이되었고 (유용한 정보를 찾기가 매우 어려웠다) 샘플 코드가 형편 없다. 몇 년 전 Android 개발을 할 때 모든 것이 훨씬 쉬워졌습니다! 이것은 또 다른 예입니다 ...

실제로 IabUtil은 버그가 있으며 자체 비동기 작업을 제대로 호출하지 않습니다. 이 문제를 안정화하는 데 필요한 완전한 해결 방법 :

1) 메서드를 flagEndAsync공개합니다. 그것은 단지 보이지 않습니다.

2) 모든 리스너 호출 iabHelper.flagEndAsync을 통해 프로 시저가 올바르게 완료된 것으로 표시되었는지 확인합니다. 모든 청취자에게 필요한 것 같습니다.

3) 발생할 수 있는 try/catch것을 잡기 위해 통화를 둘러싸고 IllegalStateException그렇게 처리합니다.


결국 Kintaro와 비슷한 일을하게되었습니다. 그러나 캐치 끝에 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.

다음 코드와 같이 somthig를 수행해야합니다.

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

그런 다음 활동의 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);
        }
    }
}

조각으로 코딩하면 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/

3 월 15 일 버전에서이 문제를 해결했습니다. (변경 사항이없는 다른 파일은 15 일에 커밋되었습니다.)

"android.test.canceled"가 전송 된 SKU 일 때 null 인 텐트 엑스트라로 인해 테스트 중에 발생한 충돌을 수정해야했습니다. 나는 변했다 :

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;

이 문제가 가끔 발생했으며, 제 경우에는 IabHelper의 onServiceConnected 메서드가 기본 서비스의 연결이 끊겼다가 다시 연결되는 경우 (예 : 간헐적 인 네트워크 연결로 인해) 두 번 이상 호출 될 수 있다는 사실을 추적했습니다.

필자의 경우 특정 작업은 "다른 비동기 작업 (launchPurchaseFlow)이 진행 중이므로 비동기 작업 (인벤토리 새로 고침)을 시작할 수 없음)"이었습니다.

내 앱이 작성되는 방식에서는 queryInventory를 완료 할 때까지 launchPurchaseFlow를 호출 할 수 없으며, onIabSetupFinished 핸들러 함수에서만 queryInventory를 호출합니다.

IabHelper 코드는 onServiceConnected가 호출 될 때마다이 핸들러 함수를 호출하며 이는 두 번 이상 발생할 수 있습니다.

안드로이드 문서 onServiceDisconnected에 대한 말한다 :

서비스 연결이 끊어졌을 때 호출됩니다. 이는 일반적으로 서비스를 호스팅하는 프로세스가 충돌하거나 종료되었을 때 발생합니다. 이렇게해도 ServiceConnection 자체가 제거되지는 않습니다. 서비스에 대한이 바인딩은 활성 상태로 유지되며 다음에 서비스가 실행될 때 onServiceConnected (ComponentName, IBinder)에 대한 호출을 받게됩니다.

문제를 설명합니다.

틀림없이 IabHelper는 onIabSetupFinished 리스너 함수를 두 번 이상 호출해서는 안되지만 다른 한편으로는 이미 수행하고 결과를 얻은 경우이 함수 내에서 queryInventory를 호출하지 않음으로써 내 앱의 문제를 해결하는 것은 사소한 일이었습니다. .


IabHelpr 클래스의 또 다른 주요 문제는 여러 메서드에서 RuntimeExcptions (IllegalStateException)를 던지는 잘못된 선택입니다. 대부분의 경우 자신의 코드에서 RuntimeExeptions를 던지는 것은 확인되지 않은 예외 라는 사실 때문에 바람직하지 않습니다 . 이는 자체 애플리케이션을 방해하는 것과 같습니다. 잡히지 않으면 이러한 예외가 발생하여 앱이 중단됩니다.

이에 대한 해결책은 자체 검사 된 예외 를 구현 하고 IllegalStateException 대신 IabHelper 클래스를 변경하여 예외를 발생 시키는 것입니다. 그러면 컴파일 타임에 코드에서 발생할 수있는 모든 곳에서이 예외를 처리해야합니다.

내 사용자 지정 예외는 다음과 같습니다.

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

나는 같은 문제가 있었고 문제는 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);
    }
}

예, 나도이 문제에 직면했지만이 문제를 해결했지만 다음을 사용하여 해결했습니다.

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

위의 방법은 모든 플래그를 중지합니다. 나를위한 작업은 확인해야합니다.


이 답변은 @Wouter가 본 문제를 직접 해결합니다.

It is true that onActivityResult() must be triggered, like many people have said. However, the bug is that Google's code isn't triggering onActivityResult() in certain circumstances, i.e. when you're pressing your [BUY] button twice when running the debug build of your app.

Additionally, one major problem is that the user may be in a shaky environment (i.e. Bus or subway) and presses your [BUY] button twice... suddenly you've got yourself an exception !

At least Google fixed this embarrassing exception https://github.com/googlesamples/android-play-billing/commit/07b085b32a62c7981e5f3581fd743e30b9adb4ed#diff-b43848e47f8a93bca77e5ce95b1c2d66

Below is what I implemented in the same class where IabHelper is instantiated (for me, this is in the Application class) :

/**
 * 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
    }
}

My [BUY] button calls this launchPurchaseWorkflow() and passes the SKU and the activity the button is in (or if you're in a fragment, the enclosing activity)

NOTE: be sure to make IabHelper.flagEndAsync() public.

Hopefully, Google will improve this code in the near future; this problem is about 3 years old and it's still an ongoing problem :(


My solution is simple

1.) Make the mAsyncInProgress variable visible outside of IabHelper

public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

2.) Use this in your Activity like:

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

A little-modified version of NadtheVlad's answer that works like charm

private void makePurchase() {

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

The logic is simple, just put the launchPurchaseFlow() thing in a method and use recursion in the catch block. You still need to make flagEndAsync() public from the IabHelper class.


I have same issue, but it resolved! I think you must be not run "launchPurchaseFlow" on UI thread, try to run launchPurchaseFlow on UI thread, it would be working fine!

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

참고URL : https://stackoverflow.com/questions/15575605/android-in-app-billing-cant-start-async-operation-because-another-async-operat

반응형