使用服务在应用程序生命周期之外继续计时器

时间:2016-08-13 22:18:23

标签: android multithreading service timer android-handler

编辑:感谢您的回复。我最终想出了一个很好的解决方案(我在下面发布),它为那些感兴趣的人使用前台服务和广播接收器。

原始问题:

我有一个简单的倒计时器,它使用一个更新textview的处理程序。我想要实现的是

  1. 即使应用关闭,也要继续使用计时器
  2. 在计时器到达其持续时间时,发出通知并唤醒手机(如果睡着了)
  3. 我已经阅读了有关使用服务的信息,因为它与活动分开运行,但我发现的所有示例对于我尝试做的事情似乎都比较复杂。

    供参考继承我的计时器课程

    NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: nil)
    
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(aMethod(_:)), name:"NotificationIdentifier", object: nil)
    
    func aMethod(notification: NSNotification){
      // Do stuff here
    }
    

    timerView和两个启动/停止按钮

    simple timer view

    我还在考虑在onStop / onDestroy期间将计时器存储在数据库中,并使用系统时间及其保存时间之间的差异来更新计时器。但这并不能解决发出通知和/或唤醒手机的问题。

2 个答案:

答案 0 :(得分:1)

您找到的示例并不太复杂 - 为了达到您想要的效果:

  1. 绑定Service,它将跟踪已用时间并将使用AlarmManager
  2. 注册闹钟
  3. 可以绑定上述Service并执行resetTimer()startTimer()getElapsedTime()等方法的片段/活动。您需要使用getElapsedTime()Handler执行查询,但1秒的超时时间太长(我使用0.1秒或类似的时间)。
  4. 最后注意:您无法使用在postDelayed()上设置的超时来增加计时器。更好地使用这样的东西:

    public void startTimer(long duration) {
       mStartTime = System.currentTimeMillis();
       mDuration = duration;
       // register alarm with AlarmManager here
    }
    
    public long getElapsedTime() {
       return System.currentTimeMillis() - mStartTime;
    }
    

答案 1 :(得分:0)

对于那些可能需要对此做出回答的人,经过一些研究后我决定使用前台服务和处理程序,因为警报管理器对于这样一个短而恒定的计时器来说效率很低。

所以在摘要中

  1. 在服务类

    • 将计时器广播到MainActivity将使用广播接收器接收它的主要活动并更新UI
    • 服务类使用自己的广播接收器来检查电话屏幕是否打开/关闭,并在睡眠状态恢复时更新计时器。
  2. 在主要活动类

    • 接收从计时器服务发送的广播并更新UI
    • 其他物流,例如何时注册/取消注册广播接收器以及向服务发送动作以停止/启动
  3. 服务类:

    //Timer service which uses a handler to monitor tick rate. Also uses a broadcast receiver
    //to update the timer if the device was in sleep mode.
    public class TimerService extends Service{
        Intent intent;
        public static final String TAG = TimerService.class.getSimpleName();
        private final Handler handler = new Handler();
        long currentTime, duration;
        long timeSinceLastOn, elapsedTimeSinceOff;
    
        @Override
        public void onCreate() {
            super.onCreate();
            currentTime = duration = elapsedTimeSinceOff = 0L;
            timeSinceLastOn = SystemClock.elapsedRealtime();
            intent = new Intent(Constants.ACTION.BROADCAST_ACTION);
    
            /**Starting Timer here**/
            handler.removeCallbacks(timerThread);
            handler.postDelayed(timerThread,0);
            /**********************/
    
            /**Broadcast receiver to check if the screen is on **/
            IntentFilter screenStateFilter = new IntentFilter();
            screenStateFilter.addAction(Intent.ACTION_SCREEN_ON);
            screenStateFilter.addAction(Intent.ACTION_SCREEN_OFF);
            registerReceiver(broadcastReceiver, screenStateFilter);
            /***************************************************/
    
        }
    
        @Override
        /**Depending on action issued by MainActivity either puts service in
         *foreground with duration or destroys the service**/
        public int onStartCommand(Intent intent, int flags, int startId) {
            if(intent != null) {
                if (intent.getAction().equals(Constants.ACTION.STARTFOREGROUND_ACTION)) {
                    if (intent.hasExtra(Constants.TIMER.DURATION))
                        duration = intent.getLongExtra(Constants.TIMER.DURATION, 0);
                    startForeground(Constants.NOTIFICATION_ID.FOREGROUND_SERVICE, createTimerNotification());
                } else if (intent.getAction().equals(Constants.ACTION.STOPFOREGROUND_ACTION)) {
                    stopForeground(true);
                    stopSelf();
                }
            }
            return START_STICKY;
        }
    
        /**Thread the handler uses to push to message queue. This creates a timer effect.**/
        private Runnable timerThread = new Runnable() {
            @Override
            public void run() {
                if(currentTime == duration){
                    stopSelf();
                    return;
                }
                currentTime += 1000;
                sendTimerInfo();
                handler.postDelayed(this,1000);
            }
        };
    
        /**Broadcasts the timer in which the MainActivity will receive it and update the UI**/
        private void sendTimerInfo(){
            Log.d(TAG, "timer running: tick is " + currentTime);
            intent.putExtra(Constants.TIMER.CURRENT_TIME, currentTime);
            sendBroadcast(intent);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d(TAG,"timer service finished");
            unregisterReceiver(broadcastReceiver);
            handler.removeCallbacks(timerThread);
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        /******************** Broadcast Receiver To Check if Screen is on**************************************/
        private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                handler.removeCallbacks(timerThread);
                /**If the screen is back on then update the timer and start it again**/
                if(intent.getAction().equals(Intent.ACTION_SCREEN_ON)){
                    Log.d(TAG,"Screen is turned on");
                    elapsedTimeSinceOff = SystemClock.elapsedRealtime() - timeSinceLastOn;
                    Log.d(TAG," screen was off and updating current time by"+elapsedTimeSinceOff);
                    currentTime += elapsedTimeSinceOff;
                    handler.postDelayed(timerThread,0);
                }
                /**Turns off the timer when the screen is off**/
                else if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)){
                    Log.d(TAG,"Screen is turned off");
                    timeSinceLastOn = SystemClock.elapsedRealtime();
                }
            }
        };
    
        /**Since this is foreground service it must have a notification**/
        private Notification createTimerNotification() {
            Intent notificationIntent = new Intent(this, MainActivity.class);
            notificationIntent.setAction(Constants.ACTION.MAIN_ACTION);
            notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                    | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                    notificationIntent,0);
    
            Bitmap icon = BitmapFactory.decodeResource(getResources(),
                    R.mipmap.ic_launcher);
    
            Notification notification = new NotificationCompat.Builder(this)
                    .setContentTitle("Service Timer")
                    .setTicker("Count up timer")
                    .setContentText("timer")
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
                    .setContentIntent(pendingIntent)
                    .setOngoing(true)
                    .build();
            return notification;
        }
    }
    

    MainActivity:

    public class MainActivity extends Activity {
    
        TextView timerView;
        Intent timerService;
        //Example duration of 3minutes
        long currentTime, duration = 180000;
    
        @Override
        protected void onStart() {
            super.onStart();
            timerService = new Intent(this, TimerService.class);
            //Register broadcast if service is already running
            if(isMyServiceRunning(TimerService.class)){
                registerReceiver(broadcastReceiver, new IntentFilter(Constants.ACTION.BROADCAST_ACTION));
            }
        }
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    
            Button startButton, stopButton;
            timerView = (TextView) findViewById(R.id.timerValue);
            startButton = (Button) findViewById(R.id.startButton);
            stopButton = (Button) findViewById(R.id.stopButton);
    
            //Button to Start the service when pushed
            startButton.setOnClickListener(new View.OnClickListener() {
    
                public void onClick(View view) {
                    if(!isMyServiceRunning(TimerService.class)) {
                        timerService.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
                        timerService.putExtra(Constants.TIMER.DURATION,duration);
                        startService(timerService);
                        registerReceiver(broadcastReceiver, new IntentFilter(Constants.ACTION.BROADCAST_ACTION));
                    }
                }
            });
    
            //Button to stop the service when pushed
            stopButton.setOnClickListener(new View.OnClickListener() {
    
                public void onClick(View view) {
                    if(isMyServiceRunning(TimerService.class)) {
                        timerView.setText("0:00");
                        timerService.setAction(Constants.ACTION.STOPFOREGROUND_ACTION);
                        startService(timerService);
                        unregisterReceiver(broadcastReceiver);
                    }
                }
            });
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if(!isMyServiceRunning(TimerService.class)) {
                //Resets timer if no service is running
                timerView.setText("0:00");
            }
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            if(isMyServiceRunning(TimerService.class)) {
                unregisterReceiver(broadcastReceiver);
                Log.d(MainActivity.class.getSimpleName(), "unregistered broadcast");
            }
        }
    
        /******************** Broadcast Receiver **************************************/
    
        //Receives the broadcast sent out by the service and updates the UI accordingly.
        private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if(!updateUI(intent)){
                    if(!updateUI(timerService)){
                        timerService.setAction(Constants.ACTION.STOPFOREGROUND_ACTION);
                        startService(timerService);
                        showTimerCompleteNotification();
                    }
                }
            }
        };
    
        //Receives the timer from the service and updates the UI
        public boolean updateUI(Intent intent){
            if(!intent.hasExtra(Constants.TIMER.CURRENT_TIME)) return false;
    
            this.currentTime = intent.getLongExtra(Constants.TIMER.CURRENT_TIME, 0L);
    
            if(this.currentTime == duration){
                timerView.setText("0:00");
                Toast.makeText(this,"Timer done",Toast.LENGTH_SHORT).show();
                return false;
            }
    
            int secs = (int) (currentTime / 1000);
            int minutes = secs / 60;
    
            timerView.setText(Integer.toString(minutes) + ":" + String.format("%02d", secs%60));
            return true;
        }
        /******************************************************************************************/
    
    
        /************* Helper Methods ****************************/
        private void showTimerCompleteNotification() {
            Intent resultIntent = new Intent(this, MainActivity.class);
            PendingIntent resultPendingIntent =
                    PendingIntent.getActivity(
                            this,
                            0,
                            resultIntent,
                            PendingIntent.FLAG_UPDATE_CURRENT
                    );
            NotificationCompat.Builder mBuilder =
                    new NotificationCompat.Builder(this)
                            .setSmallIcon(R.mipmap.ic_launcher)
                            .setContentTitle("Timer Done!")
                            .setContentText("Congrats")
                            .setContentIntent(resultPendingIntent)
                            .setColor(Color.BLACK)
                            .setLights(Color.BLUE, 500, 500)
                            .setDefaults(NotificationCompat.DEFAULT_VIBRATE)
                            .setDefaults(NotificationCompat.DEFAULT_SOUND)
                            .setStyle(new NotificationCompat.InboxStyle());
    
            // Gets an instance of the NotificationManager service
            final NotificationManager mNotifyMgr =
                    (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            // Builds the notification and issues it.
            mNotifyMgr.notify(Constants.NOTIFICATION_ID.FOREGROUND_SERVICE, mBuilder.build());
    
            //Cancel the notification after a little while
            Handler h = new Handler();
            long delayInMilliseconds = 5000;
    
            h.postDelayed(new Runnable() {
                public void run() {
                    mNotifyMgr.cancel(Constants.NOTIFICATION_ID.FOREGROUND_SERVICE);
                }
            }, delayInMilliseconds);
        }
    
        private boolean isMyServiceRunning(Class<?> serviceClass) {
            ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
                if (serviceClass.getName().equals(service.service.getClassName())) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    常数类:

    package com.example.admin.servicetimer.service;
    
    public class Constants {
    
    
        public interface ACTION {
            public static String MAIN_ACTION = "com.fahadhd.foregroundservice.action.main";
            public static final String STARTFOREGROUND_ACTION = "com.fahadhd.foregroundservice.action.startforeground";
            public static final String STOPFOREGROUND_ACTION = "com.fahadhd.foregroundservice.action.stopforeground";
            public static final String BROADCAST_ACTION = "com.fahadhd.foregroundservice.action.broadcast";
        }
        public interface TIMER {
            public static final String CURRENT_TIME = "com.fahadhd.foregroundservice.timer.current_time";
            public static final String DURATION = "com.fahadhd.foregroundservice.timer.duration";
        }
    
        public interface NOTIFICATION_ID {
            public static int FOREGROUND_SERVICE = 1;
        }
    }