终止并重新启动应用程序后前台服务未停止 - 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 实例来操作它。
我正在创建一个倒计时应用程序,它显示指定日期的剩余时间,我决定实施前台服务以在通知面板中显示剩余时间。但是,我在 "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 实例来操作它。