17

I wanted to know if there is a possibility that we could handle/detect runnable callbacks with delay (postDelayed method) on android?

For example, I have one or several splashscreen (which runs with handler.postDelayed(new Runnable()...) on my application (application for testing purpose). In this application I have also a library (that I am creating and using it in the application) and some classes that are available there which runs on an IntentService class.

Sometimes, when the application is running those splashscreen Activities (for Testing purpose), the library that I am creating, might popup some activities automatically in the UI. But it appears that if those activities comes on a splashscreen activity and that the splashscreen is being destroyed, those activities (that popup automatically) will also be destroyed and logging a "leaked window" message in the logcat.

The problem is that :

  • those activities that appears automatically in the UI should not be closed automatically, this is prohibited. It needs an interaction from the user for closing that activity and returns to the normal behaviour of the application.
  • Also, the library doesn't know anything about the UI of the application.

So my questions are (relatively to the library side that I am creating without having informations to the flow of the UI application):

  • Is there a way to detect if some postDelayed method was created in the application relatively to the Library side ? If yes, how could I handle the problem ?

P.S.: Notice that normally, I am using a Dialog for the suppose Activity who is appearing automatically.

UPDATE

Diagram

Explanation of the diagram :

Right now I have a case that a Splashscreen is being executed.

The class which extends the IntentService class, has received a Request from internet which will start an Activity.

Meanwhile the splashscreen is on postdelayed, the other Activity has been created and is showing in the UI. When X seconds has passed and the other Activity hasn't been destroyed, the Next Activity is created and destroy the other Activity automatically. In doing that, Android throws a "leaked window" message relatively to the Activity.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
Damiii
  • 1,363
  • 4
  • 25
  • 46
  • 1
    Can you provide the relevant code for those runnables and activities. – petey Aug 18 '17 at 15:29
  • @YvetteColomb the problem is that the code is confidential... So I cannot put some code unfortunately.... But I thought that my diagram would help a lot and the explanation also. What can I do to give you a better understanding of my problem ? What was the problem with my explanation ? – Damiii Aug 19 '17 at 14:11
  • One thing I still need to understand about your problem, if your pop-up activity is created from the IntentService, I assume the NextActivity have no idea about that, then how can when the NextActivity destroy your popup activity when it shows up? Is it your popup activity automatically finishes if there are other activities on top of it? – Binh Tran Aug 19 '17 at 16:21
  • Well, if the "Activity" is shown and the "NextActivity" is executed and shown right after (due to the time passed by the method "onPostDelayed"). The "Activity" will be destroyed automatically without any interaction from the user who should quit this activity by his own hand. @BinhTran – Damiii Aug 19 '17 at 22:03
  • The normal behaviour if the NextActivity goes on top of your Activity is it should only be in the background. If it is destroyed, there must be some kind of logic within your activity to do that, that's what I wanted to make clear. And one more question, your target is keeping you popup Activity on top until user dismisses it instead of being any other activity show on top, right? – Binh Tran Aug 20 '17 at 13:39
  • 2
    @Damiii I think you are mixing all sorts of terms in your question, making your question too general. It would be in your best interest if you break down the question to more specific well defined questions. – Alex.F Aug 21 '17 at 07:25
  • Are you trying to show a dialog using an activity that has already exited? It could be helpful for you to read through the answers of [Activity has leaked window that was originally added](https://stackoverflow.com/q/2850573/1449010) – Victor B Aug 22 '17 at 21:37
  • @Damiii, `Leaked Window` warning never closes any activity, it is just warning. Problem lies somewhere in code where `finish()` has been called. – Akash Kava Aug 24 '17 at 07:59
  • @AkashKava and you are right, when `NextActivity` is launched, if I am not wrong, the Android SO will remove the `Activity` by calling `onDestroyed` function. – Damiii Aug 24 '17 at 08:13
  • 1
    @Damii You can't be sure about that. `OnStop` will certainly be called. `OnDestroy` might be called just after that, half an hour later or even never if there are leaked objects. – rupps Aug 25 '17 at 19:04

8 Answers8

4

Is there a way to detect if some postDelayed method was created in the application relatively to the library side?

You can make use of MessageQueue.IdleHandler API. See LooperIdlingResource how espresso finds out whether it is appropriate time to fire in assertion.


    @Override
    public boolean queueIdle() {

      QueueState queueState = myInterrogator.determineQueueState();
      if (queueState == QueueState.EMPTY || queueState == QueueState.TASK_DUE_LONG) {
        ...
      } else if (queueState == QueueState.BARRIER) {
        ...
      }

      return true;
    }

This will help you to understand whether there is a message in the MessageQueue, but it won't tell you what exact message is there.

The solution I'd go with would be to unschedule the Runnable that you have postDelayed within onStop of the activity, because if Activity (the one from library) has been launched, than onStop of SplashScreen is being called:


public class SplashActivity extends AppCompatActivity {

  private final Runnable myRunnable = () -> {
    // launch `NextActivity`
  };
  private final Handler handler = new Handler();

  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash);

    handler.postDelayed(myRunnable, 3000);
  }

  @Override
  protected void onStop() {
    super.onStop();
    handler.removeCallbacks(myRunnable);
  }

}

azizbekian
  • 60,783
  • 13
  • 169
  • 249
2

You need to explain the problem better. I'm confused between the relation of the splash screen and the other activities, and if the problem is related to postDelayed() or to the activities lifecycle. I'd suggest a tiny graphic diagram explaining which activities launch other ones.

Regarding postDelayed(), In general, if you do

 mHandler.postDelayed(new Runnable() { ... });

You are posting an anonymous, fresh runnable everytime, so you won't be able to remove it. I'd suggest the following approach, declaring the Runnables as class members inside your library:

Runnable mLaunchSplashRunnable = new Runnable() { ... };
Runnable mLaunchContactsRunnable = new Runnable() { ... };

.
.

mHandler.postDelayed (mLaunchSplashRunnable, DELAY);
mHandler.postDelayed (mLaunchContactsRunnable, DELAY);

.
.

As runnables are now not anonymous, you can remove them from the queue anytime:

void removeLibraryDelayedRunnables() {
   mHandler.removeCallbacks(mLaunchSplashRunnable);
   mHandler.removeCallbacks(mLaunchContactsRunnable);
}

Note the previous method won't fail if there aren't any posted runnables, so it is safe to call it anytime.

A method to query the Handler if a specific Runnable is queued, afaik, does not exist, but you can maybe use a boolean flag, set it when the runnable is queued, and reset it when the Runnable is run, to indicate a runnable is pending.

If I understand your problem better I'd be able to help more.

rupps
  • 9,712
  • 4
  • 55
  • 95
2

The problem is that once you start firing activities one after another, after some time when your application has consumed lot of memory, so android assumes that previous activities can be destroyed to optimize system and avoid slow down of device, this is how mobile devices works, so your main activity which is consuming API is destroyed by system: please understand activity life cycle:

https://developer.android.com/guide/components/activities/activity-lifecycle.html

see after onStop()...this is the case with your app.

Hope this will help you...

Abdul Aziz
  • 442
  • 5
  • 12
  • Well I already know that the problem was relatively to activity life cycle and what I want is a little different... Try to force the View of the Activity to be hold and increase the time in this case of the runnable. (Something more or less that). – Damiii Aug 24 '17 at 15:14
2

Why don't you use static boolean variable to determine whether your Splash Screen is running or not when you are about to call it.

james
  • 1,967
  • 3
  • 21
  • 27
  • Because it is a SDK that I am creating. In this way, the SDK doesn't have any logic nor control from the application that is importing the SDK. – Damiii Aug 25 '17 at 08:34
1

I think you should rather invert logic in the program. The activity should be started from your activities, not from the service.

To achieve that you can register BroadcastReceiver on creation of each activity https://developer.android.com/reference/android/content/BroadcastReceiver.html and use sendBroadcast from the service https://developer.android.com/guide/components/broadcasts.html when starting an activity is needed, to command the broadcast receiver to start needed activity.

Anton Malyshev
  • 8,686
  • 2
  • 27
  • 45
  • The problem is that I am creating a SDK, and that SDK should be included on any application. In this case, the SDK don't have any view/information about the application who is using the SDK. – Damiii Aug 19 '17 at 14:08
  • But you can require your SDK users to call some methods on creating the activities using SDK. To me that's better and more reliable than trying to track user's app behaviour – Anton Malyshev Aug 20 '17 at 15:56
1

Using a callback with a temporally coupled activity may not be a very good design for your SDK. Consider using observables to get data from the network layer to an activity that needs the data.

By adding an observer to the activity that needs the data, which watches to see if the network call is done, you only expose the data to an observer that is active. This way, if the activity is closed before the call is complete, there is no "pushing" of the data to the closed activity.

This also allows you to create weak references to your library in a way where you do not need to worry about leaking windows

Brian
  • 573
  • 4
  • 8
1

Since we didn't see your code yet. My answer is going to be too generic, so sorry for that.

I think you should first check out logcat to see if there is any exception going on that closing those activities. If there is nothing about it. Just check for all the try catch blocks to be sure about it. So after you are sure that it is not about any exceptions of any kind, check for the AndroidManifest file to see 'Launch Modes' of activities. It could lead automatic closing as well.

If all your activities are in a standard mode then try to change at least one activities launch mode to disable it from closing and try again. If this doesn't make any sense as well, check the code for finish() calls.

Still no luck? Then i guess this could be a case of ANR you should check for memory leaks that freezes your application and probably closing down activities. Probably those runnables are somehow ruining your application.

I think you should have a mechanism like eventbus or broadcast receiver for internal communication in between your screens. They will run on the lifecycle of your activities and would not lead to any kind of exception or anr.

Oguz Ozcan
  • 1,694
  • 19
  • 26
1

Activities can get destroyed any time, so when this happens you need to take care of the dialogs that are being destroyed too. In the lifecycle of the Activity you will need to destroy the "Dialogs" too else you will get the "Leaked Window".

Also the Dialogs that reference the Activities would benefit from using a weakReference to the Activities, you would then use the weak reference to report the users actions to the activity.

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    listener = new WeakReference<>((MyActionListener) activity);
    activityWeakReference = new WeakReference<>(activity);
}

Before showing a Dialog I would also make sure the Activity is not in the process of finishing:

if (myActivityWeakReference.get() != null && !myActivityWeakReference.get().isFinishing()) {
  // show dialog
}

Then when you want to control rotation of the device, where the Activities get re-created you can use the following together with your dialogs:

    @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
}

/**
 * Prevent the dialog being dismissed when rotated, when using setRetainInstance
 */
@Override
public void onDestroyView() {
    if (getDialog() != null && getRetainInstance()) {
        getDialog().setDismissMessage(null);
    }
    super.onDestroyView();
}
Wayne
  • 3,359
  • 3
  • 30
  • 50