如何在Android应用程序关闭或置于后台时执行后台任务?

25

我的Android 4+应用程序连接到一个自定义的Web服务,用于每隔几分钟同步数据。为了确保在线数据始终是最新的,我希望在应用程序关闭/切换到后台时触发同步。

在iOS上,这很容易:

  • 监听AppDelegate中的applicationDidEnterBackground:
  • 使用beginBackgroundTaskWithName:启动长时间运行的后台任务,并避免在任务仍在运行时被暂停

如何在Android上实现?

第一个问题是,没有类似于applicationDidEnterBackground:的东西。到目前为止,我找到的所有解决方案都建议使用主Activity的onPause()方法。然而,每当主Activity暂停时,包括在应用程序内启动另一个Activity时,都会调用此方法。对于ActivityLifecycleCallbacks接口的onActivityPaused()方法也是如此。

那么,如何检测应用程序(而不仅仅是Activity)是关闭还是切换到后台?

第二个问题是,我没有找到关于如何运行后台任务的任何信息。所有的解决方案都指向简单地启动一个AsyncTask,但这真的是正确的解决方案吗?

AsyncTask将启动一个新任务,但当应用程序关闭/不活动时,这个任务是否也会运行呢?我需要做什么来防止应用程序和任务在几秒钟后被暂停?如何确保任务能够完成,然后再完全暂停应用程序?


you can use services for that - Vishal Patoliya ツ
你考虑过使用服务吗? - apmartin1991
使用一个服务 https://developer.android.com/reference/android/app/Service.html - Motassem Jalal
4
谢谢您提醒我关于 "Services" 的事情,我会去查一下。为什么要给我投反对票?如果问题有问题,请告诉我,以便我改进。没有任何评论的反对票并不是很有帮助。 - Andrei Herford
4个回答

23
以下代码将帮助您在应用程序关闭或后台运行时发布内容:
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.widget.Toast;

public class SendDataService extends Service {
    private final LocalBinder mBinder = new LocalBinder();
    protected Handler handler;
    protected Toast mToast;
 
    public class LocalBinder extends Binder {
        public SendDataService getService() {
            return SendDataService .this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handler = new Handler();
        handler.post(new Runnable() {
            @Override
            public void run() { 

// write your code to post content on server
            }
        });
        return android.app.Service.START_STICKY;
    }

}

对我的代码的更多解释是:

服务在后台运行,即使您的应用程序在后台也是如此,但请记住,它始终在主线程上运行,因此我创建了一个单独的线程来执行后台操作。

服务被启动为“粘性服务”,即使由于任何原因,您的服务在后台被销毁,它也会自动重新启动。

更多详细信息可以在此处找到: https://developer.android.com/guide/components/services.html

要检查您的应用程序是在前台还是后台运行,可以使用以下代码:

  private boolean isAppOnForeground(Context context) {
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
    if (appProcesses == null) {
      return false;
    }
    final String packageName = context.getPackageName();
    for (RunningAppProcessInfo appProcess : appProcesses) {
      if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
        return true;
      }
    }
    return false;
  }
}

// Use like this:
boolean foregroud = new ForegroundCheckTask().execute(context).get();

开心编程!!!


你已经使用了handler来执行后台工作,那么在多长时间后handler会被调用以进行重复的网络调用? - Kaveesh Kanwal
1
非常感谢。 Service 确实是解决问题2的完美方案。那第一个问题呢:如何正确检测应用程序是暂停/关闭而不仅仅是活动? - Andrei Herford

9

被接受的答案是不正确的。

不幸的是,作者认为向 Handler() 提交一个新的 Runnable 任务会创建一个单独的线程 - "我创建了一个单独的线程来执行你的后台操作" :

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    handler = new Handler();
    handler.post(new Runnable() {
        @Override
        public void run() { 
           // write your code to post content on server
        }
    });
    return android.app.Service.START_STICKY;
}

onStartCommand()回调始终在主线程上调用,并且新的Handler()附加到当前线程(即“main”)。因此,Runnable任务会被发布到主线程队列的末尾。

为了解决这个问题并在真正分离的线程中执行Runnable,您可以使用AsyncTask作为最简单的解决方案:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
            // ...
        }
    });
    return android.app.Service.START_STICKY;
}

在复制粘贴任何内容之前,请先考虑一下自己的需求...


我认为new Handler()会创建一个新线程或一些默认线程,但我可以向您保证,在new Handler()中运行的代码不是在主线程上运行的。因为每当我在new Handler runnable中进行UI工作时,总是会出现异常。 - Taha Malik

4
您可以使用服务来实现您想要的功能。只要您没有调用stopService(..)或stopSelf()方法,服务将在后台持续运行,即使活动组件已被销毁。
在服务中,您可以进行异步网络调用,最好使用retrofit,然后可以使用从Web服务获取的最新数据更新本地存储,如Sqlite DB。
这是官方链接:https://developer.android.com/guide/components/services.html,您可以使用一个非绑定服务来实现您想要的功能。

非常感谢。Service 确实是解决问题2的完美方案。那么第一个问题呢:如何正确检测应用程序是否已暂停/关闭,而不仅仅是活动? - Andrei Herford
你可以重写活动生命周期方法,如onPause()和onDestroy()。如果你的活动是后退堆栈中的最后一项,并且你按下了Android默认的后退按钮,那么onDestroy()会被调用,你就退出了应用程序。 - Kaveesh Kanwal

-2

对于后台操作,您可以使用WorkManager


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接