You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GridView中多Runnable+Handler定时器运行数分钟后出现跳秒问题

解决GridView多定时器运行15分钟后跳秒的问题

Hey there, let's break down why your GridView timers are skipping seconds after running for 15 minutes, and walk through how to fix these issues properly.

核心问题分析

Let's start with the key flaws in your current implementation:

1. Runnable管理完全混乱

  • Every time getView is called (like when the GridView scrolls or you call notifyDataSetChanged), you create a new Runnable and add it to updateTimerThread. Over time, this list gets stuffed with useless, duplicate Runnables—meaning multiple threads might update the same item at once, causing time display chaos.
  • You're using updateTimerThread.get(position + 1) to trigger the timer, which is a clear index mistake. Position starts at 0, so this will either grab the wrong Runnable or throw an out-of-bounds error entirely.

2. Time calculation relies on imprecise postDelayed

  • Using postDelayed(this, 1000) to update every second isn't perfectly accurate. If the main thread is busy with other UI tasks or layout calculations, the actual delay will be longer than 1 second. Your current logic accumulates this error over time, which is why you see noticeable skips after 15 minutes.
  • The way you reset startTime when starting the timer also isn't properly calibrated for paused/resumed states, adding to the inaccuracy.

3. GridView reuse causes state glitches

  • GridView recycles convertView to save memory, but you never clean up old Runnable callbacks when reusing a ViewHolder. This means old timers keep running in the background and clash with new ones, making the time jump around.

4. State switch logic is sloppy

  • When pausing or stopping a timer, you try to remove callbacks but fail because of the index error or duplicate Runnables. This leaves timers running silently in the background, so when you restart them, the time calculation is way off.

修复方案与优化指导

Here's how to fix these issues step by step:

1. Bind Runnables directly to your data model

Instead of a global list, store each timer's Runnable directly in the Orders object—this ensures one timer per data item, no duplicates. First, add these fields to your Orders class:

private Runnable timerRunnable;
private boolean isTimerRunning;

Then rewrite the getView method to manage timers properly:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;
    if (convertView == null) {
        holder = new ViewHolder();
        convertView = lf.inflate(R.layout.list_item3, parent, false);
        holder.status = convertView.findViewById(R.id.status);
        holder.tvProduct = convertView.findViewById(R.id.tvProduct);
        holder.tvRunningTime = convertView.findViewById(R.id.tvTimeRemaining);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    final Orders order = getItem(position);
    holder.tvProduct.setText("Token : " + order.getTableno());

    // Clean up old timer first to avoid duplicates
    if (order.getTimerRunnable() != null) {
        MeterActivity.mHandler.removeCallbacks(order.getTimerRunnable());
    }

    // Use WeakReference to avoid memory leaks
    final WeakReference<ViewHolder> holderRef = new WeakReference<>(holder);
    Runnable timerRunnable = new Runnable() {
        @Override
        public void run() {
            ViewHolder currentHolder = holderRef.get();
            if (currentHolder == null || !order.isTimerRunning()) {
                return;
            }

            // Calculate time accurately using absolute system time
            long currentUptime = SystemClock.uptimeMillis();
            long elapsedTime = currentUptime - order.getStartTime() + order.getTimeSwapBuff();
            
            int seconds = (int) (elapsedTime / 1000);
            int minutes = seconds / 60;
            seconds = seconds % 60;
            
            currentHolder.tvRunningTime.setText(String.format("%d:%02d", minutes, seconds));
            order.setRate((long) (minutes * order.getIntervaltime()));
            
            MeterActivity.mHandler.postDelayed(this, 1000);
        }
    };
    order.setTimerRunnable(timerRunnable);

    // Handle each state cleanly
    switch (order.getStatus()) {
        case 0: // Not Started
            holder.status.setText("Status : Not Started");
            order.setTimerRunning(false);
            holder.tvRunningTime.setText("0:00");
            break;
        case 1: // Ready to Start
            holder.status.setText("Status : Ready to Start");
            order.setTimerRunning(false);
            holder.tvRunningTime.setText("0:00");
            break;
        case 2: // Started
            holder.status.setText("Status : Started");
            order.setTimerRunning(true);
            // Calibrate start time for first run or resume
            if (order.getTimeSwapBuff() == 0) {
                order.setStartTime(SystemClock.uptimeMillis());
            } else {
                order.setStartTime(SystemClock.uptimeMillis() - order.getTimeSwapBuff());
            }
            MeterActivity.mHandler.post(timerRunnable);
            break;
        case 3: // Paused
            holder.status.setText("Status : Paused");
            order.setTimerRunning(false);
            // Save elapsed time to resume later
            long elapsedPause = SystemClock.uptimeMillis() - order.getStartTime() + order.getTimeSwapBuff();
            order.setTimeSwapBuff(elapsedPause);
            MeterActivity.mHandler.removeCallbacks(timerRunnable);
            break;
        case 4: // Stopped
            holder.status.setText("Status : Stopped");
            order.setTimerRunning(false);
            order.setTimeSwapBuff(0);
            order.setStartTime(0);
            MeterActivity.mHandler.removeCallbacks(timerRunnable);
            holder.tvRunningTime.setText("0:00");
            break;
    }

    return convertView;
}

2. Calculate time using absolute system time

Instead of accumulating time based on postDelayed intervals, we calculate elapsed time directly using SystemClock.uptimeMillis() every time the Runnable runs. This eliminates cumulative errors—even if the delay is off, the displayed time will always be accurate.

3. Clean up timers when the Activity is destroyed

To avoid memory leaks, add this to your MeterActivity's onDestroy method:

@Override
protected void onDestroy() {
    super.onDestroy();
    for (Orders order : yourOrderList) {
        if (order.getTimerRunnable() != null) {
            mHandler.removeCallbacks(order.getTimerRunnable());
        }
    }
}

4. Simplify BroadcastReceiver handling

When your BroadcastReceiver triggers a state change (start/pause/stop), just update the corresponding Orders object's status and call notifyDataSetChanged(). The Adapter will handle starting/stopping the timer automatically in getView.

内容的提问来源于stack exchange,提问作者user3329075

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:55:21