I edited the question so it's not a duplicate
of this
In MainActivity I am doing some file operations. These operations are processed in a single file. So, after that I pass the file through intent at ForceShut. This is because I want to detect when user swipes the app out from recent apps which is to say onTaskRemoved() is called, into which this file is deleted. Now no problem so far. The file is successfully transmitted through the intents, and onTaskRemoved() is called as seen from the Logs. Also the file which I try to delete in onTaskRemoved() is successfuly deleted when I swipe the app and the Logs in there all run fine until the "Application Terminated" shows. But few seconds after, I get a crash alert saying App has stopped while app was even removed from the recent. The crash though appears twice in a row then no crashs appear further on. What could be the problem ? :(
My MainActivity class looks like this
public MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//several file operations go here <--
//removed for simplification
Intent mIntent = new Intent(this, ForceShut.class);
mIntent.putExtra("file", file);
startService(mIntent);
}
}
and ForceShut class looks like this :
public class ForceShut extends Service {
File file;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId){
file =(File) intent.getExtras().get("file");
return START_STICKY;
}
#Override
public void onTaskRemoved(Intent rootIntent){
if(file.exists())
file.delete();
}
}
EDIT
So as #CommonsWare was suggesting I had forgotten to look at the LogCat, instead I was looking only at the "Run" tab logs. So I looked it over and it seems like there is a null pointer exception :
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Bundle android.content.Intent.getExtras()' on a null object reference so it seems like even after swiped onStartCommand is called again. Why could the service start over even after app was swiped ?
EDIT 2
Question is not a duplicate, as #Andreas has pointed out. I edited the question. However I found the workaround myself. I shut down the Service with stopSelf() as it seems like swiping the app out of Recents sort of doesn't get rid of the service which ocasionally restarts. Anyway hope this helps anyone
Why could the service start over even after app was swiped ?
You have started a "sticky" service. The system will automatically restart any sticky service until it is explicitly stopped.
#Override
public int onStartCommand(Intent intent, int flags, int startId){
file =(File) intent.getExtras().get("file");
return START_STICKY;
}
I don't see where you actually stop it with stopSelf().
As far as your NullPointerExceptions, simply check if the objects exist before trying to read from them.
if (intent != null && intent.hasExtra("file"))
file =(File) intent.getExtras().get("file");
The service restarts because of the START_STICKY inside onStartCommand.
You need to use START_NOT_STICKY instead to prevent the service from restarting.
Related
I am working on an Android application and I have stumbled upon a problem for which I cannot seem to find a solution. I've tried different approaches, but with no luck. I am aware there are similar questions asked, but no answer seems to help. Here's the problem:
I am adding an action button to my notifications by adding the following line to my NotificationBuilder:
.addAction(R.drawable.done,"Complete", completeTaskPI)
Here's the code for the pending intent completeTaskPI:
Intent completeTask = new Intent(getActivity(),NotificationActionReceiver.class);
completeTask.setAction("COMPLETE_TASK");
completeTask.putExtra("taskId",taskId);
PendingIntent completeTaskPI = PendingIntent.getBroadcast(getActivity(),COMPLETE_TASK_CODE,completeTask,0);
It sends a broadcast to NotificationActionReceiver when the Action Button is clicked. From here, I simply start MyIntentService:
public class NotificationActionReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
Intent completeTask = new Intent(context, MyIntentService.class);
context.startService(completeTask);
}
}
Everything works properly before this moment. The receiver receives the pending intent with all the info and starts the service. The service starts, but never gets to onHandleIntent. The code for the IntentService:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i("onHandleIntent","triggered");
}
}
I've tried creating a new IntentService, a different BroadcastReceiver, still nothing.
I'd like to note few things:
- The service is properly declared in the Android Manifest;
- I already have several other services that work just fine (with broadcast receiver, as well);
- I need the action done on a background thread and not in the broadcast receiver;
- I am assuming there is some problem with the context, although I am not sure what could be the problem.
I've been trying to locate the problem for several hours now and I just can't see it, so I thought I'd ask for help here.
Thanks in advance!
Following #pskink's advice, I've solved the problem by starting the service with the pending intent, avoiding the Broadcast Receiver entirely.
I hope this helps someone else in the future.
Thank you!
I have a simple application in which before I do anything, I must check whether the user is logged in or not.
To do this, I inherited the Application class like this:
public class GBApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
if (userIsLoggedIn()) {
Intent overviewActivity = new Intent(this, Overview.class);
overviewActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(overviewActivity);
}
}
}
userIsLoggedIn() returns a boolean stored in the SharedPreferences file.
Everything works, but when I press back from the Overview activity, it redirects me to the default activity (MainActivity), and removing it from the AndroidManifest.xml files gives error.
Also, I can't call finish() since this is a non activity class.
How can I terminate my program after I return from the Overview class? I found solutions where they pass a value to the MainActivity, call finish() from there. But this seems to complicated.
What else I can do?
This is my application tag in AndroidManifest.xml
Your plan is going to cause problems. Every time your process is created, you are starting an activity, even if an activity is not needed for this particular process.
Displaying a launcher activity — such as in response to the user tapping on a home screen launcher icon — is one way that a process might be created for your app. But there are many others:
AlarmManager
JobScheduler
a Notification
a push message (e.g., from Firebase Cloud Messaging)
a request sent to a ContentProvider that you export from your app
a request sent to a Service that you export from your app
a manifest-registered BroadcastReceiver
the user returning to your task in the overview screen
and so on
In none of those scenarios is displaying this activity necessarily appropriate. Yet, your code will display the activity in all of them, because any time Android forks a process for your app, you display this activity.
Have all of your activities (other than the login one) see if the user is logged in, and route the user to the login activity if that is needed. When the user completes the login, and the login activity finishes, the user is returned to where they were trying to go, whether that is your launcher activity or some other activity (e.g., they returned to your task after your app had been in the background for a while, so your process was terminated, but Android tries to send them back to whatever activity of yours they had been in last).
You could simply override the onBackPressed() of your overviewActivity and pilot the direction your app goes when the back button is pressed (which in your case is to shut down the app):
#Override
public void onBackPressed(){
startActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
}
The code above just shuts down your app immediately the back button is clicked once (which is pretty boring.. but simple). You could also try the code snippet below; it pops up a dialogue box:
#Override
public void onBackPressed(){
new AlertDialog.Builder(this)
.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int id){
startActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
}
})
.setNegativeButton("No", null)
.show();
}
I have been scouting around for a while, but cannot locate any information for calling final() using started services... or rather, when not using bound services. There is tons of info for bound services, but I already have two pretty large "started services" without binding, so I didn't want to modify the existing services more than absolutely necessary.
My app works by reading bluetooth data every 10 seconds, and depending on the data read, the Service will change to a new activity. However, I cannot call final() from my services, so I fear that I might be endlessly stacking activities while the application/services are running.
To change activities, I had to add Intent.FLAG_ACTIVITY_NEW_TASK. Considering the below image/definition from the developer's page, this flag looks like it might already handle my stacking issue? I do NOT allow for users to use the back button on their phones as everything is handled via confirm/cancel buttons and the services. My app MUST be this way for a few reasons. Thus, keeping the stack order isn't important to my application.
Key Points -
I want to ensure i'm not stacking up activities endlessly when starting new activities
Flagging "new task" when starting activities via my services
Stack order is not important to my app
Below is a very small cut of my code with comments to explain what i'm trying to do. Please make sure to look to the onDestroy() method of this service.
public class AlertService extends Service {
final class Threader implements Runnable{
// Scans bluetooth advertisement packets every 10 seconds
// Thread Runs until interrupted
// Stops service via service ID
stopSelf(this.serviceID);
}
#Override
public void onCreate(){
super.onCreate();
}
// Runs a thread until alert is found.
// Alert calls thread.interrupt()
#Override
public int onStartCommand(Intent intent, int flags, int startID){
enableBluetooth();
// Start Thread
thread = new Thread(new Threader(startID));
thread.start();
return START_STICKY;
}
#Override
public void onDestroy(){
thread.interrupt();
Intent alertActivity = new Intent(this, AlertActivity.class)
alertActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(alertActivity);
}
// Unused Method - We will not be binding
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
EDIT via recommendation to use android:taskAffinity -
Using android:taskAffinity won't help me in this situation. By default, all activities in an application have the same affinity. When I create a new task by setting Intent.FLAG_ACTIVITY_NEW_TASK in the intent flags, the new activity will STILL be started in the same task if the activity has the same taskAffinity of the root activity in the task. Since I am just using the default affinity, everything should have the normal stack flow. I just cannot call finish(), which means that I am stacking up tons of activities.
To answer my own question, each new activity called via Intent.FLAG_ACTIVITY_NEW_TASK, was creating a new instance of each activity and putting it on the stack. This is true. However, each activity is NOT making a new Task, which was one of my fears as well.
When I create a new task by setting Intent.FLAG_ACTIVITY_NEW_TASK in the intent flags, the new activity will STILL be started in the same task (not in a new task) if the new activity has the same taskAffinity of the root activity in the task. Since I am just using the default affinity, every activity I create is being put into the same task. This means that nothing is acting any differently than the normal flow of creating activities and such.
Though, since I have disable the back button for my application, these activities created by flagging a new task are not finished, destroyed, or removed from the stack. To solve this, I will use FLAG_ACTIVITY_CLEAR_TOP, which finds a running instance of an activity in the stack (if there is one) and closes all of the activities above it.
Since my application always starts with the home screen, then ends with the home screen, flagging "clear top" will always close all activities above my home screen. So, upon return to the home screen, the only item on the stack will be the home screen.
I will have to test this, but it seems that I will not call finish() from my home activity to achieve this result - Otherwise, upon returning to the home activity, not all of the stack will be cleared.
Here is the situation:
I start a download
I restart the device (to test the robustness of the app)
I use a boot receiver to be notified of the restart
In the onReceive method I call the clearDownloadsAfterReboot() method:
#Override
public void onReceive(final Context context, Intent intent) {
clearDownloadsAfterReboot(context);
}
The clearDownloadsAfterReboot() method get called and tries to remove the downloads like so:
private void clearDownloadsAfterReboot(final Context context){
//Added delay to make sure download has started
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
DownloadManager manager = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById (DownloadManager.STATUS_FAILED|DownloadManager.STATUS_PENDING|DownloadManager.STATUS_RUNNING|DownloadManager.STATUS_PAUSED);
Cursor c = manager.query(query);
// -------> c.getCount() returns 0 despite the download being visible
while(c.moveToNext()) {
//This never executes because c has a count of 0
int removedInt = manager.remove(c.getLong(c.getColumnIndex(DownloadManager.COLUMN_ID)));
}
}
}, 45000);
}
The cursor returns 0 so the remove() method is never called, but, I can see the download running in the status bar so there is at least 1 download running.
My questions in a nutshell: How do I stop running downloads after a reboot? And, why would the manager.query() method return a cursor which has 0 results when there is a download running?
I know there are other question regarding stopping downloads but these have not been able to solve my issue.
Thanks in advance.
Credit for the first part of this answer goes to #Lukesprog
I needed to use setFilterByStatus() instead of setFilterById(). After exchanging these two methods I was able to successfully stop the download and the manager.query() method returned a cursor containing the correct amount of downloads.
However, for some reason the notification which showed the progress of the download didn't clear after the download had been stopped. In order to clear the notification you need to call manager.remove(c.getLong(c.getColumnIndex(DownloadManager.COLUMN_ID))); twice.
My activity starts a service which runs a CountDownTimer. The timer sends broadcasts back to the activity as it counts down. The activity processes the broadcasts in the onReceive method of a BroadcastReceiver. All of this works fine.
My problem comes when the following events happen in this order:
App is stopped (via onPause())
Timer finishes
App is resumed (via onResume())
When the app is resumed the service is no longer sending broadcasts, so the activity does not know how much time is left on the timer or if it's finished. This prevents the activity from updating the UI.
I've tried a dozen ways of dealing with this, and read through many Stack Overflow questions and answers, but I've yet to find a solution. I would think that there's a way to pick up a broadcast that was sent while the activity was not active, but I've yet to find a way.
For the record, here is my relevant Activity and Service code:
activity.java
// Start service
timerIntent.putExtra("totalLength", totalLength);
this.startService(timerIntent);
// ...
// BroadcastReceiver
private BroadcastReceiver br = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getExtras() != null && inSession) {
session.setRemaining(intent.getExtras().getLong("millisUntilFinished"));
updateProgress();
}
}
};
// ...
// onResume
#Override
public void onResume() {
super.onResume();
registerReceiver(br, new IntentFilter(TimerService.COUNTDOWN_TS));
}
service.java
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
long length = intent.getExtras().getLong("totalLength");
countDownTimer = new CountDownTimer(length, 1000) {
#Override
public void onTick(long millisUntilFinished) {
timerServiceIntent.putExtra("millisUntilFinished", millisUntilFinished);
sendBroadcast(timerServiceIntent);
}
#Override
public void onFinish() {
}
};
countDownTimer.start();
return super.onStartCommand(intent, flags, startId);
}
What's the best way to process the broadcasts that the service sent while the activity was stopped?
Use the BroadcastReceiver to store the last request (SharedPreferences perhaps) it received and check it when the Activity starts.
Alternatively, instead of processing a countdown using broadcasts, just store the time that the countdown would end. The Activity can then handle the countdown all by itself as it knows when it should end. Using a service and broadcasts seem to be a little over-engineered for such a simple task.
Update:
From the way you have described your task, I see you needing to handle 2 scenarios. This is how I would likely do it.
Assuming that "XYZ" is the service\intent\whatever starting the countdown and "ABC" is the Activity displaying the progress. (ABC and XYZ could be the same activity if that is what you wanted)
Requirements:
When the countdown starts, I would make XYZ store the time that the countdown should end in SharedPreferences.
ABC is already running when the countdown starts. As Commonsware said, the Eventbus model is excellent for handling this scenario so long as XYZ and ABC are running in the same process. Just fire an event to read the preference value and count down to the specified time. If the user closes ABC and reopens it, Scenario 2 will kick in.
ABC is not running. Check in OnResume whether the countdown time has elapsed. If not, set up ABC to display the countdown again. If there is no countdown active, do something else.
If you also need to do something when the countdown has elapsed regardless of whether you have a UI active, then again Commonsware's suggestion of AlarmManager is perfect.
Let's pretend for a moment that using a Service with a CountDownTimer to track some passage of time for the purposes of updating an Activity actually is a good idea. It's not out of the question, assuming that the Service is actually doing something for real and this timing thing is some by-product.
An activity does not receive broadcasts while stopped, mostly for performance/battery reasons. Instead, the activity needs to pull in the current status when it starts, then use events (e.g., your current broadcasts) to be informed of changes in the data while it is started.
This would be simplified by using something like greenrobot's EventBus and their sticky events, as the activity would automatically get the last event when it subscribes to get events. Using greenrobot's EventBus for this purpose would also reduce the security and performance issues that you are introducing by your use of system broadcasts to talk between two Java classes in the same process.
Also, please stick with lifecycle pairs. onResume() is not the counterpart to onStop(). onStart() is the counterpart to onStop(); onResume() is the counterpart to onPause(). Initializing something in one pair (e.g., onResume()) and cleaning it up in the other pair (e.g., onStop()) runs the risk of double-initialization or double-cleanup errors.
What's the best way to process the broadcasts that the service sent
while the activity was stopped?
Using sticky broadcast intents from the service and then retrieving them from the activity would be a way to process the broadcasts that the service sent while the activity was stopped. I can only offer that as a possible solution rather than claiming it is the "best way".
http://developer.android.com/reference/android/content/Context.html#sendStickyBroadcast(android.content.Intent)
They have however, been deprecated since API level 21 due to security concerns.
Instead of using Normal broadcast you can use Ordered broadcast (sent with Context.sendOrderedBroadcast). For this along with defining a BroadcastReceiver in your activity you required to define BroadcastReceiver in your manifest with same intentfilter. Only change is while registering BroadcastReceiver in your activity you need to set priority to high, so that when your activity is running and activity's BroadcastReceiver is registered it gets called first, and inside onReceive of this BroadcastReceiver you can use abortBroadcast for getting the BroadcastReceiver called which is defined in your android manifest. Now when your activity is not running the BroadcastReceiver defined in your android manifest will get called. So this way you can have the status and if you wish you can display updates to user by notification even if your activity is not running.