2

I am using Google Play In app Billing and submit my request using the IabHelper, which under the hood uses startIntentSenderForResult.

As expected, upon completion of the call (Google Play Activity closes) the result is returned to my Activity in the onActivityResult method which then delegates it to the waiting OnIabPurchaseFinishedListener instance via IabHelper. If an error occurs during the purchase, a dialog is shown to the user (my code) in the finished listener. It appears as though the result is being returned to my application prior to the user being finished with the called play store activity.

Details: In my app I press the purchase button which launches the purchase intent, the play store is shown and an error shows up (item not available, it is inactive (deliberately for negative testing)).

04-03 20:21:14.371: D/com.satalyst.android.ui.PurchaseAction(10129): Launching purchase flow request for sku: demo_test
04-03 20:21:14.371: D/IabHelper(10129): Starting in-app billing setup.
04-03 20:21:14.446: D/IabHelper(10129): Billing service connected.
04-03 20:21:14.446: D/IabHelper(10129): Checking for in-app billing 3 support.
04-03 20:21:14.461: D/IabHelper(10129): In-app billing version 3 supported for com.satalyst.android
04-03 20:21:14.476: D/IabHelper(10129): Subscriptions AVAILABLE.
04-03 20:21:14.476: D/IabHelper(10129): Starting async operation: launchPurchaseFlow
04-03 20:21:14.476: D/IabHelper(10129): Constructing buy intent for demo_test, item type: inapp
04-03 20:21:14.506: D/IabHelper(10129): Launching buy intent for demo_test. Request code: 1
04-03 20:21:14.846: D/IabHelper(10129): Ending async operation: launchPurchaseFlow
04-03 20:21:14.846: E/IabHelper(10129): In-app billing error: Null data in IAB activity result.
04-03 20:21:14.846: D/com.satalyst.android.ui.PurchaseAction$PurchaseCompleteListener(10129): Received purchase code: -1002
04-03 20:21:14.846: E/com.satalyst.android.ui.PurchaseAction$PurchaseCompleteListener(10129): Unable to purchase item: demo_test due to error: -1002 with message: Null data in IAB result (response: -1002:Bad response received)
04-03 20:21:14.856: I/ACTIVITY-RESUME(10129): com.satalyst.android.ui.MainActivity@4151b760
04-03 20:21:15.526: D/com.satalyst.android.ui.MainActivity(10129): Request update of cache
04-03 20:21:15.601: D/com.satalyst.android.ui.HomeFragment(10129): Payload currently selected: demopayload updating UI
04-03 20:21:16.091: E/WindowManager(10129): Activity com.satalyst.android.ui.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@41a9d218 that was originally added here
04-03 20:21:16.091: E/WindowManager(10129): android.view.WindowLeaked: Activity com.satalyst.android.ui.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@41a9d218 that was originally added here
04-03 20:21:16.091: E/WindowManager(10129):     at android.view.ViewRootImpl.<init>(ViewRootImpl.java:386)
04-03 20:21:16.091: E/WindowManager(10129):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:267)
04-03 20:21:16.091: E/WindowManager(10129):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:215)
04-03 20:21:16.091: E/WindowManager(10129):     at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:140)
04-03 20:21:16.091: E/WindowManager(10129):     at android.view.Window$LocalWindowManager.addView(Window.java:537)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.Dialog.show(Dialog.java:278)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.AlertDialog$Builder.show(AlertDialog.java:932)
04-03 20:21:16.091: E/WindowManager(10129):     at com.satalyst.android.ui.PurchaseAction.showSendEmailErrorDialog(PurchaseAction.java:176)
04-03 20:21:16.091: E/WindowManager(10129):     at com.satalyst.android.ui.PurchaseAction.access$5(PurchaseAction.java:152)
04-03 20:21:16.091: E/WindowManager(10129):     at com.satalyst.android.ui.PurchaseAction$PurchaseCompleteListener.onIabPurchaseFinished(PurchaseAction.java:240)
04-03 20:21:16.091: E/WindowManager(10129):     at com.satalyst.android.iab.IabHelper.handleActivityResult(IabHelper.java:434)
04-03 20:21:16.091: E/WindowManager(10129):     at com.satalyst.android.ui.MainActivity.onActivityResult(MainActivity.java:225)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.Activity.dispatchActivityResult(Activity.java:4649)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.ActivityThread.deliverResults(ActivityThread.java:2988)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.ActivityThread.handleSendResult(ActivityThread.java:3035)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.ActivityThread.access$1100(ActivityThread.java:127)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1189)
04-03 20:21:16.091: E/WindowManager(10129):     at android.os.Handler.dispatchMessage(Handler.java:99)
04-03 20:21:16.091: E/WindowManager(10129):     at android.os.Looper.loop(Looper.java:137)
04-03 20:21:16.091: E/WindowManager(10129):     at android.app.ActivityThread.main(ActivityThread.java:4507)
04-03 20:21:16.091: E/WindowManager(10129):     at java.lang.reflect.Method.invokeNative(Native Method)
04-03 20:21:16.091: E/WindowManager(10129):     at java.lang.reflect.Method.invoke(Method.java:511)
04-03 20:21:16.091: E/WindowManager(10129):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
04-03 20:21:16.091: E/WindowManager(10129):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
04-03 20:21:16.091: E/WindowManager(10129):     at dalvik.system.NativeStart.main(Native Method)

At this point the play store application is still shown (in the foreground). It looks to me as if the error is caused because the operation returns to the calling activity (which doesn't have focus), resulting in the error when the dialog is attached (leaks immediately upon creation). Pressing "OK" in the error in the play store activity results in the following (a new activity for my application is created and given focus).

04-03 20:21:18.296: I/com.satalyst.android.ui.MainActivity(10129): Loading selected payload: demopayload
04-03 20:21:18.341: I/ACTIVITY-RESUME(10129): com.satalyst.android.ui.MainActivity@41adf048
04-03 20:21:18.601: D/com.satalyst.android.ui.MainActivity(10129): Request update of cache
04-03 20:21:18.626: D/com.satalyst.android.ui.HomeFragment(10129): Payload currently selected: demopayload updating UI

Fixing this has me stumped as I would like to show a dialog in my Activity when a purchase error occurs allowing them to email me with details so that I can help customers with purchase errors as a priority. I am sure there are some hack fixes I could do, perhaps persisting the details when the error occurs and returns prematurely, then checking every time the activity is resumed for a persisted failure and taking the appropriate action; however this feels like a nasty work around.

Stacktrace without dialog:

04-03 21:15:40.236: D/com.satalyst.android.ui.PurchaseAction(12415): Launching purchase flow request for sku: demo_test
04-03 21:15:40.236: D/IabHelper(12415): Starting async operation: launchPurchaseFlow
04-03 21:15:40.236: D/IabHelper(12415): Constructing buy intent for demo_test, item type: inapp
04-03 21:15:40.276: D/IabHelper(12415): Launching buy intent for demo_test. Request code: 1
04-03 21:15:40.806: D/IabHelper(12415): Ending async operation: launchPurchaseFlow
04-03 21:15:40.806: E/IabHelper(12415): In-app billing error: Null data in IAB activity result.
04-03 21:15:40.806: D/com.satalyst.android.ui.PurchaseAction$PurchaseCompleteListener(12415): Received purchase code: -1002
04-03 21:15:40.811: E/com.satalyst.android.ui.PurchaseAction$PurchaseCompleteListener(12415): Unable to purchase item: demo_test due to error: -1002 with message: Null data in IAB result (response: -1002:Bad response received)
04-03 21:15:40.811: I/ACTIVITY-RESUME(12415): com.satalyst.android.ui.MainActivity@414cb8e0
04-03 21:15:41.681: I/com.satalyst.android.ui.MainActivity(12415): Destroying MainActivity: com.satalyst.android.ui.MainActivity@414cb8e0
04-03 21:15:42.396: I/ACTIVITY-RESUME(12415): com.satalyst.android.ui.MainActivity@414d8f5

The code which triggers the purchase:

/**
 * Stage #1, ensure the server is contactable and has correct protocol
 * version
 */
@Override
public void onClick() {
    // Verify server can be communicated with
    final ProgressDialog dlg = new ProgressDialog(activity);
    dlg.setMessage(activity.getString(R.string.preparing_purchase));
    dlg.setIndeterminate(true);
    dlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
    dlg.show();
    AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
            try {
                versionAgent.checkCloudServiceVersion();
                dlg.dismiss();
                purchasePayload(payload);
            } catch (VersionQueryException e) {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(
                                activity,
                                activity.getResources().getString(
                                        R.string.query_version_error),
                                Toast.LENGTH_LONG).show();
                    }
                });
                Log.e(this,
                        "Purchase disabled, could not query service version");
            } catch (VersionMismatchException e) {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(
                                activity,
                                activity.getResources()
                                        .getString(
                                                R.string.query_version_update_required),
                                Toast.LENGTH_LONG).show();
                    }
                });
                Log.e(this, "Purchase disabled, update is required");
            }
        }
    });
}

/**
 * Stage #2, trigger IAB purchase of given payload
 * 
 * @param payload
 */
private void purchasePayload(@Nonnull final Payload payload) {
    final String sku = payload.getSku();
    Log.d(this, "Launching purchase flow request for sku: " + sku);
    final IabHelper helper = ((MainActivity) activity).helper;
    final OnIabPurchaseFinishedListener onPurchasedListener = new PurchaseCompleteListener();

    if (!helper.isSetup()) {
        helper.startSetup(new OnIabSetupFinishedListener() {
            @Override
            public void onIabSetupFinished(IabResult result) {
                helper.launchPurchaseFlow(activity, sku,
                        BillingConstants.PURCHASE_REQUEST,
                        onPurchasedListener);
            }
        });
    } else {
        helper.launchPurchaseFlow(activity, sku,
                BillingConstants.PURCHASE_REQUEST, onPurchasedListener);
    }
}

The onResume/onPause/onActivityResult and onDestroy in MainActivity code:

/**
 * {@inheritDoc}
 */
@Override
protected void onResume() {
    super.onResume();
    Log.i("ACTIVITY-RESUME", this.toString());
    eventBus.register(this);
}

/**
 * {@inheritDoc}
 */
@Override
protected void onPause() {
    super.onPause();
    eventBus.unregister(this);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.i(this, "Destroying MainActivity: "+ this);
}

/**
 * {@inheritDoc}
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (helper == null
            || !helper.handleActivityResult(requestCode, resultCode, data)) {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

The OnIabPurchaseFinishedListener which is triggered in onActivityResult via helper:

private final class PurchaseCompleteListener implements
        OnIabPurchaseFinishedListener {

    @Override
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
        Log.d(this, "Received purchase code: " + result.getResponse());

        if (result.isSuccess()) {
            // Removed this code since else is triggered in trouble case.
        } else {
            int responseCode = result.getResponse();
            switch (responseCode) {
            case IabHelper.BILLING_RESPONSE_RESULT_USER_CANCELED:
                Log.i(this, "Billing response received: User Canceled");
                // Do nothing
                break;
            default:
                String message = result.getMessage();
                Log.e(this, "Unable to purchase item: " + payload.getSku()
                        + " due to error: " + responseCode
                        + " with message: " + message);
                break;
            }
        }
    }
}

Activity definition in manifest XML

    <activity
        android:name="com.satalyst.android.ui.MainActivity"
        android:label="@string/app_name"
        android:launchMode="singleInstance"
        android:screenOrientation="portrait"
        android:theme="@android:style/Theme.Holo.Light" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

Error Log (full) which shows premature Activity destruction:

D/IabHelper( 4462): Launching buy intent for demo_test. Request code: 1
I/power   ( 2004): *** acquire_dvfs_lock : lockType : 1  freq : 1200000 
I/ActivityManager( 2004): START {intent.toShortString} from pid -1
D/PowerManagerService( 2004): acquireDVFSLockLocked : type : DVFS_MIN_LIMIT  frequency : 1200000  uid : 1000  pid : 2004  tag : ActivityManager
W/ActivityManager( 2004): mDVFSLock.acquire()
D/dalvikvm( 2004): JIT code cache reset in 2 ms (1048532 bytes 1/0)
D/dalvikvm( 2004): GC_CONCURRENT freed 1981K, 18% free 19557K/23623K, paused 3ms+11ms
I/ALSAModule( 1837): Terminated ALSA PLAYBACK device hifi
W/WifiStateTracker( 2004): getNetworkInfo : NetworkInfo: type: wifi[], state: UNKNOWN/IDLE, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: false
E/WifiP2pStateTracker( 2004): getNetworkInfo : NetworkInfo: type: wifi_p2p[], state: UNKNOWN/IDLE, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: false
I/SurfaceFlinger( 1834): id=48 Removed idx=3 Map Size=4
I/SurfaceFlinger( 1834): id=48 Removed idx=-2 Map Size=4
D/dalvikvm( 4462): GC_CONCURRENT freed 12727K, 37% free 44670K/69959K, paused 2ms+4ms
D/ActivityManager( 2004): Trying to launch applicationName
V/GCMRegistrar( 3130): Is registered on server: true
W/WifiStateTracker( 2004): getNetworkInfo : NetworkInfo: type: wifi[], state: UNKNOWN/IDLE, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: false
E/WifiP2pStateTracker( 2004): getNetworkInfo : NetworkInfo: type: wifi_p2p[], state: UNKNOWN/IDLE, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: false
I/SurfaceFlinger( 1834): id=49(3) createSurface 0x398ec (1x1),1 flag=400
D/Finsky  ( 3130): [1] SelfUpdateScheduler.checkForSelfUpdate: Skipping DFE self-update. Local Version [8016014] >= Server Version [-1]
W/InputManagerService( 2004): Starting input on non-focused client com.android.internal.view.IInputMethodClient$Stub$Proxy@4211eaa0 (uid=10110 pid=4462)
I/ActivityManager( 2004): Displayed shortComponentName: +105ms
V/yamaha::media::VolumeCtrl( 1837): VolumeCtrl::createVolume()
V/yamaha::media::VolumeCtrl( 1837): VolumeCtrl::setVolume()
D/yamaha::media::VolumeCtrl( 1837): VolumeCtrl::setVolume() FM Playback: Ready
D/yamaha::media::VolumeCtrl( 1837): VolumeCtrl::setVolume() VoiceCall: Ready
W/Finsky  ( 3130): [1] CarrierParamsAction.run: Saving carrier billing params failed.
E/Finsky  ( 3130): [184] FileBasedKeyValueStore.delete: Attempt to delete 'params1Fzx-vZz47HyPCTxqb1ncg' failed!
D/Finsky  ( 3130): [1] GetBillingCountriesAction.run: Skip getting fresh list of billing countries.
I/SurfaceFlinger( 1834): id=47 Removed idx=1 Map Size=4
I/SurfaceFlinger( 1834): id=47 Removed idx=-2 Map Size=4
I/power   ( 2004): *** release_dvfs_lock : lockType : 1 
D/PowerManagerService( 2004): releaseDVFSLockLocked : all DVFS_MIN_LIMIT are released 
W/ActivityManager( 2004): mDVFSLock.release()
I/ACTIVITY-IS-FINISHING( 4462): false com.satalyst.android.ui.MainActivity@416d9098
I/ACTIVITY-IS-FINISHING( 4462): false com.satalyst.android.ui.MainActivity@416d9098
I/ACTIVITY-IS-FINISHING( 4462): false com.satalyst.android.ui.MainActivity@416d9098
I/ACTIVITY-DESTROY( 4462): com.satalyst.android.ui.MainActivity@416d9098
D/com.satalyst.android.ui.MainActivity( 4462): Destroying helper.
D/IabHelper( 4462): Disposing.
D/IabHelper( 4462): Unbinding from service.
D/STATUSBAR-NetworkController( 2127): onDataActivity: direction=3
E/DataRouter( 1832): usb connection is true 
E/DataRouter( 1832): DSR is ON. Don't send DTR ON.
E/Finsky  ( 3130): [1] CheckoutPurchase.setError: type=PURCHASE_FAILED, code=-1, permissionCode=4, message=The item you requested is not available for purchase
Syntax
  • 2,155
  • 2
  • 23
  • 34
  • You might clarify your question with more details of the symptoms (e.g., complete stack trace), plus explain your definition of "disposed" (which is not a term generally used in Android development -- I am guessing that you mean "destroyed"). – CommonsWare Apr 03 '13 at 12:05
  • @CommonsWare quite right. I have added the stack trace; thank you for your feedback. NB: In this stack trace I have taken out the log message for destroy. I believe the first destroy occurs as a result of the leaked window exception which I don't know how to avoid/fix and is my core problem. – Syntax Apr 03 '13 at 12:35
  • You might try temporarily removing the dialog and doing something else in the main UI of the activity (e.g., show a crouton). If this just completely works, perhaps it's a workaround. Otherwise, you might get more clues as to what is triggering your activity to be destroyed. – CommonsWare Apr 03 '13 at 13:05
  • I removed the dialog and now I have no error. However, the first Activity instance is still destroyed after the handler completes processing and the second is still created which really leaves me stumped. It seems to be because the response occurs whilst Play still has focus, it handles the registered response handler and is destroyed.This really only leaves me the option of persisting the failure. Unfortunately I believe onActivityResult is called before OnResume in the first Activity so I cannot easily persist it in oActivityResult to be dealt with on next onResume. – Syntax Apr 03 '13 at 13:22
  • I have updated the question with additional information including all relevant source code, any feedback greatly appreciated. Thank you very much! – Syntax Apr 04 '13 at 08:24
  • @Syntax Is your MainActivity `singleInstance`? – Sherif elKhatib Apr 05 '13 at 09:39
  • @SherifelKhatib certainly, I have added the XML from the manifest in the code snippets above. Cheers – Syntax Apr 05 '13 at 13:25
  • well @Syntax that is your code's problem. Remove the "singleInstance" and try again. – Sherif elKhatib Apr 05 '13 at 13:32
  • I have disabled singleInstance and tried again. I have as much as possible aligned my code with the billing demo. Helper definition in onCreate and destruction in onDestroy; no singleInstance etc. I am still experiencing a destroy and re-create after calling billing. However, with no singleInstance the destruction isn't really surpising. It seems to me that it is expected behaviour, what I don't understand is should I (if so how) be persisting my helper instance and it's long running task in some way that survives the destruction of the first Activity instance? (the demo doesn't do this). – Syntax Apr 07 '13 at 00:41
  • Because currently, onDestroy will result in my helper instance (and it's OnIabPurchaseFinishedListener instance) being lost upon onDestroy and when the helper is recreated in onCreate the new instance won't have the listener which means the call to onActivityResult will not be handled by in app billing. – Syntax Apr 07 '13 at 00:50
  • @Syntax witch version of in-app have u implement dear? – Zala Janaksinh Apr 10 '13 at 06:10
  • Hi @ZalaJanaksinh According to the SDK manager Revision 4 of the Google Play Billing Library provides my aidl file and associated InAppBilling helper classes). – Syntax Apr 10 '13 at 08:58
  • @Syntax i talk about In-app version like 2 else 3 i don't know about in-app version 4?in-app version 4 have be realese? – Zala Janaksinh May 01 '13 at 11:06
  • @ZalaJanaksinh Revision 4 is the SDK manager revision, as I understand it is version 3 of the API with a minor bugfix applied that they released. – Syntax May 02 '13 at 03:48

4 Answers4

2

I have experienced the similar issue trying to implement IAP.

My activity had launch mode set to "singleInstance" which prohibited system to launch new activities in its task.

It seems that startIntentSenderForResult was trying to launch a new activity in its task, failed and thus returned null as a result.

When I set launch mode to less restrictive "singleTop", this problem disappeared.

crimson
  • 56
  • 1
  • 5
  • Thank you for your suggestion. I use standard activity launchMode so I do not believe this is the cause of my problem. I had changed this a long time ago, but had failed to update my code above, apologies. Before writing my blog article documenting my findings I will check the impact of singleInstance so thank you for your post! – Syntax Jul 18 '13 at 01:45
1

I had the same problem, becouse my activity has flag noHistory

<activity android:name=".BuyActivity" android:screenOrientation="portrait"
              android:noHistory="true"/>

When I removed the attribute, all is ok

kve
  • 21
  • 1
0

So, two issues:

  1. Looking at the first logcat, it looks like the failure occurred when the dialog.show() is called. I wonder if you have got the context set right for that - I have often found the this to be the cause of this failure.

  2. Without the dialog. As I understand it from the question and your comments, the core sequence is as follows. Looking at the full log, I think that Finsky is closely associated with what you're doing:

    W/Finsky ( 3130): [1] CarrierParamsAction.run: Saving carrier billing params failed. E/Finsky ( 3130): [184] FileBasedKeyValueStore.delete: Attempt to delete 'params1Fzx-vZz47HyPCTxqb1ncg' failed! D/Finsky ( 3130): [1] GetBillingCountriesAction.run: Skip getting fresh list of billing countries.

In-app Billing Error suggests that there may be issues with this process (particularly in development) which can be circumvented. Or is this the behaviour you were expecting from the in app billing?

Beyond that, ActivityManager.getProcessesInErrorState() might be interesting to call in onDestroy to see if it tells you anything about what's gone wrong ...

Community
  • 1
  • 1
Neil Townsend
  • 6,024
  • 5
  • 35
  • 52
  • Thanks for your feedback! Certainly the first error occurs as a result of showing the dialog (which immediately triggers the leaked window exception). My belief is that the real problem here is that the result is returned whilst the activity is not in the foreground (which triggers the error when showing the dialog). The second stack trace shows that even removing dialog.show() doesn't fix the core issue (failure results in a new Activity being created and disposal of the original). At the time of calling show() the context is the first activity instance (the one which called billing). – Syntax Apr 03 '13 at 13:43
  • Perhaps you could post the code which calls the Play activity and the onActivityResult? Given that it comes into onActivityResult, it seems a little unlikely that it's genuinely killing off the first activity and starting up a second and then entering it at onActivityResult. – Neil Townsend Apr 03 '13 at 13:48
  • If I've understood it right, somewhere in `MainActivity`, you are doing a `startActivityForResult` call which (eventually) takes you to `OnIabPurchaseFinishedListener`. To get back to `MainActivity` well, you need to ensure that you have called `setResult` in the activity started by `MainActivity` with the information you want to get back to `MainActivity`. I can;t spot this in your code ... – Neil Townsend Apr 04 '13 at 08:57
  • The actual call leverages the In app billing API with the following call: helper.launchPurchaseFlow(activity, sku, BillingConstants.PURCHASE_REQUEST, onPurchasedListener); NB: I only have one activity in my app (I use a view pager with fragments) so I know this instance is 100% correct (and result is returned to the first instance, just too early). – Syntax Apr 05 '13 at 00:25
  • Ok, fair point. One way to test your theory would be to put a logging statement in onCreate to see if it gets called on return. If so, your theory may well hold, if not then we need to keep looking. Alternatively, I've added an issue I think I've spotted to the answer above. – Neil Townsend Apr 05 '13 at 15:48
  • onPurchasedListener is registered with the helper instance in the launchPurchaseFlow call which belongs to the MainActivity. by doing this when the onActivityResult in MainActivity is called it delegates the call back through to the listener (through the helper instance) which was created (and who's reference I on longer hold). – Syntax Apr 05 '13 at 23:59
  • Ok, interesting point - I've tried to clarify my train of thinking in the answer. If I'm wrong, sorry for barking up the wrong tree ... – Neil Townsend Apr 06 '13 at 10:39
  • Thanks for your help Neil, I appreciate the effort mate. – Syntax Apr 06 '13 at 10:42
  • In regards to the clarification above, there is only one helper it is just visible to the PurchaseAction class (IabHelper helper = ((MainActivity) activity).helper). A reference to the on biling comlete listener is given to the single handler instance which is why it will still exist when helper is called in onActivityResult. The core problem appears to be that the activity is destroyed prematurely resulting in the loss of the helper instance with the handler registered. – Syntax Apr 08 '13 at 02:02
  • The destruction of the activity (or at least the recalling of onResume - have you put a log call in onCreate?) is clearly problematic. I've tried to correct my answer to reflect your correction of an error I'd made. By it still leaves me with a question, which I've left in the answer. If you were able to let me see the classes in full, perhaps I could spot something? – Neil Townsend Apr 08 '13 at 10:29
  • If the activity instance which called launchPurchaseFlow was still alive (hadn't been destroyed) then the reference would still exist, but because the activity is destroyed the reference (both helper and registered purchase complete listener) are lost. IMO since my code matches that in the IAB demo I think that because my activity is destroyed (whilst the one in the billing demo doesn't appear to be), this causes the loss of both instances and therefore the misbehaviour. – Syntax Apr 08 '13 at 16:52
  • OK, so the question is why is it being destroyed, which leads to the obvious question: what does `eventBus.register(this)` do ... is it possible that it causes a closedown? – Neil Townsend Apr 08 '13 at 17:10
  • eventBus.register(this) simply registers the instance to an Google Guice EventBus object to process events via @Subscribe annotated methods. Not getting any exceptions or uncaught exceptions so as far as I know there is no reason to believe it is doing any harm. – Syntax Apr 09 '13 at 00:56
  • Well, afraid I'm stumped then. It clearly shouldn't be happening. Like I say, I'm happy (on the basis of confidentiality) to look at the fuller code and see if I can spot anything, but I've probably got as far as I can, sorry not to provide a definitive answer. As you say, the call to onDestroy is not what one would expect. – Neil Townsend Apr 09 '13 at 07:47
  • Had another thought: According to the android dev pages, onDestroy is called when "someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method". Have you used onFinishing() to see whether finish was called or whether there is a memory (or other) issue? – Neil Townsend Apr 10 '13 at 07:24
  • Hi Neil, I checked and isFinishing is called 3 times each with this output; `I/ACTIVITY-IS-FINISHING(19716): false com.satalyst.android.ui.MainActivity@41c34b78` from this log call: `Log.i("ACTIVITY-IS-FINISHING", isFinishing + " " + this.toString());` – Syntax Apr 10 '13 at 09:27
  • I think that means that it is being destroyed because of memory or other issues rather than intentionally. It might be interesting to look at what the system is logging in the run up to this point. – Neil Townsend Apr 10 '13 at 10:18
  • I have added log output unfiltered to my post above `(Error Log (full) which shows premature Activity destruction:)` **NB:** I clipped it with enough to show the destruction which occurs prior to the result being set by in app billing. I don't see any errors which would suggest anything I can act on (exceptions etc). – Syntax Apr 10 '13 at 15:29
  • Couple more suggestions for follow up in the answer (and the previous one removed for brevity) – Neil Townsend Apr 10 '13 at 17:17
  • `ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);` returns null when run in onDestroy() :( – Syntax Apr 11 '13 at 04:07
  • I would love to know why my calling activity is being killed, I would assume (possibly incorrectly) that Android is smart enough to know that my first instance is awaiting a result and shouldn't be killed... I have hacked in a solution which allows subsequent instances of my Activity to correctly handle a result they didn't request. I think this really highlights a shortfall in the in app billing code as currently anyone who use's it might have their calling activity killed (standard android rules); the IabHelper instance simply isn't designed to handle this case. – Syntax Apr 11 '13 at 05:57
  • I have awarded you the 100 points for your long running effort and input, thanks heaps Neil I appreciate having had someone to work through this with me. – Syntax Apr 11 '13 at 05:58
  • Many thanks - I think you have highlighted a design error between Iab and the standard android rules. What is going on in your particular case seems a bit of a mystery, sadly, and I think you are probably right to work around it. How close to the phone's resouce limit are you running? As an aside, does it work better if you make the calling application singleTop? don't worry if you just want to ignore this comment and get on with the rest of the code :-) All the best – Neil Townsend Apr 11 '13 at 07:52
  • It is definitely related to the operating state of the device as my testers phone (also an S2) doesn't trigger the initial disposal; this really makes me feel like it is a design oversight in the in app billing code. Thanks again Neil – Syntax Apr 11 '13 at 11:22
  • Hey Neil, in case your interested I found out what caused the failures; in phone developer options I had enabled "destroy activity immediately" which causes the extremely uncommon event to occur that my IAB calling activity is destroyed as soon as it calls IAB. Whilst this could theoretically occur in the wild the IAB API and it's usage guidelines don't take it into account. So yeah, my code is now more robust than anyone else that uses IAB (assuming they follow Google's guidelines) however I doubt it will ever happen to a user in the wild. – Syntax Jul 17 '13 at 07:28
  • @Syntax That is fascinating: could you post it with an example of how to be robust to this as an answer to this question? That way it's easy for others to find in the future! – Neil Townsend Jul 17 '13 at 15:47
  • Good thinking, I will write a blog post on my blog and link it as the answer in this question! stay tuned – Syntax Jul 18 '13 at 01:30
  • After further investigation either as a result of the in app billing revamp which occurred around a month ago (now displays dialog over my activity rather than moving to the play app) or possibly because I switched to singleTop as recommended by crimson I have been able to revert my changes as even with "kill all activities" enabled IAB works as intended (yay). – Syntax Jul 23 '13 at 08:18
0

My advice is to try either:

  1. Switch from an AlertDialog to a DialogFragment.
  2. Try posting a Runnable using runOnUiThread() which opens the Dialog.
Chris Banes
  • 31,763
  • 16
  • 59
  • 50
  • Hi Chris, thanks for your suggestions; I'm not sure how those they will address the Activity being prematurely destroyed :( Once that first instance is destroyed the original in app billing helper and it's registered onPurchaseCompleteListener instances are lost. I have no idea what is causing the activity instance to be destroyed. If my understanding of the billing demo code is correct destruction of their calling activity instance would result in the same bug. I removed the alert dialog and added a log message for now, still no joy. The weird thing is that two weeks ago this worked perfectly – Syntax Apr 10 '13 at 14:43
  • I am going to look at the general logs (which can be quite noisy) to try and determine what is causing my activity to be destroyed; do you know of a suggested(better) way of doing this? I agree that the dialog issue is a red herring and the core issue is the disposal of the Activity instance, however I'm not sure what is causing it currently. – Syntax Apr 10 '13 at 15:11
  • For the log output which occurs prior to the destruction of my application please see the "Error Log (full) which shows premature Activity destruction:" section I have added to my original post (bottom). Cheers – Syntax Apr 10 '13 at 15:28