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