In one of my Android apps, I\'m trying to implement a simple grab of the inventory from Google\'s In-App billing, but it keeps giving me errors at the line of
The short answer is that your queryInventoryAsync() call should be made from inside your onIabSetupFinished() method. This is an asynchronous call, and so you cannot just proceed with using the IabHelper instance until that callback has been invoked to tell you that the helper's communication with the billing service has been established. The way your code is presently written, you have a race condition, and your queryInventoryAsync() call is going to win that race and attempt to use the IabHelper object before it has been set up, which is the cause of your problem.
Also, any further code in UI handlers that relies upon this object (e.g., the handler for a button that initiates a purchase) should test for a fully set-up IabHelper object, and should not allow the user to use that UI element until the IabHelper instance created in onCreate() has successfully completed setup. The easiest way to handle this situation is to simply disable such UI elements until the setup callback has been invoked to indicate that setup has completed successfully.
That is the easy part. The more serious problems occur when you have actions that occur immediately after your onCreate() method runs (i.e., not under the control of the user), that require the use of a fully set-up IabHelper instance. This typically occurs as a result of activity lifecycle calls - specifically, onResume() (if there is something requiring an IabHelper instance that must be done each time your app comes to the foreground, and not just when onCreate() is called) and, most notably, in onActivityResult() (which is invoked when the user completes or aborts an interaction with the billing interface - e.g., as part of making an in-app payment).
The problem is that your app may be stopped by the OS (e.g., to make room for the billing interface itself when the user initiates a purchase), causing your IabHelper instance to be destroyed along with your app, and that instance will have to be regenerated when your onCreate() is next invoked, and setup will once again be initiated in onCreate(), and once again you will need to wait for setup to complete before doing anything else with that object.
One notable situation where this can occur is during the user's interaction with the billing interface as part of the purchase process. The result of that interaction will be communicated to your app via onActivityResult(), which itself needs to use a fully set-up IabHelper object, and so if your app gets flushed from memory while the user is interacting with the billing service to make (or cancel) a purchase, then onActivityResult() will have to wait for the IabHelper instance to get set up again (after it is re-created in onCreate() before it can use it.
One way to handle this would be to set up and post to a queue of pending actions requiring an IabHelper instance, that your onResume() and/or onActivityResult() code adds to, and have that queue processed by your onIabSetupFinished() method once the IabHelper setup (initiated by onCreate()) has completed.
It's not trivial. And last time I checked, the TrivialDrive sample app did not handle the above situation.
The best way to test this kind of use case is to use the developer option "Don't Keep Activities," which causes your app to be destroyed each time the user leaves it, to simulate what the OS will do when it needs to reclaim memory, so that you can make sure your app works under those conditions.
Simply make the checkNotDisposed() method synchronised and the problem goes away. This is because it is sometimes called in a separate thread and does not always have the latest value of mDisposed, which may have been set in the main thread:
private synchronized void checkNotDisposed()
Great stuff from Carl. I "think" I am seeing a similar thing, though my app is crashing via:
java.lang.IllegalStateException: IabHelper was disposed of, so it cannot be used.
And it my case, rotating the device one way, then immediately back to the original orientation SOMETIMES causes this crash. Seems that there's a "window" of time where this crash "might" happen (due to the asynchronous nature of IAB like Carl explained).
My fix
My "fix" was to make mHelper static, and only instantiate it if (mHelper == null)
, and NOT destroy it in the activity's onDestroy() method. This way, once it's setup, it sticks around, and there's no need to worry about asynchronous operations (causes by device orientation) anymore.
Not sure if this is the right fix or not, but thought I'd mention it in case it help others.