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


76

我正在创建类似于内置SMS应用程序的应用程序。

我需要的:

  • 始终在后台运行的服务
  • 每5分钟一班。该服务检查设备的当前位置并调用Web服务
  • 如果满足某些条件,则该服务应生成一条通知(就像SMS应用程序一样)
  • 单击通知后,用户将被带到应用程序(就像SMS应用程序一样)
  • 安装应用程序后,应启动服务
  • 重新启动设备后,应启动服务

我尝试了以下操作:
-运行常规服务,直到Android
终止该服务为止,该服务才能正常运行-使用AlarmManager进行5分钟操作。间隔调用服务。但是我无法完成这项工作。

Answers:


34

始终在后台运行的服务

正如您所发现的,从任何实际意义上讲这都是不可能的。这也是不好的设计

每5分钟一班。该服务检查设备的当前位置并调用Web服务

使用AlarmManager

使用AlarmManager进行5分钟。间隔调用服务。但是我无法完成这项工作。

这是一个示例项目,显示了如何使用它以及如何使用,WakefulIntentService以便您在尝试做整个Web服务时保持清醒。

如果您仍然遇到问题,请提出一个新的问题,以解决您遇到的具体问题AlarmManager


我使用了这个示例项目,我将日志放在日志中并等待10分钟,但是没有任何工作吗?我必须打电话给任何服务来启动它吗?
Samir Mangroliya

2
@SamirMangroliya:如果您查看答案上的日期,您会发现它已经超过3年了。该示例是为Android 2.x编写的,您需要重新启动设备/仿真器才能启动警报。现在,对于Android 3.1+,甚至还不够用-您需要运行一项活动,才能使启动接收器生效。我建议您切换到github.com/commonsguy/cw-omnibus/tree/master/AlarmManager/…这是相同的基本项目,但是更新了。运行它,并确保活动运行(显示Toast),并且警报将开始。
CommonsWare 2013年

你好,如果我打电话给PollReceiver.scheduleAlarms(this); 在主要活动中,当我关闭应用程序然后重新启动时(假设我在一小时内打开应用程序超过15次),则每次创建警报5分钟?
Samir Mangroliya

1
@SamirMangroliya:安排一个等效警报PendingIntent将取消该警报PendingIntent。除此之外,请记住,这是本书中的一个示例,它并非故意解决所有情况。
CommonsWare 2013年

我发现以下帖子指出这是可能的:blogmobile.itude.com/2012/02/20/…。这种方法行得通吗?
Mark Vincze 2014年

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,我可能应该更清楚地指出,复活器是为了防止逻辑故障和许多可能使唤醒服务的事件链停止的事情,因为归因于系统关闭服务的原因通常可能是其他原因。我将研究警报管理器方法,我隐约记得一段时间前尝试过这种方法,但无法使其正常工作。
吉姆·布莱克勒

3

您可以通过一些简单的实现来做到这一点:

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

通过这三个步骤,您可以每隔5分钟唤醒大多数Android设备:

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);
}

2.构建自己的静态BroadcastReceiver:

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);
        }
    }
}

3.添加<receiver>AndroidManifest.xml

<receiver android:name=".OwnReceiver"  />

1
静态接收器无法像这样在清单中初始化,您应该使用<receiver android:name =“ className $ OwnReceiver” />
Psycho

0

据我说,当您希望您的服务运行时,始终意味着该应用程序被杀死时不应停止,因为如果您的应用程序正在运行或处于后台,则您的服务将一直在运行。在服务运行时终止应用程序时,将触发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启动服务。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.