Michael Kemp
Michael Kemp

Reputation: 300

Android: How to start a new thread within a service (and send the results back to my activity)?

I need to listen indefinitely for key-presses from an external hardware in my app, and to do so I would like to create a bound service that does the work as long as my application is running.

The problem is, this work needs to be done on a separate thread and so far I have only been able to get my service to communicate preset values back to my activity. After doing some research, I understand that this is because Services by default run on the ui thread.

...But if I run my thread in a service I have no idea how to send the processed result back to my activity.

Back when I was trying to do this process directly in an activity I would just use the runOnUiThread() method to update a text view as soon as the key press was processed, but because I am using a service now I don't have access to that method.

My DeviceService class uses a Handler and Messenger to receive responses from the client activity:

class IncomingHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {

        switch (msg.what) {

            case (MSG_START_REST):

                // This starts a worker thread.
                // I DON'T KNOW HOW TO GET THE RESULT BACK TO THE UI THREAD
                Log.v(TAG, "Starting listening for keypad");
                startREST(msg);

                // THIS IS WHERE I'M HAVING TROUBLE
                // I want to set this to the result of the above call...
                String keyPressed = "ice cream";

                try {
                    // Response
                    Message keyResponse = Message.obtain(null, KEYPRESS);
                    Bundle bundle = new Bundle();
                    bundle.putString("keyPressed", keyPressed);
                    keyResponse.setData(bundle);
                    msg.replyTo.send(keyResponse);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

                break;

            // This works.
            case (TO_UPPERCASE):
                try {
                    // Incoming data
                    String data = msg.getData().getString("data");

                    // Response
                    Message resp = Message.obtain(null, TO_UPPERCASE_RESPONSE);
                    Bundle bResp = new Bundle();
                    bResp.putString("respData", data.toUpperCase());
                    resp.setData(bResp);
                    msg.replyTo.send(resp);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;

            default:
                super.handleMessage(msg);
        }
}

// Target we publish for clients to send messages to IncomingHandler.
final Messenger mMessenger = new Messenger(new IncomingHandler());

I won't show all of my thread processing (it works fine), but here is where it finishes and gets it's result. It is passed a single line of json from which is extracts the keycode.

Again, my problem is I don't know how to get this keycode back to my activity.

private void sendKeyboardEvent(final String keyboardEvent) {

    Log.v(TAG, "Keyboard event : " + keyboardEvent);

    // Parse json to find keycode
    JSONObject jsonObject = null;
    try {
        jsonObject = new JSONObject(keyboardEvent);

        // THIS IS THE KEYCODE. IT WILL BE SOMETHING LIKE "30" or "31"
        String keycode = jsonObject.getString("pressedKeycodes");
        Log.v(TAG, "Keycode : " + keycode);

    } catch (JSONException e) {
        e.printStackTrace();
    }
}

My MainActivity also has a Handler and Messenger to interpret responses from the Service.

// Handler of incoming messages from clients.
class ResponseHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {

        switch (msg.what) {

            case (DeviceService.KEYPRESS):
                String result4 = msg.getData().getString("keyPressed");
                Toast.makeText(getApplicationContext(), "A key was pressed! -"+ result4, Toast.LENGTH_SHORT).show();
                break;

            case (DeviceService.TO_UPPERCASE_RESPONSE):
                String result = msg.getData().getString("respData");
                TextView tvResult = (TextView) findViewById(R.id.textView_result);
                tvResult.setText(result);
                break;

            default:
                super.handleMessage(msg);
        }
    }
}

And here is where I send calls to the service. This also seems to work fine.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button btnResult = (Button) findViewById(R.id.btnResult);
    btnResult.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String val = "hello";
            Message msg = Message.obtain(null, DeviceService.TO_UPPERCASE);
            msg.replyTo = new Messenger(new ResponseHandler());

            Bundle b = new Bundle();
            b.putString("data", val);
            msg.setData(b);

            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    });

    Button btnStart = (Button) findViewById(R.id.btn_start_keypad_listener);
    btnStart.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Message msg = Message.obtain(null, DeviceService.MSG_START_REST);
            msg.replyTo = new Messenger(new ResponseHandler());

            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    });
}

Tldr, I need to send the result from a thread running outside my MainActivity, back to my MainActivity and the UI thread.

Please help?

Upvotes: 0

Views: 93

Answers (1)

Walter Palladino
Walter Palladino

Reputation: 479

If you need to keep Service and Activity decoupled, Android allows you to use Broadcasts. You can check: https://developer.android.com/guide/components/broadcasts.html In your case, the Activity will register to wait for Broadcasts send from your Service so, when your Service complete the job, or any task, could send a broadcast which will be listen for your Activity. Here there is a sample: Using a broadcast intent/broadcast receiver to send messages from a service to an activity Hope it helps.

Upvotes: 2

Related Questions