我试图写一个应用程序,做一些具体的时候,它被带回前台后一段时间。是否有一种方法可以检测应用程序是被发送到后台还是被带到前台?
当前回答
这是我的解决方案。只需在您的主Application类中注册这个ActivityLifecycleCallbacks。在评论中,我提到了一个用户配置文件活动边缘情况。该活动只是一个具有透明边缘的活动。
/**
* This class used Activity lifecycle callbacks to determine when the application goes to the
* background as well as when it is brought to the foreground.
*/
public class Foreground implements Application.ActivityLifecycleCallbacks
{
/**
* How long to wait before checking onStart()/onStop() count to determine if the app has been
* backgrounded.
*/
public static final long BACKGROUND_CHECK_DELAY_MS = 500;
private static Foreground sInstance;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private boolean mIsForeground = false;
private int mCount;
public static void init(final Application application)
{
if (sInstance == null)
{
sInstance = new Foreground();
application.registerActivityLifecycleCallbacks(sInstance);
}
}
public static Foreground getInstance()
{
return sInstance;
}
public boolean isForeground()
{
return mIsForeground;
}
public boolean isBackground()
{
return !mIsForeground;
}
@Override
public void onActivityStarted(final Activity activity)
{
mCount++;
// Remove posted Runnables so any Meteor disconnect is cancelled if the user comes back to
// the app before it runs.
mMainThreadHandler.removeCallbacksAndMessages(null);
if (!mIsForeground)
{
mIsForeground = true;
}
}
@Override
public void onActivityStopped(final Activity activity)
{
mCount--;
// A transparent Activity like community user profile won't stop the Activity that launched
// it. If you launch another Activity from the user profile or hit the Android home button,
// there are two onStops(). One for the user profile and one for its parent. Remove any
// posted Runnables so we don't get two session ended events.
mMainThreadHandler.removeCallbacksAndMessages(null);
mMainThreadHandler.postDelayed(new Runnable()
{
@Override
public void run()
{
if (mCount == 0)
{
mIsForeground = false;
}
}
}, BACKGROUND_CHECK_DELAY_MS);
}
@Override
public void onActivityCreated(final Activity activity, final Bundle savedInstanceState)
{
}
@Override
public void onActivityResumed(final Activity activity)
{
}
@Override
public void onActivityPaused(final Activity activity)
{
}
@Override
public void onActivitySaveInstanceState(final Activity activity, final Bundle outState)
{
}
@Override
public void onActivityDestroyed(final Activity activity)
{
}
}
其他回答
2018: Android通过生命周期组件原生支持这一点。
2018年3月更新:现在有了更好的解决方案。看到ProcessLifecycleOwner。您将需要使用新的体系结构组件1.1.0(目前的最新版本),但它是专门为此设计的。
在这个答案中提供了一个简单的示例,但我写了一个示例应用程序和一篇关于它的博客文章。
自从2014年我写了这篇文章以来,出现了不同的解决方案。有些可以工作,有些被认为可以工作,但有缺陷(包括我的!),我们作为一个社区(Android)学会了忍受后果,并为特殊情况编写了变通方案。
永远不要假设一个代码片段就是你想要的解决方案,这是不可能的;更好的做法是,试着理解它是做什么的,以及它为什么这么做。
MemoryBoss类实际上从未被我使用过,它只是一段碰巧工作的伪代码。
除非有充分的理由不使用新的体系结构组件(确实有一些,特别是如果您的目标是超级老的api),否则就继续使用它们。它们远非完美,但ComponentCallbacks2也不是。
UPDATE / NOTES (November 2015): People has been making two comments, first is that >= should be used instead of == because the documentation states that you shouldn't check for exact values. This is fine for most cases, but bear in mind that if you only care about doing something when the app went to the background, you will have to use == and also combine it with another solution (like Activity Lifecycle callbacks), or you may not get your desired effect. The example (and this happened to me) is that if you want to lock your app with a password screen when it goes to the background (like 1Password if you're familiar with it), you may accidentally lock your app if you run low on memory and are suddenly testing for >= TRIM_MEMORY, because Android will trigger a LOW MEMORY call and that's higher than yours. So be careful how/what you test.
此外,有些人问过如何检测你什么时候回来。
下面解释了我能想到的最简单的方法,但由于有些人不熟悉它,所以我在这里添加了一些伪代码。假设你有YourApplication类和MemoryBoss类,在你的类BaseActivity扩展了Activity(如果你没有的话,你需要创建一个)。
@Override
protected void onStart() {
super.onStart();
if (mApplication.wasInBackground()) {
// HERE YOU CALL THE CODE YOU WANT TO HAPPEN ONLY ONCE WHEN YOUR APP WAS RESUMED FROM BACKGROUND
mApplication.setWasInBackground(false);
}
}
我推荐onStart,因为对话框可以暂停一个活动,所以我敢打赌,如果你所做的只是显示一个全屏对话框,你不希望你的应用程序认为“它进入了后台”,但你的里程可能会有所不同。
就这些。if块中的代码只会执行一次,即使你去了另一个活动,新的活动(也扩展了BaseActivity)将报告wasInBackground为false,所以它不会执行代码,直到onmemorytrim被调用并且标志被再次设置为true。
更新/注释(2015年4月):在你对这段代码进行复制和粘贴之前,请注意,我已经发现了一些实例,其中它可能不是100%可靠的,必须与其他方法结合才能获得最佳结果。 值得注意的是,有两个已知的实例,其中onTrimMemory回调不保证被执行:
如果你的手机锁定屏幕,而你的应用程序是可见的(比如你的设备锁定nn分钟后),这个回调不会被调用(或不总是),因为锁定屏幕只是在顶部,但你的应用程序仍然“运行”,尽管覆盖。 如果您的设备内存相对较低(并且处于内存压力之下),操作系统似乎会忽略这个调用,直接进入更关键的级别。
现在,取决于你知道你的应用什么时候进入后台有多重要,你可能需要也可能不需要扩展这个解决方案,同时跟踪活动生命周期等等。
只要记住以上几点,并拥有一个优秀的QA团队;)
更新结束
可能晚了,但在冰淇淋三明治(API 14)及以上有一个可靠的方法。
当你的应用没有更多可见的UI时,一个回调被触发。可以在自定义类中实现的回调称为ComponentCallbacks2(是的,带有一个2)。此回调仅在API级别14(冰淇淋三明治)及以上可用。
你基本上得到一个方法的调用:
public abstract void onTrimMemory (int level)
级别是20或更多
public static final int TRIM_MEMORY_UI_HIDDEN
我一直在测试这个,它总是有效的,因为20级只是一个“建议”,你可能想要释放一些资源,因为你的应用程序不再可见。
引用官方文件:
onTrimMemory(int)的级别:进程已经显示了一个用户界面,并且不再这样做。这时候应该释放大量的UI分配,以便更好地管理内存。
当然,您应该实现它来实现它所说的(清除在特定时间内未使用的内存,清除一些未使用的集合,等等。可能性是无限的(查看官方文档了解其他可能的更关键的级别)。
但是,有趣的是,操作系统告诉你:嘿,你的应用进入了后台!
这正是你一开始就想知道的。
你怎么确定你什么时候回来?
好吧,这很容易,我相信你有一个“BaseActivity”,所以你可以使用你的onResume()标记事实,你回来了。因为只有当您实际接收到对上述onTrimMemory方法的调用时,您才会说您没有返回。
它的工作原理。你不会得到假阳性。如果一项活动恢复了,那么你就百分百地回来了。如果用户再次返回,您将得到另一个onTrimMemory()调用。
您需要订阅您的Activities(或者更好的是,定制类)。
保证你总是收到这个的最简单的方法是创建一个简单的类,像这样:
public class MemoryBoss implements ComponentCallbacks2 {
@Override
public void onConfigurationChanged(final Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We're in the Background
}
// you might as well implement some memory cleanup here and be a nice Android dev.
}
}
为了使用这个,在你的应用程序实现中(你有一个,对吧?),做如下的事情:
MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMemoryBoss = new MemoryBoss();
registerComponentCallbacks(mMemoryBoss);
}
}
如果你创建了一个接口,你可以在If中添加一个else,并实现在API 14以下的任何东西中使用的ComponentCallbacks(没有2)。该回调只有onLowMemory()方法,当你进入后台时不会被调用,但你应该使用它来修剪内存。
现在启动你的App,按home键。你的onTrimMemory(最终int级别)方法应该被调用(提示:添加日志记录)。
最后一步是从回调取消注册。也许最好的地方是你的应用程序的onTerminate()方法,但是,该方法不会在真正的设备上被调用:
/** *此方法用于模拟过程环境。它将 *永远不要在生产Android设备上调用,那里有进程 *通过简单地杀死它们来移除;没有用户代码(包括此回调) 执行*。 * /
因此,除非你真的遇到了不想再注册的情况,否则你可以安全地忽略它,因为你的进程在操作系统级已经死亡了。
如果你决定在某些时候取消注册(例如,如果你为你的应用程序提供了一个关闭机制来清理和死亡),你可以这样做:
unregisterComponentCallbacks(mMemoryBoss);
就是这样。
我在谷歌Analytics EasyTracker中使用了这个,它起作用了。可以将其扩展为使用简单整数来完成您想要的任务。
public class MainApplication extends Application {
int isAppBackgrounded = 0;
@Override
public void onCreate() {
super.onCreate();
appBackgroundedDetector();
}
private void appBackgroundedDetector() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStart(activity);
}
@Override
public void onActivityResumed(Activity activity) {
isAppBackgrounded++;
if (isAppBackgrounded > 0) {
// Do something here
}
}
@Override
public void onActivityPaused(Activity activity) {
isAppBackgrounded--;
}
@Override
public void onActivityStopped(Activity activity) {
EasyTracker.getInstance(MainApplication.this).activityStop(activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
我喜欢ProcessLifecycleOwner方法,但实际上可以跳过所有这些,因为在Activity的onCreate()方法中,可以很容易地确定它是第一次运行还是后续运行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
/* savedInstanceState is always null on first run */
} else {
/* it's a subsequent run */
}
}
编辑:新的体系结构组件带来了一些有希望的东西:ProcessLifecycleOwner,参见@vokilam的回答
实际解决方案根据谷歌I/O谈话:
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(AppLifecycleTracker())
}
}
class AppLifecycleTracker : Application.ActivityLifecycleCallbacks {
private var numStarted = 0
override fun onActivityStarted(activity: Activity?) {
if (numStarted == 0) {
// app went to foreground
}
numStarted++
}
override fun onActivityStopped(activity: Activity?) {
numStarted--
if (numStarted == 0) {
// app went to background
}
}
}
是的。我知道很难相信这个简单的解决方案有效,因为我们有这么多奇怪的解决方案。
但还是有希望的。
2021年11月更新
实际设置如下
class App : Application() {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
}
}
class AppLifecycleListener : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { // app moved to foreground
}
override fun onStop(owner: LifecycleOwner) { // app moved to background
}
}
依赖关系
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version"
原来的答案
ProcessLifecycleOwner似乎也是一个很有前途的解决方案。
ProcessLifecycleOwner将分派ON_START, ON_RESUME事件,当第一个活动通过这些事件时。ON_PAUSE, ON_STOP,事件将在最后一个活动通过它们后延迟分派。这个延迟足够长,可以保证ProcessLifecycleOwner在由于配置更改而销毁和重新创建活动时不会发送任何事件。
实现可以简单到
class AppLifecycleListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() { // app moved to foreground
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() { // app moved to background
}
}
// register observer
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleListener())
根据源代码,当前延迟值为700ms。
使用此特性还需要依赖项:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
推荐文章
- 如何隐藏动作栏之前的活动被创建,然后再显示它?
- 是否有一种方法以编程方式滚动滚动视图到特定的编辑文本?
- 在Android中将字符串转换为Uri
- 如何在NestedScrollView内使用RecyclerView ?
- 移动到另一个EditText时,软键盘下一步点击Android
- Android应用中的GridView VS GridLayout
- Activity和FragmentActivity的区别
- 右对齐文本在android TextView
- 权限拒绝:start前台需要android.permission.FOREGROUND_SERVICE
- 如何更改android操作栏的标题和图标
- Android Split字符串
- 让一个链接在安卓浏览器启动我的应用程序?
- 如何在Android工作室的外部库中添加一个jar ?
- GridLayout(不是GridView)如何均匀地拉伸所有子元素
- 如何让一个片段删除自己,即它的等效完成()?