简体   繁体   中英

Foreground service not stopping after killing and relaunching app - Android Studio

I am creating a countdown app which shows the time remaining to a specified date and I decided to implement a foreground service to show the time remaining in the notification panel. However, I came across an issue when "bug testing" the app.

Currently, I have a start service button and stop service button which seems to be working fine. However, it only works when I remain in the same launch instance of the app.

Example of remaining in the same launch instance:
1) Start service (countdown in notification starts) > Stop Service (countdown in notification disappears)
2) Start service (countdown in notification starts) > Press Home Button > Launch App Again > Stop Service (countdown in notification disappears)

When i try to start service and stop service in different launch instance of the app, a problem occurs.

Example of different launch instance:
Start service (countdown in notification starts) > Press Home Button > Kill App In App Drawer > Launch App Again > Stop service (countdown in notification is supposed to disappear but doesn't)

The countdown still remains when i click the stop service button when i kill and relaunch the app (the only way to kill it is to clear storage/uninstall app). I figured it may be 2 services conflicting to change 1 variable,etc... but even after hours of trying to find the error and researching on forums, I still cant seem to find the reason why it happens.

Any help or leads would be much appreciated. Really sorry for the terrible formatting.

MainActivity.java

public class MainActivity extends AppCompatActivity implements DatePickerDialog.OnDateSetListener {

    private Handler mHandler = new Handler();

    private TextView dateText;
    private CountDownTimer countDownTimer;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // if first start
        SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
        boolean firstStart = prefs.getBoolean("firstStart", true);

        if(firstStart)
        {
            showStartDialog();
        }

        // set dateText to date_text
        dateText = findViewById(R.id.date_text);

        // show date picker when click on show_dialog button
        findViewById(R.id.show_dialog).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showDatePickerDialog();
            }
        });

        startTimer();
    }

    private void showStartDialog()
    {
        new AlertDialog.Builder(this)
                .setTitle("One Time Dialog")
                .setMessage("This should only be shown once")
                .setPositiveButton("ok", new DialogInterface.OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialog, int which)
                    {
                        showDatePickerDialog();
                    }
                })
                .create().show();

        SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean("firstStart", false);
        editor.apply();
    }

    private void showDatePickerDialog()
    {
        DatePickerDialog datePickerDialog = new DatePickerDialog(
                this,
                this,
                Calendar.getInstance().get(Calendar.YEAR),
                Calendar.getInstance().get(Calendar.MONTH),
                Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
        );
        datePickerDialog.show();
    }

    @Override
    public void onDateSet(DatePicker view, int year, int month, int dayOfMonth)
    {
        Date endDate = new Date((year-1900),month,dayOfMonth);
        SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putLong("endDate", endDate.getTime());
        editor.apply();

        startTimer();
    }

    private void startTimer()
    {
        long difference = getRemainDays();

        if(countDownTimer !=null)
        {
            countDownTimer.cancel();
            countDownTimer = null;
        }

        countDownTimer = new CountDownTimer(difference,1000) // 1 second
        {
            @Override
            public void onTick(long millisUntilFinished)
            {
                int days = (int)(millisUntilFinished/(1000*60*60*24));
                int hours = (int)((millisUntilFinished/(1000*60*60))%24);
                int mins = (int)((millisUntilFinished/(1000*60))%60);
                int sec = (int)((millisUntilFinished/(1000))%60);

                dateText.setText(String.format("%02d Days %d Hours %d Mins %d Sec",days,hours,mins,sec));
            }
            @Override
            public void onFinish()
            {
                // Done
                dateText.setText("Done");
            }
        }.start();
    }

    private long getRemainDays()
    {
        Date currentDate = new Date();

        SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
        long endDate = prefs.getLong("endDate", currentDate.getTime());

        return endDate - currentDate.getTime();
    }

    public void startService(View v){
        String input = dateText.getText().toString();

        Intent serviceIntent = new Intent(this, ExampleService.class);
        serviceIntent.putExtra("inputExtra", input);
        ContextCompat.startForegroundService(this,serviceIntent);
        mNotificationRunnable.run();
    }

    public void stopService(View v){
        Intent serviceIntent = new Intent(this,ExampleService.class);
        stopService(serviceIntent);
        mHandler.removeCallbacks(mNotificationRunnable);
    }

    private void updateNotification() {
        String input = dateText.getText().toString();

        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
                0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Example Service")
                .setContentText(input)
                .setSmallIcon(R.drawable.ic_android)
                .setContentIntent(pendingIntent)
                .build();

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(1, notification);
    }

    private Runnable mNotificationRunnable = new Runnable()
    {
        @Override
        public void run() {
            updateNotification();
            mHandler.postDelayed(this,1000);
        }
    };
}

ExampleService.java

    public class ExampleService extends Service {

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String input = intent.getStringExtra("inputExtra");

        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
                0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Example Service")
                .setContentText(input)
                .setSmallIcon(R.drawable.ic_android)
                .setContentIntent(pendingIntent)
                .build();

        startForeground(1,notification);

        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

New MainActivity.java

private TextView dateText;
private CountDownTimer countDownTimer;
private NotificationSingleton notificationSingleton;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // if first start
    SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
    boolean firstStart = prefs.getBoolean("firstStart", true);

    if(firstStart)
    {
        showStartDialog();
    }

    // set dateText to date_text
    dateText = findViewById(R.id.date_text);

    // show date picker when click on show_dialog button
    findViewById(R.id.show_dialog).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            showDatePickerDialog();
        }
    });

    notificationSingleton = NotificationSingleton.getInstance();
    startTimer();
}

private void showStartDialog()
{
    new AlertDialog.Builder(this)
            .setTitle("One Time Dialog")
            .setMessage("This should only be shown once")
            .setPositiveButton("ok", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialog, int which)
                {
                    showDatePickerDialog();
                }
            })
            .create().show();

    SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
    SharedPreferences.Editor editor = prefs.edit();
    editor.putBoolean("firstStart", false);
    editor.apply();
}

private void showDatePickerDialog()
{
    DatePickerDialog datePickerDialog = new DatePickerDialog(
            this,
            this,
            Calendar.getInstance().get(Calendar.YEAR),
            Calendar.getInstance().get(Calendar.MONTH),
            Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
    );
    datePickerDialog.show();
}

@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth)
{
    Date endDate = new Date((year-1900),month,dayOfMonth);
    SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
    SharedPreferences.Editor editor = prefs.edit();
    editor.putLong("endDate", endDate.getTime());
    editor.apply();

    startTimer();
}

private void startTimer()
{
    long difference = getRemainDays();

    if(countDownTimer !=null)
    {
        countDownTimer.cancel();
        countDownTimer = null;
    }

    countDownTimer = new CountDownTimer(difference,1000) // 1 second
    {
        @Override
        public void onTick(long millisUntilFinished)
        {
            int days = (int)(millisUntilFinished/(1000*60*60*24));
            int hours = (int)((millisUntilFinished/(1000*60*60))%24);
            int mins = (int)((millisUntilFinished/(1000*60))%60);
            int sec = (int)((millisUntilFinished/(1000))%60);

            dateText.setText(String.format("%02d Days %d Hours %d Mins %d Sec",days,hours,mins,sec));
        }
        @Override
        public void onFinish()
        {
            // Done
            dateText.setText("Done");
        }
    }.start();
}

private long getRemainDays()
{
    Date currentDate = new Date();

    SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
    long endDate = prefs.getLong("endDate", currentDate.getTime());

    return endDate - currentDate.getTime();
}

public void startService(View v){
    String input = dateText.getText().toString();

    Intent serviceIntent = new Intent(this, ExampleService.class);
    serviceIntent.putExtra("inputExtra", input);
    ContextCompat.startForegroundService(this,serviceIntent);
    notificationSingleton.mNotificationRunnable.run();
}

public void stopService(View v){
    Intent serviceIntent = new Intent(this,ExampleService.class);
    stopService(serviceIntent);
    notificationSingleton.stopService();
}

public TextView getDateText()
{
    return dateText;
}

NotificationSingleton.java

public Handler mHandler = new Handler();

private static NotificationSingleton instance;

private NotificationSingleton()
{
    //private to prevent any else from instantiating
}

public static synchronized NotificationSingleton getInstance()
{
    if (instance == null){
        instance = new NotificationSingleton();
    }
    return instance;
}

public void stopService()
{
    mHandler.removeCallbacks(mNotificationRunnable);
}

public Runnable mNotificationRunnable = new Runnable()
{
    @Override
    public void run() {
        updateNotification();
        mHandler.postDelayed(this,1000);
    }
};

private void updateNotification() {
    MainActivity mainActivity = new MainActivity();
    String input = mainActivity.getDateText().getText().toString();

    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this,
            0, notificationIntent, 0);

    Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Example Service")
            .setContentText(input)
            .setSmallIcon(R.drawable.ic_android)
            .setContentIntent(pendingIntent)
            .build();

    NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    mNotificationManager.notify(1, notification);
}

It's because the first mNotificationRunnable is never stopped, when you kill the activity and re-enter, you are removing a new one in stopService() . You can also cause this bug just by rotating the screen.

I'd suggest moving this runnable into some singleton class, where you can manipulate it even with a new activity instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM