Search code examples
android-intentandroid-activityandroid-backgroundandroid-task

How to fix app go to background after reorder to front and back


I currently managed to allow user switch between two distinct acitivties groups (let's said 4 activity classes A/B group and X/Y group) and switch by FLAG_ACTIVITY_REORDER_TO_FRONT flag, but I noticed there are some strange behaviour:

A ->(start activity)  X
X ->(reorder to front) A 
X , A ->(start) B ->(start) B2
A , B , B2 ->(reorder to front) X ->(start) Y
X , Y ->(reorder to front) A , B , B2 
X , Y , A , B <-(press back, app hide to background, B2 destroyed)  B2 
X , Y , A , B (click to foreground, B is here just fine)

How to prevent app hide to background when back from B2 ?

I noticed it only happen in same class(B and B2 is same class), if I use B and C, it will no such issue, I wanted to know what's the reason behind.

And X ->(start) Y is also the key to reproduce it.

I tried to use application-level custom instance list to detect B2's onPause() and isFinishing() and startActivty() the B, but it will always calls B's onCreate() even though using FLAG_ACTIVITY_REORDER_TO_FRONT, which make me think this is not the proper solution. The proper solution should figure out how to prevent the app hide to background.


Solution

  • I misunderstanding the FLAG_ACTIVITY_REORDER_TO_FRONT flag. I thought activity Y FLAG_ACTIVITY_REORDER_TO_FRONT activity C will retain the parent, so C backpress will go to B. No, this is not the case. FLAG_ACTIVITY_REORDER_TO_FRONT actually make the C transfer to the new parent Y (i.e. the caller), but since parent Y is on top of C, so C backpress will not able goto Y and get minimized/hide to background.

    I also misunderstanding my code create 2 stack, but actually it's only single stack. I must use FLAG_ACTIVITY_MULTIPLE_TASK to make it multple stacks.

    I also need to define correct launchMode in manifest:

    • Both launcher activity A and X must defined as singleInstance in manifest.
    • Both home page activity B and Y must defined as singleTop in manifest.
    • Other activities C and Z ...etc must defined as singleTask in manifest.

    Activity A must set flag Intent.FLAG_ACTIVITY_MULTIPLE_TASK when start home page B. X should also set flag Intent.FLAG_ACTIVITY_MULTIPLE_TASK when start home page Y.

    If the tab task already started before, the valid flags to loop to reorder all the same task activities (store in global stack list) are (moveTaskToBack(true); single line also works but it not able back to foreground):

    intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                                |Intent.FLAG_ACTIVITY_NEW_TASK
                                |Intent.FLAG_ACTIVITY_NO_ANIMATION); 
    

    p/s: The above will not work in Android 5 and 6, which will only reorder single top activity to front, so I need to add android.permission.REORDER_TASKS in manifest and do this (More info, see this bug report):

    ActivityManager tasksManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    for (final Activity a : YStackClasses) {
        if (IsBeforeAndroidNougat && (tasksManager != null)) {
            tasksManager.moveTaskToFront(a.getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION); 
        } else { 
            //Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
        }
    }
    

    If the tab task never started, should start launcher activity without set any special flag.

    To prevent click launcher will refresh due to singleInstance, I must also do checking in A and B, put it before setContentView and finish()/return; early.

    if (getIntent() != null && getIntent().hasCategory(Intent.CATEGORY_LAUNCHER)
        && getIntent().getAction() != null
        && getIntent().getAction().equals(Intent.ACTION_MAIN)) {
        //figure out latest activity and it's home activity, and reorder to front, and its entire task stack will focus on top.
    }
    

    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK will no longer auto clear all activities to refresh the app, the solution is ActivityCompat.finishAffinity(desiredTask's Top Act);, which will remove all activites below of top activitiy without affect the other task stack.

    If the class is reuse, such as C1, C2, then I must use startActivityForResult(intent, dummyResponse); to force it create new acitivty on top of same class. But don't do it when A start activity B since startActivityForResult requires single task and ignore the singleInstance launchMode, see this thread. And don't do ForResult inside onNewIntent() which might causes page generated twice in stack (The second page not generated immediately, instead only generated when back). But I can do ForResult in the caller of onNewIntent() to take effect.

    I also need to do this to make backpress work as expected in home activity to hide app and able back to the correct task page. I don't use TASK_ON_HOME(when start home page B class) flag because it's only single task direction (i.e. foreground to fixed activity instead of depends on previous top task activity):

    @Override //B class
    public void onBackPressed() {
        moveTaskToBack(true); //B task stack
        if (YStackClasses.size() > 0) { //Y task stack
            try {
                YStackClasses.get(0).moveTaskToBack(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        super.onBackPressed();
    }
    

    And be careful the app stack might cache even though relaunch automatically due to crash, so sometime you need relaunch twice to make it back to initial stack state to test, or else you will get the wrong conclusion.

    That's it, this answer might lack of code details, but it should help if you encounter same "hide to background when backpress" problem like me, you can get idea of how difficult to achieve expected behavior if you set wrong flag in one step or defined wrong launchMode in one of the activity in manifest.