androidmultithreadingrunnablelagchronometer

How to start new thread for chronometer in fragment


I'm making a chronometer app. And its obviously lagging on my older device but not so much on newer. So after research, I understood that I have to make a new thread and update UI thread from it. But no matter what I cant get rid of that lag.

Here is the chronometer fragment stripped down to important components. Any help would be appreciated. Thanks

public class ChronometerFragment extends Fragment { private static final String TAG = "ChronometerFragment";

private Button btnRestart,btnStartStop;
private TextView time;
private int currentTime;
private long startTime = 0L;
private Handler customHandler = new Handler();

long timeInMilliseconds = 0L;
long timeSwapBuff = 0L;
long updatedTime = 0L;
private int running = 0;

private TimeConverter timeConverter; CycleDataListener callback;

DecimalFormat df = new DecimalFormat("#.#####");

public ChronometerFragment(){}

public interface CycleDataListener{
    void fragmentDataUpdate(Bundle  bundle);
}

@Override
public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    callback = (CycleDataListener)getActivity();
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    final View view = inflater.inflate(R.layout.fragment_chronometer,container,false);
    initWidgets(view);
    view.setKeepScreenOn(true);
    df.setRoundingMode(RoundingMode.CEILING);
    timeConverter = new TimeConverter();

    btnRestart.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d(TAG, "onClick: button restart" );
            resetTime();
        }
    });



    btnStartStop.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.d(TAG, "onClick: button startStop");
            btnStartStop.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);

            if(running == 0){
                running = 1;
                btnStartStop.setText(R.string.stop);
                startTime = SystemClock.uptimeMillis();
               customHandler.postDelayed(updateTimerThread, 0);
            }else{
                running = 0;
                btnStartStop.setText(R.string.start);
                timeSwapBuff += timeInMilliseconds;
                customHandler.removeCallbacks(updateTimerThread);
            }
        }
    });

    
    return view;

}

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
}

private Runnable updateTimerThread = new Runnable() {
    @Override
    public void run() {
        timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
        updatedTime = timeSwapBuff + timeInMilliseconds;

        timeConverter.setUpTime(updatedTime);
        time.setText(timeConverter.getTimeStringFormat());
        customHandler.postDelayed(this, 0);

    }
};



/**
 * *********************helper methods**********************
 */



private void resetTime(){
    running = 0;
    btnStartStop.setText(R.string.start);
    timeSwapBuff += timeInMilliseconds;
    startTime = 0L;
    timeInMilliseconds = 0L;
    timeSwapBuff = 0L;
    updatedTime = 0L;
    customHandler.removeCallbacks(updateTimerThread);
    time.setText("00:00:00:000");
    
}



private void initWidgets(View v){
    btnRestart = v.findViewById(R.id.restart_button);
    btnStartStop = v.findViewById(R.id.start_stop_button);
    time = v.findViewById(R.id.currentTime);
  
}

}


Solution

  • This line cause lagging. You posted the runnable almost immediately on the mainthread.

    customHandler.postDelayed(this, 0);
    

    Change to

    customHandler.postDelayed(this, 10);
    

    The best solution is to only use the main handler to update UI. Time calculation and conversion should be handled by a background handler associated with a HandlerThread to avoid UI blocking.

    private Handler mainHandler = new Handler(Looper.getMainLooper());
    
    private HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
    
    private Handler backgroundHandler;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        handlerThread.start();
        backgroundHandler = new Handler(handlerThread.getLooper());
    }
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ...
        btnStartStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "onClick: button startStop");
                btnStartStop.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
    
                if (running == 0){
                    running = 1;
                    btnStartStop.setText(R.string.stop);
                    startTime = SystemClock.uptimeMillis();
                    backgroundHandler.post(updateTimerThread);
                } else {
                    running = 0;
                    btnStartStop.setText(R.string.start);
                    timeSwapBuff += timeInMilliseconds;
                    backgroundHandler.removeCallbacks(updateTimerThread);
                }
            }
        });
    }
    
    private Runnable updateTimerThread = new Runnable() {
        @Override
        public void run() {
            timeInMilliseconds = SystemClock.uptimeMillis() - startTime;
            updatedTime = timeSwapBuff + timeInMilliseconds;
    
            timeConverter.setUpTime(updatedTime);
            final String timeWithFormat = timeConverter.getTimeStringFormat();
            mainHandler.post(() -> time.setText(timeWithFormat);)
            backgroundHandler.postDelayed(this, 10);
    
        }
    };
    
    @Override
    public void onDestroy() {
        handlerThread.quitSafely();
        super.onDestroy()
    }