This library is an error implementation. Here is a simple way to achieve.
TakWolf opened this issue · 8 comments
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.
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.
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
and
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?
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.
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?
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.
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.
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).
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.