Search code examples
androidmultithreadingandroid-serviceclassloader

Android static variable different in service than from activity


I'm hoping someone can clear up whats going on in my code for me. I have a block of code reading and writing to my database which is run both from a service and from my activities. I need this block of code to be threadsafe regardless of if it is called from the service or the activities. As such, I took the class the block of code was in and made a static locking object to keep it threadsafe as you can see in the block of code below:

synchronized(AnalyticsMessages.sync_lock){
                Log.v("mixpanel", "locking off of: "+AnalyticsMessages.sync_lock.toString());
                String[] eventsData = mDbAdapter.generateDataString(table);
                if (eventsData != null) {
                    String lastId = eventsData[0];
                    String rawMessage = eventsData[1];
                    HttpPoster poster = getPoster(mEndpointHost, mFallbackHost);
                    HttpPoster.PostResult eventsPosted = poster.postData(rawMessage, endpointUrl);

                    if (eventsPosted == HttpPoster.PostResult.SUCCEEDED) {
                        logAboutMessageToMixpanel("Posted to " + endpointUrl);
                        logAboutMessageToMixpanel("Sent Message\n" + rawMessage);
                        //Log.v("mixpanel", "Sent Message\n" + rawMessage);
                        mDbAdapter.cleanupEvents(lastId, table);
                        Log.v("mixpanel", "removing id: "+lastId);
                    }
                    else if (eventsPosted == HttpPoster.PostResult.FAILED_RECOVERABLE) {
                        // Try again later
                        if (!hasMessages(FLUSH_QUEUE)) {
                            sendEmptyMessageDelayed(FLUSH_QUEUE, mFlushInterval);
                        }
                    }
                    else { // give up, we have an unrecoverable failure.
                        mDbAdapter.cleanupEvents(lastId, table);
                    }
                }
            }

sync_lock is defined in AnalyticsMessages like this:

private static final Object sync_lock = new Object();

It was clearly not locking properly, hence the log with the toString. The log tells me that I somehow seem to have 2 different sync_lock variables (since it prints an address in memory). I should mention that AnalyticsMessages is instantiated like this:

public static AnalyticsMessages getInstance(Context messageContext) {
    synchronized (sInstances) {
        Context appContext = messageContext.getApplicationContext();
        AnalyticsMessages ret;
        if (! sInstances.containsKey(appContext)) {
            if (MPConfig.DEBUG) Log.d(LOGTAG, "Constructing new AnalyticsMessages for Context " + appContext);
            ret = new AnalyticsMessages(appContext);
            sInstances.put(appContext, ret);
        }
        else {
            if (MPConfig.DEBUG) Log.d(LOGTAG, "AnalyticsMessages for Context " + appContext + " already exists- returning");
            ret = sInstances.get(appContext);
        }
        return ret;
    }
}

What seems to be going on is that when the service passes itself to AnalyticsMessages.getInstance as the context, I end up with one sync_lock variable, but when one of my Activities passes itself as the context here, I end up with a different one? Does this imply that an android service uses a different class loader than the main process? Is there a workaround? I would think that this would actually be a relatively common problem? Am I totally off track in what's going on here? Thanks in advance!


Solution

  • it does seem that android:process was used for my background service

    That's a common source of this specific difficulty. While, from a Java source standpoint, it's all one app, the fact that you are running two separate processes means that you have two separate object heaps, and so a global (static data member) in one heap will be distinct from the same-named global in the other heap.

    Is there a good reason to have the service running in a different process than the rest of the app?

    Usually not. It causes your app to consume more memory and more CPU time, and limits the ability of the various components (running in different processes) to interoperate, as you saw. I generally counsel against multiple processes. That being said, changing it will warrant thorough testing, particularly if you don't know why that technique was used in the first place.