Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

On which thread does onReceive() of a BroacastReceiver registered with LocalBroadcastManager run?

I register a broadcast receiver in a preference activity and send it a (sync) broadcast from a (Wakeful) IntentService. Apparently onReceive runs on the service's thread. Is this a fault of my part ? Is it documented behavior ?

Preference activity:

public final class SettingsActivity extends BaseSettings {

    private static CharSequence sMasterKey;
    private CheckBoxPreference mMasterPref;
    // Receiver
    /** If the master preference is changed externally this reacts */
    private BroadcastReceiver mExternalChangeReceiver =
        new ExternalChangeReceiver();

    public static void notifyMonitoringStateChange(Context ctx,
            CharSequence action, boolean isToggling) {
        final LocalBroadcastManager lbm = LocalBroadcastManager
            .getInstance(ctx);
        Intent intent = new Intent(ctx, ExternalChangeReceiver.class);
        intent.setAction(action.toString());
        intent.putExtra(TOGGLING_MONITORING_IN_PROGRESS, isToggling);
        lbm.sendBroadcastSync(intent);
    }

    @Override
    protected void onStart() {
        super.onStart();
        final LocalBroadcastManager lbm = LocalBroadcastManager
            .getInstance(this);
        lbm.registerReceiver(mExternalChangeReceiver, new IntentFilter(
            ac_toggling.toString()));
    }

    @Override
    protected void onStop() {
        // may not be called in Froyo
        final LocalBroadcastManager lbm = LocalBroadcastManager
            .getInstance(this);
        lbm.unregisterReceiver(mExternalChangeReceiver);
        super.onStop();
    }

    private final class ExternalChangeReceiver extends BroadcastReceiver {

        ExternalChangeReceiver() {}

        @Override
        @SuppressWarnings("synthetic-access")
        public void onReceive(Context ctx, Intent intent) {
            if (sMasterKey == null || mMasterPref == null) return; // if
            // onPostReceive has not run this will be null
            final String action = intent.getAction();
            if (ac_toggling.equals(action)) {
                final boolean isToggling = intent.getBooleanExtra(
                    TOGGLING_MONITORING_IN_PROGRESS, false);
                Log.w(ExternalChangeReceiver.class.getSimpleName(),
                    "isToggling " + isToggling);
                mMasterPref.setEnabled(!isToggling); // line 168 !!!
                refreshMasterPreference(isToggling);
            }
        }
    }
}

IntentService (LocationMonitor):

SettingsActivity.notifyMonitoringStateChange(this, ac_toggling, true);

Exception (E/AndroidRuntime):

FATAL EXCEPTION: IntentService[LocationMonitor]
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4746)
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:823)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.view.View.requestLayout(View.java:15473)
    at android.widget.AbsListView.requestLayout(AbsListView.java:1819)
    at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:813)
    at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:5958)
    at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
    at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
    at android.preference.PreferenceGroupAdapter.onPreferenceChange(PreferenceGroupAdapter.java:238)
    at android.preference.Preference.notifyChanged(Preference.java:1099)
    at android.preference.Preference.setEnabled(Preference.java:726)
    at gr.uoa.di.monitoring.android.activities.SettingsActivity$ExternalChangeReceiver.onReceive(SettingsActivity.java:168)
    at android.support.v4.content.LocalBroadcastManager.executePendingBroadcasts(LocalBroadcastManager.java:297)
    at android.support.v4.content.LocalBroadcastManager.sendBroadcastSync(LocalBroadcastManager.java:278)
    at gr.uoa.di.monitoring.android.activities.SettingsActivity.notifyMonitoringStateChange(SettingsActivity.java:54)
    at gr.uoa.di.monitoring.android.services.Monitor.abort(Monitor.java:241)
    at gr.uoa.di.monitoring.android.services.LocationMonitor.doWakefulWork(LocationMonitor.java:103)
    at com.commonsware.cwac.wakeful.WakefulIntentService.onHandleIntent(WakefulIntentService.java:94)
    at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.os.HandlerThread.run(HandlerThread.java:60)

On an emulator API 17

like image 669
Mr_and_Mrs_D Avatar asked Dec 28 '13 22:12

Mr_and_Mrs_D


People also ask

Which of the following is the thread that the onReceive () method of a BroadcastReceiver operates on?

onReceive always run in the UI thread? Yes.

What is onReceive method?

registerReceiver() method. The implementing class for a receiver extends the BroadcastReceiver class. If the event for which the broadcast receiver has registered happens, the onReceive() method of the receiver is called by the Android system.

Is broadcast receiver on main thread?

The broadcast receiver is a subclass of the BroadcastReceiver type and it must override the OnReceive method. Android will execute OnReceive on the main thread, so this method should be designed to execute quickly.


1 Answers

sendBroadcastSync() is indeed run on the thread it is called from (except if there is a race going on). Its implementation should be:

public void sendBroadcastSync(Intent intent) { // directly calls executePendingBroadcasts
        if (sendBroadcast(intent)) {
            executePendingBroadcasts();
        }
}

The race part I am not sure about enters via the sendBroadcast() call which returns true if it found any matching receiver (registered with LBM for this intent), after sending a message to a private handler associated with the main loop - abbreviated:

@Override
public boolean sendBroadcast(Intent intent) {
    synchronized (mReceivers) {
        final String action = intent.getAction();
        final ArrayList<ReceiverRecord> entries = mActions.get(action);
        if (entries == null) return false; // no receivers for that action
        ArrayList<ReceiverRecord> receivers = new ArrayList<ReceiverRecord>();
        for (ReceiverRecord receiver : entries) {
            if (receiver.broadcasting) continue;
            // match the intent
            int match = receiver.filter.match(action,
                intent.resolveTypeIfNeeded(mAppContext.getContentResolver()),
                intent.getScheme(), intent.getData(),
                intent.getCategories(), "LocalBroadcastManager");
            if (match >= 0) {
                receivers.add(receiver);
                receiver.broadcasting = true;
            }
        }
        final int size = receivers.size();
        if (size == 0) return false; // no receivers for this intent
        for (int i = 0; i < size; i++) {
            receivers.get(i).broadcasting = false;
        }
        mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
        if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
            mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
        }
        return true;
    }
}

where:

    mHandler = new Handler(context.getMainLooper()) {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_EXEC_PENDING_BROADCASTS:
                    executePendingBroadcasts();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

I am not sure if there can be a race between the main thread and the thread sendBroadcastSync() is executing - so the executePendingBroadcasts() will run on the main thread. If not then executePendingBroadcasts() runs on the thread sendBroadcastSync is run and directly calls the onReceive of the receivers

One of these days I should look into why (in executePendingBroadcasts) synchronized(mReceivers) and not synchronized(mPendingBroadcasts).

like image 100
Mr_and_Mrs_D Avatar answered Oct 02 '22 09:10

Mr_and_Mrs_D