I am beginner in android and I am doing my test project. I have such a problem when I start the Handler method then let's say refreshing is approximately desired. But after few secounds this is slower than in the begin, immediately after clicking startGameButton
There are two handler nested to make screen refreshing.
The following code is nested in onCreate() method
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
#Override
public void run() {
MainLoopClass mainLoop;
mainLoop=newMainLoopClass(HelperMethods.context,HelperMethods.rl);
handler.postDelayed(this, 100);
}
};
...
startGameButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
handler.postDelayed(runnable, 100);
}
});
My desired effect is to make refresh every 700 milisecounds
It seems what your code have perfomance side effect
Better solution for your problem is
1. Add RxJava in your app:gradle
android {
...
}
dependencies {
...
implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
...
}
add
val disposable = Observable.interval(700, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
//refresh your UI here
}
PS:
If you call subscribe in MainThread
.observeOn(AndroidSchedulers.mainThread())
is not necessary
When you want to stop update your UI (for example in onStop) - call disposable.dispose()
Related
I've got game based on CountDownTimer, which is continuously repeating countdowns. This countDown is counting time for user to react on some action related to number, if user reacts onFinish() is called by some clickListener or by itself if the time was up. Depending of succesCondition(), method success or fail is called and those methods are defining if game is still running.
OnCreate
loop = gameLoop(time).start();
MainActivity
public CountDownTimer gameLoop(int time){
return new CountDownTimer(time, time+100) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
if (!Conditions.succesCondition(number)) {
success();
} else {
fail();
}
}
};
}
public void success() {
loop.cancel();
scoreCount++;
animation.start();
}
public void fail(){
loop.cancel();
}
However this timer runs on Main thread and that provides well known issue skipped xx frames, your app might be doing too much work on its main thread and I found that this is common issue for CountDownTimer and replacing it with Handler is a solution.
I can't put this timer in AsyncTask because it performs mainly UI related tasks (TextViews, TextSwitcher, some progressBar etc. in success() method. I didn't put that in code in those methods for more clean view of the main problem. I'm trying to reconstruct CountDownTimer- like concept with handler and runnable to replace my Timer, but I'm actually stuck with nothing. As you can see I'm using only onFinish method, onTick is not necessary.
I suggest using a combination of java.util.Timer, java.util.TimerTask and Activity.runOnUiThread(). First create a Timer and call one of its schedule...()methods. Any action that needs to be done on the main (ui) thread can be wrapped in runOnUiThread(() -> { ...}). Be sure to call cancel() on TimerTask and Timer if those objects are no longer needed. Cancelling the Timer cancels the TimerTask as well.
Here is how this may look like:
public class TimerTaskActivity extends Activity {
Timer timer;
TimerTask timerTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timertask);
...
}
#Override
protected void onStart() {
super.onStart();
timer = new Timer();
timerTask = new TimerTask() {
#Override
public void run() {
runOnUiThread(() -> {
....
});
}
};
timer.scheduleAtFixedRate(timerTask, 2000, 2000);
}
#Override
protected void onPause() {
super.onPause();
timer.cancel();
}
}
You may handle this situation using AsyncTask as well overriding the onProgressUpdate method.
Here's an example about how you can achieve the behaviour to interact with your main thread from AsyncTask. The example shows the update of a download which can be easily transformed to your specific problem of timer.
Update
In my case almost all code would be in onProgressUpdate, would it
still make any sense?
No, your code will not be in onProgressUpdate. The onProgressUpdate method will only be updating your timer in your UI. As far as I could understand, the success and the fail will be triggered based on user action as well. Then those actions are triggered, you can stop the AsyncTask to update your timer as well. You just need to AsyncTask to update the timer value time to time.
You will get a callback in your Activity when the AsyncTask finishes. See the mNotificationHelper.completed(); function in the above example. When you are notified in your Activity when the timer finishes, you might then execute the following task there.
public void completed() {
if (!Conditions.succesCondition(number)) {
success();
} else {
fail();
}
}
OK. I finally figured out how to handle it with handler (hehe):
public void startGameAction() {
//My game actions
handler = new Handler();
runnable = () -> {
if (!Conditions.succesCondition(number)) {
success();
} else {
fail();
}
};
handler.postDelayed(runnable,time);
}
public void success(){
handler.removeCallbacks(runnable);
handler = null;
scoreCount++;
//other stuff
startGameAction();
}
private void fail() {
handler.removeCallbacks(runnable);
//other stuff
}
onCreate only startGame call, handler and runnable defined as class fields
startGameAction();
I'm creating a button to stop a light sensor after grabbing two equal light values for a duration of 2 seconds using the handler's delay function. It works fine at first but when I press the button again, the delay seems to get shorter and shorter, and eventually doesn't happen at all.
stateLx.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
onResume();
stateLx.setEnabled(false);
doub2x.setText(Double.toString(0));
doub1x.setText(Double.toString(0));
//Stabilization Handlers
m_handler = new Handler();
m_handlerTask = new Runnable() {
public void run() {
doub1 = movingValue[0];
doub1x.setText(Double.toString(doub1));
m_handler.postDelayed(this, 1000);
}
};
m2_handler = new Handler();
m2_handlerTask = new Runnable() {
public void run() {
doub2 = movingValue[0];
doub2x.setText(Double.toString(doub2));
m2_handler.postDelayed(this, 2000);
if (doub1 != doub2) {
instructions.setText("unequal");
} else {
instructions.setText("equal");
getInitialLightValue();
reCalculateInitial();
beerLambert(volValues, lxValuesNew);
}
}
};
//Handlers for stabilizer
m_handler.postDelayed(m_handlerTask, 1000);
m2_handler.postDelayed(m2_handlerTask, 3000);
lame.setText(Double.toString(absorbValues[0]));
stateLx.setEnabled(true);
}
});
I've tried putting the handlers both in and out of the onclick function, as well as including the onResume() function at the beginning of the button press, but I'm having no luck. Is there something wrong with my code?
You keep recreating your Handlers and Runnables. This is part of the problem, you should check to see if you are already polling before starting again.
Also, this is a side note -- but it's odd to call lifecycle methods, like onResume() yourself, since it calls up to the super.
My problem is pretty simple. I am creating a card based on the result of a HTTP query performed inside a separate thread. The card also has an onclick method and is defined inside a runOnUiThread() located inside the separate thread. However, when the device is tapped, the onclick event isn't fired.
Here is my code:
private void login() {
Runnable r = new Runnable() {
#Override
public void run() {
// irrelevant code
runOnUiThread(new Runnable() {
#Override
public void run() {
setContentView(buildError(code));
}
}
}
Thread t = new Thread(r);
t.start();
}
private View buildError(String code) {
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.ALERT);
card.setIcon(R.drawable.ic_warning_150);
if (code.equals("1"))
card.setText("Incorrect credientals");
else
card.setText("Unexpected error");
card.setFootnote("Tap to try again");
View cView = card.getView();
cView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i("Event", "Clicked"); // This is what isn't triggering
}
});
cView.setFocusable(true);
cView.setFocusableInTouchMode(true);
return cView;
}
Even though the snippet of code contains an error (can't be compiled, missing ; at the Runnable statement), you were on the right track.
The View simply needs to request the focus in order to be clickable right away. Otherwise you'll have to move the focus manually.
cView.setFocusable(true);
cView.setFocusableInTouchMode(true);
cView.requestFocus();
Reference
I've seen some similar questions and got some information but they stop shy of telling me enough to get it working.
What I'm trying to do is make a simple rhythm game where the player taps a button at regular intervals (ie. beats). I wanted to set up a way of signalling when to tap by having the button change colour, and since this would be a repeated task at regular intervals I want to use a timer object with a schedule method.
But when I try calling on this method it tells me that I can't change the UI in a non UI thread. I've tried a few ways to write a method in the main thread that I can call from the timer object but I get the same error every time. I'm assuming that I just have the wrong idea about what counts as being from the UI thread, so I was hoping someone could clear it up.
Here's a snippet of one way I tried it, just to show what my code looks like:
OnClickListener clickButton = new View.OnClickListener() {
public void onClick(View v) {
if (startBeat == 0){
startBeat = System.nanoTime();
timerStart.scheduleAtFixedRate((new TimerTask()
{
public void run()
{
flashButton();
}
}), 0, beatTime);
timerEnd.schedule(new TimerTask()
{
public void run()
{
unflashButton();
}
}, beatTolerance*2, beatTime);
return;
}
};
public void flashButton(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1transparent_very));
}
public void unflashButton(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1));
}
To be clear, this is all contained within my MainActivity class along with the OnCreate class.
if you are in an activity all you need to do is use runOnUiThread() and then place the code to change the ui element in there
public void flashButton(){
runOnUiThread(new Runnable() {
#Override
public void run() {
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1transparent_very));
}
});
}
You cannot, under any circumstances, touch a UI object from a non UI thread.
You can accomplish your intent using Handler.sendMessageDelayed
UI can only be touched by the main thread. You should post the actions you are performing on the ui thread via handler or via runOnUiThread
Try something similar to this
timerStart.scheduleAtFixedRate((new TimerTask()
{
public void run()
{
//replace MainActivity with your activity
//if inside a fragment use getActivity()
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
flashButton();
}
});
}
}), 0, beatTime);
If you are in an Activity you could surround flashButton() with an runOnUiThread.
...
runOnUiThread(new Runnable(){
public void run(){
flashButton();
}
});
...
use android.os.Handler Class. Change your code as follows:
private Handler handler=new Handler();
public void flashButton(){
handler.post(new Runnable(){
public void run(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1transparent_very));
}
});
}
public void unflashButton(){
handler.post(new Runnable(){
public void run(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1));
}
});
}
I have an AsyncTask class that I execute that downloads a big list of data from a website.
In the case that the end user has a very slow or spotty data connection at the time of use, I'd like to make the AsyncTask timeout after a period of time. My first approach to this is like so:
MyDownloader downloader = new MyDownloader();
downloader.execute();
Handler handler = new Handler();
handler.postDelayed(new Runnable()
{
#Override
public void run() {
if ( downloader.getStatus() == AsyncTask.Status.RUNNING )
downloader.cancel(true);
}
}, 30000 );
After starting the AsyncTask, a new handler is started that will cancel the AsyncTask after 30 seconds if it's still running.
Is this a good approach? Or is there something built into AsyncTask that is better suited for this purpose?
Yes, there is AsyncTask.get()
myDownloader.get(30000, TimeUnit.MILLISECONDS);
Note that by calling this in main thread (AKA. UI thread) will block execution, You probably need call it in a separate thread.
Use CountDownTimer Class in side the extended class for AsyncTask in the onPreExecute() method:
Main advantage, the Async monitoring done internally in the class.
public class YouExtendedClass extends AsyncTask<String,Integer,String> {
...
public YouExtendedClass asyncObject; // as CountDownTimer has similar method -> to prevent shadowing
...
#Override
protected void onPreExecute() {
asyncObject = this;
new CountDownTimer(7000, 7000) {
public void onTick(long millisUntilFinished) {
// You can monitor the progress here as well by changing the onTick() time
}
public void onFinish() {
// stop async task if not in progress
if (asyncObject.getStatus() == AsyncTask.Status.RUNNING) {
asyncObject.cancel(false);
// Add any specific task you wish to do as your extended class variable works here as well.
}
}
}.start();
...
change CountDownTimer(7000, 7000) -> CountDownTimer(7000, 1000) for example and it will call onTick() 6 times before calling onFinish(). This is good if you want to add some monitoring.
Thanks for all the good advice I got in this page :-)
In the case, your downloader is based upon an for an URL connection, you have a number of parameters that could help you to define a timeout without complex code:
HttpURLConnection urlc = (HttpURLConnection) url.openConnection();
urlc.setConnectTimeout(15000);
urlc.setReadTimeout(15000);
If you just bring this code into your async task, it is ok.
'Read Timeout' is to test a bad network all along the transfer.
'Connection Timeout' is only called at the beginning to test if the server is up or not.
I don't think there's anything like that built into AsyncTask. Your approach seems to be a good one. Just be sure to periodically check the value of isCancelled() in your AsyncTask's doInBackground method to end this method once the UI thread cancels it.
If you want to avoid using the handler for some reason, you could check System.currentTimeMillis periodically within your AsyncTask and exit on timeout, although I like your solution better since it can actually interrupt the thread.
Context mContext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
//async task
final RunTask tsk = new RunTask ();
tsk.execute();
//setting timeout thread for async task
Thread thread1 = new Thread(){
public void run(){
try {
tsk.get(30000, TimeUnit.MILLISECONDS); //set time in milisecond(in this timeout is 30 seconds
} catch (Exception e) {
tsk.cancel(true);
((Activity) mContext).runOnUiThread(new Runnable()
{
#SuppressLint("ShowToast")
public void run()
{
Toast.makeText(mContext, "Time Out.", Toast.LENGTH_LONG).show();
finish(); //will close the current activity comment if you don't want to close current activity.
}
});
}
}
};
thread1.start();
}
You can put one more condition to make cancellation more robust. e.g.,
if (downloader.getStatus() == AsyncTask.Status.RUNNING || downloader.getStatus() == AsyncTask.Status.PENDING)
downloader.cancel(true);
Inspiring from question I have written a method which do some background task via AsyncTask and if processing takes more then LOADING_TIMEOUT then an alert dialogue to retry will appear.
public void loadData()
{
final Load loadUserList=new Load();
loadUserList.execute();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (loadUserList.getStatus() == AsyncTask.Status.RUNNING) {
loadUserList.cancel(true);
pDialog.cancel();
new AlertDialog.Builder(UserList.this)
.setTitle("Error..!")
.setMessage("Sorry you dont have proper net connectivity..!\nCheck your internet settings or retry.")
.setCancelable(false)
.setPositiveButton("Retry", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
loadData();
}
})
.setNegativeButton("Exit", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
System.exit(0);
}
})
.show();
}
}
}, LOADING_TIMEOUT);
return;
}