We have already the check of CATEGORY_MAIN and !isTaskRoot() but even then 2 instances of activity are launched.
SplashActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("OnCreate method.");
if(checkIfActivityIsBroughtToFront() || checkIfActivityIsRootTask()) {
return; // Found that if we finish and don't return then it will run the code below, hence start the recovery task.
}
Log.i("Checking if Recovery is required ...");
new RecoveryTask(SplashActivity.this, this).execute();
}
private boolean checkIfActivityIsBroughtToFront() {
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
// Activity was brought to front and not created,
// Thus finishing this will get us to the last viewed activity
Log.i("Detecting a brought to front, no need for recovery.");
finish();
return true;
}
return false;
}
private boolean checkIfActivityIsRootTask() {
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String intentAction = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
Log.i("Main Activity is not the root. " + "Finishing Main Activity instead of launching.");
finish();
return true;
}
}
return false;
}
Logs
2015-10-22 13:42:25.581 +0300 SplashActivity INFO[main] - OnCreate method.
2015-10-22 13:42:25.587 +0300 SplashActivity INFO[main] - Checking if Recovery is required ...
2015-10-22 13:42:25.637 +0300 SplashActivity INFO[main] - OnCreate method.
2015-10-22 13:42:25.638 +0300 SplashActivity INFO[main] - Checking if Recovery is required ...
2015-10-22 13:42:25.828 +0300 GeoFenceManager INFO[pool-5-thread-1] - Removing geofences ...
2015-10-22 13:42:25.872 +0300 GeoFenceManager INFO[pool-5-thread-2] - Removing geofences ...
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="x.y.z"
android:installLocation="internalOnly" >
<application
android:name=".global.GlobalInstance"
android:allowBackup="true"
android:allowClearUserData="true"
android:hardwareAccelerated="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:largeHeap="true"
android:persistent="true" >
<activity
android:name=".activity.SplashActivity"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:theme="#style/Theme.Background" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".receiver.BootUpReceiver"
android:enabled="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
UPDATE:
This is happening after restart, the BOOT_COMPLETED listener is following
public class BootUpReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, SplashActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
Any idea how this can be prevented?
Add android:launchMode="SingleTask" to the splash activity element in the xml. And then on leaving the activity i.e. navigating away from the splash call finish(). The reason you should use this pattern as opposed to "SingleInstance" is that the user can never navigate back to the splash with the back-key (as this is not normal behavior).
private boolean checkIfActivityIsRootTask() {
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String intentAction = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
Log.i("Main Activity is not the root. " + "Finishing Main Activity instead of launching.");
finish();
return true;
}
}
return false;
} ?
if this activity is roottask,it return false;
what's RecoveryTask for?
You said:
We have already the check of CATEGORY_MAIN and !isTaskRoot()
But both the check failed. In your logs that you attached, when both instances are created, neither Detecting a brought to front, no need for recovery. nor Main Activity is not the root. " + "Finishing Main Activity instead of launching. is printed in the logs. This means that in both the methods the if part is never encountered and hence finish() never gets executed. Both the methods returned false.
Also, just because you call finish() and return in onCreate(), it doesn't mean that onStart() or onResume() won't be called.
Why does this happen? Launcher activity will never be called twice (considering that you don't internally start the activity). Probably a bug in the some SDK that the user has.
POSSIBLE FIX:
You could try setting android:launchMode="singleTop" in your manifest for the SplashActivity. This will make sure only one instance is maintained.
Related
I have a problem with Activity lifecycle and NFC:
I have a MainActivity with the AndroidManifest.xml entry:
<activity
android:name=".ui.main.MainActivity"
android:finishOnTaskLaunch="true"
android:launchMode="singleTask"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/androidbeam" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/nfctag" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
</activity>
where launchMode="singleTask" is used for NFC to prevent multiple MainActivity instances.
In MainActivity I have the following code:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Timber.d("onCreate");
setContentView(R.layout.activity_main);
handleIntent(getIntent());
}
#Override
protected void onNewIntent(Intent intent) {
Timber.d("OnNewIntent");
handleIntent(intent);
}
private void handleIntent(Intent intent){
String action = intent.getAction();
String intent_type = intent.getType();
Timber.d("Intent action:" + action + "\n " + "Intent type:" + intent_type);
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
// Handle data from intent.getParcelableExtra()
Timber.d("onNFCDataReaded");
}
// and implementing creating of activity here
}
#Override
protected void onResume() {
super.onResume();
Timber.d("onResume");
enableNFCDispatch();
}
#Override
protected void onPause() {
Timber.d("onPause");
super.onPause();
disableNFCDispatch();
}
#Override
protected void onDestroy() {
super.onDestroy();
Timber.d("OnDestroy");
}
#Override
public void onBackPressed() {
super.onBackPressed();
Timber.d("OnBackPressed");
}
}
Everything works as expected, except for one use case:
When I am opening the application first with Android Beam or an NFC tag, onCreate() is called and it passes getIntent() to handleIntent() with the data received from another phone or an NFC tag. This works fine.
But after that, when I
click onBackPressed button inside MainActivity (i.e. exiting the application), and
then hold the Home button and in the overview screen select my application,
my application is opened again and onCreate() is called again. However, getIntent() returns the old intent with same data (intent.getAction(), intent.getParcelableExtra()) as I got with Android Beam or NFC tag!
I don't understand why! I expect to receive a new intent; the same as if the app is created when I click the application icon.
Can somebody help me with this?
Here is my MainActivity lifecycle:
MainActivity: onCreate
MainActivity: handleIntent
MainActivity: Intent action: android.nfc.action.NDEF_DISCOVERED
Intent type: application/androidbeam
MainActivity: onNFCDataReaded
MainActivity: OnResume
MainActivity: OnBackPressed
MainActivity: onPause
MainActivity: OnDestroy
//After that, I am holding Home Button and selecting my application from
//OverViewScreen, and getting next Log:
MainActivity: onCreate
MainActivity: handleIntent
MainActivity: Intent action:android.nfc.action.NDEF_DISCOVERED
Intent type:application/androidbeam
- // I do not expect it here !!!!!
MainActivity: onNFCDataReaded
MainActivity: OnResume
This is expected behavior. When you bring your activity to the background and later open the activity again from history (long-press home key), Android will recreate the previous activity stack and the activity will be launched with the same parameters as it was opened before. I.e. if it was launched with intent NDEF_DISCOVERED, it will, again, receive that intent.
However, you can easily detect if the activity was launched with the original intent or if it was launched from history. In the latter case, Android adds the flag FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY to the intent. Consequently, you can test for this flag in your handleIntent() method:
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()) ||
NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {
...
}
}
In my application I have several "intents" that I use to transition between different activities in my application. I have noticed a strange behavior that occurs on Samsung devices - but not on Nexus devices - whenever a new intent is created the application launches a second "task" for this new activity! When the user goes to the multi-tasking menu they can see multiple copies of the application! This is not the desired behavior. Any and all advice would be greatly appreciated!
Manifest:
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme"
android:launchMode="singleInstance">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:launchMode="singleInstance">
</activity>
<activity
android:name=".Settings_area"
android:screenOrientation="portrait" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyDieXTCaFoIL0kJ_IM4UMBSQL3sNn92AWM" />
<activity
android:name=".MapsActivity"
android:label="#string/title_activity_maps" />
<activity android:name=".Splash"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".aboutPageActivity" />
<activity android:name=".turnOffFromNotification"
android:noHistory="true"></activity>
</application>
I have already attempted removing the launch modes as well as changing the application launch mode to singleTop and standard.
Intent that creates a second instance:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
new Handler().postDelayed(new Runnable(){
#Override
public void run() {
/* Create an Intent that will start the Menu-Activity. */
Intent mainIntent = new Intent(Splash.this,MainActivity.class);
Splash.this.startActivity(mainIntent);
Splash.this.finish();
}
}, splashDisplayLength);
return;
}
Intent that creates a third instance:
public void goToAboutPage()
{
Intent goToAboutPage = new Intent(this, aboutPageActivity.class); //create the intent to go to the map screen
startActivity(goToAboutPage); //actually go to the map screen
}
A third instance can also be created from launching a settings intent:
public void changeToSettingsScreen() //changes the screen to the setting screen
{
readyToSendPackets = false;
sendSwitch.setChecked(false);
// textView.setText("NOT sending"); //set the textview to advise users packets are not being sent
Intent goToSettings = new Intent(this, Settings_area.class);
startActivity(goToSettings);
}
I also over rode the onNewIntent Method:
protected void onNewIntent(Intent intent) {
// super.onNewIntent(intent); //REMOVED THIS TO AVOID DOUBLE INSTANTIATION ON TOUCHWIZ IF ANYTHING BREAKS LOOK HERE FIRST
setIntent(intent); //this allows us to recieve the extras bundled with the intent
// System.out.println("Here is the bindle: " + getIntent().getExtras());
if (getIntent().getExtras() != null) //check to see if there are any extras, there wont be on apps first start
{
Bundle extras = getIntent().getExtras(); //get the extras
String methodName = extras.getString("methodName"); //assign the extras to local variables
if(methodName != null && methodName.equals("turn_send_switch_off"))
{
sendSwitch.setChecked(false);
}
//else if(**other actions that may need to be performed can go here**)
}
Thank you very much for any help!!!
Usually if you have to force a single instance of the app, you should avoid putting android:launchMode="singleInstance" on each activity seeing as it would try to launch an instance for each activity.
Removing the launchMode from everything except the application should ensure that only the application runs in a single instance, although what #Shaishav said is true, most of the time you can let android deal with the lifecycle of an application by not setting the launchMode, unless you have a real need to ensure only one instance is running at a time.
I am trying to achieve following case on Android, but no success:
1) Launch Application (Launcher Activity which is a subclass of Base Activity). The Base Activity has code as follows:
///This is in BaseActivity
#Override
public void onCreate(Bundle instance)
{
super.onCreate(instance);
//Config.isLoggedIn() is a static function.
if(! Config.isLoggedIn())
{
////Config.startLoginActivity is a static function
Config.startLoginActivity(this, getIntent());
finish();
}
}
The Config.startLoginActivity functions is defined as
public static void startLoginActivity(final Context ctx, final Intent finishIntent)
{
Intent i = new Intent(ctx, ItemListActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("FINISH_INTENT", finishIntent);
ctx.startActivity(i);
}
Now, the ItemListActivity contains a list of Items as {Item1, Item2, Item3}. In ItemListActivity, I am saving the passed "finishIntent" as
///This is ItemListActivity onCreate Method
if(getIntent().hasExtra("FINISH_INTENT"))
mFinishIntent = getIntent().getParcelableExtra("FINISH_INTENT");
and the onItemListSelected method is described as follows :
#Override
public void onItemSelected(String id) {
Config.setLogInState(true);
if(mFinishIntent != null)
{
Log.i("ITEMLISTACTIVITY", "Class Name = " + mFinishIntent.getClass().getName());
Log.i("ITEMLISTACTIVITY", "Starting mFinishIntent Activity");
startActivity(mFinishIntent);
finish();
}
}
But the issue is the Main Activity is not being launched again, Android takes me to the home screen instead. While looking for a solution, I saw that Google I/O app has the same implementation and that works flawlessly but in my case it is not. I am unable to figure it out. Please help.
Thanks in Advance.
Manifest File is as follows :
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name="com.app.myapplication.ItemListActivity"
android:label="#string/app_name" >
</activity>
<activity
android:name="com.app.myapplication.MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Ok Here is a quick help which works for 100 percent which I'm using not mostly but EVERYTIME! you must past it through intent and in your case here it is how it must look like.
Intent intent = new intent(//name of your activity in which you are at the moment.this, //name of activity to which you want to go.class);
startActivity(intent);
Hope this will help
BACKGROUND
I have a task (i.e. app) with multiple activities.
QUESTION
How do I bring a task to the front w/o re-ordering the activity stack for that task?
USER SCENARIO
When the device boots (i.e. after android.intent.action.BOOT_COMPLETED broadcast is received) my app starts, call this task 1, and displays activity A (code below). As the user interacts with task 1, he/she opens activity B on the stack (activity B is now currently displayed on screen). Next the user taps the home key and opens some other task, call it task 2, then locks the screen. The user unlocks the screen, and the android.intent.action.USER_PRESENT intent is broadcast and received by my app (see manifest snippet below). My executes the startActivity() call in my IntentReceiver class as expected, but instead of just bringing the task to the foreground it creates a new task, call it task 3.
CHANGES I'VE TRIED
If I modify or change the Intent.FLAG_ACTIVITY_NEW_TASK to any other intent then I get this error message:
Calling startActivity() from outside of an Activity context requires
the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
...so it looks like I have to use Intent.FLAG_ACTIVITY_NEW_TASK.
If I change the main activity's lauchMode to "singleTask" the correct task is brought to the foreground (i.e. no new task is created), but the activity stack is reordered such that activity_A is on top of the stack.
At this point I am at a loss as to how to bring an existing task to the foreground w/o re-ordering the activity stack while listening for the android.intent.action.USER_PRESENT intent, any help would be appreciated.
BTW, This app is being delivered on to a group of employees, not the general public, on company owned android devices.
CODE SNIPPETS
To start the app I setup a broadcast receiver in my manifest file.
<application
:
<activity
android:label="#string/app_name"
android:launchMode="singleTop"
android:name=".activities.activity_A" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
:
<receiver
android:enabled="true"
android:name=".utilities.IntentReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
In my IntentReceiver class, I start my main activity...
public class IntentReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, activity_A.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
Here's a slightly hackish implementation that works for me:
Create a simple BringToFront activity that simply finish() itself on its onCreate():
public class BringToFront extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finish();
}
}
In your BroadcastReceiver, start the BringToFront activity above instead of your activity_A if the action is USER_PRESENT:
#Override
public void onReceive(Context context, Intent intent) {
Class<? extends Activity> activityClass = activity_A.class;
if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {
activityClass = BringToFront.class;
}
Intent i = new Intent(context, activityClass);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
This works because the BringToFront activity has the same taskAffinity as your activity_A and starting it will make the system bring the existing task to the foreground. The BringToFront activity then immediately exit, bringing the last activity on your task (activity_B in your scenario) to the front.
It's worth noting that on API level 11 (Honeycomb), a moveTaskToFront() method is added to the system's ActivityManager service that might probably be the better way to achieve what you want.
Ok, I was able to get this to work by adding a static global variable in my main activity (activity_A). In onCreate I set isRunning = true, and onDestory = false. Then in my IntentReceiver class I check the isRunning to determine which activity to start:
:
if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {
if (GlobalVariables.isRunning) {
activityClass = BringToFront.class;
} else {
activityClass = activity_A.class;
}
} else if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
activityClass = activity_A.class;
}
:
I have a problem with initializing my app properly after the autostart.
I've managed to get an autostart to work, after a reboot the app is shown as started but the timer's are not.
My guess is that the "onCreate" function of MyApp is not called when I call the context.startService(). The timers are set in the doActivity() function of MyApp.
I would greatly appreciate any tips on what I could be doing wrong or links to good tutorials. :)
The manifest:
<activity android:name=".MyApp"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="MyApp_Receiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>[/syntax]
MyApp_Receiver is a BoradcastReciever with the following two functions
public void onReceive(Context context, Intent intent) {
// Do Autostart if intent is "BOOT_COMPLETED"
if ((intent.getAction() != null) && (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")))
{
// Start the service
context.startService(new Intent(context, MyApp.class));
}
// Else do activity
else
MAIN_ACTIVITY.doActivity();
}
public static void setMainActivity(MyApp activity)
{
MAIN_ACTIVITY = activity;
}
MyApp extends PreferenceActivity and has an onCreate() and a doActivity(), the doActivity() reads out the preferences and sets a timer depending on them.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Show preferences
addPreferencesFromResource(R.xml.preferences);;
// Register Preference Click Listeners
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
// Prepare for one-shot alarms
if (mIntent == null)
{
mIntent = new Intent(MyApp.this, MyApp_Receiver.class);
mSender = PendingIntent.getBroadcast(MyApp.this,
0, mIntent, 0);
MyApp_Receiver.setMainActivity(this);
}
// Refresh and set all timers on start
doActivity();
}
The timers are set in the doActivity()
function of MyApp.
That will never work. MyApp is an activity, one that will not be created until the user goes in and launches it.
Read your SharedPreferences in onReceive() and set the alarms there.