终止并重新启动应用程序后前台服务未停止 - Android Studio

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

我正在创建一个倒计时应用程序,它显示指定日期的剩余时间,我决定实施前台服务以在通知面板中显示剩余时间。但是,我在 "bug testing" 应用程序时遇到了一个问题。

目前,我有一个启动服务按钮和停止服务按钮,它们似乎工作正常。但是,它仅在我保留在应用程序的同一个启动实例中时才有效。

留在同一启动实例中的示例:
1) 启动服务(通知开始倒计时)> 停止服务(通知倒计时消失)
2) 启动服务(通知开始倒计时)> 按主页按钮 > 再次启动应用程序 > 停止服务(通知倒计时消失)

当我尝试在应用程序的不同启动实例中启动和停止服务时,出现问题。

不同启动实例的示例:
启动服务(通知开始倒计时)> 按主页按钮 > 在应用程序抽屉中杀死应用程序 > 再次启动应用程序 > 停止服务(通知倒计时应该消失但没有)

当我终止并重新启动应用程序时单击停止服务按钮时,倒计时仍然存在(终止它的唯一方法是清除 storage/uninstall 应用程序)。我认为可能是 2 个服务在更改 1 个变量等方面发生冲突...但即使在尝试查找错误并在论坛上进行研究数小时后,我似乎仍然无法找到它发生的原因。

任何帮助或线索将不胜感激。真的很抱歉糟糕的格式。

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

新建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);
}

这是因为第一个 mNotificationRunnable 永远不会停止,当你杀死 activity 并重新进入时,你正在删除 stopService() 中的一个新的。您也可以通过旋转屏幕来导致此错误。

我建议将这个 runnable 移动到某个单例中 class,在那里你甚至可以使用新的 activity 实例来操作它。