即使应用程序关闭,如何保持CountDownTimer运行?

时间:2015-08-15 18:47:35

标签: android countdowntimer

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

5 个答案:

答案 0 :(得分:9)

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

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修改了这段代码。

答案 1 :(得分: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);
    }
}

谢谢!

答案 2 :(得分:0)

如果您不想使用服务,则可以将每秒钟的时间存储在Android SDK提供的CountDownTimer类的onTick()方法中的本地数据库(如共享首选项)中。然后,您可以通过检查本地数据库中的time变量是否不为null来设置时间。每次重新启动应用程序时,您基本上都会创建一个新的CountDownTimer对象。这整个过程应在onResume()函数中完成,以处理被杀死或置于后台的应用。这不是最有效的编码方式,因为每秒将数据存储到内存中会消耗内存并阻塞主UI线程,但是如果您是初学者并且Services仍然不在您的支持范围内,这将更容易理解。这是一些代码:

  @Override
protected void onResume() {
    super.onResume();

    //initialise the time (the condition to check for time stored in local database is null can be added, did not need that in this case)
    if (getIntent().hasExtra("time"))
        time = convertToMilliseconds(getIntent().getStringExtra("time"));                    
    else
        time = fitPreferences.getMealPlanTime();                                                   
    try {
        cdt = new CountDownTimer(time, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                fitPreferences.setMealPlanTime(millisUntilFinished);    // setting the value of time each second in local database.
                timevalue.setText(convertToTime(millisUntilFinished));       //display time
            }

            @Override
            public void onFinish() {
                // do whatever you want to do when the countdown ends.
            }
        }.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//to convert milliseconds to digital time format.
private String convertToTime(long milliseconds) {
    return String.format("%02d:%02d",
            TimeUnit.MILLISECONDS.toMinutes(milliseconds) % TimeUnit.HOURS.toMinutes(1),
            TimeUnit.MILLISECONDS.toSeconds(milliseconds) % TimeUnit.MINUTES.toSeconds(1));
}
//to convert digital time to milliseconds.
private long convertToMilliseconds(String time) {
    String[] array = time.split(":");
    return (Integer.parseInt(array[0]) * 60 * 1000) + (Integer.parseInt(array[1]) * 1000);
}

  @Override
protected void onPause() {
    super.onPause();
    cdt.cancel();   // stop the timer when app is killed or goes to background.
}

答案 3 :(得分:0)

使用 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 :(得分:0)

要在应用关闭时保持计时器运行,您需要使用 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 中注册接收器是否适合您。