How to run a background service in Oreo for longer period?

后端 未结 4 714
滥情空心
滥情空心 2020-12-03 15:39

Android Oreo has imposed many restrictions on running background service. Services now don\'t behave like normal in Oreo as they used to before.

But what if I have t

相关标签:
4条回答
  • 2020-12-03 16:06

    How do I prevent android system to not kill the service.

    To summarize the comments: Use a foreground service, with a notification on a dedicated channel, with the channel set to IMPORTANCE_DEFAULT. Advise the user that they can mute that channel (e.g., long-press on the Notification in the notification shade). Using a dedicated channel means that you can still raise notifications on other channels. Your notification should also be useful:

    • Have a "stop" action to stop your service, if the user wants to shut it down for a while

    • Tapping on the notification itself would lead to your activity for configuring your app's behavior

    I don't want to start a foreground service with a notification.

    Then most likely you cannot write your app.

    I cannot rule out the possibility of some bug in Android 8.x that could be exploited to have an indefinite-duration service. In fact, I'd consider it to be fairly likely that there's something floating around out there. However, this is clearly against Google intentions, meaning:

    • Exploiting that technique, without what Google would consider to be valid justification, might get your app banned from the Play Store, if that was how you planned to distribute it

    • The bug might be fixed in a future version of Android, and getting in an arms race with Google tends to be a losing proposition

    There are enough "air gesture" apps floating about (i.e., do things based on a shake) that, ideally, Google would add some dedicated low-power API for it. For example, they could add functionality to JobScheduler to allow you to register for a shake event and have your JobService be invoked in that circumstance, just as they allow you to register for changes in a ContentProvider. I have no idea whether they will ever offer such an API, but you could file a feature request for it, if you wanted.

    0 讨论(0)
  • 2020-12-03 16:11

    I discovered that we can run forground service without showing notification for android oreo and above, here is the solution first create notification with notification Channel also set channel id for notifications then start forground service with notification. now it's time to cancel notification Channel with id after 1 or 2 second that's means the notification will remove and the service will run alwayes . that's all

    0 讨论(0)
  • 2020-12-03 16:15

    Make a service unstoppable on Oreo or later without shown notification is possible (Yes We Can).

    Let me to explain how make a service stoppable ONLY BY USER and not by system (or better to say THE ONLY WAY TO STOP THEM IS UNINSTALLING YOUR APP).

    Note that even I make a service unstoppable in my point of view is not a good technique and I’m CONTRARY on that for different reasons (like battery consuming, clear user experience etc.)

    First of all you need to declare the service in manifest file.

    The separate name “:serviceNonStoppable” make the service running in a separate process and not in main app process. Is better for background processes which need to run separately. To make our own service process invisible to other processes or apps you need to set exported=false parameter. The description “@string/service_description” will say to users what your service do and why user should not stop them (you create this description in strings.xml).

      <service
          android:process=":serviceNonStoppable"
          android:name="your.package.name.serviceOn" 
          android:exported="false"
          android:description="@string/service_description" />
    

    Secondly we go to create a support class with static methods usable in different points.

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

    This is service class witch extend IntentService.

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

    And here the usage.

    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);
    
    
    
       }
    }
    
    0 讨论(0)
  • 2020-12-03 16:17

    You would not be able to run background services long running in Oreo as there are behaviour changes, now Oreo to optimise system memory, battery etc, it kills background service, to solve your issue you should use foreground service.

    Have a look at Background execution limits https://developer.android.com/about/versions/oreo/android-8.0-changes

    Hope this helps in understanding the issue....

    0 讨论(0)
提交回复
热议问题