3

I'm trying to perform a relatively simple task: I have a fragment that starts a service to pull some JSON from a remote server. I then want to pass that JSON back to the original calling fragment using a broadcast, with the BroadcastReceiver defined as an anonymous class in the fragment.

However, whenever I try to do this, I keep getting the following error:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.roundarch.codetest, PID: 21974
    android.app.RemoteServiceException: can't deliver broadcast
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1631)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6077)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

code:

public class Part3Fragment extends Fragment {

    PostCodeAdapter postCodeAdapter;
    ListView listView;
    BroadcastReceiver receiver;
    IntentFilter intentFilter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_part3, null);

        initialiseReceiver();

        View emptyView = (View) view.findViewById(R.id.empty_textview);
        ListView listView = (ListView) view.findViewById(R.id.part3_listview);
        listView.setEmptyView(emptyView);

        // TODO - the listview will need to be provided with a source for data

        // TODO - (optional) you can set up handling to list item selection if you wish

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        initialiseReceiver();
        getActivity().startService(new Intent(getActivity(), Part3Service.class));
    }

    public void initialiseReceiver(){
        receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Bundle bundle = intent.getBundleExtra("bundle");
                ArrayList<PostCodesResult.Result> postcodes = (ArrayList<PostCodesResult.Result>) bundle.getSerializable("postcodeArray");
                updatePostcodes(postcodes);
            }
        };
        intentFilter = new IntentFilter("JSON_RECEIVED");

        getActivity().registerReceiver(receiver, intentFilter);
    }

    @Override
    public void onPause() {
        super.onPause();


    }

    private void updatePostcodes(ArrayList<PostCodesResult.Result> postcodes) {
        if (postcodes.size() > 0){
            postCodeAdapter = new PostCodeAdapter(getActivity(), R.layout.part3_listview_item, postcodes);
            listView.setAdapter(postCodeAdapter);
            postCodeAdapter.notifyDataSetChanged();
        }
    }
}

And here is the code for the service:

public class Part3Service extends Service {

    private final String TAG = this.getClass().getSimpleName();

    // TODO - we can use this as the broadcast intent to filter for in our Part3Fragment
    public static final String ACTION_SERVICE_DATA_UPDATED = "com.roundarch.codetest.ACTION_SERVICE_DATA_UPDATED";

    private List<Map<String, String>> data = null;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i(TAG, "Service has started");
        new PostCodeRetriever().execute("http://gomashup.com/json.php?fds=geo/usa/zipcode/state/IL");


        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void updateData() {
        // TODO - start the update process for our data
    }

    private void broadcastDataUpdated(String jsonString) {
        Intent intent = new Intent();
        intent.setAction("JSON_RECEIVED");
        ArrayList<PostCodesResult.Result> postcodes = new ArrayList<>();
        if (jsonString != null) {
            Gson gson = new Gson();
            PostCodesResult result = gson.fromJson(jsonString, PostCodesResult.class);
            postcodes.addAll(result.getResult());
        }
        Bundle bundle = new Bundle();
        bundle.putSerializable("postcodeArray", postcodes);
        intent.putExtra("bundle", bundle);
        sendBroadcast(intent);
        stopSelf();
    }

    class PostCodeRetriever extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {

            Uri uri = Uri.parse(params[0]);

            String jsonString = "";

            try {
                URL postcodesUrl = new URL(uri.toString());
                InputStream inputStream = null;

                HttpURLConnection connection = (HttpURLConnection) postcodesUrl.openConnection();
                connection.connect();

                inputStream = connection.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                StringBuilder stringBuilder = new StringBuilder();

                String line = "";
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line);
                }

                stringBuilder.deleteCharAt(0);
                stringBuilder.deleteCharAt(stringBuilder.length() -1);

                jsonString = stringBuilder.toString();

                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                connection.disconnect();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }


            return jsonString;

        }

        @Override
        protected void onPostExecute(String jsonString) {

            broadcastDataUpdated(jsonString);
            super.onPostExecute(jsonString);
        }
    }
}

I've tried so many different things: sending only the raw JSON string via the intent, defining the broadcast receiver as a separate class, converting the JSON to a serialized ArrayList and sending that through the intent, and finally trying to include it all in a bundle and sending that.

The bizarre thing is that very similar code seems to work fine for my classmates - I can't see any differences between their code and my code that might be causing the problem (and neither can they). Additionally, if I try to just send a different String through the intent then that seems to work fine - the broadcast receiver picks it up and onReceive is called as it should be. The "Cannot deliver broadcast" error only seems to occur when attempting to deliver this specific dataset, and only for me!

Tanveer Munir
  • 1,956
  • 1
  • 12
  • 27
Chris J Davis
  • 101
  • 2
  • 6
  • is `PostCodesResult.Result` serializable? – nandsito Oct 21 '16 at 12:09
  • Yes, both the inner and outer class are serializable. – Chris J Davis Oct 21 '16 at 12:57
  • i don't know if they are the cause of the problem, but there are some issues in your Fragment. You are double-registering the Receiver (and as many times more as the Frag is resumed) but they're never being unregistered. And the receivers are being registered to an Activity. What happens to the receiver if the current Frag or its Activity go out of screen? – nandsito Oct 21 '16 at 14:18
  • I'm aware of these other problems with the code, but they are outside the scope of the question. This is just an exercise that has been put to me, and I just need to find out why the broadcast isn't being received in this one particular instance (where in all other instances, the broadcast is sent and received just fine). If I navigate away from the fragment/activity then yes, I would expect an error. But the error occurs even if I stay on that screen. – Chris J Davis Oct 21 '16 at 14:34
  • Please refer to my answer in below link https://stackoverflow.com/questions/46971153/fatal-exception-android-app-remoteserviceexception-cant-deliver-broadcast-at/48866003#48866003 – Asheesh Feb 19 '18 at 12:16
  • this solution worked with me fine https://stackoverflow.com/a/71368655/11365488 – Elfnan Sherif May 26 '22 at 15:35

2 Answers2

2

I had this issue too. I found out the I broadcast on the intent huge amount of data (which was a big byte array..) So I solved it by decreasing the size of the byte array. Check it out!

Itai.S.
  • 144
  • 13
  • 1
    I have same problem. I decrease my JsonArray and it work. But I shouldn't decrease array because that is bus tickets. Whats this problem? About boradcast or JsonArray limit? – Ahmet Yılmaz Sep 05 '19 at 11:35
  • 2
    The problem is that you can transfer data on intent bigger than 1MB. If that is the case, you should transfer data another way (e.g. save on the disc or something..) – Itai.S. Mar 02 '21 at 10:10
2

I know the original question was posted a while ago, but I recently experienced this problem, and I wanted to post what I did to fix it in case it helps someone else.

I was able to correct the problem by making the following 2 changes. I cannot definitively confirm the exact cause of the problem, so I don't know for sure if both changes were necessary. However, given what the app is doing, these are improvements over the old code, so I kept them both.

Definition of Intent action constants

The app uses a class that extends IntentService for performing background tasks. That class defines a number of static final String values that are used as the "action" values for the Intents that are broadcast when the service finishes a task.

I noticed that these strings were defined with values that matched the action's meaning (such as "SAVE_COMMENT"), but they did NOT include the app's package name.

From Android's Intents and IntentFilter documentation...

If you define your own actions, be sure to include your app's package name as a prefix.

https://developer.android.com/guide/components/intents-filters.html

I changed the value of these strings to include the package name.

I have not been able to prove this, but I'm wondering if one of the action names may have conflicted with an action defined in another app on those devices.

Switch to LocalBroadcastManager

The app was using Context::sendBroadcast() to broadcast an Intent whenever it finished a task. All of the logic performed by the app's service is only intended for that app, so I switched it to use LocalBroadcastManager::sendBroadcast() instead. This keeps the broadcast within the app, further preventing the possibility of a conflict with another app.

klaust
  • 91
  • 4