0

I spent a while trying to find out why my emulator keeps freezing, turned out to be because of a while loop I had introduced. After button is clicked, a random number (LoadG1) is outputted then disappears after 4 seconds. User then inputs a number, if it's equal to loadg1 they get a point and program is looped by having another loadg1 available for 4 secs before disappearing etc...

I can't have the loop unless it's its own thread apparently. I made a thread for it and then tried. The whole app freezing after button press didn't happen, but when the loadg1 was up for 4 seconds, the app shut down. It's as if the first line of loop worked, then failed after the 4 seconds was over. I received the following error:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

After querying this I saw that I need to put it in a runOnUiThread, so I did, only to encounter the same issue. Here's my code:

import android.os.CountDownTimer;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.Random;


public class game1 extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_game1);


final Button loseStarter1;

    loseStarter1 = (Button) findViewById(R.id.Starter1);
    loseStarter1.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            loseStarter1.setVisibility(View.GONE);

            final int[] score = {0};
            Random generateG1 = new Random();
            final int loadG1 = generateG1.nextInt(1000000)+10000;
            final TextView number = (TextView) findViewById(R.id.number);
            number.setText(" "+loadG1);

            new CountDownTimer(18000, 1000) {
                @Override
                public void onTick (long millisUntilFinished) {
                }
                public void onFinish() {
                    TextView result = (TextView) findViewById(R.id.outcome);
                    result.setText("Score: "+ score[0]);
                    TextView prompt = (TextView) findViewById(R.id.prompt);
                    prompt.setVisibility(View.GONE);
                }
            }.start();

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Runnable myRunnable = new Runnable() {
                        @Override
                        public void run() {
                            while (true) {
                                SystemClock.sleep(4000);
                                number.setVisibility(View.GONE);
                                final TextView prompt = (TextView) findViewById(R.id.prompt);
                                prompt.setText(" Enter the number");
                                final EditText input = (EditText) findViewById(R.id.enterAnswer);
                                input.setVisibility(View.VISIBLE);
                                input.setOnKeyListener(new View.OnKeyListener() {
                                    @Override
                                    public boolean onKey(View v, int keyCode, KeyEvent event) {
                                        if (event.getAction() == KeyEvent.ACTION_DOWN) {
                                            switch (keyCode) {
                                                case KeyEvent.KEYCODE_ENTER:
                                                    Editable answer = input.getText();
                                                    int finalAnswer = Integer.parseInt(String.valueOf(answer));
                                                    int finalLoadG1 = Integer.parseInt(String.valueOf(loadG1));
                                                    input.setVisibility(View.GONE);
                                                    prompt.setVisibility(View.GONE);
                                                    if (finalAnswer == finalLoadG1) {
                                                        score[0]++;
                                                    }

                                                    return true;
                                                default:
                                            }
                                        }
                                        return false;
                                    }
                                });
                            }
                        }
                    };

                    Thread myThread = new Thread(myRunnable);
                    myThread.start();

                }
            });

            }
        });
    }

}

I felt like I was so close to resolving this issue, would greatly appreciate all help given.

EDIT: I have tried an alternative solution without a loop, code here:

runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    SystemClock.sleep(4000);
                                    number.setVisibility(View.GONE);
                                    final TextView prompt = (TextView) findViewById(R.id.prompt);
                                    prompt.setText(" Enter the number");
                                    final EditText input = (EditText) findViewById(R.id.enterAnswer);
                                    input.setVisibility(View.VISIBLE);
                                    input.setOnKeyListener(new View.OnKeyListener() {
                                        @Override
                                        public boolean onKey(View v, int keyCode, KeyEvent event) {
                                            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                                                switch (keyCode) {
                                                    case KeyEvent.KEYCODE_ENTER:
                                                        Editable answer = input.getText();
                                                        int finalAnswer = Integer.parseInt(String.valueOf(answer));
                                                        int finalLoadG1 = Integer.parseInt(String.valueOf(loadG1));
                                                        input.setVisibility(View.GONE);
                                                        prompt.setVisibility(View.GONE);
                                                        if (finalAnswer == finalLoadG1) {
                                                            score[0]++;
                                                        }

                                                        return true;
                                                    default:
                                                }
                                            }
                                            return false;
                                        }
                                    });

                                }
                            });

                        }
                    }, 0, 4000);
                }
            });

Now the random number is generated and displayed for 4 seconds, but when user input is made available the app freezes. Apparently 238 frames are skipped (I believe every four seconds this is shown), and the 'main thread' is doing too much work. NOTE: EVEN WITHOUT THE RUNONUITHREAD ENCAPSULATING MY CODE, THE SAME OUTCOME OCCURS. I TRIED BOTH WAYS WITH NO DIFFERENCE FOUND.

Would honestly appreciate anyone who can help me resolve this.

User44
  • 140
  • 8

1 Answers1

1

don't do while(true) with UI operations, this is why you have stuck, Even more don't use while(true) at all - this is bad practice.

if you need to run some repeat operations use timer for this, you can start/stop timer, you can start another timer, even several in one time )

Don't make UI operations in threads, make them only on UI thread.

for hard operations use async tasks - and when all hard operations done then update UI. (in async task you can update UI by publishing progress), on stackoverflow tons of samples for async tasks.

OnKeyListener - runs on non UI thread, but please read above and avoid using while(true)

update to answer:

okay if you want to run like this and dont want to have lags then do following:

create timer - no need to start it in runOnUIThread. all codes with calculations move ti async task which you will start in timer, in async task before run - stop timer to avoid run again, make all calculations in async task, on end async task - update ui and (or you can update on publish progress) and on end of async task you can start your timer again. if you need delay before next run use Handler and postDelayed

Stepan Maksymov
  • 2,618
  • 19
  • 31
  • Could you actually tell me what needs to be changed in my code? I made an int variable == 1 and set the while loop to that variable less than 2 and tried it. Seems like the same thing as while true and I just got the same output. – User44 Oct 13 '16 at 20:07
  • just remember one time and for all - if you touch anything from UI in separate thread then you got stuck until thread will end it's work. If you want your UI to be updated - use timer, so your code in thread will finish it's work, and your UI will be updated. – Stepan Maksymov Oct 13 '16 at 21:21
  • As mentioned, even when the timer is out of the UI thread I still receive the lag issue. Please read over my latest edit, thank you. – User44 Oct 14 '16 at 11:07
  • That doesn't seem to work out, because I get an error being told that setOnKeyListener must be in the main UI thread. If you could please insert the corrected version of my code, I'm sure it would help resolve my issue. – User44 Oct 14 '16 at 17:56
  • in last version with timer - remove this line - `SystemClock.sleep(4000);` replace it with `timer.cancel();` and later when user entered number - start timer again. – Stepan Maksymov Oct 14 '16 at 18:12
  • Once you cancel a timer, you cannot start it again. If you have a valid solution, please enter it in your post. Thank you. – User44 Oct 15 '16 at 09:17
  • what not let you to start another timer again ? ) – Stepan Maksymov Oct 15 '16 at 10:30
  • How would I do this? – User44 Oct 15 '16 at 11:31
  • This has been resolved using an alternative method, thank you for your contribution. I will mark your answer as the resolution regardless, I appreciate your time. – User44 Oct 15 '16 at 16:58