I'm working on an application that uses Android's DownloadManager to download files. In my AndroidManifest.xml I register a receiver to listen for downloads completing like this
<receiver android:name=".download.DownloadCompleteBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
In the broadcast receiver's onReceive method, I launch an IntentService to perform some post-processing on the downloaded file before it's ready for use. Here's what that code looks like:
public class DownloadCompleteBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try {
final long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
context.startService(
new Intent(context, DownloadService.class)
.putExtra("downloadId", downloadId));
} catch (Throwable t) {
Log.i("Yo", Log.getStackTraceString(t));
}
}
The DownloadService class looks something like this
public class DownloadService extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
// Some processing logic
...
// Send processing complete
sendBroadcast(new Intent("com.example.package.DownloadService:PROCESSING_COMPLETE"));
}
}
This service is registered in my AndroidManifest.xml like this
<service android:name=".download.DownloadService" android:process=":Downloads"/>
Finally, I have an activity that listens for that PROCESSING_COMPLETE broadcast. That code looks like this
public class MyActivity extends AppCompatActivity {
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Update some UI
}
}
#Override
protected void onResume() {
super.onResume();
registerReceiver(receiver, new IntentFilter("com.example.package.DownloadService:PROCESSING_COMPLETE"));
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
}
And finally, the issues:
1) I am seeing delays (as much as 20 seconds) between when the download manager completes a download and when my registered BroadcastReceiver receives the download complete notifications. Is this normal? This issue/question is not as big of a concern as the next two.
2) I am seeing delays (as much as 2 minutes) between when my BroadcastReceiver calls startService() and when DownloadService.onCreate() is called. What's going on here? Sometimes my devices will show an ANR dialog asking me to wait for the app or force close it. I know I declared the service to run in a separate process, and there's some lead time to create the process and launch the service, however I'm seeing similar delays in successive runs when the process is already created. Moving DownloadService to the default process doesn't appear to improve the load time, however I believe the best practice is to run such a service in it's own process to keep it from being terminated should the app crash.
3) I am seeing delays (as much as 2 minutes) between when the DownloadService sends the PROCESSING_COMPETE broadcast and when my activity actually receives it. This is without leaving the activity then coming back (an onPause() and onResume()cycle), though my code handles unregistering and re-registering. What's going on here? I'm testing on a Galaxy S7 running 6.0. I hardly have any apps installed/running on this device that I could image would be slowing down broadcasts. Another interesting observation is that if I send multiple broadcasts, they're all delivered at the same time after the long delay.
Thanks for the help in advance!
Related
I need to have a two way communication between my activity and a running IntentService.
The scenario is like this: the app can schedule alarms which on run, start an IntentService which fetches some data from web and process it. There are three possible situations when IntentService finishes:
The app is in focus, which means that when the IntentService will finish, the app needs to refresh its views with the new data.
The app is closed and when opened after IntentService has finished the work, so the app will have access to processed data
The app is opened while the IntentService is running, in which case I need to have a way from the activity to ask the IntentService if its doing something in the background.
For 1. I have already implemented a BroadcastReceiver in my activity which gets registered with the LocalBroadcastManager. When IntentService finishes the work, sends a broadcast and the activity reacts. This works fine
For 2. There is nothing needed to be done
For 3. I don't know what to do. So far I've tried this:
In Activity:
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_SEND_TO_SERVICE));
In IntentService
private LocalBroadcastManager localBroadcastManager;
private BroadcastReceiver broadcastReceiverService = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(BROADCAST_SEND_TO_SERVICE)) {
//does not reach this place
//Send back a broadcast to activity telling that it is working
}
}
};
#Override
protected void onHandleIntent(Intent intent) {
localBroadcastManager = LocalBroadcastManager.getInstance(context);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_SEND_TO_SERVICE);
localBroadcastManager.registerReceiver(broadcastReceiverService, intentFilter);
.... //do things
}
The problem with my implementation is that n the IntentService the BroadcastReceiver does not fire onReceive. Any suggestions or maybe a simpler way for the Activity to ask the IntentService what it is doing?
LE:
Trying to get atomicboolean.
In Service:
public static AtomicBoolean isRunning = new AtomicBoolean(false);
#Override
protected void onHandleIntent(Intent intent) {
isRunning.set(true);
// do work
// Thread.sleep(30000)
isRunning.set(false);
}
In Activity, restarting the app while service is running:
Log(MyIntentService.isRunning.get());
//this returns always false, even if the intent service is running
On AndroidManifest
<service
android:name=".services.MyIntentService"
android:exported="false" />
This probably shouldn't be too hard to make but I was wondering what would be the best practice in doing it. I plan to create something like this:
Service needs to be running all the time. It also needs to start when user boot his device.
Service is processing data from database so it needs to listen for database changes for one table.
After user makes an action to insert new row to database, service should register that and start processing data.
I am not sure how to listen for those changes and process them. I know I can start a service on boot by creating new Broadcats receiver:
public class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MyService.class);
context.startService(service);
}
}
And defining it inside manifest:
<receiver android:name="MyReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
And this is simple service:
public class MyService extends Service {
private final IBinder mBinder = new MyBinder();
private ArrayList<String> list = new ArrayList<String>();
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Should check for database change here?
// fetches data from database
List<String> data = Manager.get(context).getData();
return Service.START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
}
Service should be running now. How can I now implement periodic check of my database or listen for database changes? What would the recommended design or best practice to implement this process? Any idea is most welcome :)
EDIT
Some extra explanation. User who is using the app is the only one who can update database. It's a local SQLite database which is created inside a class which extends SQLiteOpenHelper. The database is updated when user fills some predefined EditText views inside Activity and clicks on a button to save data. Service needs to process each new row inside that table. Sometimes it can happen the user turns off his device or kills his app before data is processed. In that case service needs to continue processing data after user boots his device again or starts an app.
If your app is the only one that can modify the database, and only as a result of user action, then you always know exactly the moment your table has been updated. Simply initiate the processing whenever you perform an insert.
If you want that processing done in a service, use an IntentService. IntentService processes everything on a background thread so you can do operations like network requests, and it automatically stops itself when it has no more work to do.
I don't know how long this processing of yours takes, but I don't think you should be fearful of the app being killed or the device being turned off. If the user closes the app, the system will not kill it immediately; if the OS does need to kill one of your application components to regain memory, it is less likely to kill your IntentService than, say, the Activities that are no longer being used (and by then you might be done with your processing anyway, so it won't matter). But in general if the OS is not under memory pressure then it will keep app components around as long as possible so that it takes less time if the user switches back to that app.
The only things that would actually kill your app is a device with too much memory pressure or the user is using a task killer app. If that's truly a concern for you (or if your processing takes so long that you think those situations could happen), then you can consider persisting pending processing tasks to disk before you begin processing. When a task finishes, remove it from disk. Then your boot completed receiver only needs to check if there are pending tasks that didn't complete. You can build your own solution, or you can look at something like Path's JobQueue library: https://github.com/path/android-priority-jobqueue
You could make your service a Singleton and create another BroadcastReceiver just for database changes:
public class YourService extends Service{
private static YourService sInstance;
public static YourService getInstance(){ return sInstance; }
onCreate(){
sInstance = this;
...
}
public void processSomeData(YourData data){
//do the stuff you wanna do
}
}
And your new receiver could look like this
public class DBChangeReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
YourData data;
//grab your data from the intent
YourService.getInstance().processSomeData(data);
}
}
Then just call sendBroadcast() when you update your database
I created a BroadcastReceiver and it runs only when my app shown in recent apps menu. If I remove my app from the recent apps the BroadcastReceiver will stop working.
How can I keep the BroadcastReceiver in background?
I register the BroadcastReceiver from my main activity (in OnCreate()).
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(receiver, intentFilter);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
}
};
This is not how you should register a receiver. You receiver stops working, because you construct it in onCreate, which means it will live as long as your app is alive. When the app gets destroyed, you also lose the the receiver.
If you register receiver inside an activity, you should always register it in onResume and deregister onPause, which will make it available while the activity is visible to the user. This is a use case when you want to have an active receiver while user interacts with an activity.
If you want a background receiver, you need to register it inside the AndroidManifest (with intent filter), add an IntentService and start it when you receive a broadcast in the receiver.
Here is a tutorial, you are interested in chapter 3.
If you need to be always on, start a foreground service. There is function in Service that lets you: startForeground. Then register your receiver when service is created and deregister when it's destroyed. Foreground services are quite nasty though.
Use a service with it.
Services can survive when the app dies if they have the right flag example:
public class MyService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY; //this defines this service to stay alive
}
#Override
public void onCreate() {
super.onCreate();
appStatus = APPISUP;
//This is a thread that stays alive for as long as you need
new CheckActivityStatus().execute();
//Not needed but in case you wish to lauch other apps from it
}
private class CheckActivityStatus extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
while(true) {
... //add something that breaks eventually
}
}
}
To lauch the service you have to lauch it from an activity like so:
Intent service = new Intent(getBaseContext(), MyService.class);
startService(service);
With the service the BroadcastReceiver still functions receiving whatever you want.
Note that the service sometimes stops and comes back. I haven't found out why but I'm betting on priorities of other apps that may ask the system to halt the service
I'm developing an app that makes asynchronous calls to server for notifications. Everything works fine in the main activity. Now what's bothering me, that i need a service that would poll for notifications when the app is not active (just like GMail stock app). So I start remote service in another process in the main activities onStop function like that:
#Override
public void onStop() {
super.onStop();
if(prefs.getBoolean(OPTIONS_KEY_SERVICE, false)) {
startServiceIntent = new Intent(CloudAlarmsService.MY_SERVICE);
Bundle b = new Bundle();
b.putStringArray("duidCodes", duidCodes);
startServiceIntent.putExtras(b);
Log.d("SSE_SERVICE", "Starting Service");
startService(startServiceIntent);
}
}
It works fine, i get notifications after the activity was closed. Now i would like to stop remote service on activity start (so i won't have activity and service polling server concurrently). I do it like this:
#Override
public void onResume() {
super.onResume();
stopService(new Intent(CloudAlarmsService.MY_SERVICE));
Log.d("SSE_SERVICE", "Stopping service");
}
CloudAlarmsService.MY_SERVICE is a string in my service class:
public class CloudAlarmsService extends Service {
public static String MY_SERVICE = "cloudindustries.alarms.service.BACKGROUND_SERVICE";
...
and also it is declared in manifest xml like that:
<service android:process=":alarms_poller" android:name=".CloudAlarmsService">
<intent-filter android:label="#string/serviceStopService">
<action android:name="cloudindustries.alarms.service.BACKGROUND_SERVICE" />
</intent-filter>
</service>
But service is not stopped and it keeps working. After i close the main activity another instance of service spawns and continues polling.
Is there something i am missing out?
Or maybe this is a bad use for a remote service or even there is another type of service / manager which could be used for such purpose?
Thanks for any help!
Edit:
Question clarification: Is it possible to stop remote service from different activity (which did not start the service)?
try to stop CloudAlarmsService service as:
Intent intent = new Intent();
intent.setClass(getApplicationContext(), CloudAlarmsService.class);
getApplicationContext().stopService(intent);
I have an intentservice that gets qued by the user and by my app automatically. I need to be able to kill all pending intents that are qued when the user logs out of my application, but I cannot seem to get that to work. I have tried stopService() and stopself(), but the intents continue to fire off the intentservice after the user has logged out. I would try to get the id of the intent but that is difficult as everytime the intentservice starts, the variable holding the intent id's is empty. Here is my intentservice code:
public class MainUploadIntentService extends IntentService {
private final String TAG = "MAINUPLOADINTSER";
private GMLHandsetApplication app = null;
private SimpleDateFormat sdf = null;
public boolean recStops = true;
public MainUploadIntentService() {
super("Main Upload Intent Service");
GMLHandsetApplication.writeToLogs(TAG,
"GMLMainUploadIntentService Constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Started");
if (app == null) {
app = (GMLHandsetApplication) getApplication();
}
uploadData(app);
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Finished");
}
#Override
public void onDestroy() {
GMLHandsetApplication.writeToLogs(TAG, "onDestroy Started");
app = null;
stopSelf();
GMLHandsetApplication.writeToLogs(TAG, "onDestroy completed");
}
public void uploadData(GMLHandsetApplication appl) {
//All of my code that needs to be ran
}
Unfortunately, I don't think it's possible to accomplish that with the standard IntentService methods since it doesn't offer a way to interrupt it while it's already going.
There are a few options I can think of that you can try to see if they fit your need.
Copy the IntentService code to make your own modifications to it that would allow you to remove pending messages. Looks like someone had some success with that here: Android: intentservice, how abort or skip a task in the handleintent queue
Instead of copying all the IntentService code, you might also be able to Bind to it like a normal Service (since IntentService extends Service) so you can write your own function to remove pending messages. This one is also mentioned in that link.
Rewrite the IntentService as a regular Service instead. With this option, you'd have more control over adding and removing messages.
I had what sounds like a similar situation where I was using an IntentService, and I eventually just converted it to a Service instead. That let me run the tasks concurrently and also cancel them when I needed to clear them.
Here
When should I free the native (Android NDK) handles? is the HangAroundIntentService class that has the method cancelQueue().
The class also has the method
public static Intent markedAsCancelIntent(Intent intent)
that converts an intent into a cancel intent, and
public static boolean isCancelIntent(Intent intent).
The class is based on the open-sourced Google's code.
Just a thought but inside of your onhandleintent can you have an argument that checks to see if app is running if not then don't run the code? example. In the start of your app you could have a static var
boolean appRunning;
Next in your onhandle of the intent, when you set the appRunning to false, after an onPause or onDestroy of activity, you could wrap the onhandleintent code in a boolean:
protected void onHandleIntent(final Intent intent) {
if(MainActivity.appRunning){
...
}
}
Just a thought