Search code examples
androidgridviewhandlerrunnablecountdowntimer

Multiple Runnable in Gridview with Handler Slow down timer after some minutes


I am Developing an application. Token number displaying in each Grid with timer. i have 5 status 1.not started, 2.started, 3.pause, 4.stop, 5.cancel Any time i can start,pause and stop timer through broadcastreceiver i use to refresh grid by notifydatasetchange(). once i started,pause and stop timer is working fine after sometimes(15 min) running timer seconds is skips 3 to 5 seconds. Anyone can guide me whether i am doing right one only or any mistakes help me on this. Thanks in advance Attached Snippets below Adapter,class and layouts

public class CountdownAdapter extends ArrayAdapter<Orders> {

private LayoutInflater lf;
public Context ctx;
private List<Runnable> updateTimerThread;


public CountdownAdapter(Context context, List<Orders> objects) {
    super(context, 0, objects);
    ctx = context;
    lf = LayoutInflater.from(context);
    updateTimerThread = new ArrayList<Runnable>();
}

@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 = (TextView) convertView.findViewById(R.id.status);
        holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
        holder.tvRunningTime = (TextView) convertView.findViewById(R.id.tvTimeRemaining);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    final ViewHolder finalHolder = holder;
    Runnable singlTimerThread = new Runnable() {

      public void run() {
        getItem(position).setTimeInMilliseconds(SystemClock.uptimeMillis() - getItem(position).getStartTime());
        getItem(position).setUpdatedTime(getItem(position).getTimeSwapBuff() + getItem(position).getTimeInMilliseconds());

        int secs = (int) (getItem(position).getUpdatedTime() / 1000);
        int mins = secs / 60;
        secs = secs % 60;

        finalHolder.tvRunningTime.setText("" + mins + ":" + String.format("%02d", secs));// + ":"+ String.format("%03d", milliseconds));

        getItem(position).setRate((long) (mins * getItem(position).getIntervaltime()));

        MeterActivity.mHandler.postDelayed(this, 1000);
      }
    };

    holder.tvProduct.setText("Token : " + getItem(position).getTableno());
    updateTimerThread.add(singlTimerThread);

    //0 means not started
    if (getItem(position).getStatus() == 0) {
        holder.status.setText("Status : Not Started");
    }
    //1 means intialized
    else if (getItem(position).getStatus() == 1) {
        holder.status.setText("Status : Ready to Start");
    }
    //2 means start
    else if (getItem(position).getStatus() == 2) {
        getItem(position).setStartTime(SystemClock.uptimeMillis() - getItem(position).getUpdatedTime());
        MeterActivity.mHandler.postDelayed(updateTimerThread.get(position + 1), 1000);
        holder.status.setText("Status : Started");
    }
    //3 means pause
    else if (getItem(position).getStatus() == 3) {
        holder.status.setText("Status : Paused");
        MeterActivity.mHandler.removeCallbacks(updateTimerThread.get(position + 1));
    }
    //4 means stop
    else if (getItem(position).getStatus() == 4) {
        MeterActivity.mHandler.removeCallbacks(updateTimerThread.get(position + 1));
        holder.status.setText("Status : Stopped");
    }
    return convertView;
}


class ViewHolder {
    TextView tvProduct, status;
    TextView tvRunningTime;
}}  

`

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="#565555"
    android:layout_height="wrap_content">

<TextView
    android:id="@+id/tvProduct"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="2dp"
    android:paddingRight="10dp"
    android:gravity="right|center"
    android:background="@color/gray"
    android:textColor="#203020"
    android:text="Token"
    android:visibility="visible"
    android:layout_gravity="right"
    android:textStyle="bold"
    android:textSize="15sp" />

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="left|center"
    android:textStyle="bold|italic"
    android:text="Status"
    android:id="@+id/status"
    android:padding="5dp"
    android:textColor="@color/white"
    android:textSize="13sp" /><FrameLayout
    android:layout_width="match_parent"
    android:layout_height="85dp">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/cardbackground"
        android:elevation="4dp"
        android:orientation="vertical"
        android:padding="10dp">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:background="@color/cardbackground"
        android:id="@+id/itemlayout"
        android:layout_height="wrap_content">

        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"/>

        <TextView
            android:id="@+id/tvTimeRemaining"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="visible"
            android:textSize="40sp"
            android:layout_margin="5dp"
            android:textStyle="bold|italic"
            android:textColor="@color/redglow"
            android:gravity="center"
            android:text="00:00:00 " />

    </LinearLayout>
   </RelativeLayout>
</LinearLayout>

`

Attached Screenshot

I have referred below stackoverflow link also How to handle multiple countdown timers in ListView?


Solution

  • It appears that the Runnables keep accumulating in the updateTimerThread list which would make the ui less responsive. It's not visible in the code where or how the status of your adapter items gets changed but it seems to be 0 for a bigger amount of time than it is 4 thus more callbacks are added to the list and the handler's message queue than are removed.

    Check with a log if this is the problem and move the Runnables creation in the ViewHolder at least. Ideally you should have only one runnable which you would initialize in the adapter's constructor and the runnable would keep a list of the views/view holders to update.

    Here is a code example:

    public class MeterActivity extends Activity {
    
    //...
    private CountDownAdapter adapter;
    private Timer timer;
    private boolean inScroll = false;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        //...
    
        adapter = new CountDownAdapter(this, new ArrayList<Orders>());
    }
    
    @Override
    protected void onStart() {
        super.onStart();
    
        //...
    
        list.setOnScrollListener(new AbsListView.OnScrollListener() {
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    
            }
    
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                 inScroll = scrollState != SCROLL_STATE_IDLE;
            }
        });
        //create a timer to execute tasks in a background thread
        timer = new Timer();
        //schedule a task to start in and repeat every 1000 ms
        CountdownTask task = new CountdownTask();
        adapter.setTask(task);
        timer.schedule(task, 1000, 1000);
    }
    
    @Override
    protected void onStop() {
        super.onStop();
    
        //...
    
        //stop timer while the activity is in background
        timer.cancel();
        timer = null;
    }
    
    class CountdownTask extends TimerTask {
    
        public Map<Integer, CountDownAdapter.ViewHolder> itemHolders = new ConcurrentHashMap<>();
    
        @Override
        public void run() {
            Orders item;
            if (inScroll) {
                return;
            }
            //loop through all started items and update their runningTime
            List<CountDownAdapter.ViewHolder> holders = new ArrayList<>();
            List<String> texts = new ArrayList<>();
            for (Integer position : itemHolders.keySet()) {
                item = adapter.getItem(position);
                item.setTimeInMilliseconds(SystemClock.uptimeMillis() - item.getStartTime());
                item.setUpdatedTime(item.getTimeSwapBuff() + item.getTimeInMilliseconds());
    
                int secs = (int) (item.getUpdatedTime() / 1000);
                int mins = secs / 60;
                secs = secs % 60;
    
                item.setRate((long) (mins * item.getIntervaltime()));
                holders.add(itemHolders.get(position);
                texts.add("" + mins + ":" + String.format("%02d", secs)); // + ":"+ String.format("%03d", milliseconds
    
            }
            runOnUiThread(new UpdateRunnable(itemHolders.get(position), texts));
        }
    
        class UpdateRunnable implements Runnable {
            CountDownAdapter.ViewHolder[] holders;
            String[] texts;
    
            public UpdateRunnable(CountDownAdapter.ViewHolder[] holder, String[] text) {
                this.holders = holders;
                this.texts = texts;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < holders.length; i++) {
                    holders[i].tvRunningTime.setText(texts[i]);
                }
            }
        };
    }
    }
    

    `

    public class CountDownAdapter extends ArrayAdapter<Orders> {
    
    private MeterActivity.CountdownTask task;
    
    public CountDownAdapter(Context context, List<Orders> objects) {
        super(context, 0, objects);
    }
    
    public void setTask(MeterActivity.CountdownTask task) {
        this.task = task;
    }
    
    @Override
    public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = View.inflate(getContext(), R.layout.list_item3, parent, false);
            holder.status = (TextView) convertView.findViewById(R.id.status);
            holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
            holder.tvRunningTime = (TextView) convertView.findViewById(R.id.tvTimeRemaining);
            convertView.setTag(holder);
        } else {
            //remove the holder from the TimerTask's map to disable updates
            // when the view & holder are recycled; it will be updated only if the status is 2
            task.itemHolders.remove(position);
            holder = (ViewHolder) convertView.getTag();
        }
    
        holder.tvProduct.setText("Token : " + getItem(position).getTableno());
        //0 means not started
        if (getItem(position).getStatus() == 0) {
            holder.status.setText("Status : Not Started");
        }
        //1 means intialized
        else if (getItem(position).getStatus() == 1) {
            holder.status.setText("Status : Ready to Start");
        }
        //2 means start
        else if (getItem(position).getStatus() == 2) {
            getItem(position).setStartTime(SystemClock.uptimeMillis() - getItem(position).getUpdatedTime());
    
            //add the holder to the TimerTask's map to enable updates if the item is started
            task.itemHolders.put(position, holder);            
            holder.status.setText("Status : Started");
        }
        //3 means pause
        else if (getItem(position).getStatus() == 3) {
            holder.status.setText("Status : Paused");
        //MeterActivity.mHandler.removeCallbacks(updateTimerThread.get(position + 1));
        }
        //4 means stop
        else if (getItem(position).getStatus() == 4) {
    
        //MeterActivity.mHandler.removeCallbacks(updateTimerThread.get(position + 1));
            holder.status.setText("Status : Stopped");
        }
        return convertView;
    }
    
    class ViewHolder {
        public TextView tvProduct, status;
        public TextView tvRunningTime;
    }
    
    }