I am bit confused what is the right place to use Service (for background task).
This is my scenario:
I have a class that extends Broadcast receiver. It receives WiFi state changes. Depending on the state change, I call another class. This is a pure Java class, not extending any class.
This class is instantiated by passing the Context (received with the broadcast receiver).
I need to pass the Context because, among other things, I access SharedPreferences, display a notification, etc. But this not a foreground activity.
Is this the correct way? Or should my class extend Service and work as a background task?
Is it wrong to pass the Context to initiate a class?
For example,
public class WifiStateBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
...
WifiChangeReceptionClass wifiChanged = new WifiChangeReceptionClass(context);
wifiChanged.showNotification();
...
}
What is wrong with this approach?
Try any of this:
public class WifiChangeReceptionClass{
public static void showNotification(Context context){
//showYourNotification
}
}
Or
Create an Application class that has a static method to get it's context, like so.
public class MyApplication extends Application {
...
public static MyApplication get(){
return this;
}
...
}
Then in your class, just call:
public class WifiChangeReceptionClass{
public static void showNotification(){
Context context = MyApplication.get();
//showYourNotification
}
}
Or
Just use dagger for your dependency injections.
Check docs here
It is wrong because context reference will be available as long as you are in onReceive() method. Once call back onReceive() method gets completed context will no longer be available.
It is absolutely fine as long as you are using the context within the lifecycle of broadcast receiver (you may pass it to any class). Since broadcast receiver and service, both run on UI thread, using service would not make much difference.
If you want to perform some network operation or long running operation, then you can instantiate a intent service from receiver.
Registering for broadcast receiver to get Wifi state changes might not from Android 7 its has disabled the CONNECTIVITY_CHANGED.
Apps targeting Android 7.0 (API level 24) and higher do not receive CONNECTIVITY_ACTION broadcasts if they declare the broadcast receiver in their manifest. Apps will still receive CONNECTIVITY_ACTION broadcasts if they register their BroadcastReceiver with Context.registerReceiver() and that context is still valid.
This includes connectivity change. The better option is to use JobScheduler. Refer this link
As mentioned in the response, onReceive() of BroadcastReceiver is executed on main thread. Based on android documentation for broadcast receiver
As a general rule, broadcast receivers are allowed to run for up to 10 seconds before they system will consider them non-responsive and ANR the app. Since these usually execute on the app's main thread, they are already bound by the ~5 second time limit of various operations that can happen there (not to mention just avoiding UI jank), so the receive limit is generally not of concern. However, once you use goAsync, though able to be off the main thread, the broadcast execution limit still applies, and that includes the time spent between calling this method and ultimately PendingResult.finish().
If your WifiChangeReceptionClass is doing some extensive work, then refrain from running directly in onRecevie(). Instead start Service (you have to spawn a new thread thread) or IntentService
Related
Is it possible to send an intent from a service to an Application class? Not Activity?
I wouldn't know what activity would be running at a particular time, so I am adding a boolean flag in the activity class that detects the activity and sends the appropriate data based on the broadcast received.
If your Service is active, then your Application class is active as well.
Otherwise you wouldn't be able to use getApplicationContext().
Although I'm skeptic about a service that runs forever there is a very clean way to make the Service communicate with a certain Activity, should the last one be currently active.
Such clean way is called LocalBroadcastManager.
The Activity meant to receive the data should register a BroadcastReceiver in onResume() and unregister it in onPause().
You instantiate your BroadcastReceiver in your Activity's onCreate()
this.localBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Do what you have to do here if you receive data from the Service.
}
}
You create a Filter so your Activity only listens to a certain type of signals.
private IntentFilter notifIntentFilter new IntentFilter("com.you.yourapp.MY_SIGNAL");
in onResume()
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(this.localBroadcastReceiver, notifIntentFilter);
in onPause()
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(this.localBroadcastReceiver);
Now whenever you want to send data to your Activity, your Service can call:
final Intent intent = new Intent();
intent.setAction("com.you.yourapp.MY_SIGNAL");
// put your data in intent
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
If your Activity is awake, it will respond to the signal. Otherwise, if it's in the background, or it is not instantiated it won't.
You can apply this pattern to as many Activities as you wish.
Still, I have never used this inside the Application class. But you can try to register your receiver there. It might work, since if the Application class is destroyed, the BroadcastReceiver is destroyed too and thus probably unregistered as well.
The point is, if your Application gets destroyed, your Service will be killed as well. Unless you launched it in another process. But then it will have it's own instance of Application; and this is a complex thing you probably do not want to get into details now...
Important: since the Application class is not tied to any UI component, you can do whatever you need directly inside your service. If you need to manipulate the UI, then the pattern described above will work for you.
Please read about new Android's background limitations.
Edit:
Oh yeah right, if you need your Service to call a function declared in your Application class, you can just do
((MyApplication) getApplication()).myFunctionToHandleData(Intent intent);
I didn't really understand your question though, but either of the methods described above should work for you.
I have an activity that starts a background service. Once it is started, it runs forever.
Lets say the background service needs the activity that started it to update something. Then how can I start the activity again "If it is not started" however if it is already started then send a broadcast?
Thanks
You would have to bind to the service in your activity. Then, in the service, implement the onBind and onUnbind methods to set a boolean "bound". Check the boolean to see whether the activity is active.
With activity you mean the service's process. If the service is started forever, then its process its started foerever (except when the system kill its for recovering memory purpose and recreates it later). That doesnt mean the rest of activities/fragments/services are not kill. A service is just an entry point for your application and it gives your process a position into the process priority ladder.
Its hard to say, since I don't know the details of your app, but I think that you may want to consider a bit of a redesign.
As you have noticed, Activities are ephemeral. Generally speaking, a service should not (cannot) depend on a particular Activity being active.
In fact, a well-designed service should not depend on any particular activity at all.
I'm not sure I completely follow your question. However, when communicating from a Service to an Activity I use a broadcast receiver in the Activity class.
This can be created as follows:
// register the BroadcastReceiver in your activity class
this.receiver = new NotificationReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.LISTENER");
registerReceiver(receiver, filter);
// insert in your activity class
class MyReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
if(intent.hasExtra("command")) {
if(intent.getStringExtra("command").equals("userRegistered")) {
// insert code to do something when this intent is received
}
}
}
}
// insert in your service class to send message
Intent i = new Intent("com.example.LISTENER");
i.putExtra("command", "userRegistered");
sendBroadcast(i);
I have a BroadcastReceiver, and the onReceive is called from two different postExecute methods in two different asyncTasks, in two different Activities.
I have a third activity that is running all the time called HomeActivity, and I want to publish some text to the HomeActivity's UI from the onReceive method.
Is it possible? I know that the context parameter is the context of the activity who raised the onReceive, but I want to access the HomeActivity's UI.
Here is the code
public class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// here I want to publish some text to the HomeActivity
}
}
any ideas? thanks in advance
You want to change text in your running activity based on what you receive in the onReceive of the BroadcastReceiver? Right? One way is that you can use LocalBroadcast. See LocalBroadcast Manager.
For how to implement is, there is a great example on how to use LocalBroadcastManager?.
LocalBroadcast Manager is a helper to register for and send broadcasts of Intents to local objects within your process. The data you are broadcasting won't leave your app, so don't need to worry about leaking private data.`
Your HomeActivity can registers for this local broadcast. From the MyBroadcastReceiver you send a LocalBroadcast from within the onReceive (saying that hey, I received a message. Do you want to do something now activity). Then inside your Activity you can listen to the broadcast. This way if the activity is in the forefront/is active, it will receive the broadcast otherwise it won't. So, whenever you receive that local broadcast, you may change the text etc, if activity is open.
Suppose i want to run both wakeful and non wakeful service alternatively.
Something like a toggle button. If its On then i have to start my WakefulIntentService with doWakefulWork() method.
And if its off then I have to do all same tasks in same WakefulIntentService but with a different method which don't interact with CPU and wifi in sleep mode something like non wakeful work in my word..
I just added a new method in my Service, but unable to go forward.
public class NetworkCommunicationService extends WakefulIntentService{
private static String UserID;
public NetworkCommunicationService() {
super("NetworkCommunicationService");
}
#Override
protected void doWakefulWork(Intent intent) {
if (Utils.isNetworkAvailable(this))
new SyncValidater().execute();
}
/**I have to do something like this.... */
protected void doNonWakefulWork() {
if (Utils.isNetworkAvailable(this))
new SyncValidater().execute();
}
Is their any solution for this... Or I have to create a new service to do the non wakeful work.
Please help. Thanks
Suppose i want to run both wakeful and non wakeful service alternatively
It is not clear to me what situation would make sense for this. WakefulIntentService does not support this scenario (or, if it does, it is by accident, not intention).
I would recommend switching to WakefulBroadcastReceiver. Then, when you want the service to run "wakefully", follow the WakefulBroadcastReceiver recipe and call startWakefulService(). If you want the service to run normally, just have the receiver call startService(). Note, though, that there is a chance that the service will not get an opportunity to run in this case, if the device falls asleep before the service starts.
Maybe I'm missing something. I want to write test cases for a BroadcastReceiver; specifically, it is for receiving the BOOT_COMPLETED event and setting an alarm for another receiver to handle later; it doesn't seem to be setting it properly, but the point is that I have no obvious way to test it. I can't exactly attach a debugger and wait for BOOT_COMPLETED, and I can't send a fake BOOT_COMPLETED broadcast.
Why are there instrumentation classes for Activity, Service, and Provider, but not BroadcastReceiver? Any advice for testing this?
There is nothing magical about the life cycle for the BroadcastReceiver. It's enough to test it with an AndroidTestCase. In a test case, instantiate your BroadcastReceiver, create whatever Intent you want to send and call onReceive using the Context available from AndroidTestCase or some mock Context.
E.g.
public class TestMyBroadcastReceiver extends AndroidTestCase {
public void testReceive() {
MyBroadcastReceiver r = new MyBroadcastReceiver();
Intent i = new Intent("MY_ACTION");
// TODO put extras
r.onReceive(getContext(), i);
// TODO query application state to verify results
}
}
For most cases I agree completely with https://stackoverflow.com/a/5181010/527016
There are however cases when extending AndroidTestCase is not suitable (and can cause surprises). In particular, if you are doing more complex integration testing and want to test your BroadcastReceiver with an actual Intent sent by the system. The main reason is that the onReceive method in the broadcast receiver runs on the main application thread while the tests in AndroidTestCase run in another thread. This can cause test-related threading issues in code that was not intended to run on multiple threads.
The solution to this is to subclass your test from InstrumentationTestCase instead and use the #UiThreadTest annotation to make the tests run on the same thread as the onReceive method.
For more info (and an example) see: http://olafurhelgason.blogspot.com/2012/12/threading-and-android-integration.html