Search code examples
androidandroid-broadcastreceiverandroid-8.0-oreoforeground-serviceforegroundnotification

Worker-Thread ends prematurely


I am targeting Oreo. As you know, oreo introduced limits on background task execution time. Workarounds are - according to google - to put the background task in the foreground. This is what I was trying to do, yet once the foreground service is running, it gets destroyed after some time. First the phone switches off it's screen, then once I activate it again, the background task continues. Sometimes onDestroy on the foreground service is called without the task being completed.

My goal is to have all tasks being set by enqueueWork to be executed without ondestroy being called and without phone sleep mode to interrupt it.

ForeGroundService

public class ForeGroundService extends JobIntentService {

    static final int JOB_ID = 1000;
    static final int ONGOING_NOTIFICATION_ID = 33;

    static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, ForeGroundService.class, JOB_ID, work);
    }

    Notification.Builder notification;
    NotificationManager mNotificationManager;

    @RequiresApi(api = Build.VERSION_CODES.O)
    void einleitung(String Titel, String Text)
    {
        Intent notificationIntent = new Intent(this, ForeGroundService.class);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(this, 0, notificationIntent, 0);


        mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(Titel,
                    Text,
                    NotificationManager.IMPORTANCE_HIGH);
            channel.setSound(null,null);
            mNotificationManager.createNotificationChannel(channel);
        }
        notification =
                new Notification.Builder(this,Titel)
                        .setContentTitle(Titel)
                        .setContentText(Text)
                        .setSmallIcon(R.drawable.kleinesicon)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pendingIntent)
                        .setTicker("setTicker");
        mNotificationManager.notify(ONGOING_NOTIFICATION_ID, notification.build());
        startForeground(ONGOING_NOTIFICATION_ID, notification.build());
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    void vordergrund(String Titel, String Text)
    {
        notification.setContentTitle(Titel);
        notification.setContentText(Text);
        mNotificationManager.notify(ONGOING_NOTIFICATION_ID, notification.build());
    }
    PowerManager.WakeLock wakeLock;
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onHandleWork(Intent intent) {
        if (beginn) {
            einleitung("Test", "Test");
            beginn = false;
        }
        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "MyWakelockTag");
        wakeLock.acquire();
        //Do Work

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Intent local = new Intent();
        local.setAction("de.test.action");
        this.sendBroadcast(local);
        stopForeground(true);
        //toast("Fertig");
        if (wakeLock != null)
            wakeLock.release();
    }

    final Handler mHandler = new Handler();
}

MainActivity

public class MainActivity extends AppCompatActivity {
    private int JI = 1000;
    private BroadcastReceiver updateUIReciver;

    @RequiresApi(api = Build.VERSION_CODES.O)
    void somefunction(someparameters)
    {
        Intent mServiceIntent = new Intent();
        mServiceIntent.putExtra...
        ForeGroundService.enqueueWork(getBaseContext(),ForeGroundService.class,JI,mServiceIntent);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(updateUIReciver);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
        IntentFilter filter = new IntentFilter();
        filter.addAction("de.test.action");
        updateUIReciver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                ForeGroundService.shouldContinue = false;
            }
        };
        registerReceiver(updateUIReciver,filter);


        btnB.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.O)
            public void onClick(View v) {
                if (startcondition)
                {

                    Intent startIntent = new Intent(MainActivity.this, MyService.class);
                    startIntent.setAction(Constants.ACTION.START_ACTION);
                    startService(startIntent);


                    Intent serviceIntent = new Intent(MainActivity.this,ForeGroundService.class);
                    startForegroundService(serviceIntent);

                    somefunction(someparameters);
                }
                else
                {
                    Intent stopIntent = new Intent(MainActivity.this, MyService.class);
                    stopIntent.setAction(Constants.ACTION.STOP_ACTION);
                    startService(stopIntent);
                }
            }
        });
    }
}

EDIT: I made it work with sandhya sasane's solution and

public int onStartCommand(Intent intent, int flags, int startId)
{
    if (beginn) {
        executorService = Executors.newFixedThreadPool(1);
        beginn = false;
    }
    final Intent i2 = intent;
    executorService.execute(new Runnable(){
        @Override
        public void run(){
            abarbeiten(i2);
        }
    });
    return START_STICKY;
}

Important is the 1 in newFixedThreadPool(1); to only have one thread run at once


Solution

  • I am targeting Oreo. As you know, oreo introduced limits on background task execution time.

    Yes, it does. I can understand you, as google has made the things very odd and complex first..., then again complicated... then again... then again... And now developers like me and you, and your question and problem, denotes the outcome / result / proof of that.

    Workarounds are - according to google ...

    Please save time and yourself too... Google documentation is worst.. i have given -10 out of 10 for their documentation.

    to put the background task in the foreground.

    You have a wrong perception of what foreground concept is..!! Read complete answer word by word carefully, Your problem will get solved..!!

    This is what I was trying to do, yet once the foreground service is running, it gets destroyed after some time...

    Now very simply... Your Concept and implementation, both are wrong..., So Try with a new sample project and guidelines provided here along with sample working and tested code across 4.0 to latest android P .

    First the phone switches off it's screen, then once I activate it again, the background task continues. Sometimes onDestroy on the foreground service is called without the task being completed.

    It does not relate to foreground service, in any way.... forget this.

    My goal is to have all tasks being set by enqueueWork to be executed without ondestroy being called and without phone sleep mode to interrupt it.

    Forget this too... Lets first see what a foreground service is and how it is created...


    What is foreground service

    • A service which remains active (It does not mean... continuously running like never ending do-while loop)
    • Remain active until next boot / reboot
    • Even if user removes app from recents, it remains
    • But It does not remain active post next boot
    • It needs to be restarted by user by opening app again or via a broadcast receiver of ON_BOOT_COMPLETE or by a AlarmManager or By a JobScedular

    When to use

    As per my view users do not like a permanent notification showing message ^This is running in foreground and may discharge your battery soon^ , Again user would not be able to swipe it away and can only force stop or uninstall app to stop it. So it is as per my implementations point of view , ^Developers must use this for implementing runtime receivers as post - oreo devices do not welcomes static receivers implemented by extending Broadcastreceiver and placing its intent entry in manifest.xml file... Even if developer tries to do this that receiver will never get called on post - oreo devices ..., Yes it will get called below oreo devices. So implement just a ON_BOOT_COMPLETE receiver and rest all in a service.

    How to implement a foreground service

    Right click on project structure and make a service named RunnerService and then generate all mandatory methods. it does not require you to type all code manually.. Generate it as said. Sample foreground service :

    public class RunnerService extends Service
    {
    NotificationManager mNotifyManager;
    NotificationCompat.Builder mBuilder;
    NotificationChannel notificationChannel;
    String NOTIFICATION_CHANNEL_ID = "1";
    
    
    public RunnerService() { }
    
    @Override
    public void onCreate()
    {
        super.onCreate();
    
        Log.d("RUNNER : ", "PROGRAMMED.... \n");
    
        Bitmap IconLg = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
    
        mNotifyManager = (NotificationManager) getApplicationContext().getSystemService(NOTIFICATION_SERVICE);
        mBuilder = new NotificationCompat.Builder(this, null);
        mBuilder.setContentTitle("App Name")
                .setContentText("Foreground service...")
                .setTicker("Foreground service...")
                .setSmallIcon(R.drawable.ic_menu_slideshow)
                .setLargeIcon(IconLg)
                .setPriority(Notification.PRIORITY_HIGH)
                .setVibrate(new long[] {100})
                .setVisibility(Notification.VISIBILITY_PUBLIC)
                .setOngoing(true)
                .setAutoCancel(false);
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        {
            notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "My Notifications", NotificationManager.IMPORTANCE_HIGH);
    
            // Configure the notification channel.
            notificationChannel.setDescription("Channel description");
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.RED);
            notificationChannel.setVibrationPattern(new long[]{100});
            notificationChannel.enableVibration(true);
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            mNotifyManager.createNotificationChannel(notificationChannel);
    
            mBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
            startForeground(1, mBuilder.build());
        }
        else
        {
            mBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
            mNotifyManager.notify(1, mBuilder.build());
        }
    
    
    
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        Log.d("RUNNER : ", "\n IT IS ACTIVE UNTIL NEXT BOOT....");
        return START_STICKY;
    }
    
    @Override
    public void onDestroy()
    {
        Log.d("RUNNER : ", "\n IT WILL BE AGAIN ACTIVE BY ANDROID OS AUTOMATICALLY, DO NOT WORRY AND DONT CODE TO START IT AGAIN !!....");
        super.onDestroy();
    }
    
    
    @Override
    public IBinder onBind(Intent intent)
    {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("NOT_YET_IMPLEMENTED_BY_DEVELOPER");
    }
    }
    

    How to start it

    It depends on which android you are targeting below oreo or post oreo ... I will prefer to on all like below :

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
    {
        this.startForegroundService(new Intent(this, RunnerService.class));
    }
    else
    {
        this.startService(new Intent(this, RunnerService.class));
    }
    

    Either from MainActivity or any ON_BOOT_RECEIVER, or from wherever you want, just start it as said here...

    How to test is in foreground

    By removing it from recents... It will call onDestroy but it will be never destroyed you will not be able to swipe away notification. This means a success.

    How to test it quick

    With a sample new project with a MainActivity just calling service in said manner.

    What next..?

    Yes you can ask your next tasks here only..., I will keep updating and guiding... I hope you have kept enqueueWork concept and all your concepts aside and do not thinking on it...

    Lets go step by step and let me know the updates....

    UPDATE 2

    You should try it on emulator only... If success then try it on actual devices... Here is a problem again...

    There are many mobile phone manufacturers in the world now, which takes stock android from google as it is open source and modifies it to disable all services on BOOT. It only keeps Google , WhatsApp, FaceBook , Twitter and major market leaders... As if they do not allow them no one will purchase their devices ...

    Examples :

    1. Vivo = FunTouchOs
    2. Oppo = ColorOs
    3. There is a huge list....

    Do not check on this for BOOT_COMPLETE..., IT will not work as they are modified the android..

    But i want to test it on actual device

    Then test it on such device which os is purely from google and having android os.

    Then what should i do for other os modified from android

    There are tricks ..., But lets go step by step.... I will let you know , once you success in this..!!

    UPDATE : 3

    As it is not clear what is the requirement i am making some assumptions and writing answer :

    What you can do to implement foreground execution is :

    1. Implement foreground service as i depicted
    2. Use local broadcastmanager to broadcast events of your own.
    3. in onCreate of a foreground service register runtime receiver to receive that broadcasts
    4. On receiving broadcasts call to the methods of user defined class with context of foreground service. And perform all tasks from there.
    5. Unregister receiver from onDestroy of foreground service.

    What you can do to implement background execution is :

    If you are having repeating tasks and wants to execute it in background even if the app is removed from recents ... Then :

    1. Use Firebase Job Dispatcher which uses GooglePLAYServices
    2. If you use forever then that job will be triggered automatically even if system is rebooted and even if app is not in foreground or background or in recents...

    As of now i do not see any need of JobIntentService and therefore its static enqueueWork method; More resolution and details are needed for solving your problem.