【问题标题】:How to keep a CountDownTimer running even if the app is closed?即使应用程序关闭,如何保持 CountDownTimer 运行?
【发布时间】:2015-08-15 18:47:35
【问题描述】:

我整个暑假都在学习如何编码和构建我的第一个应用程序(在 android studio 中)。因此,我不是专业的编码员。这周我遇到了一个问题。基本上,我正在开发一个测验应用程序,当用户回答错误时,他必须等待 5 分钟。问题是当倒数计时器启动并且用户关闭应用程序(通过关闭应用程序我的意思是销毁它而不只是按下主页按钮)时,倒数计时器停止。我想知道即使应用程序关闭,我如何才能保持计时器运行。您的帮助将不胜感激,如果你们给我一个示例代码,那就太好了。 提前致谢!

【问题讨论】:

    标签: android countdowntimer


    【解决方案1】:

    在服务中运行它:使用在您的活动中创建广播接收器并让服务发送广播。

    package com.example.cdt;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.CountDownTimer;
    import android.os.IBinder;
    import android.util.Log;
    
    public class BroadcastService extends Service {
    
        private final static String TAG = "BroadcastService";
    
        public static final String COUNTDOWN_BR = "your_package_name.countdown_br";
        Intent bi = new Intent(COUNTDOWN_BR);
    
        CountDownTimer cdt = null;
    
        @Override
            public void onCreate() {       
                super.onCreate();
    
                Log.i(TAG, "Starting timer...");
    
                cdt = new CountDownTimer(30000, 1000) {
                    @Override
                    public void onTick(long millisUntilFinished) {
    
                        Log.i(TAG, "Countdown seconds remaining: " + millisUntilFinished / 1000);
                        bi.putExtra("countdown", millisUntilFinished);
                        sendBroadcast(bi);
                    }
    
                    @Override
                    public void onFinish() {
                        Log.i(TAG, "Timer finished");
                    }
                };
    
                cdt.start();
            }
    
            @Override
            public void onDestroy() {
    
                cdt.cancel();
                Log.i(TAG, "Timer cancelled");
                super.onDestroy();
            }
    
            @Override
            public int onStartCommand(Intent intent, int flags, int startId) {       
                return super.onStartCommand(intent, flags, startId);
            }
    
            @Override
            public IBinder onBind(Intent arg0) {       
                return null;
            }
    }
    

    来自主要活动:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    
        startService(new Intent(this, BroadcastService.class));
        Log.i(TAG, "Started service");
    }
    
    private BroadcastReceiver br = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            
            updateGUI(intent); // or whatever method used to update your GUI fields
        }
    };
    
    @Override  
    public void onResume() {
        super.onResume();        
        registerReceiver(br, new IntentFilter(BroadcastService.COUNTDOWN_BR));
        Log.i(TAG, "Registered broacast receiver");
        }
    
    @Override
    public void onPause() {
        super.onPause();
        unregisterReceiver(br);
        Log.i(TAG, "Unregistered broacast receiver");
    }
    
    @Override
    public void onStop() {
        try {
            unregisterReceiver(br);
        } catch (Exception e) {
            // Receiver was probably already stopped in onPause()
        }
        super.onStop();
    }
    @Override
    public void onDestroy() {        
        stopService(new Intent(this, BroadcastService.class));
        Log.i(TAG, "Stopped service");
        super.onDestroy();
    }
    
    private void updateGUI(Intent intent) {
        if (intent.getExtras() != null) {
            long millisUntilFinished = intent.getLongExtra("countdown", 0);
            Log.i(TAG, "Countdown seconds remaining: " +  millisUntilFinished / 1000);            
        }
    }
    

    请注意,我从 How to run CountDownTimer in a Service in Android? 获得此代码,我为自己的 android countDownTimer 进行了修改。

    【讨论】:

    • 应用程序关闭后服务如何继续运行? onPause/Stop 通过 onDestroy 停止服务取消注册接收器。我知道如果从任务管理器中删除该应用程序,则该活动将不存在,但如果它的 countDownTimer 仍在滴答作响,您希望该服务继续运行
    • 由于后台进程限制,这不再适用于 Oreo+
    【解决方案2】:

    从这里下载源代码Android Countdown Timer Run In Background

    activity_main.xml

    <RelativeLayout android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:android="http://schemas.android.com/apk/res/android">
    
    
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/et_hours"
                android:hint="Hours"
                android:inputType="time"
                android:layout_marginRight="5dp"
                />
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/btn_timer"
            android:layout_above="@+id/btn_cancel"
            android:text="Start Timer"/>
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:id="@+id/btn_cancel"
            android:text="cancel timer"/>
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/tv_timer"
            android:layout_centerInParent="true"
            android:textSize="25dp"
            android:textColor="#000000"
            android:text="00:00:00"/>
    
        </RelativeLayout>
    

    MainActivity.java

    package com.countdowntimerservice;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.content.SharedPreferences;
    import android.preference.PreferenceManager;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button btn_start, btn_cancel;
        private TextView tv_timer;
        String date_time;
        Calendar calendar;
        SimpleDateFormat simpleDateFormat;
        EditText et_hours;
    
        SharedPreferences mpref;
        SharedPreferences.Editor mEditor;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
            listener();
    
    
        }
    
        private void init() {
            btn_start = (Button) findViewById(R.id.btn_timer);
            tv_timer = (TextView) findViewById(R.id.tv_timer);
            et_hours = (EditText) findViewById(R.id.et_hours);
            btn_cancel = (Button) findViewById(R.id.btn_cancel);
    
    
    
            mpref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
            mEditor = mpref.edit();
    
            try {
                String str_value = mpref.getString("data", "");
                if (str_value.matches("")) {
                    et_hours.setEnabled(true);
                    btn_start.setEnabled(true);
                    tv_timer.setText("");
    
                } else {
    
                    if (mpref.getBoolean("finish", false)) {
                        et_hours.setEnabled(true);
                        btn_start.setEnabled(true);
                        tv_timer.setText("");
                    } else {
    
                        et_hours.setEnabled(false);
                        btn_start.setEnabled(false);
                        tv_timer.setText(str_value);
                    }
                }
            } catch (Exception e) {
    
            }
    
    
    
        }
    
        private void listener() {
            btn_start.setOnClickListener(this);
            btn_cancel.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
    
            switch (v.getId()) {
                case R.id.btn_timer:
    
    
                    if (et_hours.getText().toString().length() > 0) {
    
                        int int_hours = Integer.valueOf(et_hours.getText().toString());
    
                        if (int_hours<=24) {
    
    
                            et_hours.setEnabled(false);
                            btn_start.setEnabled(false);
    
    
                            calendar = Calendar.getInstance();
                            simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
                            date_time = simpleDateFormat.format(calendar.getTime());
    
                            mEditor.putString("data", date_time).commit();
                            mEditor.putString("hours", et_hours.getText().toString()).commit();
    
    
                            Intent intent_service = new Intent(getApplicationContext(), Timer_Service.class);
                            startService(intent_service);
                        }else {
                            Toast.makeText(getApplicationContext(),"Please select the value below 24 hours",Toast.LENGTH_SHORT).show();
                        }
    /*
                        mTimer = new Timer();
                        mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 5, NOTIFY_INTERVAL);*/
                    } else {
                        Toast.makeText(getApplicationContext(), "Please select value", Toast.LENGTH_SHORT).show();
                    }
                    break;
    
    
                case R.id.btn_cancel:
    
    
                 Intent intent = new Intent(getApplicationContext(),Timer_Service.class);
                 stopService(intent);
    
                    mEditor.clear().commit();
    
                    et_hours.setEnabled(true);
                    btn_start.setEnabled(true);
                    tv_timer.setText("");
    
    
                    break;
    
            }
    
        }
    
        private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String str_time = intent.getStringExtra("time");
                tv_timer.setText(str_time);
    
            }
        };
    
        @Override
        protected void onResume() {
            super.onResume();
            registerReceiver(broadcastReceiver,new IntentFilter(Timer_Service.str_receiver));
    
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            unregisterReceiver(broadcastReceiver);
        }
    }
    

    Timer_Service.java

    package com.countdowntimerservice;
    
    
    import android.app.Service;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.os.Handler;
    import android.os.IBinder;
    import android.preference.PreferenceManager;
    import android.support.annotation.Nullable;
    import android.util.Log;
    
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.TimeUnit;
    
    public class Timer_Service extends Service {
    
        public static String str_receiver = "com.countdowntimerservice.receiver";
    
        private Handler mHandler = new Handler();
        Calendar calendar;
        SimpleDateFormat simpleDateFormat;
        String strDate;
        Date date_current, date_diff;
        SharedPreferences mpref;
        SharedPreferences.Editor mEditor;
    
        private Timer mTimer = null;
        public static final long NOTIFY_INTERVAL = 1000;
        Intent intent;
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            mpref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
            mEditor = mpref.edit();
            calendar = Calendar.getInstance();
            simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
    
            mTimer = new Timer();
            mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 5, NOTIFY_INTERVAL);
            intent = new Intent(str_receiver);
        }
    
    
        class TimeDisplayTimerTask extends TimerTask {
    
            @Override
            public void run() {
                mHandler.post(new Runnable() {
    
                    @Override
                    public void run() {
    
                        calendar = Calendar.getInstance();
                        simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
                        strDate = simpleDateFormat.format(calendar.getTime());
                        Log.e("strDate", strDate);
                        twoDatesBetweenTime();
    
                    }
    
                });
            }
    
        }
    
        public String twoDatesBetweenTime() {
    
    
            try {
                date_current = simpleDateFormat.parse(strDate);
            } catch (Exception e) {
    
            }
    
            try {
                date_diff = simpleDateFormat.parse(mpref.getString("data", ""));
            } catch (Exception e) {
    
            }
    
            try {
    
    
                long diff = date_current.getTime() - date_diff.getTime();
                int int_hours = Integer.valueOf(mpref.getString("hours", ""));
    
                long int_timer = TimeUnit.HOURS.toMillis(int_hours);
                long long_hours = int_timer - diff;
                long diffSeconds2 = long_hours / 1000 % 60;
                long diffMinutes2 = long_hours / (60 * 1000) % 60;
                long diffHours2 = long_hours / (60 * 60 * 1000) % 24;
    
    
                if (long_hours > 0) {
                    String str_testing = diffHours2 + ":" + diffMinutes2 + ":" + diffSeconds2;
    
                    Log.e("TIME", str_testing);
    
                    fn_update(str_testing);
                } else {
                    mEditor.putBoolean("finish", true).commit();
                    mTimer.cancel();
                }
            }catch (Exception e){
                mTimer.cancel();
                mTimer.purge();
    
    
            }
    
            return "";
    
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.e("Service finish","Finish");
        }
    
        private void fn_update(String str_time){
    
            intent.putExtra("time",str_time);
            sendBroadcast(intent);
        }
    }
    

    谢谢!

    【讨论】:

    • 嗨,Deepshikha!你能帮我们一个 COUNTUP 计时器吗?它在后台运行,就像你上面做的一样,但是计时器向上工作,有点像秒表。谢谢
    【解决方案3】:

    使用 System.currentTimeMillis() 和管理倒计时的最佳方式。在到处搜索后查看下面的代码此代码完美运行(这是 Kotlin 解决方案)

    TimerService : Service()

    override fun onStartCommand(intent:Intent?, flags:Int, startId:Int):Int {
        val  mSharePref= getSharedPreferences("BillingActivity", MODE_PRIVATE)
        val day  = mSharePref.getLong("futureTime", 0)
        val curDateTime = System.currentTimeMillis()
        var diff = day - curDateTime
        if (diff < 0)diff = 0L
        Toast.makeText(this, "$diff", Toast.LENGTH_SHORT).show()
        countDown(diff)
        return START_STICKY
      }
    
    private fun countDown(day: Long){
        cdt?.cancel()
        val  mSharePref= getSharedPreferences("BillingActivity", MODE_PRIVATE)
        cdt = object : CountDownTimer(day, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                val mDays = millisUntilFinished / (24 * 60 * 60 * 1000)
                val mHours = millisUntilFinished / (60 * 60 * 1000) % 24
                val mMinutes = millisUntilFinished / (60 * 1000) % 60
                val mSeconds = millisUntilFinished / 1000 % 60
                val timeFormat = String.format(Locale.getDefault(), "Day: %02d, Time: %02d:%02d:%02d", mDays, mHours, mMinutes, mSeconds)
                bi.putExtra("time", timeFormat)
                bi.putExtra("milis", millisUntilFinished )
                sendBroadcast(bi)
            }
    
            override fun onFinish() {
                bi.putExtra("time", "Finish")
                sendBroadcast(bi)
            }
        }.start()
    }
    

    在主要活动中

    主要活动

    override fun onResume() {
        super.onResume()
        startService(Intent(this, TimerService::class.java))
    }
    

    谢谢你

    【讨论】:

      【解决方案4】:

      要在应用关闭时保持计时器运行,您需要使用service

      import android.app.Service
      import android.content.Intent
      import android.os.CountDownTimer
      import android.os.IBinder
      import java.util.concurrent.TimeUnit
      
      class TimerService : Service() {
      
          private val finishedIntent = Intent(ACTION_FINISHED)
      
          private val tickIntent = Intent(ACTION_TICK)
      
          private lateinit var timer: CountDownTimer
      
          override fun onCreate() {
              timer = createTimer()
          }
      
          override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
              timer.start()
              return START_NOT_STICKY
          }
      
          override fun onBind(intent: Intent?): IBinder? = null
      
          override fun onDestroy() {
              timer.cancel()
          }
      
          private fun createTimer(): CountDownTimer =
              object : CountDownTimer(COUNTDOWN_LENGTH, COUNTDOWN_INTERVAL) {
                  override fun onTick(millisUntilFinished: Long) {
                      tickIntent.putExtra(TIME_LEFT_KEY, millisUntilFinished)
                      sendBroadcast(tickIntent)
                  }
      
                  override fun onFinish() {
                      sendBroadcast(finishedIntent)
                      stopSelf() // Stop the service within itself NOT the activity
                  }
              }
      
          companion object {
      
              const val ACTION_FINISHED: String = "your.pkg.name.ACTION_FINISHED"
      
              const val ACTION_TICK: String = "your.pkg.name.ACTION_TICK"
      
              const val TIME_LEFT_KEY: String = "timeLeft"
      
              private val COUNTDOWN_INTERVAL = TimeUnit.SECONDS.toMillis(1)
      
              private val COUNTDOWN_LENGTH = TimeUnit.MINUTES.toMillis(5)
          }
      }
      

      将服务添加到 AndroidManifest.xml

      <application ...>
          <service android:name=".TimerService"
                   android:description="@string/timer_service_description"
                   android:exported="false"/>
         ...
      </application
      

      在活动中收听服务的广播。

      class MainActivity : AppCompatActivity() {
      
          private val timerReceiver = TimerReceiver()
      
          override fun onCreate(savedInstanceState: Bundle?) {
              // ...
              val intent = Intent(this, TimerService::class.java)
              startService(intent) // This kicks off the timer when the activity is launched. Move to a click handler if needed.
          }
      
          override fun onResume() {
              super.onResume()
              registerReceiver(timerReceiver, IntentFilter(TimerService.ACTION_TICK))
              registerReceiver(timerReceiver, IntentFilter(TimerService.ACTION_FINISHED))
          }
      
          override fun onPause() {
              unregisterReceiver(timerReceiver)
              super.onPause()
          }
      
          private inner class TimerReceiver : BroadcastReceiver() {
      
              override fun onReceive(context: Context?, intent: Intent?) {
                  if (intent == null) return
      
                  when (intent.action) {
                      TimerService.ACTION_TICK -> {
                          val timeLeft = intent.getLongExtra(TimerService.TIME_LEFT_KEY, 0)
                          updateUIForTick(timeLeft)
                      }
                      TimerService.ACTION_FINISHED -> updateUIForTimerFinished()
                  }
              }
          }
      }
      

      请参阅this SO answer,了解在 onCreate、onStart 或 onResume 中注册接收器是否适合您。

      【讨论】:

        猜你喜欢
        • 2023-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-26
        • 1970-01-01
        • 1970-01-01
        • 2022-01-23
        相关资源
        最近更新 更多