Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This library is an error implementation. Here is a simple way to achieve. #15

Closed
TakWolf opened this issue Jul 7, 2017 · 8 comments
Closed

Comments

@TakWolf
Copy link

TakWolf commented Jul 7, 2017

public class ApplicationListener implements Application.ActivityLifecycleCallbacks {

    private int foregroundCount = 0; 

    @Override
    public void onActivityStarted(Activity activity) {
        if (foregroundCount <= 0) {
            // TODO becomes foreground
        }
        foregroundCount++;
    }

    @Override
    public void onActivityStopped(Activity activity) {
        foregroundCount--;
        if (foregroundCount <= 0) {
            // TODO goes background
        }
    }

    /*
     * The following callbacks, we do not need
     */

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

    @Override
    public void onActivityResumed(Activity activity) {}

    @Override
    public void onActivityPaused(Activity activity) {}

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}

    @Override
    public void onActivityDestroyed(Activity activity) {}

}

That is.

If ActivityA go to ActivityB,the callbacks is :

A.onPause()
B.onStart()
B.onResume()
A.onStop()

If ActivityB return ActivityA, the callbacks is:

B.onPause()
A.onStart()
A.onResume()
B.onStop()

So, set a counter to statistics onStart() and onStop() callbacks. if the counter is > 0, Application is in foreground, opposite in background.

@steveliles
Copy link
Owner

There are so many edge cases and quirks, i doubt that the solution you've posted here (which I believe was suggested at Google IO) covers them all - it certainly did not when I originally wrote this lib, because that was exactly what I started out with.

@TakWolf
Copy link
Author

TakWolf commented Jul 7, 2017

Test for all scenes, there is only one scene need to special treatment: android.configChanges

So compat this scene, update the codes:

public class ApplicationListener implements Application.ActivityLifecycleCallbacks {

    private int foregroundCount = 0;
    private int bufferCount = 0;

    @Override
    public void onActivityStarted(Activity activity) {
        if (foregroundCount <= 0) {
            // TODO becomes foreground
        }
        if (bufferCount < 0) {
            bufferCount++;
        } else {
            foregroundCount++;
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (!activity.isChangingConfigurations()) {
            foregroundCount--;
            if (foregroundCount <= 0) {
                // TODO goes background
            }
        } else {
            bufferCount--;
        }
    }

    /*
     * The following callbacks, we do not need
     */

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

    @Override
    public void onActivityResumed(Activity activity) {}

    @Override
    public void onActivityPaused(Activity activity) {}

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}

    @Override
    public void onActivityDestroyed(Activity activity) {}

}

I don't know what's the other edge cases and quirks.
But make me puzzled at

final WeakReference<Activity> ref = new WeakReference<>(activity);

and

currentActivity = new WeakReference<>(activity);

Why use handler delay check in onPaused ?
This makes the logic complicated and leaving the hidden dangers (WeakReference).

Can provide an edge cases to let me make a test. Or give me some reason about that?

@steveliles
Copy link
Owner

Check the blog post and comments ...

http://steveliles.github.io/is_my_android_app_currently_foreground_or_background.html

Besides config changes, another significant problem is: if you receive a phone call onStop is not called.

@TakWolf
Copy link
Author

TakWolf commented Jul 8, 2017

About receive a phone call:
In my test, that's really did not call onStop, only call onPause(System version 4.4, 5.1 and 7.0).
But does in calling means application enter background?

In iOS, there is a class named AppDelegate (similar to Androdi.Application

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

}

In my understanding, iOS only have the application level callbacks. Android only have the controller level callbacks.
But in concept, they are the same.
applicationWillResignActive and applicationDidBecomeActive is similar to activity.onPause and activity.onResume, means hang up statius switch.
applicationDidEnterBackground and applicationWillEnterForeground means iOS application's visibility switch.
activity.onStop and activity.onStart means activity's visibility switch.

In iOS, if receive a phone call, it will only call applicationWillResignActive, but not call applicationDidEnterBackground. (This behavior can be simulated by a real machine.)

So, can i think that, Android 's behavior is same to iOS, although onStop not call?
And should this case really need to be handled specifically?

@TakWolf
Copy link
Author

TakWolf commented Jul 10, 2017

About request permissions, like case #12 :

In Android, it will only call onPause, not call onStop (Because activity is still visiable);
In iOS, it will only call applicationWillResignActive, not call applicationDidEnterBackground

So request permissions means application paused, but not enter background.

Android 's behavior is same to iOS.

@TakWolf
Copy link
Author

TakWolf commented Jul 10, 2017

So use the onPause hack way to handle this service may be not a good idea.
More reasonable approach is: distinguish between two cases.

Use onStart and onStop to listen application switch foreground and background in a stable way.
Use onResume and onPause to listen application level resumed or paused in a hack way.
Choose one of it depending on your situation.

@steveliles
Copy link
Owner

but does in calling means application enter background?

Since API 11 Android does guarantee to call onStop before killing your application. However it does not guarantee to call it in a timely fashion if you are simply moved to the background. Phone calls are one case where this manifests in the real world - there may be others too, e.g. receiving calls from other apps such as skype, whatsapp, slack, etc. - I don't know.

The phone call case is very clear-cut. You are most definitely put in the background when a phone call is received, so the onStart/onStop method simply does not work as a mechanism for determining the foreground status of an app.

The permissions case is a much more grey area IMHO, but there are also ways around that because your app instigates the permission request, so you know when a permissions request is in flight.

More assistance for that case could perhaps be baked into the library (runtime permissions weren't a thing when I wrote Foredroid, and I've not worked with native Android much since then).

@TakWolf
Copy link
Author

TakWolf commented Jul 11, 2017

In Android, there is no definition about application enter background or application paused(For this reason, we have to simulate one). The problems is what is the reasonable behavior? Because there is no definition, I tend to find a reference, like iOS. What the behavior in iOS? According to the reasons I have said above, only use onStop() and onStart() can simulate a same behavior to iOS. The same behavior means, I can use the same logic and process to write the service for the two platforms. I think this is a better practice.

In iOS, request permissions and recived a calling means application paused, but not enter background. Android is the same behavior, so do not need to do any special treatment, it is great!

But there are always annoying demands that we need to achieve, like request permissions and recived a calling. The boss or the users wants these behaviors to behave as application enter background. Fortunately, these cases can be handle specially:

Recive a calling can be listen by using BroadcastReceiver and android.intent.action.PHONE_STATE;
Request permissions can be listen by set flags in your own action and onRequestPermissionsResult.

So you can handle them using an elegant way. More importantly, you do not need anything hacking!
All the code is clear and stable, users can combine solutions according to their own situation, rather than using a blurry logic of the boundary.

@TakWolf TakWolf closed this as completed Jul 11, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants