16

I would like to finish() a paused activity that is underneath a transparent activity.

I have an activity, called activity A. Two things can happen while activity A is active;

  • we can launch (a transparent) Activity B

  • we can receive an asynchronous callback to finish activity A.

These two actions happen very close to each other. The code looks like this

public class ActivityA extends Activity
{
    public class DataHandler implements ContentLoader.OnDataListener
    {
        @Override
        public void onData(Cursor data)
        {
            _binder.bind(data);
        }
    }

    //If this callback is executed while Activity A is paused, it will not go into onStop until it the activity above it is finished
    private class LoaderCallbacks extends ContentLoader.LoaderCallbacks
    {
        public LoaderCallbacks(ContentLoader loader)
        {
            super(loader);
        }

        @Override
        public void onLoadFinished(
                Loader<Cursor> loader,
                Cursor cursor)
        {
            if (cursor == null || cursor.getCount() <= 0)
            {
                Log.d("Eric", "* ON FINISH *");
                finish();
                finishagain();
                return;
            }

            super.onLoadFinished(loader, cursor);
        }
    }
}

Inside of a listfragment shown by this activity there is a mechanism for launching Activity B

public class FragmentA extends ListFragment
{
    //Some fragment functions here...

        @Override
    public void onListItemClick(
            ListView list,
            View view,
            int position,
            long id)
    {
            Intent intent = new Intent();
            intent.setAction(Intent.LAUNCH_ACTIVITY_B);
            getActivity().sendBroadcast(intent)
    }
}

My problem is when the callback to finish activity A is called AFTER activity B is launched, then Activity A is not being finished immediately. It remains in the paused state until Activity B is finished, and then both finish. This is a race condition, and I've confirmed this by trying to finish again, while in the paused state, using a simple waiting thread. All the finish calls are performed on the main thread, as expected.

private void finishagain()
{
    Handler handler = new Handler();
    int LOCK_HOME_DELAY = 5000;
    handler.postDelayed(new Runnable()
    {
        public void run()
        {
            if (notfinished){
                Log.d("Eric", "*************** FINISH AGAIN ****************");
                finish(); //Does nothing while the activity is paused
            }
            else{
                Log.d("Eric", "* Times up do nothing *");
            }

        }
    }, LOCK_HOME_DELAY);
}

Here are my logs (some package names may be redacted)

    10-10 18:23:05.168 74-98/system_process I/ActivityManager: Displayed somepackage/com.eric.activity.A: +894ms
    10-10 18:23:07.135 74-98/system_process I/ActivityManager: Displayed somepackage/com.eric.activity.B: +343ms
    10-10 18:23:07.102 547-547/somepackage D/Eric: * Times up do nothign *
    10-10 18:23:07.231 547-547/somepackage D/Eric: * ON FINISH *
    10-10 18:23:08.220 547-547/com.eric.Status D/Eric: * Times up do nothign *
    10-10 18:23:08.305 547-547/com.eric.Status D/Eric: * Times up do nothign *
    10-10 18:23:12.305 547-547/com.eric.Status D/Eric: *************** FINISH AGAIN ****************
    10-10 18:23:12.305 74-668/system_process W/ActivityManager: Finishing task with all activities already finished
    10-10 18:23:12.305 74-668/system_process W/ActivityManager: Duplicate finish request for ActivityRecord{3627639c u0 somepackage/com.eric.activity.A t2292 f}

(Notice the timestamps - I call finish at :07 seconds, and it doesnt finish. finishAgain() calls finish again at :12 seconds, and it appears to close here, but I've seen it finish later too. Also note the "duplicate finish request" - to me it looks like the finish was queued or something).

How can I get Activity A to finish when it is paused underneath the transparent Activity B?

TO be honest, I'm surprised this is an issue; I thought activities on the backstack should be readily killeable, but perhaps not those in the onPause state? I haven't been able to find documentation on this, perhaps someone knows the relevant doc/code?

EDIT see my answer

Eric S.
  • 1,502
  • 13
  • 31
  • Not sure what you're whole project is, but can you add some insight into why you want to do this? Perhaps you can override `onBackPressed()` in ActivityB, and never allow the app to go back to Activity A unless you force kill it? Sounds hacky, just a thought. – AdamMc331 Oct 10 '16 at 19:16
  • @McAdam331 Motivation is simple design decision; just want user to see the Activity underneath Activity A. Specifically, Activity A is a "warning message" activity - when activity B is launched, the warning will have expired, and the user should see the activity underneath it. Activity B is mostly transparent and displays the status of an action (for example "Loading module 1 of 10") – Eric S. Oct 10 '16 at 19:21
  • Have you considered using a DialogFragment for this sort of experience? – AdamMc331 Oct 10 '16 at 19:22
  • DialogFragment would be the normal way to go, but this dialog is launched and controlled asynchronously and inter-processes. A dialogfragment is still tied to a specific activity - we need to launch this dialog over anything at anytime. – Eric S. Oct 10 '16 at 19:25
  • Anything at anytime? Even if your main app is closed? – AdamMc331 Oct 10 '16 at 19:26
  • 2
    Sounds good. I don't really know about your activity issue here, so I was just trying to understand the purpose of the UX. Let me know what you figure out. :) – AdamMc331 Oct 10 '16 at 19:30
  • I don't understand the paused activity is still executing log entries, i'm missing the code (thread ?) that makes that possible ?must b b4 paused ? – Jon Goodwin Oct 29 '16 at 12:58
  • @JonGoodwin Pausing the activity doesn't kill the process or anything, so you can run whatever code you want that isn't UI related. – Eric S. Oct 30 '16 at 20:22

4 Answers4

8

Hmmm this question is trickier than it looks, the transparent Activity causes problems. You explained it well, but people dont normally answer the question, they spill-out something they are familiar with.

"If an activity has lost focus but is still visible (that is, a new non-full-sized or transparent activity has focus on top of your activity), it is paused. A paused activity is completely alive (it maintains all state and member information and remains attached to the window manager), but can be killed by the system in extreme low memory situations." see android doc.

you say:

//If this callback is executed while Activity A is paused, it will not go into onStop until it the activity above it is finished

You cannot go into stopped state while Activity A is visible.Android destroying activities, killing processes
This may help:

View topLevelLayout = findViewById(R.id.top_layout);
topLevelLayout.setVisibility(View.INVISIBLE);

In my answer the default case is asynchronous (it's a broadcast). Register each Activity (that you want to later kill, ActivityA) for local broadcast when it gets created. When it goes to the background (paused state), (i.e., a new activity comes to the top of the stack), its onPause() {onStop()} will get called, but it can still receive broadcasts. You just need to make sure you call unregisterReceiver() in onDestroy() rather than in onPause() {onStop()}.

Here'a some sample code to give you the idea:

    /** your "kill" method (ActivityA) **/
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction("com.package.ACTION_KILL");//some string
    //  sendBroadcast(broadcastIntent);//global broadcast
    LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);

     /**    The receiver (ActivityA) **/
        BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver(); 
        protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.package.ACTION_KILL");//some string
    LocalBroadcastManager.getInstance(this).registerReceiver(myBroadcastReceiver     {
     @Override
     public void onReceive(Context context, Intent intent) {
          Log.d("onReceive","KILL in progress");
          //At this point you should do your stuff ;O)
          finish();//kill it
         //System.exit(0)// to clear static variables
         //android.os.Process.killProcess(android.os.Process.myPid());//"cave man" kill                          }
      }, intentFilter);
   }

    protected void onDestroy(
    {
      LocalBroadcastManager.getInstance(this).unregisterReceiver(myBroadcastReceiver );
       super.onDestroy();
    }

https://developer.android.com/training/basics/activity-lifecycle/index.html https://developer.android.com/reference/android/app/Activity.html https://developer.android.com/reference/android/content/BroadcastReceiver.html

Community
  • 1
  • 1
Jon Goodwin
  • 9,053
  • 5
  • 35
  • 54
2

You can shut down an activity by calling its finish() method. You can also shut down a separate activity that you previously started by calling finishActivity().

You can visit the developer page here

so now, if you are using to many activities, the way for finish is ..

  1. Starting the other activities, only need the class, intent, and request code.

    public void startAcc(View v){
    Intent i = new Intent(this, SecondActivity.class);
    startActivityFromChild(this,i,0);
    }
    
  2. Finishing the activies from other in pause. Only need the child class and the request code that you should use to initialize.

    finishActivityFromChild(MainActivity.this,0);
    

In your case should use startActivityFromChild to start the other activity, and finishActivityFromChild before call finish(), when the loadercallback is onLoaderFinished.

I hope this can help you.

K. Jorge
  • 53
  • 7
1

I don't know why are you making it soooo hard and complicated for yourself such a simple thing. finish activity A after activity B has finished loading.

I don't understand the use case of things like ContentLoader, LoaderCallbacks, Fragment, ListFragment etc. I even wonder why Fragments still are not deprecated?!!! a heavy 2209-lined class just for showing a ViewGroup inside another View?! well I can inflate and add that ViewGroup to the view myself, why would I have to use Fragments?!! anyway.

well they created the Application class for a reason. why don't you use that?!

I'm only presuming your question is: how to finish Activity A after Activity B has finished loading:

"we can launch (a transparent) Activity B we can receive an asynchronous callback to finish activity A."

If you want to get notified by an event, well create an event listener for that instead of using those complicated classes and methods.

here is how:

you have an Application class like this:

public class App extends Application{

    private static ArrayList<ActivityLoadingFinishedListener> activityListeners;

public static interface ActivityLoadingFinishedListener{
    void OnActivityFinishedLoading();
}

public static void addActivityListener(ActivityLoadingFinishedListener listener){
    if(activityListeners == null){
        activityListeners = new ArrayList<ActivityLoadingFinishedListener>();
    }
    activityListeners.add(listener);
}

public static void removeAllActivityListeners(){
    if(activityListeners != null){
        activityListeners.clear();
    }
}

public static void notifyAllActivityListeners(){
    if(activityListeners != null){
        for(ActivityLoadingFinishedListener alfl:activityListeners){
            alfl.OnActivityFinishedLoading();
        }
    }
}
}

then inside activity A:

        Intent intent = new Intent(A.this, B.class);
        App.addActivityListener(new ActivityLoadingFinishedListener() {

            @Override
            public void OnActivityFinishedLoading() {
                finish();
            }
        });
        startActivity(intent);

then inside activity B:

@Override
protected void onResume() {
    super.onResume();
    App.notifyAllActivityListeners();
}

this is just an example to show you that you can create your own events easily. they're so useful, simple, fast, and reliable.

you can change my example to fit your needs.

and for your question: "How can I get Activity A to finish when it is paused underneath the transparent Activity B?"

you can use the same approach. when Activity B is resumed, it should notify all the listeners(which activity A is one of them).

M D P
  • 4,525
  • 2
  • 23
  • 35
  • Thanks for your reply. My question is not how to notify a listener, but rather why my notification to finish an activity doesn't happen work immediately. – Eric S. Oct 30 '16 at 20:29
1

Edit Took a look into services/java/com/android/server/am/ActivityStack.java to find the line that returns the "Duplicate finish request for ActivityRecord..." log (line 3424 of linked version). It's located in a function called finishActivityLocked(...) that checks a finished flag.

I then came across this in line 3505 of the same function

else if (r.state != ActivityState.PAUSING) {
            // If the activity is PAUSING, we will complete the finish once
            // it is done pausing; else we can just directly finish it here.
            if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r);
            return finishCurrentActivityLocked(r, index,
                    FINISH_AFTER_PAUSE) == null;
        } 

I didnt have the time to look too deeply, but it's possible I met this condition, or a condition similar to it. I'm not sure what constitutes PAUSING versus PAUSED (both states are listed). Maybe this isn't the exact logic that I was caught in, but clearly the state machine for the activity stack is very nuanced and they do have a mechanism for queuing finish requests.

So in conclusion, I cant provide a definitive line number, but I'm fairly confident that

1) the activity state machine queues the finish request

2) the specific condition is somewhere in the 4k lines of ActivityState.java.

original post

I ended up writing a workaround for this problem. The problem, as you recall, is that a finish() call to an activity paused under a transparent activity is delayed. My workaround is to bring the paused activity up with no animations and kill it immediately. It is relatively unnoticeable performance-wise.

Still not sure about the core issue, and I haven't spent too much time digging into the AOSP source, so I've put up a bounty for any definitive explanation on how finish works on paused activities.

private boolean notfinished;

/**
 * This function is used to finish an IR if it is stuck in a paused state.
 *
 * An activity will not finish() while paused underneath another activity. This function will
 * force a finish immediately by relaunching our activity and killing it immediately.
 *
 * This function assumes the launchmode is singletask or singleinstance, and does
 * not contain flags that clear or change stack (like FLAG_ACTIVITY_CLEAR_TOP)
 */
private void finishagain()
{
    Handler handler = new Handler();
    handler.postDelayed(new Runnable()
    {
        public void run()
        {
            if (notfinished)
            {
                Log.d(TAG, "ContentActivity couldn't finish immediately. " +
                        "This may happen if the Activity is paused when finish is called. Trying again");
                Intent i = getIntent();
                i.putExtra(MyConstants.FINISH_STATUS_FLAG, true);
                i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                startActivity(i) ;
            }
        }
    }, DELAY_CHECK_FINISHED);
}    

@Override
public void onStart()
{
    super.onStart();
    notfinished = true;
}

@Override
public void onResume()
{
    super.onResume();
    //There is a case where we need to take this activity out of paused state to kill it
    if (getIntent().getBooleanExtra(MyConstants.FINISH_STATUS_FLAG, false))
    {
        Log.d(TAG, "Finishing ContentActivity");
        finish();
        overridePendingTransition(0, 0); //Blocks the default finishing transition animation
    }
}

@Override
public void onStop()
{
    super.onStop();
    notfinished = false;
}

//sample useage
  private class LoaderCallbacks extends ContentLoader.LoaderCallbacks
  {
    public LoaderCallbacks(ContentLoader loader)
    {
        super(loader);
    }

    @Override
    public void onLoadFinished(
            Loader<Cursor> loader,
            Cursor cursor)
    {
        if (cursor == null || cursor.getCount() <= 0)
        {
            finish();
            finishagain();
            return;
        }

        super.onLoadFinished(loader, cursor);
        setContentDescription();
    }
}
Eric S.
  • 1,502
  • 13
  • 31