This question already has answers here:
Accessing views from other thread (Android)
(5 answers)
Closed 4 years ago.
in a tuner application on which I am working right now, I wanted to implement an Indicator which shows if the right pitch is played. For this I have implemented that the color of the indicator only changes when I hold the pitch for atleast 2 seconds.
public void thr(final double v1, final double v2) {
new Thread() {
#Override
public void run() {
super.run();
int i = 0;
while (pitchInHz >= v1 && pitchInHz < v2) {
if(i == 20){ //2 Sekunden
imageView.setBackgroundColor(getResources().getColor(holo_green_light));
}else {
imageView.setBackgroundColor(getResources().getColor(darker_gray));
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
running = false;
}
}.start();
Everytime my application tries to use the thread, the app just crashes.
some ideas why this is happening?
if(pitchInHz >= 72.41 && pitchInHz < 92.41) {
//e
noteText.setText("e"); //this is working
if(!running) {
thr(72.41, 92.41);
running = true;
}
I'm supposing it's because you're trying to access a view from a different thread than the main thread, which you cannot do such thing.
Try running like this:
public void thr(final double v1, final double v2) {
new Thread() {
#Override
public void run() {
super.run();
int i = 0;
while (pitchInHz >= v1 && pitchInHz < v2) {
if(i == 20){ //2 Sekunden
runOnUiThread(new Runnable() {
#override
public void run() {
imageView.setBackgroundColor(getResources().getColor(holo_green_light));
}
})
}else {
runOnUiThread(new Runnable() {
#override
public void run() {
imageView.setBackgroundColor(getResources().getColor(darker_gray));
}
})
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
running = false;
}
}.start();
Related
I made two timers. At some point one of them is increasing and other one is decreasing. I made two integers to increase/decrease every second and use setText for TextView. But for some reason it's not updating. I printed out integers and code was working but text isn't changing for TextView. Here's my code:
TextView timerone, timertwo;
int turn = 1;
int timerOne = 20;
int timerTwo = 20;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_begin_after);
timerone = findViewById(R.id.timerone);
timertwo = findViewById(R.id.timertwo);
timerone.setText(String.valueOf(timerOne));
timertwo.setText(String.valueOf(timerTwo));
Thread counterThread=new Thread(()->{
try{
while(true){
if(turn % 2 == 0) {
timerOne++;
timerTwo--;
}else{
timerOne--;
timerTwo++;
}
Thread.sleep(1000);
}
}catch (Exception e){
}
});
counterThread.start();
}
If you want to modify views from off the UI thread, you need to use runOnUiThread or you will get an error. The following works to update the text views inside the loop.
TextView timerone = findViewById(R.id.timerone);
TextView timertwo = findViewById(R.id.timertwo);
timerone.setText(String.valueOf(timerOne));
timertwo.setText(String.valueOf(timerTwo));
Thread counterThread=new Thread(()->{
try{
while(true){
if(turn % 2 == 0) {
timerOne++;
timerTwo--;
}else{
timerOne--;
timerTwo++;
}
runOnUiThread(() -> {
timerone.setText(String.valueOf(timerOne));
timertwo.setText(String.valueOf(timerTwo));
});
Thread.sleep(1000);
}
}catch (Exception e){
}
});
counterThread.start();
Note, if your version of Java doesn't support lambdas, you can use this instead
runOnUiThread(new Runnable() {
#Override
public void run() {
timerone.setText(String.valueOf(timerOne));
timertwo.setText(String.valueOf(timerTwo));
}
});
Try this solution
Thread counterThread=new Thread(()->{
try{
while(true){
if(turn % 2 == 0) {
timerOne++;
timerTwo--;
}else{
timerOne--;
timerTwo++;
}
// Here you will be updating textview's
runOnUiThread(new Runnable() {
#Override
public void run() {
timerone.setText(String.valueOf(timerOne));
timertwo.setText(String.valueOf(timerTwo));
}
});
Thread.sleep(1000);
}
}catch (Exception e){
}
});
counterThread.start();
The problem is that the method runs only after I press button second time, but the goal is to work for both methods one after another when I press button for the first time.
The first method is runThreadWork(); and after that runThreadRest(); when the first is completed.
This is the part for the button.
btnStartTimer.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(workMinutes > 0 || workSeconds > 0){
runThreadWork();
}else if(isTimerCompleted){
runThreadRest();
}
}
});
That's is the part for the method, basically both methods are the same in logic, but only change are variables for values, except TextView variable isn't changed for both methods, I want to use one TextView to show the output from both methods one after another.
private void runThreadWork() {
new Thread() {
public void run() {
while (workMinutes > 0 || workSeconds > 0) {
while (workSeconds > 0) {
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
workSeconds--;
String timeFormatedCountDownWork = String.format("Work time: " + "%02d:%02d", workMinutes, workSeconds);
txtTimeCountDown.setText(timeFormatedCountDownWork);
if(workSeconds == 0 && workMinutes > 0){
workMinutes--;
workSeconds = 60;
}
}
});
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
isTimerCompleted = true;
}
I need to calculate time between two time. System.currentTimeMillis() returns same value everytime when it called in Thread.
My code is:
#Override
protected void onCreate(Bundle savedInstanceState) {
// Other codes..
start_sec = Math.round(System.currentTimeMillis()/1000);
fin = false;
runThread();
// Other codes..
}
private void runThread() {
new Thread() {
public void run() {
while (i++ < 61) {
if (!running) return;
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
if(!fin){
int len = Math.round(System.currentTimeMillis()/1000) - start_sec;
Log.d("current time: ",String.valueOf( Math.round(System.currentTimeMillis()/1000)));
Log.d("difference is: ", String.valueOf(len));
if(len < 0 && len > 58){
fin=true;
}
timerec.getLayoutParams().width = metrics.widthPixels *(60- len)/60;
timerec.requestLayout();
}
else{
end_game();
running= true;
}
}
});
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
Here is the logs:
...
D/current time:: 1492337024
D/difference is:: 0
D/current time:: 1492337024
D/difference is:: 0
....
It returs same "time". What is the solution?
take time as long. and don't divide it by 1000. the time difference is fraction of seconds.that's why it is showing the same time as you are rounding it.
the difference between two cycle in a while loop is so much less than a second and when your calculating the difference by seconds (you divide current millisecond with 1000) it makes the same second and the difference is 0 seconds.
try to print difference in milliseconds (without dividing).
Try this:
#Override
protected void onCreate(Bundle savedInstanceState) {
// Other codes..
start_sec = System.currentTimeMillis();
fin = false;
runThread();
// Other codes..
}
private void runThread() {
new Thread() {
public void run() {
while (i++ < 61) {
if (!running) return;
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
if(!fin){
int len = System.currentTimeMillis() - start_sec;
Log.d("current time: ",String.valueOf( System.currentTimeMillis()));
Log.d("difference is: ", String.valueOf(len));
if(len < 0 && len > 58){
fin=true;
}
timerec.getLayoutParams().width = metrics.widthPixels *(60- len)/60;
timerec.requestLayout();
}
else{
end_game();
running= true;
}
}
});
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
Math.round() causes the problem.
long len = System.currentTimeMillis()/1000 - start_sec;
Log.d("current time: ",String.valueOf(System.currentTimeMillis()/1000));
Log.d("difference is: ", String.valueOf(len));
This code works altought dividing.
Im a beginner android developer, so bear with me:
Im getting this error: "CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views"
Anyways, i have a media player with two threads, the first one updates a circular progress bar and the second one updates a text view that i want to use to show the time in the mp3 file. The first thread gives me no errors and runs perfectly fine. (I implemented this before the textview update)
The second thread however gives me the error in the title. I've looked into handlers asynctasks and runonuithread but I can't figure out how to utilize any of them since im using a while loop that's constantly updating it.
Also, why is only the second one giving me an error?
new Thread(new Runnable() {
#Override
public void run() {
ProgressBar myProgress = (ProgressBar) findViewById(R.id.circle_progress_bar);
int currentPosition = 0;
int total = mediaPlayer.getDuration();
myProgress.setMax(total);
while (mediaPlayer != null && currentPosition < total) {
try {
Thread.sleep(1000);
currentPosition = mediaPlayer.getCurrentPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
myProgress.setProgress(currentPosition);
}
}
}).start();
new Thread(new Runnable() {
#Override
public void run() {
TextView currentTime = (TextView) findViewById(R.id.textView9);
int currentPosition = 0;
int total = mediaPlayer.getDuration();
while (mediaPlayer != null && currentPosition < total) {
try {
Thread.sleep(1000);
currentPosition = mediaPlayer.getCurrentPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
currentTime.setText(getTimeString(currentPosition));
}
}
}).start();
And here's the code for getTimeString:
private String getTimeString(long millis) {
StringBuffer buf = new StringBuffer();
int hours = (int) (millis / (1000*60*60));
int minutes = (int) (( millis % (1000*60*60) ) / (1000*60));
int seconds = (int) (( ( millis % (1000*60*60) ) % (1000*60) ) / 1000);
buf
.append(String.format("%02d", hours))
.append(":")
.append(String.format("%02d", minutes))
.append(":")
.append(String.format("%02d", seconds));
return buf.toString();
}
do
myProgress.setMax(total);
in
runOnUIThread(new Runnable () {
#Override
public void run () {
myProgress.setMax(total);
}
});
Explanation
Views in android are only work/change/created on UI threads only. Other worker thread donot modify UI elements in Android, because Android is single threaded application.
You can also use AsyncTask which have onPreExecute() and onPostExecute() methods which run on UI thread to post updates to UI
As ρяσѕρєя K say, you can't update view in non-ui thread(Main Thread), so you can use a handler to finish this work
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
// TODO : change currentTime and myProgress as a class number //
TextView currentTime = (TextView) findViewById(R.id.textView9);
ProgressBar myProgress = (ProgressBar) findViewById(R.id.circle_progress_bar);
if (msg.what == 0) {
currentTime.setText(getTimeString(msg.arg1));
} else if (msg.what == 1) {
myProgress.setProgress(msg.arg1);
}
}
};
Then in you two thread, replace
// myProgress.setProgress(currentPosition);
handler.obtainMessage(1, currentPosition, 0).sendToTarget();
// currentTime.setText(getTimeString(currentPosition));
// param1 -> msg.what, param2 -> msg.arg1, parm3 -> msg.arg2
handler.obtainMessage(0, currentPosition, 0).sendToTarget();
Do you Ui work in run On Ui thread :
example:
new Thread(new Runnable() {
#Override
public void run() {
int currentPosition = 0;
int total = mediaPlayer.getDuration();
while (mediaPlayer != null && currentPosition < total) {
try {
Thread.sleep(1000);
currentPosition = mediaPlayer.getCurrentPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
runOnUIThread(new Runnable () {
#Override
public void run () {
ProgressBar myProgress = (ProgressBar) findViewById(R.id.circle_progress_bar);
myProgress.setMax(total);
myProgress.setProgress(currentPosition);
}
});
}
}
}).start();
Eclipse is offering final but I can't increase the i variable.
#Override
public void onClick(View v) {
final TextView tv = (TextView) findViewById(R.id.tvSayac);
int i = 1;
do {
try {
new Thread(new Runnable() {
public void run() {
tv.post(new Runnable() {
public void run() {
tv.setText(Integer.toString(i));
}
});
}
});
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i < 16);
}
A final is an entity that can not be changed after it is initialized.
Final (Java)
What you could do is create a variable within the scope of the do/while loop that is final with the value of i and send that into the function.
The easiest solution here is to create a class:
public class FinalCounter {
private int val;
public FinalCounter(int intialVal) {
val=intialVal;
}
public void increment(){
val++;
}
public void decrement(){
val--;
}
public int getVal(){
return val;
}
public static void main(String[] arg){
final FinalCounter test = new FinalCounter(0);
test.increment(); // 1
test.increment(); // 2
test.increment(); // 3
test.increment(); // 4
test.increment(); // 5
test.decrement(); // 4
System.out.println(test.getVal()); // prints 4
}
}
I think it is possible to create a local copy of the variable i. Try this:
#Override
public void onClick(View v) {
final TextView tv = (TextView) findViewById(R.id.tvSayac);
int i = 1;
do {
final int localCopy = i; // Create here a final copy of i
try {
new Thread(new Runnable() {
public void run() {
tv.post(new Runnable() {
public void run() {
// use here the copy
tv.setText(Integer.toString(localCopy));
}
});
}
}).start(); // Don't forget to start the Thread!
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i < 16);
}
By creating a final local copy:
the compiler won't complain anymore
because of Java copies by value, you will only increase i and not localCopy.
I suppose you want to start the Thread as well...
EDIT: Indeed, you were right. You have to create the local final copy inside the loop. Check the new code.
A final variable can only be initialized once not necessarily when you are defining it. It can be set any time within the constructor , but only once. In your case when you are incrementing i using i++, you are trying to assign the incremented value to i again which is not allowed.
You could create a counter class like that and increment it. This way, the reference of the Counter object could be final but you could still set its value ?
What I did was add a:
private int i;
Before this:
#Override
public void onClick(View v) {
final TextView tv = (TextView) findViewById(R.id.tvSayac);
i = 1;
do {
try {
new Thread(new Runnable() {
public void run() {
tv.post(new Runnable() {
public void run() {
tv.setText(Integer.toString(i));
}
});
}
});
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i < 16);
}
And you'll be able to use your variable as usual after that, without having to mark it as final.