Search code examples
androidbroadcastreceiversharedpreferencesandroid-contextbootcompleted

Best way to share data between android app components


I'm working on an app that consist of many component.
The app is using AlarmManager to do some polling from a server. There are also regular Activities that present data (that stored on Sqlite and SharedPreferences)

Everything worked great until I tried to add a feature that start the polling when the device finish boot (BOOT_COMPLETED) when I did it i discovered that I cant access the SharedPreferences with the Context I get from the onReceive(Context context, Intent intent) method of the Class that extends BroadcastReceiver.

Another thing is that I'm using Singleton to handle all the SharedPreferences and DB functionality. this Singleton holds the Context of the first lunched activity of the app (LoginActivity). and use it all over the app and the Polling BroadcastReciver.

So I understand (believe...) that when the device finishes the Boot I get different Context (Not the LoginActivity context I used to get) and this is the source of the problem (is it???)

After all this preamble what I really need is a BEST-PRACTICE approach for a task like that - How to store and get data on SharedPreferences and DB all over the app:

  1. when the user run it
  2. when it do backgroung tasks via AlarmManager
  3. when it autostart via BOOT_COMPLETED Broadcast

without suffering this Context issues. An Example will be awesome.

EDIT: Here is my code snippets:

ConnectionManager.java - this class holds REST requests implementations and stores stuff to the SharedPreferences:

public class ConnectionManager {

    //There are many more variables here - irrelevant for the example

    private CookieStore _cookieStore;
    private static ConnectionManager _instance;
    private SharedPreferences _sharedPref;
    private Context _context;
    private DataPollingBroadcastReceiver _dataPoller;

    private ConnectionManager(Context caller) {
        _context = caller;
        _sharedPref = PreferenceManager.getDefaultSharedPreferences(_context);
    }

    public static ConnectionManager getInstance(Context caller) {
        if (_instance == null) {
            _instance = new ConnectionManager(caller);
        }
        return _instance;
    }

public void setPollingActive(boolean active) {
    if (active) {
        SharedPreferences.Editor editor = _sharedPref.edit();
        editor.putString("myapp.polling", "true");
        editor.commit();
        startRepeatingTimer();
    } else {
        SharedPreferences.Editor editor = _sharedPref.edit();
        editor.putString("myapp.polling", "false");
        editor.commit();
        cancelRepeatingTimer();
    }
}

private void startRepeatingTimer() {
    if (_dataPoller!= null) {
        _dataPoller.SetAlarm(_context);
    } else {
        Toast.makeText(_context, "_dataPoller object is null",
                Toast.LENGTH_SHORT).show();
    }
}

private void cancelRepeatingTimer() {
    if (_dataPoller!= null) {
        _dataPoller.CancelAlarm(_context);
    } else {
        Toast.makeText(_context, "_dataPoller object is null",
                Toast.LENGTH_SHORT).show();
    }
}

    //There are many more methods here - irrelevant for the example

} 

MainBootListener.java: this class suppose to activate the polling mechanism - it's not working since there are exception on the ConnectionManager.

public class MainBootListener extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Activated by boot event",
                Toast.LENGTH_LONG).show();
        ConnectionManager cm = ConnectionManager.getInstance(context);
        cm.setPollingActive(true);
    }
}

DataPollingBroadcastReceiver.java : this class polling the data from the server

public class DataPollingBroadcastReceiver extends BroadcastReceiver {

    private ConnectionManager _mngr;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (_mngr == null) {
            _mngr = ConnectionManager.getInstance(context);
        }
        PowerManager pm = (PowerManager) context
            .getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(
            PowerManager.PARTIAL_WAKE_LOCK, TAG);
        // Acquire the lock
        wl.acquire();
        // You can do the processing here update the widget/remote views.
        Bundle extras = intent.getExtras();
        StringBuilder msgStr = new StringBuilder();
        Format formatter = new SimpleDateFormat("hh:mm:ss");
        msgStr.append(formatter.format(new Date()));
        // /////
        _mngr.updateDataFromServer();
        msgStr.append(" [Updated AccessControlTable]");
        Log.i(TAG, msgStr.toString());
        Toast.makeText(context, msgStr, Toast.LENGTH_SHORT).show();
        // ////
        // Release the lock
        wl.release();
    }

    public void SetAlarm(Context context) {
        if (_mngr == null) {
            _mngr = ConnectionManager.getInstance(context);
        }
        AlarmManager am = (AlarmManager) context
            .getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, DataPollingBroadcastReceiver.class);
        intent.putExtra(ONE_TIME, Boolean.TRUE);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
            1000 * _mngr.getPollingIntervalInSeconds(), pi);
    }

    public void CancelAlarm(Context context) {
        if (_mngr == null) {
            _mngr = ConnectionManager.getInstance(context);
        }
        Intent intent = new Intent(context, DataPollingBroadcastReceiver.class);
        PendingIntent sender = PendingIntent
            .getBroadcast(context, 0, intent, 0);
        AlarmManager alarmManager = (AlarmManager) context
            .getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(sender);
    }
}

of course there are more classes - I trier to bring the minimum requiered.

EDIT The exceptions I recived where 2: if the polling mechanism was active from the application (the singletone holds the LoginActivity as Context) and I closed the application from task manager, the polling stopped and showed this exception:

12-29 14:02:03.061: E/AndroidRuntime(9402): FATAL EXCEPTION: main
12-29 14:02:03.061: E/AndroidRuntime(9402): java.lang.RuntimeException: Unable to start receiver my.app.DataPollingBroadcastReceiver : java.lang.NullPointerException
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.handleReceiver(ActivityThread.java:2277)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.access$1500(ActivityThread.java:140)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.os.Handler.dispatchMessage(Handler.java:99)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.os.Looper.loop(Looper.java:137)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.main(ActivityThread.java:4898)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at java.lang.reflect.Method.invokeNative(Native Method)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at java.lang.reflect.Method.invoke(Method.java:511)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at dalvik.system.NativeStart.main(Native Method)
12-29 14:02:03.061: E/AndroidRuntime(9402): Caused by: java.lang.NullPointerException
12-29 14:02:03.061: E/AndroidRuntime(9402):     at my.app.ConnectionManager.<init>(ConnectionManager.java:172)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at my.app.ConnectionManager.getInstance(ConnectionManager.java:196)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at my.app.DataPollingBroadcastReceiver .onReceive(DataPollingBroadcastReceiver .java:27)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.handleReceiver(ActivityThread.java:2270)
12-29 14:02:03.061: E/AndroidRuntime(9402):     ... 10 more

the secont exception acured when the app wasn't running and I sent BOOT_COMPLETED from adb, than the singletone tried to be init. with the BroadcastReciver Context. this was the exception:

12-26 11:54:58.556: E/AndroidRuntime(12373): FATAL EXCEPTION: main
12-26 11:54:58.556: E/AndroidRuntime(12373): java.lang.RuntimeException: Unable to start receiver my.app.MainBootListener : java.lang.NullPointerException
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2277)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.access$1500(ActivityThread.java:140)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.os.Handler.dispatchMessage(Handler.java:99)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.os.Looper.loop(Looper.java:137)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.main(ActivityThread.java:4898)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at java.lang.reflect.Method.invokeNative(Native Method)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at java.lang.reflect.Method.invoke(Method.java:511)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at dalvik.system.NativeStart.main(Native Method)
12-26 11:54:58.556: E/AndroidRuntime(12373): Caused by: java.lang.NullPointerException
12-26 11:54:58.556: E/AndroidRuntime(12373):    at my.app.ConnectionManager.<init>(ConnectionManager.java:172)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at my.app.ConnectionManager.getInstance(ConnectionManager.java:196)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at my.app.MainBootListener .onReceive(MainBootListener .java:14)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2270)
12-26 11:54:58.556: E/AndroidRuntime(12373):    ... 10 more

Solution

  • Your problem should be related to the fact that the context you receive in the BR is a ReceiverRestrictedContext - and I bet you get the exception ReceiverCallNotAllowedException. You always must post an exception if you have one - so please post it so we can understand what goes on exactly! That being said - you are doing too much in your receiver !

    And, please, please, please, simplify your code. Example :

    public void setPollingActive(boolean active) {
        _sharedPref.edit().putBoolean("myapp.polling", active).commit();
        (active) ? startRepeatingTimer() : cancelRepeatingTimer();
    }
    

    Also you do not need the wakelock in the receiver if you are woken up by the alarm manager ! The alarm manager holds a wakelock ! If you do a lot of stuff in your receiver you do need a WakefulIntentService though there.
    Finally if you want a singleton do it right and use an enum. Your implementation is wrong - it is not thread safe to begin with.

    EDIT: as per the exception posted the problem was not context related - it was a NPE due to a static field becoming null at some point