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>
`
I have referred below stackoverflow link also How to handle multiple countdown timers in ListView?
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;
}
}