0

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?

Crispert
  • 1,102
  • 7
  • 13
user3329075
  • 43
  • 1
  • 6
  • https://medium.com/@ali.muzaffar/using-concurrency-and-speed-and-performance-on-android-d00ab4c5c8e3 – Adil Jan 27 '18 at 07:38

1 Answers1

0

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;
}

}
Crispert
  • 1,102
  • 7
  • 13
  • I have tried. Only one runnable with timer instead of multiple runnable, runnable updating UI properly but facing same issue delay in seconds after some times... – user3329075 Jan 28 '18 at 11:29
  • By delay you mean the time displayed in the adapter's views or the actual time when the Runnable is executed? – Crispert Jan 28 '18 at 11:45
  • Then you could try a TimerTask instead of using the handler for the main thread as the main thread might be too busy with rendering and UI event delivery/processing. It also helps if there are a lot of views in the adapter or the time needed to compute the values displayed in the views is large. Remember the views have to be updated on the UI thread. – Crispert Jan 28 '18 at 16:55
  • It's working fine with real device, but in Emulator running incorrect and how we can handle while scrolling? it's not synchronized. – user3329075 Feb 05 '18 at 11:49
  • To handle scroll you can set an OnScrollListener for the list view list.setOnScrollListener(...) and pause the countdown during scroll if that is what you want. You can toggle a flag in onScrollStateChange() and check its value in the CountdownTask to skip execution. I edited the code to make it more efficient, as using many UpdateRunnables is unnecessary and too heavy on the CPU. – Crispert Feb 05 '18 at 14:35
  • During scroll timer has to run... It should not pause or stop.. task.itemHolders.remove(position); it's pausing running timer in grid.... – user3329075 Feb 07 '18 at 18:08