1

I am starting to migrate some PC Java applications to Android environments being a complete newbie concerning Android platform.

I found a problem when I tried to use a Service reference as context for a Toast message.

This is the relevant part of my Service code:

public class ServicePFPE extends Service {

    Timer messageSimulator;
    TimerTask messagePoll;

    private class MessageReceptionTask extends TimerTask
    {
        public MessageReceptionTask(Context c) {
            context = c;
        }

        @Override
        public void run() {
            String shownText = "Message received!! on " + (new Date(System.currentTimeMillis())).toString();    
            //doToast(shownText, context);    //THIS LINE MAKES THE APP CRASH!
            System.out.println(shownText);    //But I can do this
        }    

        private Context context;
    }

    public ServicePFPE() {
        super();
        messageSimulator = new Timer();
        messagePoll = new MessageReceptionTask(this);
    }

    @Override    
    public IBinder onBind(Intent intent)    
    {    
            doToast("Service: onBind");    
            return null;    
    }    

    ...
    ...
    ...

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {    
        doToast("Service: onStartCommand");    
        messageSimulator.schedule(messagePoll, 5000, 5000);    
        return super.onStartCommand(intent, flags, startId);    
    }    

    ...
    ...
    ...

    private void doToast(String msg) { doToast(msg, this); }
    private void doToast(String msg, Context con) {
           Toast.makeText(con,msg,Toast.LENGTH_SHORT).show(); 
    }
}    

When the scheduled task runs reaching doToast call Android notifies that "Unfortunatelly, myAPP has stopped".

I think it has something to do with the fact I am using the service context in a different thread but I don't know for sure.

Could you confirm if that is the case? What is the right way to run a timer from a service and be able to use its context? If that is not possible, can I get a context for that thread so I can generate Toasts user messages.

3 Answers3

1

It depends on what you really need, if you are planning to show simple notifications, maybe instead of toasts you can use Android notification bar (which is the standard way to show them). For example you can use:

  /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.local_service_started);

        NotificationManager mNM;
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.stat_sample, text,
                System.currentTimeMillis());

        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, LocalServiceActivities.Controller.class), 0);

        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.local_service_label),
                       text, contentIntent);

        // Send the notification.
        mNM.notify(NOTIFICATION, notification);
    }

however, if you just want toasts, you can show them from the service, your problem is that the timertask is being executed in a different thread that the UI thread (where the service is running). to "post" this code to the UI thread you can do it directly with something like this:

Handler handler;

    @Override
    public void onCreate() {
        // Handler will get associated with the current thread, 
        // which is the main thread.
        handler = new Handler();
        super.onCreate();
    }

    private void runOnUiThread(Runnable runnable) {
        handler.post(runnable);
    }

Source

And finally if you want fully interaction between service and activities, you have several ways:

  1. Use binders, for simple communications, this is moreless what you need.
  2. Use a messenger, to more complicated communications.
  3. If you only need dialogs you are always able to launch new activities in dialog mode.
  4. AIDL...

Documentation about 1 & 2 here and here

  1. Binders: They let you bind different objects in your application letting them access directly to the object itself and its functions, example from android doc:

    public class LocalService extends Service { // Binder given to clients private final IBinder mBinder = new LocalBinder(); // Random number generator private final Random mGenerator = new Random();

        /**
         * Class used for the client Binder.  Because we know this service always
         * runs in the same process as its clients, we don't need to deal with IPC.
         */
        public class LocalBinder extends Binder {
            LocalService getService() {
                // Return this instance of LocalService so clients can call public methods
                return LocalService.this;
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        /** method for clients */
        public int getRandomNumber() {
          return mGenerator.nextInt(100);
        }
    }
    
    public class BindingActivity extends Activity {
        LocalService mService;
        boolean mBound = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            // Bind to LocalService
            Intent intent = new Intent(this, LocalService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            // Unbind from the service
            if (mBound) {
                unbindService(mConnection);
                mBound = false;
            }
        }
    
        /** Called when a button is clicked (the button in the layout file attaches to
          * this method with the android:onClick attribute) */
        public void onButtonClick(View v) {
            if (mBound) {
                // Call a method from the LocalService.
                // However, if this call were something that might hang, then this request should
                // occur in a separate thread to avoid slowing down the activity performance.
                int num = mService.getRandomNumber();
                Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
            }
        }
    
        /** Defines callbacks for service binding, passed to bindService() */
        private ServiceConnection mConnection = new ServiceConnection() {
    
            @Override
            public void onServiceConnected(ComponentName className,
                    IBinder service) {
                // We've bound to LocalService, cast the IBinder and get LocalService instance
                LocalBinder binder = (LocalBinder) service;
                mService = binder.getService();
                mBound = true;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName arg0) {
                mBound = false;
            }
        };
    }
    
  2. Messenger: More advanced & complicated, in this way you can send messages from one object to another:

    public class MessengerService extends Service { /** Command to the service to display a message */ static final int MSG_SAY_HELLO = 1;

        /**
         * Handler of incoming messages from clients.
         */
        class IncomingHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_SAY_HELLO:
                        Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
        /**
         * Target we publish for clients to send messages to IncomingHandler.
         */
        final Messenger mMessenger = new Messenger(new IncomingHandler());
    
        /**
         * When binding to the service, we return an interface to our messenger
         * for sending messages to the service.
         */
        @Override
        public IBinder onBind(Intent intent) {
            Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
            return mMessenger.getBinder();
        }
    }
    
    
    
     public class ActivityMessenger extends Activity {
            /** Messenger for communicating with the service. */
            Messenger mService = null;
    
            /** Flag indicating whether we have called bind on the service. */
            boolean mBound;
    
            /**
             * Class for interacting with the main interface of the service.
             */
            private ServiceConnection mConnection = new ServiceConnection() {
                public void onServiceConnected(ComponentName className, IBinder service) {
                    // This is called when the connection with the service has been
                    // established, giving us the object we can use to
                    // interact with the service.  We are communicating with the
                    // service using a Messenger, so here we get a client-side
                    // representation of that from the raw IBinder object.
                    mService = new Messenger(service);
                    mBound = true;
                }
    
                public void onServiceDisconnected(ComponentName className) {
                    // This is called when the connection with the service has been
                    // unexpectedly disconnected -- that is, its process crashed.
                    mService = null;
                    mBound = false;
                }
            };
    
            public void sayHello(View v) {
                if (!mBound) return;
                // Create and send a message to the service, using a supported 'what' value
                Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
                try {
                    mService.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
            }
    
            @Override
            protected void onStart() {
                super.onStart();
                // Bind to the service
                bindService(new Intent(this, MessengerService.class), mConnection,
                    Context.BIND_AUTO_CREATE);
            }
    
            @Override
            protected void onStop() {
                super.onStop();
                // Unbind from the service
                if (mBound) {
                    unbindService(mConnection);
                    mBound = false;
                }
            }
        }
    

In case you want to show activities as fancy dialogs to show the updates you can use a regular activity with this theme:

<activity android:theme="@android:style/Theme.Dialog" />
Community
  • 1
  • 1
Guillermo Merino
  • 3,197
  • 2
  • 17
  • 34
  • Genius! This answer should be used as a tutorial for Android platforms beginners to understand communication mechanism between Activities and Services. I finally decided to use notifications and messages. – Pablo Francisco Pérez Hidalgo Mar 22 '14 at 10:13
0

Any UI related code should be ran on the UI thread using RunOnUiThread method.

Tarik
  • 10,810
  • 2
  • 26
  • 40
  • 1
    See http://stackoverflow.com/questions/7942083/updating-ui-from-a-service-using-a-handler on how to do that. – Tarik Mar 16 '14 at 17:50
  • I was going to say you need a Handler to do this. @Tarik is right. – mttdbrd Mar 16 '14 at 17:52
  • I had read that you can use the Binder mechanism to pass messages between the Service and the activity and that is what I'll do in order to the activity to perform the toasts. However, I initially thought I could trace what was happening in the scheduled code showing this toasts directly. I see I can't. – Pablo Francisco Pérez Hidalgo Mar 16 '14 at 18:03
0

you should set a global context like this:

public static Activity currentActivity=null;

and after run your main activity or any activity that runs service set context like this:

MainActivity.currentActivity = this;

after that in toast use this context:

 Toast.makeText(MainActivity.currentActivity," text", Toast.LENGTH_LONG);

hope use full

Mahdi
  • 6,139
  • 9
  • 57
  • 109
  • I didn't understand you. Do yo mean to declare a *ServicePFPE* attribute containing a reference to the activity which raised the intent starting the service? – Pablo Francisco Pérez Hidalgo Mar 16 '14 at 17:49
  • no, just define a global activity like my example and set the value in your sevice, then you can call toast from your Service by the Context of currentActivity. just try it. it works well – Mahdi Mar 16 '14 at 18:04
  • using static references to Context is strongly discouraged, it prevents the GC to recycle this memory, and any context keeps references to all the application resources, so in case you exit the application it will keep a reference to this object. (more info here http://android-developers.blogspot.com.es/2009/01/avoiding-memory-leaks.html) – Guillermo Merino Mar 16 '14 at 21:25