如何让一个服务始终在后台运行?

79

我正在创建一个类似于内置短信应用的应用。

我需要:

  • 一个始终在后台运行的服务
  • 该服务每5分钟检查设备的当前位置并调用web服务
  • 如果满足某些条件,则该服务应生成通知(就像短信应用程序一样)
  • 当点击通知时,用户会进入该应用程序(就像短信应用程序一样)
  • 安装该应用程序后,服务应该启动
  • 当设备重新启动时,服务应该启动

我尝试过:

- 运行定期服务,在Android杀死服务之前可以正常工作

- 使用AlarmManager来实现每5分钟调用服务。但是我无法使其正常工作。

5个回答

36

一个始终在后台运行的服务

这在实际意义上是不可能的,正如您已经发现的那样。这也是糟糕的设计

每5分钟,服务检查设备的当前位置并调用Web服务

使用AlarmManager

使用AlarmManager使5分钟间隔调用服务。但我无法使其工作。

这里有一个示例项目,展示了如何使用它,以及使用WakefulIntentService来保持清醒,同时尝试完成整个Web服务的事情。

如果您在使用AlarmManager时遇到困难,请提出新问题,讨论您所遇到的具体问题。


我使用了这个示例项目,我放置了日志并等待了10分钟,但什么都没有工作?我需要调用任何服务来启动它吗? - Samir Mangroliya
2
@SamirMangroliya:如果你看一下答案上的日期,你会发现它已经超过3年了。这个例子是为Android 2.x编写的,你需要重新启动设备/模拟器才能启动闹钟。现在,对于Android 3.1+,即使如此也是不够的--你需要在on-boot接收器生效之前运行一个活动。我建议你切换到https://github.com/commonsguy/cw-omnibus/tree/master/AlarmManager/Wakeful,这是同样基本的项目,但更加更新。运行它,并确保活动运行(显示一个“Toast”),闹钟将开始。 - CommonsWare
1
@SamirMangroliya:为等效的“PendingIntent”安排闹钟将取消该“PendingIntent”的任何现有闹钟。除此之外,请记住,这是一本书中的示例,并且故意不尝试解决所有情况。 - CommonsWare
@MarkVincze:我不知道“this”是什么。你引用的博客文章与这个SO问题或我的回答没有太大关系。 - CommonsWare
这是关于“始终在后台运行的服务”的事情,你曾经说过:“这在任何实际意义上都不可能”。在博客文章中,他们配置一个应用程序,以便在手机开机时自动在后台启动。我是否有什么误解? - Mark Vincze
显示剩余9条评论

26

我的其中一个应用程序做了非常相似的事情。为了在给定时间后唤醒服务,我建议使用postDelayed()

拥有一个处理程序字段:

private final Handler handler = new Handler();

并且需要复习一下 Runnable

private final Runnable refresher = new Runnable() {
  public void run() {
    // some action
  }
};

你可以在可运行对象中触发通知。

在服务构建时,以及每次执行开始后,像这样启动它:

handler.postDelayed(refresher, /* some delay in ms*/);

onDestroy() 中删除该帖子

handler.removeCallbacks(refresher);

要在启动时启动服务,您需要一个自动启动器。这将放在您的清单文件中。

<receiver android:name="com.example.ServiceAutoStarter">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
  </receiver>

而且ServiceAutoStarter看起来是这样的:

public class ServiceAutoStarter extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    context.startService(new Intent(context, UpdateService.class));
  }
}

阻止操作系统关闭服务比较棘手。此外,您的应用程序可能会出现RuntimeException并崩溃,或者您的逻辑可能会停滞。

在我的情况下,似乎通过使用BroadcastReceiver始终在屏幕上刷新服务有所帮助。因此,如果更新链条停滞不前,它将随着用户使用手机而被唤醒。

在服务中:

private BroadcastReceiver screenOnReceiver; 
在你的服务中,onCreate() 方法中。
screenOnReceiver = new BroadcastReceiver() {

  @Override
  public void onReceive(Context context, Intent intent) {
    // Some action
  }
};

registerReceiver(screenOnReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));

然后在 onDestroy() 中注销您的服务,使用以下代码:

unregisterReceiver(screenOnReceiver);

2
我通常很喜欢你的回答,但这个...不太行。 :-( 例如,你建议服务注册一个接收器来在被杀死后复活自己。接收器将随着服务一起被杀死;因此,这应该没有任何影响。此外,在Android上,永久服务的整个概念都很糟糕,这也是用户使用任务管理器或“设置”应用程序中的“正在运行的服务”屏幕进行反击的原因。只有极少数情况需要真正的永久服务--绝大多数情况下,“AlarmManager”就足够了。 - CommonsWare
4
谢谢 CWare,我应该更清楚地表明,复活者的作用是为了防止逻辑故障和可能导致服务唤醒链条中断的各种因素。因为通常被归因于系统关闭服务的原因实际上可能是其他问题。我会研究闹钟管理器方法,我模糊地记得以前曾尝试过这个方法,但无法让它起作用。 - Jim Blackler

4

您可以通过简单的实现方式来完成这个操作:

public class LocationTrace extends Service implements LocationListener{

    // The minimum distance to change Updates in meters
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters
    private static final int TWO_MINUTES = 100 * 10;

    // The minimum time between updates in milliseconds
    private static final long MIN_TIME_BW_UPDATES = 1000 * 10; // 30 seconds

    private Context context;

    double latitude;
    double longitude;

    Location location = null;
    boolean isGPSEnabled = false;
    boolean isNetworkEnabled = false;
    protected LocationManager locationManager;


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        this.context = this;
        get_current_location();
//      Toast.makeText(context, "Lat"+latitude+"long"+longitude,Toast.LENGTH_SHORT).show();
        return START_STICKY;
    }


    @Override
    public void onLocationChanged(Location location) {
        if((location != null) && (location.getLatitude() != 0) && (location.getLongitude() != 0)){

            latitude = location.getLatitude();
            longitude = location.getLongitude();

            if (!Utils.getuserid(context).equalsIgnoreCase("")) {
                Double[] arr = { location.getLatitude(), location.getLongitude() };

               // DO ASYNCTASK
            }
        }

    }


    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }

    /*
    *  Get Current Location
    */
    public Location get_current_location(){

        locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

        isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

        if(!isGPSEnabled && !isNetworkEnabled){



        }else{
            if (isGPSEnabled) {

                if (location == null) {
                    locationManager.requestLocationUpdates(
                            LocationManager.GPS_PROVIDER,
                            MIN_TIME_BW_UPDATES,
                            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

                    if (locationManager != null) {
                        location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                        if (location != null) {
                            latitude = location.getLatitude();
                            longitude = location.getLongitude();
        //                  Toast.makeText(context, "Latgps"+latitude+"long"+longitude,Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }
            if (isNetworkEnabled) {

                locationManager.requestLocationUpdates(
                        LocationManager.NETWORK_PROVIDER,
                        MIN_TIME_BW_UPDATES,
                        MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

                if (locationManager != null) {

                    if (location != null) {
                        latitude = location.getLatitude();
                        longitude = location.getLongitude();
        //              Toast.makeText(context, "Latgps1"+latitude+"long"+longitude,Toast.LENGTH_SHORT).show();
                    }
                }
            }
        }

        return location;
    }


    public double getLatitude() {
        if(location != null){
            latitude = location.getLatitude();
        }
        return latitude;
    }

    public double getLongitude() {
         if(location != null){
             longitude = location.getLongitude();
         }

        return longitude;
    }


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

    @Override
    public void onDestroy() {
        if(locationManager != null){
            locationManager.removeUpdates(this);
        }
        super.onDestroy();
    }

}

你可以通过以下方式启动服务:
/*--Start Service--*/
startService(new Intent(Splash.this, LocationTrace.class));

在清单文件中:

 <service android:name=".LocationTrace">
            <intent-filter android:priority="1000">
                <action android:name="android.location.PROVIDERS_CHANGED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
  </service>

1
通过以下三个步骤,您可以唤醒大多数Android设备的每5分钟: 1. 为不同的API设置替代AlarmManager:
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(getApplicationContext(), OwnReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(getApplicationContext(), 0, i, 0);

if (Build.VERSION.SDK_INT >= 23) {
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
}
else if (Build.VERSION.SDK_INT >= 19) {
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
} else {
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
}

构建自己的静态广播接收器:
public static class OwnReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

       //do all of your jobs here

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent i = new Intent(context, OwnReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);

        if (Build.VERSION.SDK_INT >= 23) {
            am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
        }
        else if (Build.VERSION.SDK_INT >= 19) {
            am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
        } else {
            am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (1000 * 60 * 5), pi);
        }
    }
}

在AndroidManifest.xml中添加。
<receiver android:name=".OwnReceiver"  />

1
静态接收器不能像这样在清单文件中初始化,而应该使用<receiver android:name="className$OwnReceiver" />。 - Farhan Ibn Wahid

1
根据我的理解,当您希望您的服务始终运行时,意味着当应用程序被终止时它不应该停止,因为如果您的应用程序正在运行或在后台运行,您的服务将会一直运行。当您杀死应用程序而服务正在运行时,将触发onTaskRemoved函数。
@Override
        public void onTaskRemoved(Intent rootIntent) {
            Log.d(TAG, "onTaskRemoved: removed");
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis() + 10000);
((AlarmManager) getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), PendingIntent.getService(getApplicationContext(), 0, new Intent(getApplicationContext(), RegisterReceiverService.class), 0));
            super.onTaskRemoved(rootIntent);
        }

基本上,一旦您关闭应用程序,服务将在10秒后启动。 注意:如果您想使用


AlarmManager.ELAPSED_REALTIME_WAKEUP

您需要等待至少15分钟才能重新启动该服务。

请记住,您还需要使用BroadcastReceiver在重新启动时启动该服务。


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