Android 如何在Oreo中长期运行后台服务?

Android 如何在Oreo中长期运行后台服务?,android,android-8.0-oreo,Android,Android 8.0 Oreo,Android Oreo对运行后台服务施加了许多限制。服务现在不像以前那样在Oreo中正常运行 但如果我必须在后台长时间运行一个服务呢 我正在开发一个应用程序,当用户摇晃手机时启动手电筒。要实现这一点,我必须将传感器侦听器代码放入服务中 如何防止android系统不终止服务 PS:我不想用通知启动前台服务 如何防止android系统不终止服务 总结评论:使用前台服务,在专用频道上发送通知,频道设置为IMPORTANCE\u DEFAULT。建议用户可以禁用该频道(例如,长按通知阴影中的通知)。使

Android Oreo对运行后台服务施加了许多限制。服务现在不像以前那样在Oreo中正常运行

但如果我必须在后台长时间运行一个服务呢

我正在开发一个应用程序,当用户摇晃手机时启动手电筒。要实现这一点,我必须将传感器侦听器代码放入服务中

如何防止android系统不终止服务

PS:我不想用通知启动前台服务

如何防止android系统不终止服务

总结评论:使用前台服务,在专用频道上发送通知,频道设置为
IMPORTANCE\u DEFAULT
。建议用户可以禁用该频道(例如,长按通知阴影中的
通知
)。使用专用通道意味着您仍然可以在其他通道上发出通知。您的通知也应该有用:

  • 如果用户希望关闭服务一段时间,请执行“停止”操作以停止您的服务

  • 点击通知本身将导致您配置应用程序行为的活动

我不想用通知启动前台服务

那么你很可能无法编写应用程序

我不能排除Android 8.x中可能存在一些漏洞,这些漏洞可能被利用来提供无限期服务。事实上,我认为很可能会有一些东西漂浮在那里。然而,这显然违背了谷歌的意图,即:

    利用这项技术,如果没有谷歌认为是正当的理由,可能会让你的应用程序被禁止从游戏商店,如果这是你打算如何分配它

  • 该漏洞可能会在未来的Android版本中修复,而与谷歌展开军备竞赛往往是一个失败的提议


有足够多的“空中手势”应用程序(即,根据震动来做事情),理想情况下,谷歌会为其添加一些专用的低功耗API。例如,他们可以将功能添加到
JobScheduler
,以允许您注册震动事件,并在这种情况下调用
JobService
,就像他们允许您注册
ContentProvider
中的更改一样。我不知道他们是否会提供这样的API,但如果你愿意,你可以为它提交一个功能请求。

你将无法在Oreo中长期运行后台服务,因为有行为变化,现在Oreo为了优化系统内存、电池等,它会杀死后台服务,要解决您的问题,您应该使用前台服务

查看后台执行限制


希望这有助于理解问题。

在Oreo或更高版本上,如果没有显示通知,则可以提供不可阻挡的服务(是的,我们可以)

让我来解释一下如何让一项服务只能由用户而不是由系统停止(或者更好地说,停止它们的唯一方法是卸载你的应用程序)

请注意,在我看来,即使我让一项服务势不可挡也不是一项好的技术,而我恰恰相反,因为不同的原因(如电池消耗、清晰的用户体验等)

首先,您需要在清单文件中声明服务

单独的名称“:serviceNonStoppable”使服务在单独的进程中运行,而不是在主应用程序进程中运行。对于需要单独运行的后台进程更好。 要使我们自己的服务流程对其他流程或应用程序不可见,您需要设置exported=false参数。 描述“@string/service_description”将向用户说明您的服务是做什么的,以及为什么用户不应该停止它们(您在strings.xml中创建此描述)


其次,我们将创建一个支持类,其中包含可在不同点使用的静态方法

import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;

import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class Utils {

   // This is a support class witch have static methods to use everywhere

   final static int NOTIFICATION_INT_CHANNEL_ID = 110211; // my daughter birthday but you can change that with your number
   final static String NOTIFICATION_STRING_CHANNEL_ID = "put.a.random.id.here"; //if you write "the.pen.is.on.the.table" is the same

   final static int TEST_THIS = 111; // or you can put here something else
   final static String BROADCAST_MSG_ID = "BROADCAST_MSG_ID"; // or you can put here something else
   final static String APP_MESSAGE = "your.package.name.action.APP_MESSAGE"; // or you can put here pippo.pluto.and.papperino

   static void returnUpMyService(final Context context) {
      try {
         //to avoid crashes when this method is called by service (from itself) make sure the service is not alredy running (maybe is in cache)
         if (killServiceIfRun(context)) {
            startServiceOn(context);
         }

      } finally {
         System.out.println(" I'm trying to start service ");
      }
   }


   private static boolean killServiceIfRun(final Context context) {

      boolean isRunning = isMyServiceRunning(context);
      if (!isRunning) { return true; }

      try {
         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

         // maybe killing process is not terminated by system in this fase
         //I force to kill them by my one
         if (manager != null) {
            manager.killBackgroundProcesses(getServicename(context));
            return true;
         }
         return true;
      } catch (Exception e) {
         System.out.println("killServiceIfRun error: " + e.toString());
      }

      return false;

   }


   private static boolean isServiceInCache(final Context context) {

      ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
      if (manager != null && manager.getRunningAppProcesses() != null) {

         if (manager.getRunningAppProcesses().size() > 0) {
            for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {

               if (process.processName != null) {
                  if (process.processName.equalsIgnoreCase(getServicename(context))) {
                     // Here we know that the service is running but sleep brrrrrrrr
                     if (process.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {
                        return true;
                     }
                  }
               }
            }
         }
      }

      return false;
   }


   static void StartMyService(Context context) {

      // If the sevice is running doesn't need to restart
      if (isMyServiceRunning(context) && !isServiceInCache(context)) {
         return;
      }

      // If service is running but is in chache is the same like killed, so we need to kill them
      if (isServiceInCache(context)) {
         // this method at first kill and after that start the service
         returnUpMyService(context);

      } else {
         //Otherwise we start own service
         startServiceOn(context);
      }

   }


   private static void startServiceOn(final Context context) {
      // After we had been sure about that service doesn't exist
      // we make a schedule to restart them
      new ScheduledThreadPoolExecutor(1).schedule(() -> {

         //Create an instance of serviceOn
         serviceOn service = new serviceOn();

         //prepare the launch intent
         Intent launchIntent = new Intent(context, service.getClass());

         // Now we start in background our service
         context.startForegroundService(launchIntent);

         // I put 50 ms to allow the system to take more time to execute GC on my killed service before
      }, 50, TimeUnit.MILLISECONDS);
   }

   private static boolean isMyServiceRunning(final Context context) {

      ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
      if (manager != null && manager.getRunningAppProcesses() != null) {

         if (manager.getRunningAppProcesses().size() > 0) {
            for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
               if (process != null && process.processName != null && process.processName.equalsIgnoreCase(getServicename(context))) {
                  return true;
               }
            }
         }
      }

      return false;

   }


   static void SendMsgToService(Context context, int id, Map<String, Object> params) {

      try {
         Intent mServiceIntent = new Intent(APP_MESSAGE);

         if (params != null) {

            for (Map.Entry<String, Object> entry : params.entrySet()) {
               //System.out.println(entry.getKey() + "/" + entry.getValue());

               if (entry.getValue() instanceof String) {
                  mServiceIntent.putExtra(entry.getKey(), (String) entry.getValue());
               } else if (entry.getValue() instanceof Integer) {
                  mServiceIntent.putExtra(entry.getKey(), (Integer) entry.getValue());
               } else if (entry.getValue() instanceof Float) {
                  mServiceIntent.putExtra(entry.getKey(), (Float) entry.getValue());
               } else if (entry.getValue() instanceof Double) {
                  mServiceIntent.putExtra(entry.getKey(), (Double) entry.getValue());
               } else if (entry.getValue() instanceof byte[]) {
                  mServiceIntent.putExtra(entry.getKey(), (byte[]) entry.getValue());

               }
            }
         }

         mServiceIntent.putExtra(BROADCAST_MSG_ID, id);
         context.sendBroadcast(mServiceIntent);

      } catch (RuntimeException e) {
         System.out.println(e.toString());
      }

   }


   private static String getServicename(final Context context) {
      //                                 the name declared in manifest you remember?
      return context.getPackageName() + ":serviceNonStoppable";
   }


}
导入android.app.ActivityManager;
导入android.content.Context;
导入android.content.Intent;
导入java.util.Map;
导入java.util.concurrent.ScheduledThreadPoolExecutor;
导入java.util.concurrent.TimeUnit;
类Utils{
//这是一个支持类,在任何地方都可以使用静态方法
最终静态整数通知\u int\u CHANNEL\u ID=110211;//我女儿的生日,但您可以用您的号码更改
最后的静态字符串通知\u String\u CHANNEL\u ID=“put.a.random.ID.here”;//如果您写入“the.pen.is.on.the.table”则相同
final static int TEST_THIS=111;//或者您可以在这里放置其他内容
最后一个静态字符串BROADCAST\u MSG\u ID=“BROADCAST\u MSG\u ID”//或者您可以在这里放置其他内容
最后一个静态字符串APP_MESSAGE=“your.package.name.action.APP_MESSAGE”//或者您可以将pippo.pluto.and.papperino放在这里
静态void returnUpMyService(最终上下文){
试一试{
//为避免服务(从自身)调用此方法时发生崩溃,请确保服务不总是在运行(可能在缓存中)
if(killServiceIfRun(上下文)){
StartService(上下文);
}
}最后{
System.out.println(“我正在尝试启动服务”);
}
}
私有静态布尔killServiceIfRun(最终上下文){
布尔isRunning=isMyServiceRunning(上下文);
如果(!isRunning){返回true;}
试一试{
ActivityManager=(ActivityManager)context.getSystemService(context.ACTIVITY_服务);
//在这种情况下,系统可能不会终止杀戮过程
//我强迫他们用我的一个
if(manager!=null){
killbackgroundprocesss(getServicename(context));
返回true;
}
返回true;
}捕获(例外e){
System.out.println(“killService
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;

import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class Utils {

   // This is a support class witch have static methods to use everywhere

   final static int NOTIFICATION_INT_CHANNEL_ID = 110211; // my daughter birthday but you can change that with your number
   final static String NOTIFICATION_STRING_CHANNEL_ID = "put.a.random.id.here"; //if you write "the.pen.is.on.the.table" is the same

   final static int TEST_THIS = 111; // or you can put here something else
   final static String BROADCAST_MSG_ID = "BROADCAST_MSG_ID"; // or you can put here something else
   final static String APP_MESSAGE = "your.package.name.action.APP_MESSAGE"; // or you can put here pippo.pluto.and.papperino

   static void returnUpMyService(final Context context) {
      try {
         //to avoid crashes when this method is called by service (from itself) make sure the service is not alredy running (maybe is in cache)
         if (killServiceIfRun(context)) {
            startServiceOn(context);
         }

      } finally {
         System.out.println(" I'm trying to start service ");
      }
   }


   private static boolean killServiceIfRun(final Context context) {

      boolean isRunning = isMyServiceRunning(context);
      if (!isRunning) { return true; }

      try {
         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

         // maybe killing process is not terminated by system in this fase
         //I force to kill them by my one
         if (manager != null) {
            manager.killBackgroundProcesses(getServicename(context));
            return true;
         }
         return true;
      } catch (Exception e) {
         System.out.println("killServiceIfRun error: " + e.toString());
      }

      return false;

   }


   private static boolean isServiceInCache(final Context context) {

      ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
      if (manager != null && manager.getRunningAppProcesses() != null) {

         if (manager.getRunningAppProcesses().size() > 0) {
            for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {

               if (process.processName != null) {
                  if (process.processName.equalsIgnoreCase(getServicename(context))) {
                     // Here we know that the service is running but sleep brrrrrrrr
                     if (process.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {
                        return true;
                     }
                  }
               }
            }
         }
      }

      return false;
   }


   static void StartMyService(Context context) {

      // If the sevice is running doesn't need to restart
      if (isMyServiceRunning(context) && !isServiceInCache(context)) {
         return;
      }

      // If service is running but is in chache is the same like killed, so we need to kill them
      if (isServiceInCache(context)) {
         // this method at first kill and after that start the service
         returnUpMyService(context);

      } else {
         //Otherwise we start own service
         startServiceOn(context);
      }

   }


   private static void startServiceOn(final Context context) {
      // After we had been sure about that service doesn't exist
      // we make a schedule to restart them
      new ScheduledThreadPoolExecutor(1).schedule(() -> {

         //Create an instance of serviceOn
         serviceOn service = new serviceOn();

         //prepare the launch intent
         Intent launchIntent = new Intent(context, service.getClass());

         // Now we start in background our service
         context.startForegroundService(launchIntent);

         // I put 50 ms to allow the system to take more time to execute GC on my killed service before
      }, 50, TimeUnit.MILLISECONDS);
   }

   private static boolean isMyServiceRunning(final Context context) {

      ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
      if (manager != null && manager.getRunningAppProcesses() != null) {

         if (manager.getRunningAppProcesses().size() > 0) {
            for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
               if (process != null && process.processName != null && process.processName.equalsIgnoreCase(getServicename(context))) {
                  return true;
               }
            }
         }
      }

      return false;

   }


   static void SendMsgToService(Context context, int id, Map<String, Object> params) {

      try {
         Intent mServiceIntent = new Intent(APP_MESSAGE);

         if (params != null) {

            for (Map.Entry<String, Object> entry : params.entrySet()) {
               //System.out.println(entry.getKey() + "/" + entry.getValue());

               if (entry.getValue() instanceof String) {
                  mServiceIntent.putExtra(entry.getKey(), (String) entry.getValue());
               } else if (entry.getValue() instanceof Integer) {
                  mServiceIntent.putExtra(entry.getKey(), (Integer) entry.getValue());
               } else if (entry.getValue() instanceof Float) {
                  mServiceIntent.putExtra(entry.getKey(), (Float) entry.getValue());
               } else if (entry.getValue() instanceof Double) {
                  mServiceIntent.putExtra(entry.getKey(), (Double) entry.getValue());
               } else if (entry.getValue() instanceof byte[]) {
                  mServiceIntent.putExtra(entry.getKey(), (byte[]) entry.getValue());

               }
            }
         }

         mServiceIntent.putExtra(BROADCAST_MSG_ID, id);
         context.sendBroadcast(mServiceIntent);

      } catch (RuntimeException e) {
         System.out.println(e.toString());
      }

   }


   private static String getServicename(final Context context) {
      //                                 the name declared in manifest you remember?
      return context.getPackageName() + ":serviceNonStoppable";
   }


}
import android.app.IntentService;
import android.app.Notification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class serviceOn extends IntentService {

   // Needed to keep up notifying without show the icon
   private ScheduledExecutorService notifyer = null;


   // don't remove this. cause error becouse we declare this service in manifest
   public serviceOn() {
      super("put.a.constant.name.here");
   }


   // We need this class to capture messages from main activity
   private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {

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

         if (intent != null) {
            if (intent.getAction() != null) {

               if (intent.getAction().equals(Utils.APP_MESSAGE)) {

                  int msgID = intent.getIntExtra(Utils.BROADCAST_MSG_ID, -1);

                  switch (msgID) {

                     case Utils.TEST_THIS:

                        String message = intent.getStringExtra("message");
                        if (!TextUtils.isEmpty(message)) {
                           System.out.println(message);
                        }
                        //Do your task here
                        //Do your task here
                        //Do your task here
                        //Do your task here
                        break;

                  }

               }
            }
         }
      }

   };


   @Override
   protected void onHandleIntent(@Nullable Intent intent) { }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
      return START_STICKY;
   }


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


      try {
         // First of all we need to register our receiver
         List<String> actions = Arrays.asList(
         Utils.APP_MESSAGE, // this is the string which identify our mesages
         Intent.ACTION_SCREEN_ON, // this event is raised on sreen ON by system
         Intent.ACTION_SCREEN_OFF, // this event is raised on screen OFF by system
         Intent.ACTION_TIME_TICK);// this event is raised every minute by system (helpful for periodic tasks)

         for (String curIntFilter : actions) {
            IntentFilter filter = new IntentFilter(curIntFilter);
            registerReceiver(broadcastReceiver, filter);
         }
      } catch (RuntimeException e) {
         e.printStackTrace();
      }


      final Notification notificationDefault = new NotificationCompat.Builder(getApplicationContext(), Utils.NOTIFICATION_STRING_CHANNEL_ID)
                                               .setOngoing(true) //Ongoing notifications do not have an 'X' close button, and are not affected  by the "Clear all" button
                                               .setCategory(Notification.CATEGORY_SERVICE) // indicate this service is running in background
                                               .setSmallIcon(R.drawable.ic_radio) // put here a drawable from your drawables library
                                               .setContentTitle("My Service")  // Put here a title for the notification view on the top

                                               // A smaller explanation witch system show to user this service is running
                                               // in background (if existing other services from other apps in background)
                                               .setContentText("My Service is unstoppable and need to run in background ")
                                               .build();


      // This is an efficient workaround to lie the system if we don't wont to show notification icon on top of the phone but a little aggressive 
      notifyer = Executors.newSingleThreadScheduledExecutor();
      notifyer.scheduleAtFixedRate(() -> {

         try {
            // Here start the notification witch system need to permit this service to run and take this on.
            // And we repeat that task every 15 seconds 
            startForeground(Utils.NOTIFICATION_INT_CHANNEL_ID, notificationDefault);

            //immediately after the system know about our service and permit this to run
            //at this point we remove that notification (note that is never shown before)
            stopForeground(true);

            //better not invoke Exception classes on error, make all a little heavy
         } finally {
            // Log here to tell you your code is called
            System.out.println(" Service is running");
         }

         // So, the first call is after 1000 millisec, and successively is called every 15 seconds for infinite
      }, 1000, 15000, TimeUnit.MILLISECONDS);

   }


   @Override
   public void onDestroy() {

      // unregister the receiver
      unregisterReceiver(broadcastReceiver);

      // stop the notifyer
      if (notifyer != null) {
         notifyer.shutdownNow();
         notifyer = null;
         System.out.println(" notifyer.shutdownNow() ");
      }


      final Context context = getBaseContext();

      try {

         new Thread() {

            @Override
            public void run() {

               // The magic but dirty part
               // When the system detect inactivity by our service decides to put them in cache or kill it
               // Yes system you can kill me but I came up stronger than before
               Utils.returnUpMyService(context);
            }
         }.start();

      } finally {
         System.out.println("You stop me LOL ");
      }

   }


}
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;

import java.util.HashMap;

class MyActivity extends Activity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      // Sstart the first time
      Utils.StartMyService(this);

      // Test after 3 seconds
      new Handler().postDelayed(() -> {
         Utils.SendMsgToService(X_App.getContext(), Utils.TEST_THIS, new HashMap<String, Object>() {{
            put("message", "Hello from main activity");
         }});
      }, 3000);



   }
}