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

Posted

技术标签:

【中文标题】如何始终在后台运行服务?【英文标题】:How to always run a service in the background? 【发布时间】:2011-02-03 17:06:33 【问题描述】:

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

我需要什么:

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

我尝试过的: - 运行一个正常的服务,直到 android 终止该服务 - 使用 AlarmManager 制作 5 分钟。对服务的间隔调用。但我无法完成这项工作。

【问题讨论】:

【参考方案1】:

始终在其中运行的服务 背景

正如您所发现的,这是not possible 在任何真正意义上的术语。也是bad design。

每 5 分钟。该服务检查 设备的当前位置和 调用网络服务

使用AlarmManager

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

这里有一个sample project 展示了如何使用一个,以及WakefulIntentService 的使用,这样您在尝试做整个 Web 服务的事情时保持清醒。

如果您仍然遇到问题,请提出一个新问题,了解您在使用 AlarmManager 时遇到的让您感到悲伤的具体事情。

【讨论】:

我使用了这个示例项目,我输入了日志并等待 10 分钟,没有任何工作?我必须调用任何服务来启动它? @SamirMangroliya:如果您查看答案上的日期,您会发现它已经超过 3 岁。此示例是为 Android 2.x 编写的,您需要重新启动设备/模拟器才能启动警报。现在,对于 Android 3.1+,即使这还不够——您需要在启动接收器生效之前运行一个活动。我建议您切换到github.com/commonsguy/cw-omnibus/tree/master/AlarmManager/…,这是相同的基本项目,但更新的更多。运行它,并确保活动运行(显示Toast),警报将开始。 你好,如果我打电话给 PollReceiver.scheduleAlarms(this);在主要活动中,当我关闭应用程序然后重新启动(假设在一小时内我打开应用程序超过 15 次)然后它每次创建警报 5 分钟? @SamirMangroliya:为等效的PendingIntent 安排警报将取消该PendingIntent 的任何现有警报。除此之外,请记住,这是书中的一个例子,它故意不尝试解决所有场景。 我发现了以下帖子,说明这是可能的:blogmobile.itude.com/2012/02/20/…。这种方法有效吗?【参考方案2】:

我的一个应用程序做了非常相似的事情。要在给定时间后唤醒服务,我推荐postDelayed()

有一个处理程序字段:

private final Handler handler = new Handler();

还有一个复习Runnable

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

您可以在 runnable 中触发通知。

在服务构建时,每次执行后都像这样启动它:

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

【讨论】:

我通常喜欢你的答案,但这个……不是那么多。 :-(例如,您建议服务注册一个接收器,以便在被杀死后复活。接收器将与服务一起被杀死;因此,这应该没有效果。此外,永久服务的整个概念很糟糕在 Android 上,这也是用户使用任务杀手或“设置”应用程序中的“运行服务”屏幕进行反击的原因。在极少数情况下需要真正持久的服务——在绝大多数情况下,AlarmManager 就足够了。 感谢 CWare,我可能应该更清楚地说明,resurrector 是为了防止逻辑故障和许多可以停止唤醒服务的事件链的事情,因为系统关闭服务通常可以是别的东西。我将研究警报管理器的方法,我隐约记得前段时间尝试过,但无法使其正常工作。【参考方案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>

【讨论】:

【参考方案4】:

通过这三个步骤,您可以每 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。构建自己的静态广播接收器:

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.将&lt;receiver&gt; 添加到AndroidManifest.xml

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

【讨论】:

静态接收器不能像这样在 Manifest 中初始化,你应该使用 【参考方案5】:

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

【讨论】:

以上是关于如何始终在后台运行服务?的主要内容,如果未能解决你的问题,请参考以下文章

我可以通过始终在后台运行的服务跟踪 gps 纬度和经度吗?

我想让一个app始终在后台运行,不被杀掉进程,怎么设置?

如何使用cron检查程序是否在后台运行,并在需要时启动它?

如何让android的service一直在后台运行

什么是后台运行

让应用程序始终在 Android 上后台运行