0

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);
    }
    
Wonton
  • 1,033
  • 16
  • 33
  • `ConcurrentModificationException` does not necessarily happen due to violated thread safety. It happens when you iterate over a collection and try to modify it (add/remove items) simultaneously. Check if you have such code in the class. – Maxim Blumental Jun 30 '17 at 22:54
  • Then I was completely wrong because I thought that synchronized blocks, in fact, avoided simultaneously access to the same code or variable. So, if a thread performs and Add and other thread performs an Iteration in the same array, how should I make this to avoid a ConcurrentModificationException? – Wonton Jun 30 '17 at 23:01
  • It could only one thread: you might have a for-loop where you call add,remove or any other operation changing the number of items. Do you have such code? – Maxim Blumental Jun 30 '17 at 23:05
  • I need a couple more details: invocation of `initInAppPurchasesManager()` and all usages of `requestProductsListeners`. – Maxim Blumental Jun 30 '17 at 23:25
  • I edit again the question. Regarding to "all usages of requestProductsListeners", it's only used in the class described in my question, it's not called anywhere else. – Wonton Jun 30 '17 at 23:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/148096/discussion-between-maxim-blumental-and-wonton). – Maxim Blumental Jul 01 '17 at 00:16

0 Answers0