I have a client/server application that needs to be able to launch different Activities. I have a working TCP thread that runs continuously in the background and a working handler in my MainAcitivty which the TCP thread uses to send messages. The problem is getting that handler to launch anything other than strings. My TCP thread creates an object of the MainActivity on start up so it can access my handler which it must do since my handler is not static. Everything works fine if I run it from a button on my MainActivity but I get nullpointexceptions on everything when launched from my handler. I believe it dislikes my Context but I can’t find a work around. Thanks
Handler TCP_handler = new Handler()
{
#Override
public void handleMessage(Message msg) {
Message.obtain();
Bundle bundle = msg.getData();
switch( msg.what ){
case 1:
// this stuff works
String aResponse1 = bundle.getString("messageStringL1");
String aResponse2 = bundle.getString("messageStringL2");
if(aResponse1 != null)
textViewLineOne.setText(aResponse1);
if(aResponse2 != null)
textViewLineTwo.setText(aResponse2);
break;
case 2:
// Method 1
// nullpointer exception error
Intent i = new Intent(MainActivity.this, IdleScreen.class);
startActivity(i);
// Method 2
// nullpointer exception error
Toast.makeText(MainContextSaved, "This is Toast!!!", Toast.LENGTH_SHORT).show();
// Method 3
// this launches but can only write to the MainActivty textview
runOnUiThread(IdleScreenUI);
break;
}
}
};
private Runnable IdleScreenUI = new Runnable() {
#Override
public void run() {
// this is the new screen I want to display
setContentView(R.layout.idlescreen ); // nullpointer exception error
// this is a textview in the MainActivity and it works
// textViewLineOne.setText("hello");
// null pointer exception error
Toast.makeText(MainContextSaved, "This is Toast!!!", Toast.LENGTH_SHORT).show();
}
};
My TCP thread creates an object of the MainActivity on start up.
Even if you create the object of the activity , that is not a real activity context. thats why your unable to start the other activity.
If I understood your problem correctly, when you try to start the other activity from handler, the MainActivity is in foreground(in stack).
Assuming that you have launched the MainActivity and your TCP operations are done in background.
If your background TCP operations are done from a service,then when the MainActivity is started you can bind to the service and share the activity context to the service.
So now with the MainActivity context you can send Message to the handler.
Here is a sample I created..
CustomService.java
public class CustomService extends Service {
private final IBinder mIBinder = new LocalBinder();
// temporary handler
private Handler mHandler = new Handler();
// context to hold MainActivity handler
private Context mActivityContext = null;
#Override
public int onStartCommand(Intent intent, int flag, int startId) {
// for testing Iam sending an empty message to the handler after 10 seconds
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
if (mActivityContext != null) {
((MainActivity) mActivityContext).TCP_handler.sendEmptyMessage(2);
}
}
}, 10000);
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mIBinder;
}
public void setActivityContext(Activity activityContext) {
mActivityContext = activityContext;
}
public class LocalBinder extends Binder {
public CustomService getInstance() {
return CustomService.this;
}
}
}
Now , you can start the service from activity and bind a service connection.
MainActivity.java
public class MainActivity extends ActionBarActivity {
CustomService customService = null;
TextView textViewLineOne;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// start the service, even if already running no problem.
startService(new Intent(this, CustomService.class));
// bind to the service.
bindService(new Intent(this,
CustomService.class), mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
customService = ((CustomService.LocalBinder) iBinder).getInstance();
// pass the activity context to the service
customService.setActivityContext(MainActivity.this);
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
customService = null;
}
};
#Override
protected void onDestroy() {
super.onDestroy();
if (customService != null) {
// Detach the service connection.
unbindService(mConnection);
}
}
// Add your handler code stuff here..
}
Handler class doesn't have a startActivity() method, does it!
You can use a static context and store the value of the activity in it in onCreate() then call context.startActivity()
Intent mIntent = new Intent(context,YourActivity.class);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
Related
I have a fragment with a button. When clicked it tells a service to start polling sensors and then insert the sensor data into a database on a background thread. When the button is pushed again, the service will stop. When the Stop button is pushed, there may still be tasks in the executor queue that is inserting into the DB, so during this time I want to display a progress dialog, and dismiss it once the entire queue is clear. The fragment with the button looks like this:
public class StartFragment extends Fragment implements View.OnClickListener {
Button startButton;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_start, container, false);
startButton = (Button) view.findViewById(R.id.startButton);
startButton.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v) {
if (recording has not yet started){
mainActivity.startService(new Intent(mainActivity, SensorService.class));
} else {
//I want to display a progress dialog here when the service is told to stop
//Once all executor task queue is clear, I want to dismiss this dialog
mainActivity.stopService(new Intent(mainActivity, SensorService.class));
}
}
}
When the button is clicked the first time, the following service will start:
public class SensorService extends Service implements SensorEventListener {
public static final int SCREEN_OFF_RECEIVER_DELAY = 100;
private SensorManager sensorManager = null;
private WakeLock wakeLock = null;
ExecutorService executor;
Runnable insertHandler;
private void registerListener() {
//register 4 sensor listeners (acceleration, gyro, magnetic, gravity)
}
private void unregisterListener() {
sensorManager.unregisterListener(this);
}
public BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive("+intent+")");
if (!intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
return;
}
Runnable runnable = new Runnable() {
public void run() {
Log.i(TAG, "Runnable executing...");
unregisterListener();
registerListener();
}
};
new Handler().postDelayed(runnable, SCREEN_OFF_RECEIVER_DELAY);
}
};
public void onSensorChanged(SensorEvent event) {
//get sensor values and store into 4 different arrays here
//insert into database in background thread
executor.execute(insertHandler);
}
#Override
public void onCreate() {
super.onCreate();
//get sensor manager and sensors here
PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
registerReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
//Executor service and runnable for DB inserts
executor = Executors.newSingleThreadExecutor();
insertHandler = new InsertHandler();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
startForeground(Process.myPid(), new Notification());
registerListener();
wakeLock.acquire();
return START_STICKY;
}
#Override
public void onDestroy() {
//Prevent new tasks from being added to thread
executor.shutdown();
try {
//Wait for all tasks to finish before we proceed
while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
Log.i(TAG, "Waiting for current tasks to finish");
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
if (executor.isTerminated()){
//Stop everything else once the task queue is clear
unregisterReceiver(receiver);
unregisterListener();
wakeLock.release();
dbHelper.close();
stopForeground(true);
//Once the queue is clear, I want to send a message back to the fragment to dismiss the progress dialog here
}
}
class InsertHandler implements Runnable {
public void run() {
//get sensor values from 4 arrays, and insert into db here
}
}
So I want to display the dialog on the 2nd button press. Then once it is pressed again, service will stop, and I want to wait until the queue is clear and then send a dismiss event back to the fragment to dismiss the progress dialog.
Showing the dialog is easy. I can just add progress dialog code in the onClick method of the fragment, before stopService is called
I'm having difficulty with figuring out how to send a message back in onDestroy of the SensorService to dismiss that dialog
Whats the best way of doing this without resorting to external libraries?
Is there some way that the BroadcastReceiver I'm using in SensorService can be used? Or maybe it's better to create a new Handler in the fragment and somehow pass it through to the service so it can send a message back to the fragment?
EDIT:
I have tried the following based on one of the answers below:
Added a MessageHandler class to my fragment class:
public static class MessageHandler extends Handler {
#Override
public void handleMessage(Message message) {
int state = message.arg1;
switch (state) {
case 0:
stopDialog.dismiss();
break;
case 1:
stopDialog = new ProgressDialog(mainActivity);
stopDialog.setMessage("Stopping...");
stopDialog.setTitle("Saving data");
stopDialog.setProgressNumberFormat(null);
stopDialog.setCancelable(false);
stopDialog.setMax(100);
stopDialog.show();
break;
}
}
}
Created a new instance of MessageHandler in my fragment (tried placing this in a variety of places...same results):
public static Handler messageHandler = new MessageHandler();
The service is then started from my fragment using:
Intent startService = new Intent(mainActivity, SensorService.class);
startService.putExtra("MESSENGER", new Messenger(messageHandler));
getContext().startService(startService);
In my SensorService BroadcastReceiver I create the messageHandler:
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
Then I show the dialog at the very beginning of SensorService onDestroy:
sendMessage("SHOW");
and dismiss it at the very end of that same method:
sendMessage("HIDE");
My sendMessage method looks like this:
public void sendMessage(String state) {
Message message = Message.obtain();
switch (state) {
case "SHOW":
message.arg1 = 1;
break;
case "HIDE" :
message.arg1 = 0;
break;
}
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
So I can start the Service OK, but when I press it again to stop, I get this:
java.lang.RuntimeException: Unable to stop service com.example.app.SensorService#21124f0: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.Messenger.send(android.os.Message)' on a null object reference
and its referring to Line 105 of SensorService where I have messageHandler.send(message)
Thoughts on what might be wrong?
In activity:
protected BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, final Intent intent) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if(intent.hasExtra("someExtraMessage")){
doSomething(intent.getStringExtra("someExtraMessage"));
}
}
});
}
};
#Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("message-id"));
}
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
}
public void doSomething(){
//...
}
Then somewhere from service:
Context context = BamBamApplication.getApplicationContext(); // Can be application or activity context.
// BamBamApplicaiton extends Application ;)
Intent intent = new Intent("message-id");
intent.putExtra("someExtraMessage", "Some Message :)");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Actually you are doing wrong from the very beginning :) all the services are running on main thread, so here you must better start all hard processing to async task to move this in background otherwise you will stuck your app, or you will get sudden unexpected crashes.
Here are you sample of async task that parses json api response in background with Typed result by parameter.
class ParseJsonInBackground<T> extends AsyncTask<String, Void, ApiResponseModel<T>> {
private ProcessResponse<T> func;
private Type inClass;
public ParseJsonInBackground(ProcessResponse<T> f, Type inClass){
this.func = f;
this.inClass = inClass;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected ApiResponseModel<T> doInBackground(String... json) {
Gson gson = new Gson();
try {
ApiResponseModel<T> result = (ApiResponseModel<T>) gson.fromJson(json[0], inClass);
return result;
}catch(Exception e){
ApiResponseModel<T> result = new ApiResponseModel<T>();
result.data = null;
result.success = false;
result.error = new ArrayList<>();
result.error.add(new ErrorModel(0, "Parsing error", "Parsing error"));
return result;
}
}
#Override
protected void onPostExecute(ApiResponseModel<T> result) {
Utils.hideLoadingProgress(mContext);
if(result != null && func != null){
if(result.success){
func.onSuccess(result);
}else{
func.onError(result);
}
}
}
}
and sample how to call:
new ParseJsonInBackground<T>(responseFunc, inClass).execute(json.toString());
make attention! - don't use any views in processing coz this will stuck main thread, make database processing in similar async task, don't write to often to database make recording with transactions.
I would propose doing this via Handler messages: you send a message from the Service to your Activity which has to register as a callback handler (implement http://developer.android.com/reference/android/os/Handler.Callback.html). Use a custom message code (message.what) and listen for it. Keep in mind to send this to the main looper of your application (from the service).
You may also check this comment which illustrates this kind of interaction with some more code: https://stackoverflow.com/a/20595215/4310905
It turns out that the code in the Edit of my original question works, but I have to shuffle around some of my code:
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
The above needs to be moved to onStartCommand of SensorService instead of being in the BroadcastReceiver
I looked up on the internet, but couldn't find an example covering my scenario. What I am trying to do is:
1) To start and bind to a service as soon as my activity starts (done)
2) The service then binds itself to another service looking for a user
input from a connected device, and saves a string a string to a variable (done)
3) I would like to send back this string to the activity, so I can check what it
is and based on it to make a network call.
Now number 3) is my challenge. I managed to do it with a Timer that runs for one second and then checks the value written in the service, but somehow this doesn't seem to be the right way and I think that there might be a more mature solution. However, I can't seem to figure it out.
I've taken the code from the documentation and only added the timer. It is just one service in this example that just generates a random number (this will normally be replaced by my second service).
This is the code for the service:
public class LocalService extends Service {
private final IBinder mBinder = new LocalBinder();
private final Random mGenerator = new Random();
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
And this is the code in my activity:
public class MainActivity extends AppCompatActivity {
LocalService mService;
boolean mBound = false;
Timer timer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timer = new Timer();
}
#Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
timer.schedule(new MyTimerTask(new Handler(), this), 1000, 1000); // run on every second
}
#Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mConnection);
mBound = false;
}
timer.cancel();
timer.purge();
}
private class MyTimerTask extends TimerTask {
Handler handler;
MainActivity ref;
public MyTimerTask(Handler handler, MainActivity ref) {
super();
this.handler = handler;
this.ref = ref;
}
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if (mBound) {
int num = ref.mService.getRandomNumber();
// just as an example, raise a toast to see if it works
// but otherwise the value will be handled
Toast.makeText(ref, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
});
}
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
My question is: is this a good approach (it works) or is it bad and what's the alternative?
You can use LocalBroadcastManager to send broadcasts from your Service to your Activity. For example, in your Service declare:
public static final String BROADCAST_INTENT = "broadcast_intent";
public static final String BROADCAST_VALUE = "broadcast_value";
private LocalBroadcastManager broadcastManager;
public void onCreate() {
super.onCreate();
broadcastManager = LocalBroadcastManager.getInstance(this);
}
Now whenever you want to send a String to your Activity you can do so like this:
private void sendBroadcast(String value) {
Intent intent = new Intent(BROADCAST_INTENT);
intent.putExtra(BROADCAST_VALUE, value);
broadcastManager.sendBroadcast(intent);
}
In your Activity declare a BroadcastReceiver:
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
handleIntent(intent);
}
};
Register the receiver when you bind to your Service:
IntentFilter broadcastIntentFilter = new IntentFilter();
broadcastIntentFilter.addAction(StreamService.BROADCAST_INTENT);
LocalBroadcastManager.getInstance(context).registerReceiver((broadcastReceiver), broadcastIntentFilter);
And unregister where you unbind from your Service:
LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver);
Now when your service sends the broadcast you can handle it in your Activity:
private void handleIntent(Intent intent) {
if (intent.getAction().equals(StreamService.BROADCAST_INTENT)) {
String value = intent.getStringExtra(StreamService.BROADCAST_VALUE, "default");
}
}
I would like to send back this string to the activity, so I can check what it is and based on it to make a network call.
Use LocalBroadcastManager, greenrobot's EventBus, Square's Otto, or some other in-process event bus implementation. Raise an event when you have changed data. Have the activity register with the bus to find out about the event. Have the activity use the changed data when the change occurs.
is this a good approach
No.
I am trying to make my own MusicPlayer for android. Where i came to a problem is running some things in background. Main activity manages GUI and up to now all the songs are playing. I wanted to separate GUI and music playing classes. I want to put music managing part in Service and leave other things as they are now.
My problem is that i can't organize communication between Activity and Service as lot of communication is happening between them including moving objects in both directions. I tried many techniques that I searched here on Stack Overflow but every time I had problems. I need Service to be able to send objects to Activity and vice versa. When I add widget i also want it to be able to communicate with Service.
Any tips are appreciated, if you need source code place comment bellow but now in this transition it became chaotic.
Is there any more advanced tutorial on this than calling one method that returns random number from service? :P
EDIT: Possible solution is to use RoboGuice library and move objects with injection
I have implemented communication between Activity and Service using Bind and Callbacks interface.
For sending data to the service I used Binder which retruns the Service instace to the Activity, and then the Activity can access public methods in the Service.
To send data back to the Activity from the Service, I used Callbacks interface like you are using when you want to communicate between Fragment and Activity.
Here is some code samples for each:
The following example shows Activity and Service bidirectional relationship:
The Activity has 2 buttons:
The first button will start and stop the service.
The second button will start a timer which runs in the service.
The service will update the Activity through callback with the timer progress.
My Activity:
//Activity implements the Callbacks interface which defined in the Service
public class MainActivity extends ActionBarActivity implements MyService.Callbacks{
ToggleButton toggleButton;
ToggleButton tbStartTask;
TextView tvServiceState;
TextView tvServiceOutput;
Intent serviceIntent;
MyService myService;
int seconds;
int minutes;
int hours;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent(MainActivity.this, MyService.class);
setViewsWidgets();
}
private void setViewsWidgets() {
toggleButton = (ToggleButton)findViewById(R.id.toggleButton);
toggleButton.setOnClickListener(btListener);
tbStartTask = (ToggleButton)findViewById(R.id.tbStartServiceTask);
tbStartTask.setOnClickListener(btListener);
tvServiceState = (TextView)findViewById(R.id.tvServiceState);
tvServiceOutput = (TextView)findViewById(R.id.tvServiceOutput);
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
Toast.makeText(MainActivity.this, "onServiceConnected called", Toast.LENGTH_SHORT).show();
// We've binded to LocalService, cast the IBinder and get LocalService instance
MyService.LocalBinder binder = (MyService.LocalBinder) service;
myService = binder.getServiceInstance(); //Get instance of your service!
myService.registerClient(MainActivity.this); //Activity register in the service as client for callabcks!
tvServiceState.setText("Connected to service...");
tbStartTask.setEnabled(true);
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
Toast.makeText(MainActivity.this, "onServiceDisconnected called", Toast.LENGTH_SHORT).show();
tvServiceState.setText("Service disconnected");
tbStartTask.setEnabled(false);
}
};
View.OnClickListener btListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
if(v == toggleButton){
if(toggleButton.isChecked()){
startService(serviceIntent); //Starting the service
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); //Binding to the service!
Toast.makeText(MainActivity.this, "Button checked", Toast.LENGTH_SHORT).show();
}else{
unbindService(mConnection);
stopService(serviceIntent);
Toast.makeText(MainActivity.this, "Button unchecked", Toast.LENGTH_SHORT).show();
tvServiceState.setText("Service disconnected");
tbStartTask.setEnabled(false);
}
}
if(v == tbStartTask){
if(tbStartTask.isChecked()){
myService.startCounter();
}else{
myService.stopCounter();
}
}
}
};
#Override
public void updateClient(long millis) {
seconds = (int) (millis / 1000) % 60 ;
minutes = (int) ((millis / (1000*60)) % 60);
hours = (int) ((millis / (1000*60*60)) % 24);
tvServiceOutput.setText((hours>0 ? String.format("%d:", hours) : "") + ((this.minutes<10 && this.hours > 0)? "0" + String.format("%d:", minutes) : String.format("%d:", minutes)) + (this.seconds<10 ? "0" + this.seconds: this.seconds));
}
}
And here is the service:
public class MyService extends Service {
NotificationManager notificationManager;
NotificationCompat.Builder mBuilder;
Callbacks activity;
private long startTime = 0;
private long millis = 0;
private final IBinder mBinder = new LocalBinder();
Handler handler = new Handler();
Runnable serviceRunnable = new Runnable() {
#Override
public void run() {
millis = System.currentTimeMillis() - startTime;
activity.updateClient(millis); //Update Activity (client) by the implementd callback
handler.postDelayed(this, 1000);
}
};
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Do what you need in onStartCommand when service has been started
return START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//returns the instance of the service
public class LocalBinder extends Binder{
public MyService getServiceInstance(){
return MyService.this;
}
}
//Here Activity register to the service as Callbacks client
public void registerClient(Activity activity){
this.activity = (Callbacks)activity;
}
public void startCounter(){
startTime = System.currentTimeMillis();
handler.postDelayed(serviceRunnable, 0);
Toast.makeText(getApplicationContext(), "Counter started", Toast.LENGTH_SHORT).show();
}
public void stopCounter(){
handler.removeCallbacks(serviceRunnable);
}
//callbacks interface for communication with service clients!
public interface Callbacks{
public void updateClient(long data);
}
}
Update: July 10 2016
IMO I think using BroadcastReceiver for custom events is better way
as the Messengers mentioned don't handle activity recreation on device
rotation as well as possible memory leaks.
You may create custom BroadCast Receiver for events in the activity, Then you may also use Messengers.
In your Activity
create a MessageHandler class as
public static class MessageHandler extends Handler {
#Override
public void handleMessage(Message message) {
int state = message.arg1;
switch (state) {
case HIDE:
progressBar.setVisibility(View.GONE);
break;
case SHOW:
progressBar.setVisibility(View.VISIBLE);
break;
}
}
}
Now you can have it's instance as
public static Handler messageHandler = new MessageHandler();
Start your Service with this Handler object as an extra data as
Intent startService = new Intent(context, SERVICE.class)
startService.putExtra("MESSENGER", new Messenger(messageHandler));
context.startService(startService);
In your Service you receive this object from the intent and initialize the Messenger variable in Service as
private Messenger messageHandler;
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
sendMessage(ProgressBarState.SHOW);
And then write a method sendMessage to send messages to activity.
public void sendMessage(ProgressBarState state) {
Message message = Message.obtain();
switch (state) {
case SHOW :
message.arg1 = Home.SHOW;
break;
case HIDE :
message.arg1 = Home.HIDE;
break;
}
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
The sample code above shows and hides a ProgressBar in Activity as messages are received from Service.
Intents are good solution for communication between Activitiy and Service.
A fast solution for receive intents in your service is subclassing IntentService class. It handles asynchronous requests expressed as Intents using a queue and worker thread.
For communication from service to Activity you can broadcast the intent but instead of using normal sendBroadcast() from Context, a more efficent way is to use LocalBroadcastManager from support library.
Example service.
public class MyIntentService extends IntentService {
private static final String ACTION_FOO = "com.myapp.action.FOO";
private static final String EXTRA_PARAM_A = "com.myapp.extra.PARAM_A";
public static final String BROADCAST_ACTION_BAZ = "com.myapp.broadcast_action.FOO";
public static final String EXTRA_PARAM_B = "com.myapp.extra.PARAM_B";
// called by activity to communicate to service
public static void startActionFoo(Context context, String param1) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
context.startService(intent);
}
public MyIntentService() {
super("MyIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM_A);
// do something
}
}
}
// called to send data to Activity
public static void broadcastActionBaz(String param) {
Intent intent = new Intent(BROADCAST_ACTION_BAZ);
intent.putExtra(EXTRA_PARAM_B, param);
LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.sendBroadcast(intent);
}
}
Example Activity
public class MainActivity extends ActionBarActivity {
// handler for received data from service
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(MyIntentService.BROADCAST_ACTION_BAZ)) {
final String param = intent.getStringExtra(EXTRA_PARAM_B);
// do something
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter();
filter.addAction(MyIntentService.BROADCAST_ACTION_BAZ);
LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.registerReceiver(mBroadcastReceiver, filter);
}
#Override
protected void onDestroy() {
LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.unregisterReceiver(mBroadcastReceiver);
super.onDestroy();
}
// send data to MyService
protected void communicateToService(String parameter) {
MyIntentService.startActionFoo(this, parameter);
}
}
I think there is a problem with the correct answer. I have not enough reputation to comment on it.
Right in the answer:
Activity call bindService() to get pointer to Service is ok. Because service context is maintained when connection is maintained.
wrong in the answer:
service pointer to Activity class to call back is bad way. Activity instance maybe not null during Activity context is being Release => exception here.
solution for the wrong in the answer:
service send intent to Activity. and Activity receiver intent via BroadcastReceiver.
Note:
in this case, Service and Activity in the same Process, you should use LocalBroadcastManager to send intent. It make performance and security better
This is a simple example of communication between activity and service
Activity
MyReceiver myReceiver; //my global var receiver
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layourAwesomexD);
registerReceiver();
}
//When the activity resume, the receiver is going to register...
#Override
protected void onResume() {
super.onResume();
checkStatusService(); // verficarStatusServicio(); <- name change
registerReceiver();
}
//when the activity stop, the receiver is going to unregister...
#Override
protected void onStop() {
unregisterReceiver(myReceiver); //unregister my receiver...
super.onStop();
}
//function to register receiver :3
private void registerReceiver(){
//Register BroadcastReceiver
//to receive event from our service
myReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MyService.SENDMESAGGE);
registerReceiver(myReceiver, intentFilter);
}
// class of receiver, the magic is here...
private class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context arg0, Intent arg1) {
//verify if the extra var exist
System.out.println(arg1.hasExtra("message")); // true or false
//another example...
System.out.println(arg1.getExtras().containsKey("message")); // true or false
//if var exist only print or do some stuff
if (arg1.hasExtra("message")) {
//do what you want to
System.out.println(arg1.getStringExtra("message"));
}
}
}
public void checkStatusService(){
if(MyService.serviceStatus!=null){
if(MyService.serviceStatus == true){
//do something
//textview.text("Service is running");
}else{
//do something
//textview.text("Service is not running");
}
}
}
Service
public class MyService extends Service {
final static String SENDMESAGGE = "passMessage";
public static Boolean serviceStatus = false;
#Override
public void onCreate() {
super.onCreate();
serviceStatus=true;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {return null;}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//you service etc...
passMessageToActivity("hello my friend this an example of send a string...");
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
passMessageToActivity("The service is finished, This is going to be more cooler than the heart of your ex...");
System.out.println("onDestroy");
serviceStatus=false;
}
private void passMessageToActivity(String message){
Intent intent = new Intent();
intent.setAction(SENDMESAGGE);
intent.putExtra("message",message);
sendBroadcast(intent);
}
}
if we don't unregister BroadcastReceiver we will have an error, you need to unregister when the activity go onPause, onStop, onDestroy...
if you don't register BroadcastReceiver when you back to activity, it will not listen anything from the service... the service will send information to BroadcastReceiver but it will not receive anything because it isn't registered.
When you create more than one service, the following services are going to begin in onStartCommand.
You can pass information to service with intent and you get it in onStartCommand
Difference about return in onStartCommand: Difference between START_STICKY and START_REDELIVER_INTENT? and check the official website of google: Services
The best way in this case is to communicate by doing broadcasting from your service for different actions and receiving it in your activity. You can create a custom broadcast and send some codes defining specific events like complete, change, prepare etc...
Most easy and efficient way will be using EventBus from GreenRobot.
Use simple 3 steps:
1 Define events
public static class MessageEvent { /* Additional fields if needed */ }
2 Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
3 Post events:
EventBus.getDefault().post(new MessageEvent());
Very easy yet powerful way is to use EventBus you can add it to your gradle build and enjoy the easy publisher/subscriber pattern .
I'm working on my first Android app and I have a problem with services. I have one activity and one service, in the activity there is a button which calls a method in the service and in the service I have one timer which after 20 seconds shows something in Logcat. In the activity I start the service with startService(intet) and then bind to it in order to keep it working even after closing the activity as it was suggested in many topics.
If I hit the back button or home button the app works properly, but if I hold the home button and then close the app I do not see the log, but if I go to app manager under the Running tab I can see my app and the service is running!
I do not want to use alarm manager. There reason that I am using a timer here is that I want to make sure my service is really working! I just want to do something in the service and make sure it will work even if the app is closed.
public class BoundService extends Service {
private final IBinder myBinder = new MyLocalBinder();
#Override
public IBinder onBind(Intent intent) {
return myBinder;
}
public int onStartCommand(Intent intent, int flags, int startId)
{
return START_STICKY;
}
#Override
public void onDestroy()
{
super.onDestroy();
Toast.makeText(this, "Service Stopped", Toast.LENGTH_LONG).show();
}
public void testNotification()
{
int interval = 20000; // 20 Second
Handler handler = new Handler();
Runnable runnable = new Runnable(){
public void run() {
Log.d("BoundService", "Timer");
}
};
handler.postAtTime(runnable, System.currentTimeMillis()+interval);
handler.postDelayed(runnable, interval);
}
public class MyLocalBinder extends Binder {
BoundService getService() {
return BoundService.this;
}
}
}
Activity:
public class MainActivity extends Activity {
BoundService myService;
boolean isBound = false;
public void test(View view)
{
myService.testNotification();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BoundService.class);
startService(intent);
bindService(intent, myConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection myConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
MyLocalBinder binder = (MyLocalBinder) service;
myService = binder.getService();
isBound = true;
}
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Thanks
Manual says: "When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it". Thats why your handler works in activity thread and can't continue to process events after activity was killed. Try something like this in your service:
Thread worker = null;
Handler handler = null;
#Override
public void onCreate() {
worker = new Thread(new Runnable() {
#Override
public void run() {
Looper.prepare();
handler = new Handler();
Looper.loop();
}
});
worker.start();
}
#Override
public void onDestroy() {
handler.getLooper().quit();
worker = null;
handler = null;
}
Edit:
You must also call startForeground within your service (using some notification) because activity manager kills app process when you closing app if there are no foreground service running and service crashes and will be restarted later because of START_STICKY. It is strange behavior but I saw this on real device too.
In first Activity I've started this service:
public class TaskMsg extends Service
{
private static Timer timer;
private Context ctx;
public IBinder onBind(Intent arg0)
{
return null;
}
public void onCreate()
{
super.onCreate();
timer = new Timer();
ctx = this;
startService();
}
private void startService()
{
timer.scheduleAtFixedRate(new mainTask(), 0, 35000);
}
private class mainTask extends TimerTask
{
public void run()
{
toastHandler.sendEmptyMessage(0);
}
}
public void onDestroy()
{
timer.cancel();
super.onDestroy();
}
private final Handler toastHandler = new Handler()
{
#Override
public void handleMessage(Message msg)
{
// for example, 2*3;
}
}
}
In handleMessage method where I have a few operation. It's working fine.
But I have problem, because I would like to call to activity (from this Service) that in service has new result operation. In all Activity in my project, I have method Update. I need only information that Handler in Service returned new information to all Activity or current Activity.
May be you can use a custom broadcast receiver. From the service you can broadcast the message and can write a receiver in your activity. In onReceive() in your activity, you can call to your update() method.
Hope you understand what I mean
Thank You! Is it good solution for few activities?
Intent intent = new Intent(Activity1.UPDATE);
sendBroadcast(intent);
intent = new Intent(Activity2.UPDATE);
sendBroadcast(intent);
intent = new Intent(Activity3.UPDATE);
sendBroadcast(intent);