任务是指在执行特定作业(任务 task)时与用户交互的activity。这些Activity按照各自打开的顺序 排列在堆栈(返回栈)中。设备的屏幕 桌面的图标(launcher)是多数据任务的的起点。当用户触摸应用启动器中的图标时,该用户的任务将出现在前台。如果应用不存在任务(应用未曾使用),则会创建一个新的任务,并且该应用的"主Activity将作为堆栈中的根Activity打开"。
萨嘎网站制作公司哪家好,找创新互联!从网页设计、网站建设、微信开发、APP开发、响应式网站设计等网站项目制作,到程序开发,运营维护。创新互联于2013年成立到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联。
当Activity启动另一个Activity时,该新Activity会被推送到堆栈的顶部,如果当前app只有一个栈 且app处于前台,那么这个栈顶的Activity用户应该是可见的,而启动栈顶这个Activity(启动者)仍然保留在堆栈中,但是是处于停止状态。Activity停止时系统会保留其用户信息 在Activity onSaveInstanceSate、 onRestoreInstanceState(Bundle savedInstanceState) 方法 具体可以查看这两个方法。当用户按"返回"按钮时,当前Activity(所谓当前Activity一定是用户可以看到的在栈顶的)会从堆栈顶部弹出(Activity被销毁 走onDestory方法)这里说销毁也不太严格 因为正常情况下会被销毁 在不存在内泄漏导致Activity回收不掉的情况。栈顶的Activity被销毁 它下面的Activity就会恢复执行(下面这个Activity露头你可以看到它了)。堆栈中的Activity永远不会重新排序,我们操作app的功能时 仅仅是 进入页面(压栈)退出页面(弹栈),因此返回栈(弹栈)以 "后进先出"的对象结构运行。
任务是一个有机整体,当用户开始新任务或通过点击"home键" 这个时候应用会切换到“后台”,在后台时该任务中的所有Activity全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅是失败焦点而已
如何更通过api查看当前应用中的任务
发现当前只有一个任务
同时通过这个任务我们可以查看当前栈顶的activity如下图
参考:
手机休眠引发的“血案”
使设备保持唤醒
目的为了后台能够执行定时任务,避免因为设备息屏等操作导致CPU进入睡眠状态,定时任务被暂停,这就需要能够唤醒CPU,使CPU能够起来工作
具有唤醒CPU功能, 唤醒CPU与唤醒屏幕非同一功能。
AlarmManager是安卓系统封装的用于管理RTC 模块,RTC(实时时钟)是一个独立的硬件时钟,可以在CPU休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用AlarmManager来定时执行任务,CPU可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。
AlarmManager 定时任务测试:
MI8 UD:
测试1: 创建一个 Service, Service 中启动一个 AlarmManager 定时任务
息屏后会继续打印Log,但息屏超过1min 后,log 停止输出:
测试2: 创建一个前台通知Service
Service + StartForground + 前台通知 方式,
MI8 UD 息屏后仍继续打印log.
MI 8 + MI 10 经过测试,在长时间息屏状态下, AlarmManager 也会存在不工作情况。
另外,设备处于低电耗模式下, AlarmManager 会停止工作或延迟工作,解决办法: AlarmManager 利弊
手机长时间不操作,CPU 就会进入睡眠状态,会导致 Timer 中的定时任务无法正常运行。
息屏后,TimerTask 停止工作,再次亮屏后,继续工作
同样会由于息屏导致CPU睡眠, Handler 停止工作
太“重”了,使用起来。 影响设备耗电量。
WorkManager 也可以运行后台任务,用于在APP进程被kill后,系统依然可以运行的任务,不要用于APP被杀后,后台服务即停止的任务。
总结:
Timer并不太适用于那些需要长期在后台运行的定时任务。为了能让电池更加耐用,每种手机都会有自己的休眠策略,Android 手机就会在长时间不操作的情况下自动让 CPU 进入到睡眠状态,这就有可能导致 Timer 中的定时任务无法正常运行。
Alarm具有唤醒 CPU 的功能,即可以保证每次需要执行定时任务的时候 CPU 都能正常工作。
AlarmManager 定时任务最小间隔5S, 如何设置间隔 5s, 也是按照 5s 间隔执行。
Android DozeMode
1、普通线程sleep的方式,可用于一般的轮询Polling
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//todo
try {
Thread.sleep(iDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
优点:非常简单的实现,逻辑清晰明了,也是最常见的写法
缺点:在sleep结束后,并不能保证竞争到cpu资源,这也就导致了下次执行时间必定=iDelay,存在时间精度问题
2、Timer定时器
//Timer + TimerTask结合的方法
private final Timer timer = new Timer();
private TimerTask timerTask = new TimerTask() {
@Override
public void run() {
//todo
}
};
启动定时器方法:
timer.schedule(TimerTask task, long delay, long period)
立即执行
timer.schedule(timerTask, 0, 1000); //立刻执行,间隔1秒循环执行
延时执行
timer.schedule(timerTask, 2000, 1000); //等待2秒后再执行,间隔1秒循环执行
关闭定时器方法:timer.cancel();
优点:纯正的定时任务,纯java SDK,单独线程执行,比较安全,而且还可以在运行过程中取消执行
缺点:基于单线程执行,多个任务之间会相互影响,多个任务的执行是串行的,性能较低,而且timer也无法保证时间精确度,是因为手机休眠的时候,无法唤醒cpu,不适合后台任务的定时
3、ScheduledExecutorService
private Runnable runnable2 = new Runnable() {
@Override
public void run() {
//todo
}
};
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(runnable2, 0, 1, TimeUnit.SECONDS);
关于scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 方法说明:
command:需要执行的线程
initialDelay:第一次执行需要延时的时间,如若立即执行,则initialDelay = 0
period:固定频率,周期性执行的时间
unit:时间单位,常用的有MILLISECONDS、SECONDS和MINUTES等,需要注意的是,这个单位会影响initialDelay和period,如果unit = MILLISECONDS,则initialDelay和period传入的是毫秒,如果unit = SECONDS,则initialDelay和period传入的是秒
补充一下: 还有一个方法跟上面的很相似:scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit),这个也是带延迟时间的调度,并且也是循环执行,唯一的不同就是固定延迟时间循环执行,上面的是固定频率的循环执行。那这两者的区别?
举例子:
使用scheduleAtFixedRate,任务初始延迟3秒,任务执行3秒,任务执行间隔为5秒:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Log.e(TAG, "schedule just start! time =" + simpleDateFormat.format(System.currentTimeMillis()));
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
SystemClock.sleep(3000L);
Log.e(TAG, "runnable just do it! time =" + simpleDateFormat.format(System.currentTimeMillis()));
}
}, 3, 5, TimeUnit.SECONDS);
执行结果截图:
使用scheduleWithFixedDelay,任务初始延迟3秒,任务执行3秒,任务执行延迟为5秒:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Log.e(TAG, "schedule just start! time =" + simpleDateFormat.format(System.currentTimeMillis()));
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
SystemClock.sleep(3000L);
Log.e(TAG, "runnable just do it! time =" + simpleDateFormat.format(System.currentTimeMillis()));
}
}, 3, 5, TimeUnit.SECONDS);
执行结果截图:
从这两者的运行结果就可以看到区别了:scheduleAtFixedRate是相对于任务执行的开始时间,而scheduleWithFixedDelay是相对于任务执行的结束时间。
优点:ScheduledExecutorService是一个线程池,其内部使用的延迟队列,本身就是基于等待/唤醒机制实现的,所以CPU并不会一直繁忙。解决了TimerTimerTask存在的问题,多任务处理时效率高
缺点:取消时需要打断线程池的运行,而且和外界的通信不太好处理
4、使用Handler中的postDelayed方法
private Handler mHandler = new Handler();
private Runnable runnable = new Runnable() {
@Override
public void run() {
//todo
mHandler.postDelayed(this, iDelay);
}
};
mHandler.post(runnable); //立即执行
mHandler.postDelayed(runnable, iDelay); //延时执行
mHandler.removeCallbacks(runnable); //取消执行
优点:比较简单的android实现,适用UI线程
缺点:没想到,手动捂脸。。。。我估计是使用不当会造成内存泄露吧
5、Service + AlarmManger + BroadcastReceiver
任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。
当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 因此,返回栈以“后进先出”对象结构运行。
上述文字摘自 Android开发者官网
默认行为的场景 :当前的task包含4个activity–当前activity下面有3个activity。当用户按下HOME键返回到程序启动器(application launcher)后,选择了一个新的应用程序(事实上是一个新的task),当前的task就被转移到后台,新的task中的根activity将被显示在屏幕上。过了一段时间,用户按返回键回到了程序启动器界面,选择了之前运行的程序(之前的task)。那个task,仍然包含着4个activity。当用户再次按下返回键时,屏幕不会显示之前留下的那个activity(之前的task的根activity),而显示当前activity从task栈中移出后栈顶的那个activity。