I'm developing a simple custom View to show a gauge composed by a background image above which i print a number (rpm) and draw a pointer.
To achieve it I defined a class (MyGauge) that extends ImageView class. In the onDraw() member I drawLine() and drawText() a number sent by the activity and collected by SetRpm() member; then I force an invalidate() call.
The activity runs a Thread (a Runnable instance) to generate a different number every second; the thread dispatches a Message to the activity; the Handler receives the message and sets the number by SetRpm() function.
The error I receive is:
03-12 09:39:33.280 1796-1827/com.stemmo.termi_up W/dalvikvm: threadid=16: thread exiting with uncaught exception (group=0xb178e678)
03-12 09:39:33.280 1796-1827/com.stemmo.termi_up E/AndroidRuntime: FATAL EXCEPTION: Thread-78
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:5908)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:869)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4253)
at android.view.View.invalidate(View.java:10539)
at android.view.View.invalidate(View.java:10494)
at com.stemmo.termi_up.CustomViews.MyGauge.SetRpm(MyGauge.java:61)
at com.stemmo.termi_up.GaugeActivity$2.handleMessage(GaugeActivity.java:54)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.stemmo.termi_up.GaugeActivity$3.run(GaugeActivity.java:83)
at java.lang.Thread.run(Thread.java:841)
So, the handler is in the thread's context??
How can i solve it?
Do I need to AsyncTask object instead of the Runnable one?
A view in Android can only be updated from the UI thread. You can always post a Runnable to a view like this
MyGauge gauge = ... // your custom view
gauge.post(new Runnable() {
view.SetRPM();
});
I answer cause i better write down the code ..
This is the code on the onCreate() of the Activity:
final Handler myHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle data = msg.getData();
String v = data.getString("value");
myGauge.SetRpm(v);
}
};
// Simul thread
nValue = 0;
bUpDown = true;
Runnable thread = new Runnable() {
#Override
public void run() {
while(true) {
if (bUpDown) // Up
...
}
Bundle data = new Bundle();
data.putString("value", String.valueOf(nValue));
Message msg = new Message();
msg.setData(data);
myHandler.dispatchMessage(msg);
// Delay
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(thread).start();
The SetRpm() member inside MyGauge is:
public void SetRpm(String newRpm)
{
m_strRpm = newRpm;
invalidate();
}
I found the issue with your code. Please replace your code line
myHandler.dispatchMessage(msg); with myHandler.sendMessage(msg);
It will fix the Exception "Only the original thread that created a view hierarchy can touch its views."
Related
I used HandlerThread and then used its looper to create a new Handler so that it can run operations on a non-UI thread. In the runnable which is posted to the handler, I added Toast messages to be displayed. I expected that to cause a problem as you can't touch UI components from the non-UI thread, but it still works and that toast is still being shown. Can anyone explain why toast is being displayed from the non-UI thread?
//Inside a Fragment class
private Handler handler;
private HandlerThread mHandlerThread = null;
public void startHandlerThread() {
mHandlerThread = new HandlerThread("HandlerThread");
mHandlerThread.start();
handler = new Handler(mHandlerThread.getLooper());
}
private Runnable submitRunnable = new Runnable() {
#Override
public void run() {
//do some long running operations here
//Thread.sleep(2000);
//Check whether currentLooper is the Main thread looper
boolean isUiThread = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? Looper.getMainLooper().isCurrentThread()
: Thread.currentThread() == Looper.getMainLooper().getThread();
if (isUiThread) {
// You are on the UI thread
Log.d("Thread", "Main thread");
} else {
// You are on the non-UI thread
Log.d("Thread", "Not Main thread"); //This will be printed
}
Toast.makeText(getContext(), "toast is shown", Toast.LENGTH_SHORT).show();
}
};
submitButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
handler.post(submitRunnable);
}
});
I checked Toast.java and saw that the looper initializes itself with Looper.myLooper().
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
}
From the doc:
myLooper(): Return the Looper object associated with the current thread.
And the currentThread is the HandlerThread, not the main thread.
Hence, I am unable to understand how the toast is being displayed from the non-UI thread, or if it is something plain simple I am missing to see.
For showing Toast you used getContext() as a context.
getContext() - Returns the context the view is currently running in. Usually the currently active Activity.
While you are using fragment so it will take activity context where fragment will resides in activity.
That's why Toast shown.
I have a list view which is populated asynchronously from http responses
Even using CopyOnWriteArrayList and calling runOnUiThread, I am still getting this exception
07-21 16:20:56.390: E/AndroidRuntime(2003): FATAL EXCEPTION: main
07-21 16:20:56.390: E/AndroidRuntime(2003): java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131034172, class android.widget.ListView) with Adapter(class android.widget.ArrayAdapter)]
The code I am using is,
onRandomAddEventFromRemoteEnd(){
if (!deviceNames.contains(name)) {
deviceNames.add(name);
runOnUiThread(notifyAdapterDataChanged);
}
}
where deviceNames is a CopyOnWriteArrayList and notifyAdapterDataChanged is the Runnable
Runnable notifyAdapterDataChanged = new Runnable() {
#Override
public void run() {
dummyAdapter.notifyDataSetChanged();
}
};
Your error
Make sure the content of your adapter is not modified from a background thread, but only from the UI thread
is telling that somehow you might be wrongly modifying your UI.
I would suggest instead calling runOnUiThread(notifyAdapterDataChanged); you'd better call a function runThread() and change UI there as:
void runThread(){
myThread = new Thread() {
public void run() {
try {
runOnUiThread(new Runnable() {
#Override
public void run() {
//Change ui here
dummyAdapter.notifyDataSetChanged();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
};
myThread.start();
}
I am having issues using another thread in Android for checking the availability of a web server.
I start a new thread to avoid:
NetworkOnMainThreadException
This is the log cat:
E/AndroidRuntime(17753): FATAL EXCEPTION: Thread-2370
E/AndroidRuntime(17753): Process: com.example.c3po, PID: 17753
E/AndroidRuntime(17753): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime(17753): at com.example.c3po.MainActivity$1SecondThread.run(MainActivity.java:72)
The code used in the second thread
class SecondThread extends Thread {
public void run() {
TextView pingResult = (TextView) findViewById(R.id.checkStatus); // to display result
EditText userText = (EditText) findViewById(R.id.userData); // take in user url
String result = userText.getText().toString();
try {
InetAddress address = InetAddress.getByName(result); // user input is result (a URL)
boolean b = address.isReachable(3000);
String str = String.valueOf(b); // turning the value of the boolean into string
pingResult.setText(str); // value displays as true or false - LINE 72
}
catch (UnknownHostException e) {pingResult.setText("WRONG");} // will fill with helpful message later
catch (IOException e) {pingResult.setText("WRONG");}
}
And the button to trigger the thread:
Button sendPing = (Button) findViewById(R.id.pingButton);
sendPing.setOnClickListener(new OnClickListener() {
public void onClick (View activity_main) {
SecondThread thread = new SecondThread();
thread.start();
}
});
Line 72 is commented. I have tried googling the specific issue, but have got mixed results.
Any help would be appreciated.
Many thanks
You are updating ui from the background thread. You cannot do that. Ui needs to updated on the ui thread. Use AsyncTask.
You could use
runOnUiThread(new Runnable() {
#Override
public void run() {
pingResult.setText(str);
}
});
Similarly for pingResult.setText("WRONG");
But better to use AsyncTask as it would be a easier. You could do your background computation in doInBackground return result ie String in this case and update ui in onPostExecute
I have a button that I don't want to be clickable until a certain amount of time has run (say, 5 seconds?) I tried creating a thread like this
continueButtonThread = new Thread()
{
#Override
public void run()
{
try {
synchronized(this){
wait(5000);
}
}
catch(InterruptedException ex){
}
continueButton.setVisibility(0);
}
};
continueButtonThread.start();
But I can't modify the setVisibility property of the button within a different thread. This is the error from the LogCat:
10-02 14:35:05.908: ERROR/AndroidRuntime(14400): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Any other way to get around this?
The problem is that you can touch views of your activity only in UI thread. you can do it by using runOnUiThread function. I would like to suggest you to use
handler.postDelayed(runnable, 5000)`
You must update your view from UI-thread. What you are doing is you are updating from non-ui-thread.
Use
contextrunOnUiThread(new Runnable(){
#Override
public void run() {
// TODO Auto-generated method stub
}});
or use handler and signalize hand.sendMessage(msg) when you think is right time to update the view visibility
Handler hand = new Handler()
{
#Override
public void handleMessage(Message msg) {
/// here change the visibility
super.handleMessage(msg);
}
};
You can use the postDelayed method from the View class (A Button is a child of View)
here is simple answer i found
Button button = (Button)findViewBYId(R.id.button);
button .setVisibility(View.INVISIBLE);
button .postDelayed(new Runnable() {
public void run() {
button .setVisibility(View.VISIBLE);
}
}, 7000);
How to call Main thread from secondary thread in Android?
The simplest way is to call runOnUiThread(...) from your thread
activity.runOnUiThread(new Runnable() {
public void run() {
... do your GUI stuff
}
});
My recommendation to communicate threads in the same process is sending messages between those threads. It is very easy to manage this situation using Handlers:
http://developer.android.com/reference/android/os/Handler.html
Example of use, from Android documentation, to handling expensive work out of the ui thread:
public class MyActivity extends Activity {
[ . . . ]
// Need handler for callbacks to the UI thread
final Handler mHandler = new Handler();
// Create runnable for posting
final Runnable mUpdateResults = new Runnable() {
public void run() {
updateResultsInUi();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
[ . . . ]
}
protected void startLongRunningOperation() {
// Fire off a thread to do some work that we shouldn't do directly in the UI thread
Thread t = new Thread() {
public void run() {
mResults = doSomethingExpensive();
mHandler.post(mUpdateResults);
}
};
t.start();
}
private void updateResultsInUi() {
// Back in the UI thread -- update our UI elements based on the data in mResults
[ . . . ]
}
}
You'll need a Handler that passes the information back to the main thread.
Also, it's good to remember that if you get your secondary thread through an AsyncTask, you have the option to call onProgressUpdate(), onPostExecute(), etc., to do work on the main thread.
Sample code using HandlerThread
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Handler responseHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message msg) {
//txtView.setText((String) msg.obj);
Toast.makeText(MainActivity.this,
"Result from UIHandlerThread:"+(int)msg.obj,
Toast.LENGTH_LONG)
.show();
}
};
HandlerThread handlerThread = new HandlerThread("UIHandlerThread"){
public void run(){
/* Add your business logic to pupulate attributes in Message
in place of sending Integer 5 as in example code */
Integer a = 5;
Message msg = new Message();
msg.obj = a;
responseHandler.sendMessage(msg);
System.out.println(a);
}
};
handlerThread.start();
}
}
Explanation:
In above example, HandlerThread post a Message on Handler of UI Thread, which has been initialized with Looper of UI Thread.
final Handler responseHandler = new Handler(Looper.getMainLooper())
responseHandler.sendMessage(msg); sends Message from HandlerThread to UI Thread Handler.
handleMessage processes Message received on MessageQueue and shows a Toast on UI Thread.