6

I have a DashClockExtension that sometimes doesn't update.

It's using a LocalBroadcastReceiver to update the extension similar to http://bit.ly/1e4uMl0. The receiver is registered in the onInitialize() method:

@Override
    protected void onInitialize(boolean isReconnect) {
        super.onInitialize(isReconnect);

        LocalBroadcastManager broadcastMgr = LocalBroadcastManager.getInstance(this);
        if (mDashClockReceiver != null) {
            try {
                broadcastMgr.unregisterReceiver(mDashClockReceiver);
            } catch (Exception ignore) {}
        }
        mDashClockReceiver = new DashClockUpdateReceiver();
        broadcastMgr.registerReceiver(mDashClockReceiver, new IntentFilter(UPDATE_DASHCLOCK));
    }

That's how I send the broadcast:

public static void updateDashClock() {
    LocalBroadcastManager.getInstance(Application.getContext()).sendBroadcast(new Intent(UPDATE_DASHCLOCK));
}        

That's my BroadcastReceiver:

private class DashClockUpdateReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        retrieveDataAndUpdateWidget();
    }
}

I noticed that while the broadcast is triggered the receiver doesn't receive the event sometimes.
I tested it by killing my app but that doesn't reproduce the issue so I'm at a loss why this would happen.

Anyone?

Akash Singh
  • 5,171
  • 2
  • 26
  • 55
Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85
  • Can you post more code. How exactly are you trying to update the data? If the publishUpdate fails, the old data still remains unless you call publishUpdate(null) – Madhur Ahuja Dec 11 '13 at 05:35
  • The problem is not the update code itself but the fact that the broadcast isn't received by the broadcast receiver. Usually it works fine but "sometimes" it doesn't and I'd like to know why and what I can do to prevent this from happening. Initially I thought when the app is shut down the service would not be bound again by DashClock widget itself and so the BroadcastReceiver would not be registered but that's not the case. The service starts and is bound by the widget just fine. – Emanuel Moecklin Dec 11 '13 at 16:44

3 Answers3

2

Please take a look at how the GCM broadcast receiver is implemented in Google's GCM Client example.

It might be because your application was killed by the Android OS because it needed more memory (or for whatever reason) and then you will not receive the broadcasts in your broadcast receiver.

I hope it helps, if not, please provide us with some logs and more information.

Best of luck!

Assaf Gamliel
  • 11,935
  • 5
  • 41
  • 56
  • The BroadcastReceiver needs to be registered dynamically because it has to be a member class or we won't be able to call publishUpdate(ExtensionData) (a method of DashClockExtension which is a Service). See also: http://stackoverflow.com/a/15571547/534471). Having a BroadcastReceiver defined in the manifest would allow us to receive the broadcast but we couldn't update the widget because there's no way to call publishUpdate(ExtensionData). – Emanuel Moecklin Dec 20 '13 at 18:43
1

After running extensive tests, I got some results that might be interessting to other developers too:

  • It happens indeed when the app is killed by Android (I didn't wait for Android to kill the app but killed it manually of course)
  • DashClock Widget does bind the service again after a while. My guess is that it binds the service again when it runs the scheduled ui update which is roughly once an hour (according to the DashClockExtension javadoc). I didn't confirm that guess by going through the source code though.
  • The extension will therefore update eventually but only after the regular update cycle has run which might not happen for up to an hour.

Here's my workaround:

  • DashClock Widget listens to ACTION_PACKAGE_CHANGED Intents (http://developer.android.com/reference/android/content/Intent.html#ACTION_PACKAGE_CHANGED) to find all DashClockExtensions (service with ACTION_EXTENSION intent filter)
  • By disabling and re-enabling the DashClockExtension service when my app starts, I can "force" DashClock Widget to bind the service right away.
  • I tested this and it takes a couple of seconds after the app is killed and restarted till the DashClock widget ui updates again.

Here's how I disable, enable the service:

configureComponent("mypackagename.DashClockService", false); // disable
configureComponent("mypackagename.DashClockService", true);  // enable

private void configureComponent(Context context, String className, boolean enable) {
    PackageManager pkMgr = context.getPackageManager();
    String packageName = context.getPackageName();
    ComponentName component = new ComponentName(packageName, className);
    int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
    pkMgr.setComponentEnabledSetting(component, newState, PackageManager.DONT_KILL_APP);
}
Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85
  • While this solution works enabling and disabling the service has undesired side-effects on Samsung devices. Shortcuts e.g. can lose their custom icon and/or description (it's reverting back to the app icon and name). I did however find a better way to reliably re-connect to DashClockWidget after the app has crashed (see my new answer). – Emanuel Moecklin May 13 '14 at 04:37
1

While my first answer works, it has some undesired side-effects. I therefore came up with a better solution with no side-effects.

Instruct DashClockWidget to observe a certain Uri using addWatchContentUris and then when it's time to update the widget just call notifyChange on that Uri:

public class DashClockService extends DashClockExtension {
    private static final Uri URI_BASE = Uri.parse("content://com.myauthority");
    private static final String URI_PATH_SEGMENT = "dashclock/update";

    public static void updateWidget(Context context) {
        ContentResolver contentResolver = context.getContentResolver();
        Uri uri = Uri.withAppendedPath(URI_BASE, URI_PATH_SEGMENT);
        contentResolver.notifyChange(uri, null);
    }

    @Override
    protected void onInitialize(boolean isReconnect) {
        super.onInitialize(isReconnect);

        removeAllWatchContentUris();
        Uri uri = Uri.withAppendedPath(URI_BASE, URI_PATH_SEGMENT);
        addWatchContentUris(new String[] {uri.toString()});
    }

updateWidget can be called from anywhere in the code to update the widget. Most importantly it can be called when the app starts, e.g. after it has been killed by Android and is restarting (I'm calling the update in Application.onCreate()).

Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85