I have a BroadcastReceiver which listens for an intent in onDestroy() callback. And there is a blocking while which goes on till bluetooth discoverability is switched off. Once discoverability is off, the changeModeReceiver will call its onReceive() and set destroy_ok to true, and hence breaking out of the while loop. But, this is not giving desired results.
Toast message, "In onDestroy()" is not getting printed
"In onDestroy()" is getting printed in the logcat
The bluetooth is still switched on
The code is as follows.
boolean destroy_ok = false;
protected void onDestroy(){
System.out.println("In onDestroy()");
Toast.makeText(getApplicationContext(), "In onDestroy()", Toast.LENGTH_LONG).show();
BroadcastReceiver changeModeReceiver = new BroadcastReceiver(){
public void onReceive(Context context, Intent intent){
String mode = intent.getStringExtra(BluetoothAdapter.EXTRA_SCAN_MODE);
if (mode.equals(BluetoothAdapter.SCAN_MODE_NONE))
destroy_ok = true;
}
};
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
registerReceiver (changeModeReceiver, filter);
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,1);
startActivity(discoverableIntent);
while (!destroy_ok){}
unregisterReceiver(changeModeReceiver);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled())
adapter.disable();
System.out.println("Leaving onDestroy()");
super.onDestroy();
}
The onDestroy method (as well as all other activity lifecycle methods, view callback methods, etc.) is called on the application's main UI thread, so no, you shouldn't block for a significant period of time when called. Doing so will likely result in lag, and may even spawn an ANR (application not responding) error if you block for more than 5-10 seconds.
Note: do not count on this method being called as a place for saving
data! For example, if an activity is editing data in a content
provider, those edits should be committed in either onPause() or
onSaveInstanceState(Bundle), not here. This method is usually
implemented to free resources like threads that are associated with an
activity, so that a destroyed activity does not leave such things
around while the rest of its application is still running. There are
situations where the system will simply kill the activity's hosting
process without calling this method (or any others) in it, so it
should not be used to do things that are intended to remain around
after the process goes away.
http://developer.android.com/reference/android/app/Activity.html#onDestroy()
So simply said, use onPause() for such operations. Also, I would use a Service or a new Thread in your case.
Related
When my service is running and I can see my app on the screen, everything works fine. My service sends broadcast messages and I can see them in my "MainActivity". But as soon as I push the home button and my app is no longer in the foreground, it stops. The service is still running but the broadcasts don't arrive at my "dead" or "pausing" app. This is my code:
Main Activity:
onCreate:
Intent intent = new Intent(getApplicationContext(), MainGate_Service.class);
startService(intent);
#Override
protected void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver(ServiceReceiver, new IntentFilter("MainGate_ring"));
}
#Override
protected void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(this).unregisterReceiver(ServiceReceiver);
}
private BroadcastReceiver ServiceReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String message = intent.getStringExtra("ServiceMessage");
if(message != null) {
if(alarmActive && message.equals("ring"))
new soundPlayer().execute();
}
setNoti(message);
}
}
Service:
#Override
protected void onHandleIntent(Intent intent) {
ring = rcv_ring();
Intent ServiceIntent = new Intent("MainGate_ring");
ServiceIntent.putExtra("ServiceMessage", ring);
LocalBroadcastManager.getInstance(this).sendBroadcast(ServiceIntent);
}
whenever you press home button onStop gets call and there you are unregister receiver so there is not broadcast receiver who can receive broadcast.
LocalBroadcastManager.getInstance(this).unregisterReceiver(ServiceReceiver);
Remove above line from onStop() or unregister it whenever your service stop.
onStop#LocalBroadcastManager.getInstance(this).unregisterReceiver(ServiceReceiver);
literally unregister receiver. So there is no receiver to receive broadcasted message from service.
The problem is here
#Override
protected void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(this).unregisterReceiver(ServiceReceiver);
}
Activity.onStart() :
Called after onCreate(Bundle) — or after onRestart() when the activity had been stopped, but is now again being displayed to the user. It will be followed by onResume().
Activity.onStop()
Called when you are no longer visible to the user. You will next receive either onRestart(), onDestroy(), or nothing, depending on later user activity.
So as soon as you leave your activity onStop() is called and you unregister your receiver.
Don't put your unregister code in onDestroy() either, because it may or may not be called and the Activity might be killed without calling it.
As suggested by #Naveen Dissanayake you might want to reconsider your logic.
You register your BroadcastReceiver inside activity, so, it will depend on Activity's lifecycle. When you press back button, activity goes into 'stopped' state - methods onPause and onStop is called. Android can also destroy this activity in it is low on resources.
Service, on the other hand, i smeant to be running inndefinetely, even when ui is not ready. If you wanat to receive notifications from Activity, there is two
possible solutions:
- Create, store and manage BroadcastReceiver in Application instance - Application class is still running until your app is destroyed. It seems like you want to play sound when service notify you about some action.
Register BroadcastReceiver in onCreate and unregister in onDestroy in notifications.
- Another solution - use another Service if you want to trigger some action or IntentService, reacting to that broadcast.
I woud consider solution - create some ResponseService, start it along with your MainGate_Service (from Application) and stop it from application too. In that Service register BroadcastReceiver or, add IntentFilter into manifest if you want it to start even when app is not running. In your Activity, bind to that service. Serive will know if some UI is attached (if Activity is bound), and, if it is, will notify activity. If it don't - will do some other things (perhaps, show notification)
If you want to receive Broadcast even if your Activity is in
Background then,
Register in onCreate(...)
#Override
protected void onCreate(Bundle sis) {
super.onCreate(sis);
setContentView(...);
LocalBroadcastManager.getInstance(this).registerReceiver(ServiceReceiver, new IntentFilter("MainGate_ring"));
}
Unregister in onDestroy()
#Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).registerReceiver(ServiceReceiver, new IntentFilter("MainGate_ring"));
}
I currently have a service that processes some stuff, and it is started with startService.
I was wondering, can I call `stopService immediately after I start the service and expect it to stop the service after the processing is done?
Or does Android kill the service when I call that command?
One hopes that "processes some stuff" is done in a background thread, assuming that it will take more than a couple of milliseconds.
Android is largely oblivious to such a background thread. stopService() will trigger onDestroy() of the service, and the service will go away. The thread, however, will continue to run, until it terminates on its own, or until the process is terminated.
can I call `stopService immediately after I start the service and expect it to stop the service after the processing is done?
Only if "the processing" is done on the main application thread (e.g., in the body of onStartCommand()), which, again, is not a good idea if such work will take more than a couple of milliseconds. And, if that indeed is the case, there's no good reason for having a service in the first place.
If you want to have a service that:
Has a background thread, and
Automatically shuts down when the work is complete (avoiding the need for stopService())
then use an IntentService.
Android can't kill just a single Service. All it can do is to kill the whole process and everything running within. Most apps will have just 1 process so this usually means Android kills everything or nothing. Most of the times nothing.
The lifecycle of a Service or Activity tells Android whether it may kill the process safely or not. The Processes and Threads describes the order in which processes are kill if there is demand for memory.
It is important to know that a Thread started from a Service / Activity it is not affected at all by onDestroy etc. It just keeps running. Android simply does not know about that thread and won't stop it for you.
That means if you want to do some background processing you have link the lifecycle of such threads to the lifecycle of your Activity / Service or Android may just kill the process and thus your thread.
Quick example of a Service that prints to logcat every second while running. Not based on IntentService since that's more or less intended for tasks with an end.
public class MyService extends Service {
public static void start(Context context) {
context.startService(new Intent(context, MyService.class));
}
public static void stop(Context context) {
context.stopService(new Intent(context, MyService.class));
}
private final ExecutorService mBackgroundThread = Executors.newSingleThreadExecutor();
private Future<?> mRunningTask;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// startService -> start thread.
if (mRunningTask == null) {
// prevents task from being submitted multiple times.
// actually not necessary when using a single thread executor.
mRunningTask = mBackgroundThread.submit(mRunnable);
}
return START_STICKY;
}
private Runnable mRunnable = new Runnable() {
#Override
public void run() {
while (!Thread.interrupted()) {
try {
// Do something
Log.d("Service", "I'm alive");
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Service", "Got interrupted", e);
Thread.currentThread().interrupt();
}
}
}
};
#Override
public void onDestroy() {
// stopService > kill thread.
mBackgroundThread.shutdownNow();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
According to the documentation:
stopService(Intent service)
Request that a given application service be stopped. If the service is
not running, nothing happens. Otherwise it is stopped. Note that calls
to startService() are not counted -- this stops the service no matter
how many times it was started.
Note that if a stopped service still has ServiceConnection objects
bound to it with the BIND_AUTO_CREATE set, it will not be destroyed
until all of these bindings are removed. See the Service documentation
for more details on a service's lifecycle.
I have a BroadcastReceiver registered in the UI thread that grabs some information from the Bundle in its onReceive method. I need these values before I proceed in my main thread.
Is there any way to wait for the onReceive to finish before trying to use those values? I am running into timing issues where onReceive sets the values AFTER I try to use them. Having the thread sleep doesn't work, since they're on the same thread.
Would it make sense to register the receiver in an AsyncTask, and call wait() on the main thread, then have onReceive notify() once it completes?
String a = "hi";
IntentFilter filter = new IntentFilter();
filter.addAction(MY_CUSTOM_INTENT);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Set the variable values here
a = "bye";
}
};
getApplicationContext().registerReceiver(receiver, filter);
// Get the values, I am getting a = "hi" here because the onReceive code has
// not been reached yet
// How can I guarantee that a = "bye" from this method?
getA();
where method is something like
String getA() {
return a;
}
You seem to be over-complicating things. It's hard to know what you're really after based on the example code, but the code that comes after registerReceiver() should just do whatever else it needs to do and then return, without waiting for or hoping for the Broadcast to have been received. onReceive() should include whatever code you want to have executed at that point (which may well just be a method call, e.g. updateA("bye").
Well, if your going to block the main thread either way, might as well do it elegantly.. Register the receiver with the AsyncTask, and use a Dialog to let the user know what you are doing or that you are loading something.
I am new to Android programming and am trying to understand the concept of BroadcastReceivers. In order to help myself, I am just trying to write a small application that monitors Wifi signal strength.
Now, from my understanding I can simply wait to receive the RSSI_CHANGED_ACTION broadcasted by the system. The RSSI should change frequently which means I should be receiving this notification frequently...however, never do I receive it once. I have watered my code down to the bare minimum so it just logs a message when the notification is received.
public class RssiActivity extends Activity {
public BroadcastReceiver rssiReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.d("Rssi", "RSSI changed");
}
};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
public void onResume() {
super.onResume();
registerReceiver(rssiReceiver, new IntentFilter(WifiManager.RSSI_CHANGED_ACTION));
Log.d("Rssi", "Registered");
}
#Override
public void onPause() {
super.onPause();
unregisterReceiver(rssiReceiver);
Log.d("Rssi", "Unregistered");
}
}
I have already seen this post Android: How to monitor WiFi signal strength and it doesn't seem to help me. I have also tried the code sample here http://android-er.blogspot.com/2011/01/check-rssi-by-monitoring-of.html and it never updated the RSSI value either. I'm quite confused as to why this is. Any help you can give me would be greatly appreciated. Thanks!
So, I had the same problem that you did, wanting to an updated RSSI value as the user walked around, etc, and I could not solve it using RSSI_CHANGED_ACTION.
Like the issue you're having, my callback would not be called correctly. Strangely, it was only called once, when the activity was created, and then never again.
My Workaround
In your onCreate(), register a callback for SCAN_RESULTS_AVAILABLE_ACTION. Then call WifiManager.startScan().
Now, in your callback, do:
WifiManager wifiMan=(WifiManager)getActivity().getSystemService(Context.WIFI_SERVICE);
int newRssi = wifiMan.getConnectionInfo().getRssi();
wifiMan.startScan();
Now you have a loop, where the callback initiates a scan, receives the results, and initiates another scan.
It's gross and will suck a lot of power, however, you can watch the RSSI values change as you walk around.
Full Code
(note that I use onResume and onPause to register and unregister, so it will only scan repeatedly, e.g. waste battery, when the activity is onscreen)
#Override
public void onResume() {
super.onResume();
//Note: Not using RSSI_CHANGED_ACTION because it never calls me back.
IntentFilter rssiFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
this.registerReceiver(myRssiChangeReceiver, rssiFilter);
WifiManager wifiMan=(WifiManager)getActivity().getSystemService(Context.WIFI_SERVICE);
wifiMan.startScan();
}
#Override
public void onPause() {
super.onPause();
this.unregisterReceiver(myRssiChangeReceiver);
}
/**
* Broadcast receiver to update
*/
private BroadcastReceiver myRssiChangeReceiver
= new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent arg1) {
WifiManager wifiMan=(WifiManager)getActivity().getSystemService(Context.WIFI_SERVICE);
wifiMan.startScan();
int newRssi = wifiMan.getConnectionInfo().getRssi();
Toast.makeText(getActivity(), ""+newRssi, Toast.LENGTH_SHORT).show();
}};
Sorry I'm so late, I just had to find out I had to solve your problem :P
WifiManager.RSSI_CHANGED_ACTION is triggered when the RSSI levels change. I.E. you lose or win a wifi bar. It does not happen that often.
My guess is it's sticky so it triggers when registered.
As said, the best way I found to solve the problem is through WifiManager.SCAN_RESULTS_AVAILABLE_ACTION .
are you shure that it has to trigger (meaning are you shure the signal strength is changing)? have you read the BroadcastReciever Dokumentation?
Note: If registering a receiver in your Activity.onResume() implementation, you should unregister it in Activity.onPause(). (You won't receive intents when paused, and this will cut down on unnecessary system overhead). Do not unregister in Activity.onSaveInstanceState(), because this won't be called if the user moves back in the history stack.
Try to register your reciever inside of your AndroidManifest.
I'm trying to revamp my multi-activity app to use just once instance of a LocationListener which I intend to implement in a service. Prior to doing this I've been experimenting with a stub activity and a stub service to see what happens under error conditions.
I want to see what happens if I attempt to unbind from a service which has already been unbound and avoid any errors if this should happen. The activity has two buttons to bind/unbind. If I deliberately hit the unbind twice in succession I do get a runtime error.
What condition can I test for at the point marked '<<<<<' in the code below to skip calling unbind again?
My activity code is
public void myClickHandler(View target) {
switch (target.getId()) {
case R.id.bind:
Log.d("STAG", "Activity One pressed BIND button");
mServiceConnected = bindService(new Intent(
"com.nbt.servicetest.LOCATIONSERVICE"), mServconn,
Context.BIND_AUTO_CREATE);
break;
case R.id.unbind:
Log.d("STAG", "Activity One pressed UNBIND button");
try{
if (mServconn != null) // <<<< What to put here if already unbound?
unbindService(mServconn);}
catch(Exception e){
Log.d("STAG", "Exception " + e.getMessage());
}
break;
}
}
ServiceConnection mServconn = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("STAG", "Activity One service connected");
mIbinder = service;
}
#Override
public void onServiceDisconnected(ComponentName name) {
Log.d("STAG", "Activity One service disconnected");
}
};
The service is starting/stopping OK. I've put log lines in the service code with the same tag on all the pertinent lines. The output is :
STAG(2945): Activity One onCreate
STAG(2945): Activity One onStart
STAG(2945): Activity One onResume
STAG(2945): Activity One pressed BIND button
STAG(2945): Loc service ONCREATE
STAG(2945): Loc service ONBIND
STAG(2945): Activity One service connected
STAG(2945): Activity One pressed UNBIND button
STAG(2945): Loc service ONUNBIND
STAG(2945): Loc service ONDESTROY
STAG(2945): Activity One pressed UNBIND button
STAG(2945): Exception Service not registered: com.nbt.servicetest.ServiceTesterActivityOne$1#43b8b290
I note that the activity's onServiceDisconnected() never gets called, is this normal?
The simplest thing to do would be to introduce another variable, say, isServConnBound, and add checks on both bind and unbind actions. Of course, remember to update the variable after you call bindService and unbindService.
I agree with vhallac - just use boolean flags. What are your concerns with this approach? As for me there's nothing to be afraid of.
As to why "the activity's onServiceDisconnected() never gets called" - yes, this is normal. Look what API says on this callback:
Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed.
Your process has neither crashed nor been killed, so this is an expected behavior. Even more, since you have your service in the same process, you'll never get this called. This is important when you bind to a service that runs in another process (inter process communication).