Why Is Only the UI Thread Allowed to Modify the Ui

Why is only the UI thread allowed to modify the UI?

Several GUI frameworks have this limitation. According to the book Java Concurrency in Practice the reason for this is to avoid complex locking. The problem is that GUI controls may have to react to both events from the UI, data binding and so forth, which leads to locking from several different sources and thus a risk of deadlocks. To avoid this .NET WinForms (and other UIs) restricts access to components to a single thread and thus avoids locking.

Why non UI thread can modify UI?

Short answer: you can do it as long as you do it before the mainthread enter onResume()

Detail:

Usually you get a error like this when modifying UI from non UI thread

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

and this exception is throwed here in the ViewRootImpl class

void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}

the mThread is the main thread. After the ViewRootImpl instance is created, UI modify opertations will need to check thread.

So when did the ViewRootImpl instance is created?

android.view.WindowManagerImpl.java

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

android.view.WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
...
root = new ViewRootImpl(view.getContext(), display);
...
}

So when did the addView method is called?

android.app.ActivityThread.java

final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}

}

.

 public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
...
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}

here you can see the ViewRootImpl instance is created around RESUME_ACTIVITY event, so before the main thread handles the resume event, you are allowed to modify UI from other threads. you just need to be quick.

It is not recommended to do that though.

Why can only the UI thread in Android update the UI?

Documentation states that Android UI toolkit is not thread-safe. Thus, single thread model ensures UI is not modified by different threads at the same time.

Why cant a thread that is not the UI thread access the view?

Yes your right: You can't modify views on another thread for the sake of security (that's why it's called UI thread). It prevents you from leaving the UI data in an inconsistent state which might crash your application and would be really hard to debug. So the android API simply forbids that (and that's a good idea). That's a common UI pattern which you'll find in the most APIs.

You can update any view with post() or runOnUiThread():

anyView.post(new Runnable() {
public void run() {
// do update here
}
});

Why this pattern?
Synchronization is not for free. It impacts the performance. So it's easier to keep modifications to UI on the same thread.

If I could modify data from different threads what could happen?
For example: Thread A is changing the color of a view and thread B is reading the color at the same tim. Since multi-threading doesn't guarantee which instruction is executed first you might get unexpected results. The color is black (0|0|0) before, thread A wants to set white (255|255|255) and start with setting the red component to 255, thread B starts to read and gets the whole color before thread A had a chance to finish and got the color red (255|0|0) instead black.

This is a simple example which would impact a visual effect but if that happens for some really important data your application will crash horribly and such an error is so nasty and hard to debug. There's a lot to learn about multi-threading and maybe this java tutorial is a good starting point...

Why we cant update UI from worker thread? same as other variables/object

The answer -I think - you are looking for (why and how the exception is raised) is because there is explicit code inside the Control class's Handle property that will check if the code that wants to access the property is running on the same thread that created the Control (the User-interface thread).

You can check the reference source for the Handle property here. The actual thread check occurs inside the implementation of the InvokeRequired property, that you can also check here.

Early versions of the .net Framework did not include this check, so it was very easy to access the user interface from a different thread. The reason why we can't shouldn't do it is because there is a great portion of the Win32 API code base that is not thread-safe, so calling it from a single thread is the only way to guarantee no concurrency problems will happen in a multi-threaded application.

The Handle property will be accessed before the control internally calls any related Win32 API function that interacts with it (you can think of the handle as the 'this' reference inside the WIN32 API), so it was a very good candidate for centralizing the cross-thread check.

If I understand correctly from your comments, you want to simulate the same behavior (to raise an exception) when a worker thread tries to update a certain value (not necessarily a user interface element) 'owned' by a different thread. In this case you could adopt the same strategy that the framework is using.

If you analyze the code for InvokeRequired, it is simply comparing the current thread's ID against the value returned by SafeNativeMethods.GetWindowThreadProcessId() and returning true if there is a mismatch. This causes the cross-thread exception to be raised in the Handle property's getter accessor. In your case you could store the ID of the thread that is allowed to access the variable or resource and manually raise the exception if there is a mismatch between this stored ID and the ID of the thread attempting to access the guarded value.

Problem with updating UI with background thread

Undoubtedly, you should use different thread for heavy processes in java. But, Anything related to UI changes, initializations like Views, Animations should be done in UI(Main) Thread which is created with your application.

You could also do:

 new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap =
processBitMap("image.png");

new Handler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});

}

Even if you do a background job in another thread, Handler guarantees, setting image is done in UI/Main thread.

Update the UI outside the main thread

Use activity.runOnUiThread

Acivity.runOnUiThread(new Runnable() {
public void run() {
//something here
}
});

Understanding UI thread

There are many answers concerning the case. I will try to answer exactly to the questions.

What happens to these tasks when android application gone to background?

Nothing is interrupted. Handler will post a Message on MessageQueue on the specified time (unless the hosting process is killed).

Will these tasks are processed even in the background?

From the standpoint of Handler: it doesn't know whether the app is in foreground or background. All it knows, is that it should post a Message some time later.

Will the complete UI thread be suspended in background? Or? All the tasks posted to UI thread are suspended?

What happens to UI thread when there are no tasks posted to it after the activity is completely loaded? Will it be suspended, if there are no tasks are in the Looper?

UI thread won't be "suspended" in any case (unless the hosting process is killed). It's just that it will be idle as a result of MessageQueue being empty - thus no job is needed to be done.

As a side note, refrain from performing UI related actions when app goes to background, because the state of view hierarchy won't be saved correctly. Assume you have performed textView.setText("bob") at a time, when app is in background. Now, the state of this TextView won't be saved, because onSaveInstanceState() has already been executed and won't be executed again, thus if process of the app is killed and recreated (i.e. as a consequence of system resources shortage), then the activity will be restored with a state that it had possessed when onSaveInstanceState() was called. Now user won't see "bob".

You can cancel the scheduled event using Handler#removeCallbacks() API.



Related Topics



Leave a reply



Submit