I have a Service and BroadcastReceiver in my application, but how do I launch the service directly from the BroadcastReceiver? Using
startService(new Intent(this, MyService.class));
does not work in a BroadcastReceiver, any ideas?
EDIT:
context.startService(..);
works, I forgot the context part
Don't forget
context.startService(..);
should be like that:
Intent i = new Intent(context, YourServiceName.class);
context.startService(i);
be sure to add the service to manifest.xml
use the context from the onReceive method of your BroadcastReceiver to start your service component.
#Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, YourService.class);
context.startService(serviceIntent);
}
Best Practice :
While creating an intent especially while starting from BroadcastReceiver, dont take this as context.
Take context.getApplicationContext() like below
Intent intent = new Intent(context.getApplicationContext(), classNAME);
context.getApplicationContext().startService(intent);
try {
Intent intentService = new Intent(context, MyNewIntentService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intentService );
} else {
context.startService(intentService );
}
} catch (Exception e) {
e.printStackTrace();
}
It's better to use ContextCompat:
Intent serviceIntent = new Intent(context, ForegroundService.class);
ContextCompat.startForegroundService(context, serviceIntent);
Because a receiver's onReceive(Context, Intent) method runs on the main thread, it should execute and return quickly. If you need to perform long running work, be careful about spawning threads or starting background services because the system can kill the entire process after onReceive() returns. For more information, see Effect on process state To perform long running work, we recommend:
Calling goAsync() in your receiver's onReceive() method and passing the BroadcastReceiver.PendingResult to a background thread. This keeps the broadcast active after returning from onReceive(). However, even with this approach the system expects you to finish with the broadcast very quickly (under 10 seconds). It does allow you to move work to another thread to avoid glitching the main thread.
Scheduling a job with the JobScheduler
developer.android.com
Related
I am using Intent Service to monitor Geofence transition. For that I am using following call from a Sticky Service.
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
getGeofencingRequest(),
getGeofencePendingIntent()
)
and the Pending Intent calls Transition service (an IntentService) like below.
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the
//same pending intent back when calling addgeoFences()
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
This worked fine Pre Oreo. However, I had to convert my sticky service to a JobScheduler and I need to convert GeofenceTransitionsIntentService which is an intentService to JobIntentService.
Having said that I am not sure how to return create a PendingIntent for JobIntentService, because I need to call enqueueWork for JobIntentService.
Any suggestions/pointer would be appreciated.
Problem
I had the same issue when migrating from IntentService to JobIntentService on Android Oreo+ devices.
All the guides and snippets I've found are incomplete, they leave out the breaking change this migration has on the use of PendingIntent.getServce.
In particular, this migration breaks any Alarms scheduled to start a service with the AlarmManager and any Actions added to a Notification that start a service.
Solution
Replace PendingIntent.getService with PendingIntent.getBroadcast that starts a BroastcastReceiver.
This receiver then starts the JobIntentService using enqueueWork.
This can be repetitive and error prone when migrating multiple services.
To make this easier and service agnostic, I created a generic StartJobIntentServiceReceiver that takes a job ID and an Intent meant for a JobIntentService.
When the receiver is started, it will start the originally intended JobIntentService with a job ID and actually forwards the Intent's original contents through to the service behind the scenes.
/**
* A receiver that acts as a pass-through for enqueueing work to a {#link android.support.v4.app.JobIntentService}.
*/
public class StartJobIntentServiceReceiver extends BroadcastReceiver {
public static final String EXTRA_SERVICE_CLASS = "com.sg57.tesladashboard.extra_service_class";
public static final String EXTRA_JOB_ID = "com.sg57.tesladashboard.extra_job_id";
/**
* #param intent an Intent meant for a {#link android.support.v4.app.JobIntentService}
* #return a new Intent intended for use by this receiver based off the passed intent
*/
public static Intent getIntent(Context context, Intent intent, int job_id) {
ComponentName component = intent.getComponent();
if (component == null)
throw new RuntimeException("Missing intent component");
Intent new_intent = new Intent(intent)
.putExtra(EXTRA_SERVICE_CLASS, component.getClassName())
.putExtra(EXTRA_JOB_ID, job_id);
new_intent.setClass(context, StartJobIntentServiceReceiver.class);
return new_intent;
}
#Override
public void onReceive(Context context, Intent intent) {
try {
if (intent.getExtras() == null)
throw new Exception("No extras found");
// change intent's class to its intended service's class
String service_class_name = intent.getStringExtra(EXTRA_SERVICE_CLASS);
if (service_class_name == null)
throw new Exception("No service class found in extras");
Class service_class = Class.forName(service_class_name);
if (!JobIntentService.class.isAssignableFrom(service_class))
throw new Exception("Service class found is not a JobIntentService: " + service_class.getName());
intent.setClass(context, service_class);
// get job id
if (!intent.getExtras().containsKey(EXTRA_JOB_ID))
throw new Exception("No job ID found in extras");
int job_id = intent.getIntExtra(EXTRA_JOB_ID, 0);
// start the service
JobIntentService.enqueueWork(context, service_class, job_id, intent);
} catch (Exception e) {
System.err.println("Error starting service from receiver: " + e.getMessage());
}
}
}
You will need to replace package names with your own, and register this BroadcastReceiver per usual in your AndroidManifest.xml:
<receiver android:name=".path.to.receiver.here.StartJobIntentServiceReceiver"/>
You are now safe to use Context.sendBroadcast or PendingIntent.getBroadcast anywhere, simply wrap the Intent you want delivered to your JobIntentService in the receiver's static method, StartJobIntentServiceReceiver.getIntent.
Examples
You can start the receiver, and by extension your JobIntentService, immediately by doing this:
Context.sendBroadcast(StartJobIntentServiceReceiver.getIntent(context, intent, job_id));
Anywhere you aren't starting the service immediately you must use a PendingIntent, such as when scheduling Alarms with AlarmManager or adding Actions to Notifications:
PendingIntent.getBroadcast(context.getApplicationContext(),
request_code,
StartJobIntentServiceReceiver.getIntent(context, intent, job_id),
PendingIntent.FLAG_UPDATE_CURRENT);
As #andrei_zaitcev suggested, I implemented my custom BroadCastReceiver and call enqueueWork() of the Service, which works perfectly.
I have an application that using "AlarmService". For handling alarms i have a Broadcast receiver. That receiver has to start certain activity. Code i'm using for achieving that is following:
#Override
public void onReceive(Context context, Intent intent) {
...other code....
Intent intIntent = new Intent(context, MainActivity.class);
intIntent .putExtra("IsAlarm", true);
Intent alarmChooser = Intent.createChooser(intIntent , "Alarm");
alarmChooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(alarmChooser);
}
That works but only if activity isn't shown already (if it's not in the foreground). If called activity is already opened nothing happens. How can i overcome that?
Is there a flag that will start the activity if it's not started OR send intent to it even if it's in the foreground?
P.S. i tried using dedicated "broadcast" above the provided code. Reciever for that broadcast is registered programmatically in the MainActivity: "onResume" would register dedicated receiver, "onPause" would unregister it. That way in case MainActivity is already on it will receive a broadcast but then i have a problem when phone goes to "stand by" - "dedicated" receiver is unregistered.
Check in the activity onNewIntent callback
there should be the new intent from the receiver
I think you don't need the chooser:
#Override
public void onReceive(Context context, Intent intent) {
Intent intIntent = new Intent(context, MainActivity.class);
intIntent.putExtra("IsAlarm", true);
context.startActivity(intIntent);
}
I'm adding the new Geofencing API to my Android app, and I'd like to catch the transitions with a BroadcastReceiver.
I tried to run the Geofencing with a BroadcastReceiver instead of an IntentService, but the BroadcastReceiver is never called at all. I registered the BroadcastReceiver with an IntentFilter correctly.
This is my binding function:
private PendingIntent getTransitionPendingIntent() {
if(transitionPendingIntent == null) {
Intent intent = new Intent(context, GeofenceListener.class);
intent.setAction(GeofenceUtil.ACTION_TRANSITION_OCCURED);
return PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
}
else
return transitionPendingIntent;
}
Can someone tell me why the BroadcastReceiver is not triggered?
Why does it work with an IntentService and not with a BroadcastReceiver?
I got it working with a BroadcastReciever instead of an IntentService. More details here:
Android Geofence eventually stop getting transition intents
I want to start a Service use an Activity. To be specific, it is IntentService. But I have two problems:
When starting my code
private void switchService(boolean isEnable, String serviceName){
Intent intent = new Intent();
intent.setClassName(TestPhoneServiceActivity.this, serviceName);
if(isEnable){
startService(intent);
}
else{
if(isServiceRunning(serviceName)){
stopService(intent);
while(isServiceRunning(serviceName)){
}
Toast.makeText(this, "Service stopped Successfully!", Toast.LENGTH_LONG).show();
}
}
}
DDMS has the error:Unable to start service Intent { cmp=com.xx.android/.AndroidPhoneService }: not found
But the path is right. So what is the problem?And also I want to call a system service. Should I write it into configuration file?
Then I start IntentService use intent.setClass.
ActivityManager manager = (ActivityManager)getSystemService(ACTIVITY_SERVICE)
I can not find this service object. Is the IntentService finished and destroyed automatically?And the life cycle of IntentService is different from the other Service?
You should pass in the class of the service instead of the name of the class of the service like so
Intent intent = new Intent();
intent.setClassName(TestPhoneServiceActivity.this, MyIntentService.class);
if(isEnable){
startService(intent);
}
The intent service stops itself after work has completed if you start multiple of the same intent service it will be queued behind and executed sequentially, so the else block is kind of redundant in your case.
And make sure you have declared the service inside your manifest.
The parts of this application in question are an IntentService (DatabaseService) and an AppWidgetProvider (LotWidget). In LotWidget, when a button is pressed, a PendingIntent is fired which sends a broadcast to itself, and is received in onReceive(). All of this works fine so far. In onReceive, I read the action from the Intent as well as the various extras, and either start this IntentService, or start the main Activity. Starting the activity works fine, but for some reason I can't understand, the DatabaseService isn't being started. I know code leading up to the DatabaseService intent is being sent by testing with Log.
Here is the onReceive code:
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if(intent.getAction().equals(RETURN_DATA_WIDGET) && initialize(context)){
updateWidget(context);
}
else if(intent.getAction().equals(INTERNAL_INC)){
Log.d("WIDG", "Incrememnt");
Intent incr = new Intent(context, DatabaseService.class);
incr.setAction(INCREMENT);
incr.putExtra("incval", intent.getIntExtra("incval", 999));
context.startService(intent);
}
else if(intent.getAction().equals(INTERNAL_UP)){
Log.d("WIDG", "UPDATE");
Intent upd = new Intent(context, DatabaseService.class);
upd.setAction(UPDATE_COUNT);
context.startService(intent);
}
else if(intent.getAction().equals(OPEN_APP)){
Intent open = new Intent(context, TheLotActivity.class);
open.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(open);
}
}
Using the same intents to start the DatabaseService from the main Activity works fine. My manifest defines the DatabaseService as a Service like so:
<service android:name="com.bsprague.thelot.DatabaseService" >
</service>
I know the Service isn't being started because a Log at the very beginning of onHandleIntent isn't being displayed, though it displays fine when this Intent is sent from the main Activity. Is there something in Android that I'm missing that makes it so you can't start a Service from a BroadcastReceiver?
You are setting an action on the Intent, and your <intent-filter> does not have that action. You might consider replacing the setAction() calls with putExtra() calls instead.
Never mind -- as you pointed out in a comment, since you are specifying the component, all other routing elements (e.g., action) are ignored. Your error from your updated comment suggests that your Intent is picking up the wrong component, LotWidget instead of DatabaseService.