安卓应用IsPurchased函数触发NullPointerException,求偶发崩溃排查思路
Hey there, since you're more comfortable with C++ and mentioned you've added multiple safeguards without luck, let's walk through this NPE issue clearly and practically.
First, let's map the crash stack to your Java code: the NullPointerException hits at line 13 of IsPurchased, which directly corresponds to this line:
if (mOwnedItems.size()>0)
This tells us the most likely culprit is mOwnedItems being null when this line executes. Let's break down why this might happen, even though you note it's "filled elsewhere":
Possible Root Causes
- Uninitialized or late initialization: The
mOwnedItemsBundle might not have been assigned a value yet when the nativeGameThreadcallsIsPurchased. Since native threads run independently of the Android main thread, it's possible the code that fillsmOwnedItems(like an in-app purchase query callback running on the main thread) hasn't completed before the native thread triggers this check. Think of this like a C++ pointer being dereferenced before it's assigned to a valid object. - Null assignment in error paths: When handling in-app purchase failures (e.g., network errors, user cancellation), the code responsible for filling
mOwnedItemsmight be setting it tonullinstead of an emptyBundle. An empty Bundle would return0forsize(), which is safe—but anullBundle will throw an NPE when you callsize(). - Thread safety issues: If
mOwnedItemsis being modified or reset on the main thread while the native thread is accessing it, there's a race condition where the native thread could see anullvalue (even if it was previously valid). Unlike C++ where you might use mutexes or atomic variables, Java requires explicit synchronization for shared state across threads.
Why Your Existing Safeguards Didn't Catch This
Looking at your Java code, you've already added checks for theData and ownedSkus, but you missed a critical check for mOwnedItems itself. That's why the NPE is still happening—this is the unprotected null reference slipping through.
Fixes to Implement
Here are actionable steps tailored to your background:
- Add an immediate null check for
mOwnedItems: This is the quickest fix to stop the crash. Modify yourIsPurchasedmethod like this:private boolean IsPurchased(String theData) { if (theData == null || mOwnedItems == null) return false; // Add mOwnedItems check here boolean aResult = false; if (mOwnedItems.size() > 0) { // Rest of your existing code... } return aResult; } - Validate the "filled elsewhere" logic: Check the code that assigns
mOwnedItems. Ensure it always assigns a validBundle(even an empty one) instead ofnull, especially in error scenarios. In C++ terms, this is like ensuring a pointer always points to a valid object, not a null address. - Add thread safety if needed: If
mOwnedItemsis accessed across multiple threads, wrap accesses in asynchronizedblock, or markmOwnedItemsasvolatileto ensure threads see the latest value. For example:
Or use a synchronized block when accessing it:private volatile Bundle mOwnedItems; // Mark as volatile for cross-thread visibilitysynchronized(this) { if (mOwnedItems == null) return false; // Rest of the code that uses mOwnedItems }
Bonus: JNI Layer Check (Optional)
Since you're comfortable with C++, you could add a debug check in the JNI IsPurchased method to log if g_theGlobalRefToActivityInstance is null (though the crash stack points to the Java method, so this is less likely). But the Java null check for mOwnedItems is the priority here.
内容的提问来源于stack exchange,提问作者Raptisoft

