我想我们无法在任何工作线程中更新任何UI view
元素(TextView
,EditText
等),但在下面的示例中,我能够更新来自工作线程的views
,并非所有时间,但有时只是。
请参阅以下示例,我在工作线程中更新UI元素 -
public class AndroidBasicThreadActivity extends AppCompatActivity
{
public static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_android_basic_thread);
textView = (TextView) findViewById(R.id.textview);
MyAndriodThread myTask = new MyAndriodThread();
Thread t1 = new Thread(myTask, "Bajrang Hudda");
t1.start();
}
}
这是我的工作线程 -
class MyAndriodThread implements Runnable
{
@Override
public void run()
{
AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
}
}
相信我,我没有得到任何例外 -
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
我可以在模拟器上看到更新UI。
但如果我在工作线程中执行任何繁重的任务(休眠),那么我得到了预期的上述异常,为什么会发生这样的事情?我是android多线程的新手,请让我从这种困惑中解脱出来。
如果我将我的工作线程更改为此,我将获得以上异常 -
class MyAndriodThread implements Runnable
{
@Override
public void run()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
System.out.println("Child thread completed.");
}
}
如果我的UI或主线程正在等待(join()),那么我得到的确切输出完全没有异常,请看 -
MyAndriodThread myTask = new MyAndriodThread();
Thread t1 = new Thread(myTask, "Anhad");
t1.start();
try
{
t1.join();
} catch (InterruptedException e)
{
e.printStackTrace();
}
更新
首先我认为它的渲染方案然后我通过使用ProgressBar改变了我的程序..然后我得到了意想不到的输出...仍然没有例外意味着我可以在工作线程中更新我的进度条。见下文 -
@Override
public void run()
{
for(int i = 1; i<=10; i++)
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
activity.progressBar.setProgress(i);
}
}
答案 0 :(得分:7)
所以 Bajrang Hudda 我猜你很满意 ShadyGoneInsane 的答案。我真的同意他的观点,一旦呈现视图并将其附加到Window,您就无法直接从工作线程访问它。
要回答有关progressBar的更新问题,ProgressBar会在内部使用Runnable更新其进度,然后post(r)
使用其处理程序。
为了详细解释,当您尝试从工作线程progressBar.setProgress(i)
更新ProgressBar时,progressBar首先检查是否在MainThread上进行了调用,如果是,则直接更新;否则它会创建一个新的Runnable RefreshProgressRunnable
来更新进度,然后post(r)
使用其处理程序。
例如new Handler().post(r)
我确信您在view.post(r)
ProgressBar source code检查方法setProgress()
希望这可以解除你的怀疑。
答案 1 :(得分:5)
这里的原因是线程只能在TextView对象中进行更改,直到它在UI屏幕上不可见,即不呈现。只有在视图呈现之后,除主线程之外的任何线程都不会对任何UI组件进行更改。
所以当你这样做时:
class MyAndriodThread implements Runnable
{
@Override
public void run()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
System.out.println("Child thread completed.");
}
}
您正试图在视图渲染后更改文本,这会导致异常
虽然在下面的代码片段中,线程能够在TextView对象中进行更改,因为它在UI屏幕上不可见,即未呈现。
class MyAndriodThread implements Runnable
{
@Override
public void run()
{
AndroidBasicThreadActivity.textView.setText("Hello!! Android Team :-) From child thread.");
}
}
正如@JohnnyAW所指出的那样 - 当你使用join()时,你会延迟渲染,直到工作者准备就绪,所以即使工作线程休眠也不会出现异常
当你执行此操作Thread.sleep(2000);
时,TextView
在两者之间呈现,然后您尝试触摸此workerThread中导致崩溃的视图
您还可以尝试Thread.sleep(0);
,看看它不会引发任何异常,您的应用也不会崩溃。
答案 2 :(得分:1)
这种行为确实发生了。正如在android的开发者门户网站上清楚地提到的那样。
有一个显式引用的段落,表明
非主线程上的许多任务的最终目标是更新UI对象。但是,如果其中一个线程访问视图层次结构中的对象,则可能导致应用程序不稳定:如果工作线程在任何其他线程引用该对象的同时更改该对象的属性,则结果是未定义的。
所以这可能发生并且未定义。
https://developer.android.com/topic/performance/threads.html
答案 3 :(得分:-1)
您无法从后台线程更新ui线程中的任何内容,为此您需要使用Handler
或使用AsyncTask.onProgressUpdate
,有关详细信息,请详细查看AsyncTask
<强>更新强>
建议不要在不涉及Handler
或使用Activity.runOnUiThread(Runnable)
的情况下从工作线程更新UI。在您从TextView
更新MyAndriodThread
时,您的视图处于转换状态,如果您尝试更新UI,则视图可能会获得更新或导致崩溃,{base URL
3}}:
如果工作线程同时更改该对象的属性 任何其他线程引用该对象的时间,结果是 未定义
答案 4 :(得分:-3)
您应该尝试使用 BroadcastReceiver 来更新用户界面。 例如, 一旦您的工作线程完成了它的任务,它应该使用以下方式发送广播:
Intent intent = new Intent();
intent.setAction("com.example.harshil.charotarexplore.android.action.broadcast");
sendBroadcast(intent);
在你班上:
private static final String BROADCAST = "com.example.harshil.charotarexplore.android.action.broadcast";
private BroadcastReceiver locationReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
locationReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().matches(BROADCAST)) {
onLocationAvailable();
}
}
};
在 onStart
:
registerReceiver(locationReceiver, new IntentFilter(BROADCAST));