Search code examples
androidbroadcastreceiverandroid-servicecountdowntimer

Countdown timer not run when user close application


I had created countdown timer to display timeout it works well when user minimize application but it stops when user close application. i had added code below kindly help me it's an emergency.

this is MainActivity.java

public static final String TAG = "Demo";
    TextView t1 ;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        t1 = (TextView)findViewById(R.id.t1);
         startService(new Intent(this, count_servie.class));
    }
    private BroadcastReceiver br = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {            
            updateGUI(intent);
        }
    };
    public void onResume() {
        super.onResume();        
        registerReceiver(br, new IntentFilter(count_servie.COUNTDOWN_BR));
        }
    private void updateGUI(Intent intent) {
        if (intent.getExtras() != null) {
            long millisUntilFinished = intent.getLongExtra("countdown", 0);
            t1.setText("Countdown seconds remaining: " +  millisUntilFinished / 1000);
        }
    }

this is my count_servie.java

public class count_servie extends Service {
    public static final String COUNTDOWN_BR = "com.demo.DSemo.countdown_br";
    Intent bi = new Intent(COUNTDOWN_BR);
    CountDownTimer cdt = null;
        public void onCreate() {       
            super.onCreate();
            cdt = new CountDownTimer(30000, 1000) {
                public void onTick(long millisUntilFinished) {
                    bi.putExtra("countdown", millisUntilFinished);
                    sendBroadcast(bi);
                }
                public void onFinish() {
                }
            };
            cdt.start();
        }
        public int onStartCommand(Intent intent, int flags, int startId) {   
            return super.onStartCommand(intent, flags, startId);
        }
        public IBinder onBind(Intent arg0) {       
            return null;
        }

Thanks in advance.


Solution

  • You need to return START_STCKY in your onStartCommand() method for the service to run even when app is closed.

     ....
     public int onStartCommand(Intent intent, int flags, int startId) {   
         return START_STCKY;
     } 
     ....
    

    You can refer thislink for correctly implementing a service.

    Alternatively, you can refer this SO question.

    Update Use a Foreground Service to avoid your Service being killed. In order to make your service Foreground, replace your onStartCommand code with this

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Intent notificationIntent = new Intent(this, MainActivity.class);
    
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);
    
        Notification notification = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Timer")
                .setContentText("Doing some work...")
                .setContentIntent(pendingIntent).build();
    
        startForeground(1337, notification);
    
        cdt = new CountDownTimer(30000, 1000) {
            public void onTick(long millisUntilFinished) {
    
                bi.putExtra("countdown", millisUntilFinished);
                sendBroadcast(bi);
            }
    
            public void onFinish() {
                stopForeground(true);
            }
        };
        cdt.start();
        return START_STICKY;
    }
    

    Udpate 2: Counter using Service and SharedPreferences

    Replace your Actvity's code with this:

    import android.content.Context;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.os.Handler;
    import android.support.v4.os.ResultReceiver;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    import java.util.Calendar;
    import java.util.Date;
    
    public class MainActivity extends AppCompatActivity {
    
    private static final String TAG = MainActivity.class.getSimpleName();
    private static final String SHARED_PREF = "MyPref";
    private final static int MAX_COUNTER = 30;
    
    public static final String KEY_COUNTER_SECONDS = "seconds";
    public static final String KEY_SAVED_COUNTER = "saved_counter";
    public static final String KEY_SAVED_TIME_MILLI = "saved_time_milli";
    
    MyResultReceiver mReceiver;
    TextView mTvCounter;
    SharedPreferences mSharedPref;
    long mMaxCounterValueInSeconds = MAX_COUNTER;
    long mCurCounterValue = 0;
    boolean mShouldSaveValues;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvCounter = (TextView) findViewById(R.id.tv_counter);
        mReceiver = new MyResultReceiver(null);
        mSharedPref = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
    
        //register listener
        MyService.registerReceiver(mReceiver);
    
        //get values from shared pref
        long savedCounter = mSharedPref.getLong(KEY_SAVED_COUNTER, -1);
        long savedTime = mSharedPref.getLong(KEY_SAVED_TIME_MILLI, -1);
    
        //if -1 counter was running when app was closed, get saved values from shared pref
        if (savedTime != -1) {
            //elapsedTime is the time spent in seconds while the app was in background
            long elapsedTime = (getCurrentTimeInMilli() - savedTime)/1000; //convert to sec
    
            mCurCounterValue = savedCounter + elapsedTime;
    
            if(mCurCounterValue < MAX_COUNTER){
                //calculate current counter value from values retrieved from shared pref
                mMaxCounterValueInSeconds = MAX_COUNTER - mCurCounterValue;
                //start the value with updated max count value
                startService(mMaxCounterValueInSeconds);
            }else{
                mCurCounterValue = MAX_COUNTER;
            }
        }else{
            //if counter was not running, start the service with max count value = MAX_COUNTER
            startService(mMaxCounterValueInSeconds);
        }
    
        //update text view
        mTvCounter.setText("" + mCurCounterValue);
    }
    
    private void startService(long maxCounter){
        mShouldSaveValues = true;
    
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putLong(KEY_COUNTER_SECONDS, maxCounter);
        intent.putExtras(bundle);
        startService(intent);
    }
    @Override
    protected void onPause() {
        super.onPause();
    
        //stop the service
        stopService(new Intent(this, MyService.class));
        //unregister listener
        MyService.unregisterReceiver();
    
        if(mShouldSaveValues) {//save the values only when counter has started
            //save values in the shared preference
            SharedPreferences.Editor editor = mSharedPref.edit();
            Log.d(TAG, "saving counter: " + Long.parseLong(mTvCounter.getText().toString()));
            editor.putLong(KEY_SAVED_COUNTER, Long.parseLong(mTvCounter.getText().toString()));
            editor.putLong(KEY_SAVED_TIME_MILLI, getCurrentTimeInMilli());
            editor.apply();
        }
    }
    
    /**
     * This method returns current time in milli seconds
     *
     * @return time in milliseconds
     */
    private long getCurrentTimeInMilli() {
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        long timeInMilli = date.getTime();
        return timeInMilli;
    }
    
    /**
     * ResultReceiver is used to get values from MyService.class
     * It is registered in onResume() &
     * unregistered in onPause()
     */
    class MyResultReceiver extends ResultReceiver {
    
        public MyResultReceiver(Handler handler) {
            super(handler);
        }
    
        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);
            String strMilliFinished = resultData.getString(MyService.KEY_MSG);
            updateUI(Long.parseLong(strMilliFinished));
        }
    
        private void updateUI(final long milliFinished) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
    
                    mCurCounterValue++;
                    mTvCounter.setText("" + mCurCounterValue);
    
                    if(milliFinished == 0) {
                        //resetting counter values
    
                        mShouldSaveValues = false;
                        mMaxCounterValueInSeconds = MAX_COUNTER;
                        mCurCounterValue = 0;
    
                        SharedPreferences.Editor editor = mSharedPref.edit();
                        editor.putLong(KEY_SAVED_COUNTER, -1);
                        editor.putLong(KEY_SAVED_TIME_MILLI, -1);
                        editor.apply();
    
                    }
                }
            });
        }
    }
    }
    

    Replace your Service code with this:

    import android.app.Notification;
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.CountDownTimer;
    import android.os.IBinder;
    import android.support.v4.app.NotificationCompat;
    import android.support.v4.os.ResultReceiver;
    import android.util.Log;
    
    public class MyService extends Service {
    
    public static final String KEY_MSG = "msg";
    
    CountDownTimer cdt = null;
    private static ResultReceiver mReceiver;
    
    public MyService() {
    }
    
    public static void registerReceiver(ResultReceiver receiver) {
        mReceiver = receiver;
    }
    
    public static void unregisterReceiver() {
        mReceiver = null;
    
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
        Bundle bundle = intent.getExtras();
        long maxCounterValueInSeconds = bundle.getLong(MainActivity.KEY_COUNTER_SECONDS);
        long maxCounter = maxCounterValueInSeconds * 1000;
        cdt = new CountDownTimer(maxCounter, 1000) {
            public void onTick(long millisUntilFinished) {
    
                sendMessage(1, "" + millisUntilFinished);
            }
    
            public void onFinish() {
                sendMessage(1, "" + 0);
                stopSelf();
            }
        };
        cdt.start();
        return START_STICKY;
    }
    
    private void sendMessage(int resultCode, String message) {
    
        if (mReceiver != null) {
            Bundle bundle = new Bundle();
            bundle.putString(KEY_MSG, message);
            mReceiver.send(resultCode, bundle);
        }
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        cdt.cancel();
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    }
    

    Note: I am using ResultReceiver instead of BroadcastReciver