android-16是什么意思,每日一问 很多时候我们说"Android16.6ms刷新一次屏幕" 正确吗?...

本文详细解析了Android系统的屏幕刷新机制,包括onDraw回调频率、ViewRootImpl、Choreographer、DisplayEventDispatcher等组件的工作原理,并对比了6.0和9.0系统的差异。重点讨论了刷新频率变化和缓存机制在不同版本中的表现。

哈哈哈,终于支持markdown啦

每过16.6ms都会回调一次View的onDraw方法吗?

不是的,这个频率,说的是系统发出屏幕刷新信号频率(但我也不太确定是不是真的16.6ms(在源码里没看到过)),但是在onDraw中收到回调的时机,是不确定的(各种原因,如代码写法、设备性能等)。

那它的刷新机制是怎样的呢?

屏幕刷新大致流程:

View调用invalidate方法;

ViewRootImpl会把doTraversal任务(处理View的测量、布局、绘制),post到Choreographer中(在系统下一次发出同步信号的时候,这个doTraversal任务会被执行);

Choreographer会借助DisplayEventReceiver的scheduleVsync方法,在native层(IDisplayEventConnection.cpp)通过Binder(跨进程)发出一个REQUEST_NEXT_VSYNC的Tag(发去了哪里不知道,我找不到那个类);

在DisplayEventDispatcher.cpp中,会看到一个handleEvent方法(可以猜测,系统在下一次发出屏幕刷新信号时,(间接或直接)回调的就是这个方法),它里面会调用android_view_DisplayEventReceiver.cpp中的dispatchVsync方法,这个方法最终去了哪里呢?

回到java层,在DisplayEventReceiver中会看到一个同名同签名的dispatchVsync方法,并且上面有注释写着:Called from native code. (可以知道,这个方法是在native层被调用的),它里面会调用onVsync方法,

而onVsync方法在Choreographer中内部类的实现,最终是会调用doFrame方法的,这个doFrame方法,里面会把刚刚在ViewRootImpl中post到Choreographer里的doTraversal任务(处理View的测量、布局、绘制)执行!!。

以上提到的native层代码,分别(按顺序)在以下链接中能看到:

这个机制在版本迭代中有何变化?

刚刚分别试了下手头上的6.0和9.0系统的测试机,

在一个自定义View中的onDraw方法不断invalidate和打印被回调的时间:

private long lastTime;

@Override

public void onDraw(Canvas canvas) {

LogUtil.print(SystemClock.uptimeMillis() - lastTime);

lastTime = SystemClock.uptimeMillis();

invalidate();

}

发现在6.0的手机是上这样的(随机截取20条log):

onDraw: 14

onDraw: 0

onDraw: 13

onDraw: 0

onDraw: 14

onDraw: 1

onDraw: 14

onDraw: 1

onDraw: 13

onDraw: 0

onDraw: 14

onDraw: 1

onDraw: 14

onDraw: 1

onDraw: 13

onDraw: 1

onDraw: 10

onDraw: 1

onDraw: 12

onDraw: 1

而9.0系统的手机是这样的(随机截取20条log):

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 16

onDraw: 15

onDraw: 15

onDraw: 16

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 15

onDraw: 14

onDraw: 15

onDraw: 15

可以看到,在6.0系统上,好像有个缓存机制,不断地invalidate的话,每两次中有一次是<=1ms就回调了,可以猜想这一次没有等待系统的屏幕刷新信号就直接回调了onDraw方法。

而9.0系统则比较稳定,onDraw方法每一次被回调都间隔了15ms左右.

<think>好的,我现在需要解决个关于Android开发的题:每隔80毫秒非常准时地执行个方法。用户希望这个定时执行尽可能准确,所以我要仔细分析可能的方法和它们的优缺点。 首先,我想到Android中常用的定时任务方法,比如Handler.postDelayed、Timer、ScheduledExecutorService,还有AlarmManager。不过,这些方法各自有不同的特性和适用场景。比如,Handler和Timer可能在主线程运行,如果主线程有延迟,它们的执行时间可能不准确。而ScheduledExecutorService虽然比Timer更可靠,但仍然可能受系统负载影响。 然后,用户提到需要“非常准时”,所以可能需要更高精度的定时机制。Android系统本身并不是实时操作系统,所以绝对的准时可能很难,但可以尽量优化。接下来,我想到Choreographer,它通常用于VSync同步,比如在UI渲染时使用。它的FrameCallback可以提供个相对精确的定时,因为它是基于屏幕刷新的频率(通常是60Hz,约16.6ms帧)。不过,80ms大约是5帧的时间,这样可能通过累加帧数来实现每隔5帧执行一次。但这种方法是否可行呢?需要检查时间间隔是否准确,比如5*16.6≈83ms,接近但不完全准确,可能会有误差。不过如果用户可以接受轻微误差,这可能是个选项。 另外,考虑使用Handler结合SystemClock.uptimeMillis()来补偿延迟。例如,每次执行任务后,计算下一次执行的时间点,如果发现延迟了,就调整后续的执行时间。这种方法需要自己实现个循环,并在每次执行后重新调度任务,确保累积误差不会太大。这可能比简单的postDelayed更准确,因为每次都会修正下一次的时间点。 还有,Java中的ScheduledThreadPoolExecutor允许指定核心线程数,并且使用scheduleAtFixedRate方法。但这个方法的题在于,如果任务执行时间超过间隔,或者系统繁忙,后续任务会被延迟或排队,导致时间不准确。不过,如果任务本身执行时间很短,可能影响较小。 另个可能性是使用AlarmManager的setExact方法,但AlarmManager在较新的Android版本中精度可能受限,尤其是省电模式和其他优化可能会影响其准确性。此外,AlarmManager更适合较长时间间隔或唤醒设备执行任务,不太适合高频的80ms间隔。 然后,我还需要考虑是否需要在后台执行,如果应用在后台,某些定时方法可能被系统限制,导致任务无法准时执行。这种情况下,可能需要使用前台服务来保持进程优先级,但这会增加实现的复杂性。 此外,Android的WorkManager并不适合这种高频定时任务,它更多用于延迟任务和周期性任务,但最小间隔是15分钟,所以这里显然不适用。 再考虑Native层的方法,比如使用JNI调用C/C++代码,利用更高精度的定时器。但这样会增加开发难度,并且不同设备上的表现可能不致,还可能引起兼容性题。 综合以上分析,可能的解决方案是使用Choreographer的FrameCallback,结合Handler的自适应调度,或者使用个独立的线程循环,通过精确的时间计算和睡眠来尽可能接近80ms的间隔。例如,在子线程中循环执行,计算每次执行的时间差,然后动态调整下一次的等待时间,使用SystemClock.sleep()或者更精确的等待方法,比如LockSupport.parkNanos(),来减少误差。 但是,需要注意的是,线程的睡眠并不完全精确,尤其是在用户空间,可能会受到系统调度的影响。因此,即使这样,也无法保证绝对准时,但可以尽可能减少误差。 最后,可能需要测试不同方法在实际设备上的表现,选择最适合当前场景的方案。同时,需要考虑到任务执行的时间,如果任务本身耗时较长,必须确保其执行时间远小于80ms,否则无论哪种方法都会产生累积延迟。</think>在Android开发中实现每隔80ms准时执行方法,需综合考虑系统限制和精度需求。以下是分步解决方案及注意事项: --- ### **1. 常规方案及缺陷分析** - **`Handler.postDelayed()`/`Timer`** - **题**:依赖主线程消息队列,易受UI操作或ANR影响,误差较大。 - **`ScheduledExecutorService`** - **改进**:使用独立线程(如`Executors.newSingleThreadScheduledExecutor()`),但仍受系统负载影响。 - **`AlarmManager`** - **不适用**:最小间隔为分钟级,且系统会限制精确唤醒。 --- ### **2. 高精度方案推荐** #### **方案:Choreographer + 帧同步(适用于UI相关操作)** - **原理**:基于屏幕刷新率(通常60Hz≈16.67ms/帧),每5帧触发一次(5×16.67≈83.3ms)。 - **实现**: ```kotlin val choreographer = Choreographer.getInstance() var frameCount = 0 val callback = object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { frameCount++ if (frameCount % 5 == 0) { // 每5帧执行一次 yourMethod() frameCount = 0 } choreographer.postFrameCallback(this) } } choreographer.postFrameCallback(callback) ``` - **优点**:与VSync同步,稳定性高。 - **缺点**:间隔固定为83.3ms,存在约3ms误差。 --- #### **方案二:Handler + 动态补偿(最佳实践)** - **原理**:每次执行后计算下一次准确时间点,补偿延迟。 - **实现**: ```kotlin private val handler = Handler(Looper.getMainLooper()) private val interval = 80L private var nextTime = SystemClock.uptimeMillis() private val task = object : Runnable { override fun run() { val currentTime = SystemClock.uptimeMillis() yourMethod() // 计算下一次执行时间(动态补偿延迟) nextTime += interval val delay = nextTime - currentTime handler.postDelayed(this, maxOf(0, delay)) } } // 启动任务 handler.postDelayed(task, interval) ``` - **优点**:自动纠正累积误差,精度较高。 - **注意**:任务本身执行时间需远小于80ms--- #### **方案三:独立线程 + 精确休眠** - **原理**:在子线程中循环执行,通过`System.nanoTime()`高精度计时。 - **实现**: ```kotlin private val isRunning = AtomicBoolean(true) thread { var nextTime = System.nanoTime() val intervalNs = 80L * 1_000_000 // 80ms转纳秒 while (isRunning.get()) { yourMethod() // 计算下一次执行时间并休眠 nextTime += intervalNs val sleepTimeNs = nextTime - System.nanoTime() if (sleepTimeNs > 0) { LockSupport.parkNanos(sleepTimeNs) } else { // 超时,重置时间点避免累积延迟 nextTime = System.nanoTime() } } } ``` - **优点**:避开主线程干扰,精度更高。 - **缺点**:线程调度仍受系统影响,需处理生命周期(如页面退出时停止线程)。 --- ### **3. 注意事项** 1. **避免主线程阻塞**:任务逻辑需轻量,否则引发ANR。 2. **设备兼容性**:部分低端设备帧率可能波动(如60Hz→50Hz),影响方案精度。 3. **后台限制**:应用退到后台时,系统可能限制CPU唤醒,需结合前台服务维持优先级。 4. **性能监控**:使用`Systrace`工具分析定时任务的实际执行间隔。 --- ### **4. 终极方案:Native层实现** 若对精度要求极高(如音视频同步),可通过JNI调用C/C++的`高精度定时器`(如`timerfd` + `epoll`): 1. 创建Native线程,使用`clock_nanosleep`或`pthread_cond_timedwait`。 2. 结合Linux的实时调度策略(`SCHED_FIFO`),需Root权限。 --- ### **总结** - **推荐方案**:优先使用 **Handler动态补偿**,平衡精度与实现复杂度。 - **极端需求**:考虑Native层方案,但需权衡维护成本和设备兼容性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值