In one of my Android apps I'm using a singleton class to manage InApp billing. Inside this class I'm using a list of interfaces to notify responses from the InApp billing server.
Since my app is multi thread I would like this list to be syncronized, so I've done this:
public class InAppPurchasesManager {
...
private List<InAppPurchasesInterface> requestProductsListeners;
...
public static InAppPurchasesManager getInstance() {
if (mInstance == null) {
mInstance = new InAppPurchasesManager();
}
return mInstance;
}
private InAppPurchasesManager() {}
public void initInAppPurchasesManager(Application app) {
ctxt = app.getApplicationContext();
mInstance.requestProductsListeners = Collections.synchronizedList(new ArrayList());
}
...
public void addRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
if(!requestProductsListeners.contains(listener))
requestProductsListeners.add(listener);
}
}
public void removeRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
requestProductsListeners.remove(listener);
}
}
private void responseOnRequestProductsAndPurchase(boolean success)
{
callingIabHelper = false;
synchronized(requestProductsListeners)
{
for(InAppPurchasesInterface listener : requestProductsListeners)
listener.onRequestProductsAndPurchased(success);
return;
}
}
...
public interface InAppPurchasesInterface {
void onServiceStarted(boolean success);
void onRequestProductsAndPurchased(boolean success);
void onRequestProductPurchased(Purchase purchase, int error);
}
}
But I'm still receiveing a lot of crashes due to concurrent access to requestProductListeners.
This is a crash sample:
Fatal Exception: java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
at com.mypackage.managers.InAppPurchasesManager.responseOnRequestProductsAndPurchase(InAppPurchasesManager.java:173)
at com.mypackage.managers.InAppPurchasesManager.access$100(InAppPurchasesManager.java:24)
at com.mypackage.managers.InAppPurchasesManager$2.onQueryInventoryFinished(InAppPurchasesManager.java:145)
at com.mypackage.auxiliary.inappbilling.IabHelper$2$1.run(IabHelper.java:741)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:7329)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Since I'm not an expert with syncronization and multithreading I'm sure I'm doing something wrong. Should any of the solutions of these questions work? https://stackoverflow.com/a/2120409
Maybe adding "synchronized" to the definition of the methods that use requestProductsListeners?
EDIT: I finally added synchronized but the crashes due to "java.util.ConcurrentModificationException" keep appearing.
This is my code now:
public class InAppPurchasesManager {
...
private List<InAppPurchasesInterface> requestProductsListeners;
...
public static InAppPurchasesManager getInstance() {
if (mInstance == null) {
mInstance = new InAppPurchasesManager();
}
return mInstance;
}
private InAppPurchasesManager() {}
public void initInAppPurchasesManager(Application app) {
ctxt = app.getApplicationContext();
mInstance.requestProductsListeners = Collections.synchronizedList(new ArrayList());
}
...
public synchronized void addRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
if(!requestProductsListeners.contains(listener))
requestProductsListeners.add(listener);
}
}
public synchronized void removeRequestProductListener(InAppPurchasesInterface listener)
{
synchronized(requestProductsListeners)
{
requestProductsListeners.remove(listener);
}
}
private synchronized void responseOnRequestProductsAndPurchase(boolean success)
{
callingIabHelper = false;
synchronized(requestProductsListeners)
{
for(InAppPurchasesInterface listener : requestProductsListeners)
listener.onRequestProductsAndPurchased(success);
return;
}
}
...
public interface InAppPurchasesInterface {
void onServiceStarted(boolean success);
void onRequestProductsAndPurchased(boolean success);
void onRequestProductPurchased(Purchase purchase, int error);
}
}
What am I missing?
EDIT 2:
To answer the comment of Maxim Blumental I add the following information:
addRequestProductListener method (add a listener to the array) is called in OnResume() method of some fragments which need the products and their prices.
responseOnRequestProductsAndPurchase method (iterate through the array of listeners calling the callback) is called once Google has sent the information about products and their prices.
removeRequestProductListener method (remove the listener from the array) is called once the callback just mentioned has finished.
initInAppPurchasesManager is only called in
public class MyApp extends MultiDexApplication { @Override public void onCreate() { super.onCreate(); InAppPurchasesManager.getInstance().initInAppPurchasesManager(this); }