Why is there no CalledFromWrongThreadException when a new thread operates UI immediately? - java

I was trying to dynamically add a view in to another view in MainActivity:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout.xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.aronho.testfragment.MainActivity">
<FrameLayout
android:id="#+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
view_test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFF00">
<TextView
android:text="testString"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
I know this code will throw CalledFromWrongThreadException when I start the app:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View view=vi.inflate(R.layout.view_test,null);
final FrameLayout parentView=(FrameLayout) findViewById(R.id.myView);
parentView.addView(view);
new Thread(new Runnable(){
#Override
public void run() {
try{
Thread.sleep(3000);
}catch (Exception e){
}
parentView.removeView(view);
}
}).start();
}
}
but when I remove the Thread.sleep(3000);,eg:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View view=vi.inflate(R.layout.view_test,null);
final FrameLayout parentView=(FrameLayout) findViewById(R.id.myView);
parentView.addView(view);
new Thread(new Runnable(){
#Override
public void run() {
parentView.removeView(view);
}
}).start();
}
}
the app doesn't throw CalledFromWrongThreadException despite I try to remove a view using a new thread. Why would that happen?

The root cause of the crash is the ViewRootImpl.checkThread () method.
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views.");
}
However, the invalidate () method is not called when the view is not measured , the invalidate() method will eventually execute to the checkThread().
ok , see :
new Thread(new Runnable() {
#Override
public void run() {
System.err.println("getMeasuredHeight:" + parentView.getMeasuredHeight());
System.err.println("getMeasuredWidth:" + parentView.getMeasuredWidth());
parentView.removeView(view);
}
}).start();
will get that:
W/System.err: getMeasuredWidth:0
W/System.err: getMeasuredHeight:0
can see, the measurement process has not been completed , so we can change the UI without triggering the invalidate() and not trigger exception.
then, keep it sleep 50 ms:
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("getMeasuredHeight:" + parentView.getMeasuredHeight());
System.err.println("getMeasuredWidth:" + parentView.getMeasuredWidth());
parentView.removeView(view);
}
}).start();
will get that:
W/System.err: getMeasuredHeight:360
W/System.err: getMeasuredWidth:1080
can see, the measurement process has been completed, then , when we change the UI the invalidate will be triggering,and then it call the checkThread().

In short, avoid manipulating Android UI elements off the UI Thread; you're going to run into unpredictable behaviour.
You can synchronize your update to the UI by using Activity.runOnUiThread(Runnable r), which should alleviate the problem. i.e.
new Thread(new Runnable(){
#Override
public void run() {
try{
Thread.sleep(3000);
// Synchronize on the UI Thread.
MainActivity.this.runOnUiThread(new Runnable() { #Override public final void run() {
// Remove the View.
parentView.removeView(view);
} });
}
catch (Exception e){
e.printStackTrace();
}
}
}).start();
However, there is a much better construct you should be using; you should never underestimate the computational burden of allocating and running a new Thread!
You can achieve the exact same functionality using:
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
/* Update the UI. */
}, 3000));
This will achieve the same behaviour, without leaving you having to worry about managing synchronization along the UI Thread. Additionally, you won't be launching a whole new Thread whose only job is to sleep!
This should perform a great deal better.

No need to use a thread for a delay, instead :
new Thread(new Runnable(){
#Override
public void run() {
try{
Thread.sleep(3000);
}catch (Exception e){
}
parentView.removeView(view);
}
}).start();
Do :
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
parentView.removeView(view);
}
}, 3000);

Related

setVisibility on ProgressBar in Android

I have the following progress bar in my Android application:
<ProgressBar
android:id="#+id/progressBar"
android:layout_width="200sp"
android:layout_height="200sp"
android:layout_marginTop="60sp"
android:visibility="gone"
android:indeterminate="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
I also have an ImageView with an image in it. What I want to do is while I'm making a call to an API, make the image invisible and show the ProgressBar in its place, then when it ends show the image again and hide the ProgressBar. However, this does not work:
searchBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ProgressBar progressBar = findViewById(R.id.progressBar);
ImageView image = findViewById(R.id.image);
image.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
makeRequest();
}
});
thread.start();
try {
thread.join();
progressBar.setVisibility(View.INVISIBLE);
image.setVisibility(View.VISIBLE);
} catch (InterruptedException e) {
}
}
});
I've tried various combinations with View.INVISIBLE and View.GONE, nothing works. I've also searched through similar questions on here but none of them answers my question.
I thought maybe it's because it disappears and reappears so quickly I don't notice it, but even if I do Thread.sleep() the progress bar still doesn't show up.
What am I doing wrong?
Calling thread.join() within OnClickListener blocks the UI thread and hence block any UI updates.
To fix your code, you can remove thread.join() and put progressBar.setVisibility(View.INVISIBLE);image.setVisibility(View.VISIBLE); within run() after makeRequest() but warp with runOnUiThread()
searchBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ProgressBar progressBar = findViewById(R.id.progressBar);
ImageView image = findViewById(R.id.image);
image.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
makeRequest();
runOnUiThread(new Runnable() {
#Override
public void run() {
progressBar.setVisibility(View.INVISIBLE);
image.setVisibility(View.VISIBLE);
}
});
}
});
thread.start();
}
});

Editing TextView outside the UI Thread

I have the below code:
new Thread(new Runnable(){
public void run(){
try {
pos = Calculo.Calcular();
mostrarFrases(pos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
where mostrarFrases is:
void mostrarFrases(int pos){
Mostrar = (TextView) findViewById(R.id.Texto);
Mostrar.setText(Frases[pos*2+1], 0, Frases[pos*2+1].length);
}
It works if i haven't any thread but without it does not work. I need thread because i need to wait until Calculo.Calcular() finish its work.
Rule: You cannot manipulate UI elements outside the UI thread.
Here is the proper way to do things:
//Asuming that your activiy is named MainActivity
new Thread(new Runnable() {
public void run() {
try {
pos = Calculo.Calcular();
//Manipulate your UI elements as following
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
mostrarFrases(pos);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
You're not allowed to touch Views on any thread other than the UI thread.
To solve this, try the following:
void mostrarFrases(final int pos){
runOnUiThread(new Runnable() {
public void run() {
Mostrar = (TextView) findViewById(R.id.Texto);
Mostrar.setText(Frases[pos*2+1], 0, Frases[pos*2+1].length);
}});
}
You should not manipulate UI elements on any thread except the main UI thread.
So, in your case you got two choices: either use runOnUIThread(Runnable), or use AsyncTask and do UI manipulations in onPostExecute().
I'd go with AsyncTask - it is intended for this kind of scenarios.
UI manipulation must be done in main/ui thread. You have two options for this case:
You have a reference to your Activity object.
myActivity.runOnUiThread(...);
Bind a handler to the main thread and:
// Main thread
Handler handler = new Handler();
// Another thread
handler.post(new Runnable(){...});

Why my thread doesn't work?

I make an app that can count down. But it doesn't work and it just shows 100 in textview.
Here is my code:
public class MainActivity extends Activity {
private TextView textView;
private Button start;
Thread thread;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView1);
start = (Button) findViewById(R.id.button1);
start.setOnClickListener(onStart);
thread = new Thread( //it's my thread
new Runnable() {
#Override
public void run() {
for (int i = 99; i > 0; i--) {
Log.i("Where am I?", "I'm in for loop .");
try {
textView.setText(String.valueOf(i));
} catch (Exception e) {
Log.e("Exception.getCause", String.valueOf(e.getCause()), e.getCause());
}
Log.i("INDEX", String.valueOf(i));
}
}
});
}
private View.OnClickListener onStart = new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i("Where am I?", "I in View.OnClickListener .");
thread.start();
}
};
}
Update your TextView using runOnUiThread as below...
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(String.valueOf(i));
}
});
Update:
For delay in count you can use Handler as below. Create an object of Handler and create a Thread.
private Handler mTimerHandler = new Handler();
private Runnable mTimerExecutor = new Runnable() {
#Override
public void run() {
//add your code here which should execute after the specified delay
}
};
Then pass that thread inside postDelayed() method Handler with the time which should be delayed to execute the thread as below...
mTimerHandler.postDelayed(mTimerExecutor, 1000);
And if you want to cancel the thread to execute, use removeCallbacks() method as below...
mTimerHandler.removeCallbacks(mTimerExecutor);
Catching for Exception inside the Thread is kind of misleading. As matter of fact, textView.setText(String.valueOf(i)); executed in a Thread different from the UI Thread should make you app crashes for CalledFromTheWrongThreadException. You should use an Handler to execute that line in the UI Thread's context
textView.setText(String.valueOf(i));
has to be used in UI thread only.
textView.setText(String.valueOf(i));
This is UI action, You can handle UI action only in a main thread.
You can send a message to the handle of activity.
You cannot use Thread class to interact with UI you should use AsyncTask or Handler classes.
Sample tutorial: http://www.vogella.com/tutorials/AndroidBackgroundProcessing/article.html
Use AsyncTask instead.
Checkout:
http://www.vogella.com/tutorials/AndroidBackgroundProcessing/article.html

Changing background image at certain time interval

I was trying to develop a android application in which the background changes every 5 seconds I wrote code something like this :
public class BgView extends Activity implements Runnable {
ImageView img;
int [] setImg = {R.drawable.one,R.drawable.two};
RelativeLayout layout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bg_view);
layout = (RelativeLayout) findViewById(R.id.first);
Thread t = new Thread(new BigView);
t.start();
}
#Override
public void run() {
for (int i = 0; i < 2; ++i) {
layout.setBackgroundResource(setImg[i]);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
It compiled fine but when i tried to run this in emulator it stopped after 5 seconds and in logcat i saw an exception which says that i cannot touch Activity class view from other function or something like that.
so i put the whole for loop inside the onCreate() method and now i don't get any exception but all i see is a blank screen for 5 seconds after that i get the last image
I know this question has been repeated but i just don't want to copy paste the code...i want to know why this happened and how can we make this working with least changes
edit :
i get following exception :
E/AndroidRuntime(904): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
And i want to know why putting whole for loop inside onCreate() method didn't work
Only UI thread can/should update the UI views, In your code you are trying to update the views directly from the new thread.
You should create a handler in your activity and then invoke that from your thread at required interval. Then handler should then update the UI. Try the code(I have not compiled the code, just typed it so ther might be typos/errors)
public class BgView extends Activity implements Runnable {
ImageView img;
final int [] setImg = {R.drawable.one,R.drawable.two};
RelativeLayout layout;
interface MyHandler {
void letMeKnow(int i);
}
private MyHandler handler = new MyHandler(){
#Override
void letMeKnow(int i){
layout.setBackgroundResource(setImg[i]);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bg_view);
layout = (RelativeLayout) findViewById(R.id.first);
Thread t = new Thread(new BigView);
t.start();
}
#Override
public void run() {
for (int i = 0; i < 2; ++i) {
handler.letMeKnow(i);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} `
Put this line inside ui thread like that as you can't modify in the ui thread from another thread
runOnUiThread(new Runnable() {
#Override
public void run()
{
layout.setBackgroundResource(setImg[i]);
}
});
And I think that the right way to do it is using alarm . Refer to this link Android alarms
Hope this helps.
Your run method should be re-written like below:
public void run() {
for (int i = 0; i < 2; ++i) {
runOnUiThread(new Runnable() {
public void run()
{
layout.setBackgroundResource(setImg[i]);
}
});
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Start an Activity from Splash Screen, should I use run() or runOnUiThread()?

I have a Splash Screen (Logo Activity) to show the company name for 3 seconds before app starts. I start Main Activity from a thread, here is the code:
public class Logo extends Activity {
Thread t;
public boolean dead = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.logo);
t = new Thread() {
public void run() {
try {
Intent i = new Intent(Logo.this, Main.class);
Thread.sleep(3000);
if (!dead) {
startActivity(i);
}
finish();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
}
The Main Activity is called from a worked thread, is this correct? What are the differents with this code (using runOnUiThread)?
...
if (!dead) {
runOnUiThread(new Runnable() {
#Override
public void run() {
Intent i = new Intent(Logo.this, Main.class);
startActivity(i);
}
});
}
...
I see no difference with this code in debug mode (The same threads, the same operation, etc.). Which is correct?
Starting an intent I think is not an UI operation. runOnUI thread runs UI operation on UI thread. So you can use either of thread (runOnUI or normal). May be normal thread will be good in this situation. But I would like to suggest you use timer instead.
To be honest, I don't like the Thread.sleep. PLease take a look at my solution:
new Timer().schedule(new TimerTask() {
#Override
public void run() {
// Do your work here like... startActivity...
}
}, SPLASH_DURATION); // SPLASH_DURATION IS IN MILLISECONDS LIKE 3000
Also you can block the user to prevent the back key like this:
#Override
public void onBackPressed() {
// do nothing! disable user interaction!
}
You should use AsyncTask in "doInBackground" background thread and than sleep your thread(this thread not UIThread) "PostExecute" run on UI Thread than start your new activity
private class mSplashViewer extends AsyncTask<Void,Void,Void>{
protected void doInBackground(Void params){
Thread.currentThread().sleep(3000);
return null;
}
protected void onPostExecute(){
startActivity(...);
}
}

Categories