Android: BackStack misbehaves, if application was started from another app - android

I have an application, which starts with a SplashScreenActivity. Afterwards, a LoginActivity is shown, or if the user is already logged in, a MainActivity is shown. If the application is already running, SplashScreenActivity is dismissed with the following
//SplashScreenActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Adding this check for following cases
if (!isTaskRoot())
{
String intentAction = getIntent().getAction();
if (getIntent().hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
if(getIntent().getCategories().contains(GCMIntentService.INTENT_CATEGORY_GH_NOTIFICATION)){
finish();
return;
}
}
Problem occurs
If I start the application from another activity like PlayStore, it resumes at the right activity if already running. This is the Intent I'm using to reproduce within a second app
//AnotherApplication.apk
Intent launchIntent = getPackageManager().getLaunchIntentForPackage("my.package.name");
startActivity(launchIntent);
However, this action is somehow breaking the Backstack. Instead of closing the application on backpress in the MainActivity, it restarts the application.
//MainActivity.class
#Override
public void onBackPressed() {
if (getNavDrawerMain().isDrawerOpen()) {
getNavDrawerMain().closeDrawer();
} else {
closeApp();
}
}
protected void closeApp() {
if (doubleBackToExitPressedOnce) {
//super.onBackPressed(); //i tried both, but behaviour is the same
finish();
return;
}
this.doubleBackToExitPressedOnce = true;
new Handler().postDelayed(new Runnable() {
#Override
public void run()
doubleBackToExitPressedOnce = false;
}
}, 500);
}
I used breakpoints and found out that MainActivity:onDestroy() get called, but instead of resuming application to the HomeScreen, it always restarts and I don't know why.
I tried the following:
- Used different launchmodes like singleTask and singleInstance, but it didn't make any difference. onNewIntent is called, but if i call finish, HomeActivity restarts
- as commeted below, i tried moveTaskToBack(true), but Activity is restaring too (and we really want to close the app instead of moving it to the BackStack)

Try with moveTaskToBack(true); instead of finish(); to close the app. It will then go to OnRestart() and then OnStart()->OnResume() (and won't go to OnCreate).
And make sure you don't have the "Don't keep activities" marked at Developer Options in your Android Settings (destroy every activity as soon as the user leaves it).

Trying adding this flag to your intent starting your app: RESET_TASK_IF_NEEDED, URL=http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
What it does:
If set, and this activity is either being started in a new task or bringing to the top an existing task, then it will be launched as the front door of the task.
You may also use:
http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_REORDER_TO_FRONT
What it does:
If set in an Intent passed to Context.startActivity(), this flag will cause the launched activity to be brought to the front of its task's history stack if it is already running.
Which one you use depend on the desired end result.
If you can't control who starts you you need to set or launch mode to single task or single instance.
Described here:
http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en
The interesting part:
singleTask
This mode is quite different from standard and singleTop. An Activity with singleTask launchMode is allowed to have only one instance in the system (a.k.a. Singleton). If there is an existed Activity instance in the system, the whole Task hold the instance would be moved to top while Intent would be delivered through onNewIntent() method. Otherwise, new Activity would be created and placed in the proper Task.

Related

how to exit from all the activities(including resuming activities)

I have 4 activities. First one is splash activity and second is main menu activity and third activity is specific option from main activity and fourth one is result of third activity. I am using the noHistory="true" for second activity in manifest file. I have exit option in option menu for all activity. Once I exit from first three activity in app again I open app means it going launching application (there no resuming activities) but when I was exit from fourth activity then again I will open app means it show third activity instead of launch the app.
Exit code in option menu is
flag_activity_clear_top
flag_activity_no_history
but the previous activity is not close. How can we exit from all activities (including resuming activity)?
If I use the noHistory="true" attribute for third activity means it working properly, but when I press the home screen then again I reopen app it show splash activity instead of resuming activity. So I avoid to use the noHistory attribute in third activity.
Kindly please give solution for exit from all activities.
using Intent
Intent intent = new Intent(getApplicationContext(), FirstActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("FLAG_ACTIVITY_CLEAR_TOP", true);
startActivity(intent);
added in OnCreate your All Activity
if (getIntent().getBooleanExtra("FLAG_ACTIVITY_CLEAR_TOP", false)) {
finish();
}
Use sharePreference and check the value inside shared preference (inside onResume or OnStart()) every time you launch the application and navigate accordingly.
To close all Activities you can try call
finishAffinity();
This method finish current activity as well as all activities immediately below it in the current task that have the same affinity.
finishAffinity() can be called from Context i.e (Activity, Service etc)
If every time you close the application you want to close all Activities you just have to call finishAffinity in your OnDestroy() method or in onClick like
#Override
protected void onDestroy() {
super.onDestroy();
finishAffinity();
}
or
finishButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finishAffinity();
}
});
Also example for menu
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.exit_item) {
finishAffinity();
}
return super.onOptionsItemSelected(item);
}
And you should NOT use any Flags for start or close an Activity, just call finishAffinity() where you wanna exit from application and finish all activities.
In this video you can see how it works https://youtu.be/L5w8mhB9aNk

Android's recent apps list redirecting to another app

I have code in an App (let'call it app 'A') that starts an activity on a second app (let's call it app 'B') when the user clicks a button.
So far so good but I'd expect to later be able go back to app 'A' using the system's overview screen (aka recent apps list), but when I try to I'm redirected back to App B automatically, as if I was clicking the button a second time.
The phone is running Android KitKat and the code looks like this:
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
startActivity(new Intent("com.some.package.MAIN"));
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
});
So far so good but I'd expect to later be able go back to app 'A' using the system's overview screen (aka recent apps list), but when I try to I'm redirected back to App B automatically, as if I was clicking the button a second time.
This is the default behaviour of Android.
Android Activity life cycle uses a context based statck called Task.
So the App 'B' is simply inside the the Task of calling Activity App 'A'.
You can override this behaviour by using the following flag.
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
Intent i=new Intent("com.some.package.MAIN");
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
});
1) FLAG_ACTIVITY_NEW_TASK - If set, this activity will become the start of a new task on this history stack. A task (from the activity that started it to the next task activity) defines an atomic group of activities that the user can move to. Tasks can be moved to the foreground and background; all of the activities inside of a particular task always remain in the same order.
2) FLAG_ACTIVITY_CLEAR_TOP - If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
3) FLAG_ACTIVITY_SINGLE_TOP - If set, the activity will not be launched if it is already running at the top of the history stack.

Resume background activity on notification

I've read some answers here, I think I have what I need in order to achieve my result, but I need some help.
My app launches an notification on specific conditions, and I need my app to behave as follow:
if there is an instance of my main activity running in background I need to make it to the foreground (I found this on the site: intent.setFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);, so I think this point is solved;
if there isn't any activity of the app running in background I need to start the app from the beginning (and this can be achieved starting the launcher activity of the app.);
My question is: how can I make the app search for any istance of itself running in background? Because the activity that I need to reorder to front with the Intent flag is different from the launcher activity.
The notification is handled by a service that check periodically some infos from the internet.
Thanks for the help.
What you need is just a simple Activity that decides what to do. Here is an example:
public class NotificationActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if the app was already running
if (isTaskRoot()) {
// App wasn't running, so start the app from the beginning
Intent startIntent = new Intent(this, MyStartingActivity.class);
startActivity(startIntent);
} else {
// App was already running, bring MainActivity to the top
Intent reorderIntent = new Intent(this, MainActivity.class);
reorderIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(reorderIntent);
}
// We are done now so just finish
finish();
}
}
Set up your notification to start this activity. Make sure that in the manifest the task affinity of this activity is the same as the task affinity of the other activities in your application (by default it is, if you haven't explicitly set android:taskAffinity).

Android Lifecycle Behavior

Having an issue with lifecycle events and need some help if possible. I have read the other posts with similar issue and error however, still stuck .... Explaination may be a little long.
I have an App that requires the user to login when any 'backgrounding' event occurs (App switch, screen sleep, phone call and so on). Previously I was using the method of 'getRunningTasks' to check is my app is no longer top of stack to trigger the login flags and activity. I am now using a suggested timer method via onPause / onResume in my Application class which works great.
Now the issue at hand, and the questions:
App into background by Device Home button.
App is in the background, all my checks and flags are properly set. When I relaunch the app (Icon press), it loads the last activity, then from the onResume function - performs a startIntent for the login activity - based on the flag set in the timer event. The user logs in, and returns to the activity they were on (Stock List). All Good.
App into background by App Switch
timer event fires and all login flags are properly done as previous example.
--> Click 'device Home'button, then App icon --- App loads properly and loads the Login Activity, users logs in -- all good.
Problem area
2b. App into background by App Switch --> Example, Android pull down menu, jump to device settings - then return to app via the device back button
timer event fires and all login flags are properly done as previous example.
--> Click device 'return/back' button to return directly to the app.
User goes to Login screen, clicks 'Login' button - gets app crash due to :
java.lang.RuntimeException: Performing pause of activity that is not resumed
followed by
java.lang.RuntimeException: Performing stop of activity that is not resumed
My own system Logs in the lifecycle events all seem to fire in the appropriate order.
Basic overview from app re-launch:
-> DashBoard onResume
-> check for login flag and call intent for Login activity
Dashboard onPause
Login onResume
Login - user login hit button
Login onPause --- activity finish
Return to Dashboard onResume
-> Load details
The only diff is in the last senario ... the DashBoard onPause happens after the Login onResume. Not sure if this is the problem, or an Async delay issue with the lifecycle events.
My Manifest includes:
android:minSdkVersion="10" --- Maintaining old API due to clients with older devices
android:targetSdkVersion="19"
Any help or suggestions on what may be out of whack here? or how to avoid this error.
Thanks
--
Some code:
From BaseActivity.java
~~~~~~
private AppPreferences mAppPrefs;
protected MyApplication mMyApp;
#Override
protected void onPause() {
super.onPause();
//start timer
mMyApp.startActivityTransitionTimer();
}
#Override
protected void onResume() {
super.onResume();
//update current Activity Name
mMyApp.setCurrentActivity(this);
mMyApp.stopActivityTransitionTimer();
mAppPrefs = new AppPreferences(getApplicationContext());
if (mAppPrefs.getAppDestroyed() == true
|| mAppPrefs.getExitType() == AppPreferences.EXITTYPE_FULLEXIT) {
// we want to fully exit/close the app
this.finish();
} else {
if (mAppPrefs.getForceLoginState() == true) {
//~~~~ process login events
Intent intent = new Intent(this, LoginScreen.class);
startActivity(intent);
return;
}
}
}
DashboardActivity extends BaseActivity
{
#Override
protected void onPause() {
super.onPause();
if (((MyApplication)this.getApplication()).getProcessingLogout()){
// if we are already processing a login, do not continue here
return;
}
// ~~~ activity specific onPause event such as unregisterReceivers
((MyApplication)this.getApplication()).startActivityTransitionTimer();
}
#Override
protected void onResume() {
super.onResume();
if (((MyApplication)this.getApplication()).getProcessingLogout()){
// if we are already processing a login, do not continue here
return;
}
// ~~~ activity specific onResume events such as registerReceivers
}
}

Notification to restore a task rather than a specific activity?

I have a foreground service that keeps a connection open with the server as long as the user is logged into the application. This is so that the connection is kept alive and can receive messages directly from the server even when the application has been sent into the background by the user pressing Home.
The application has a number of Activities, any of which could be the active one when it is sent into the background.
I would like to allow the user to click on the notification to restore the current Activity. I understand how to restore a particular activity, but wondered if there is a way to restore the last Activity that the user was on? Of course I could keep track of the the last one, and then call that from the Notification callback, but thought there might be a way at a task level?
Thanks for any advice you can offer.
What you need is just a simple Activity that does nothing. Here is an example:
public class NotificationActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Now finish, which will drop the user in to the activity that was at the top
// of the task stack
finish();
}
}
Set up your notification to start this activity. Make sure that in the manifest the task affinity of this activity is the same as the task affinity of the other activities in your application (by default it is, if you haven't explicitly set android:taskAffinity).
When the user selects this notification, if your application is running, then the NotificationActivity will be started on top of the topmost activity in your application's task and that task will be brought to the foreground. When the NotificationActivity finishes, it will simply return the user to the topmost activity in your application (ie: wherever the user left it when it went into the background).
This won't work if your application isn't already running. However, you have 2 options to deal with that:
Make sure the notification isn't present in the notification bar when your application is not running.
In the onCreate() method of the NotificationActivity, check if your application is running, and if it isn't running call startActivity() and launch your application. If you do this, be sure to set the flag Intent.FLAG_ACTIVITY_NEW_TASK when starting the application so that the root activity of the task is not NotificationActivity.
Works very well, thanks David! The following class checks if the application is already running and if not, starts it before finishing (as suggested by David in option 2).
public class NotificationActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// If this activity is the root activity of the task, the app is not running
if (isTaskRoot())
{
// Start the app before finishing
Intent startAppIntent = new Intent(getApplicationContext(), MainActivity.class);
startAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(startAppIntent);
}
finish();
}
}
There is a simpler solution that does not require the extra activity. See this post for details. Basically, the notification starts the (possibly existing) task the same way it is started when you click the launcher icon while the app ist in the background.
My solution, which emulates the behaviour of the launcher (bringing up the task to the foreground):
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClassName(MyApplication.class.getPackage().getName(), MainActivity.class.getName());
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
This works, no doubts about it but the problem is when you set your intent as ACTION_MAIN. Then you will not be able to set any bundle to the intent. I mean, your primitive data will not be received from the target activity because ACTION_MAIN can not contain any extra data.
Instead of this, you can just set your activities as singleTask and call your intent normally without setting ACTION_MAIN and receive the intent from onNewIntent() method of your target activity.
But be aware if you call, super.onNewIntent(intent); then a second instance of the activity will be created. Just don't call super method.
I combined David Wasser's and Raginmari's solution by doing that approach to the root activity of your app then it will work for both cases when your app was already started or haven't been started.
public class YourRootActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (!isTaskRoot()) // checks if this root activity is at root, if not, we presented it from notification and we are resuming the app from previous open state
{
val extras = intent.extras // do stuffs with extras.
finish();
return;
}
// OtherWise start the app as usual
}
}

Categories

Resources