android – 修改AsyncTask中的视图doInBackground()不会(总是)抛出异常

前端之家收集整理的这篇文章主要介绍了android – 修改AsyncTask中的视图doInBackground()不会(总是)抛出异常前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在播放一些示例代码时,我遇到了一些意想不到的行为.

由于“众所周知”,您无法从另一个线程修改UI元素,例如一个AsyncTask的doInBackground().

例如:

  1. public class MainActivity extends Activity {
  2. private TextView tv;
  3.  
  4. public class MyAsyncTask extends AsyncTask<TextView,Void,Void> {
  5. @Override
  6. protected Void doInBackground(TextView... params) {
  7. params[0].setText("Boom!");
  8. return null;
  9. }
  10. }
  11.  
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. LinearLayout layout = new LinearLayout(this);
  16. tv = new TextView(this);
  17. tv.setText("Hello world!");
  18. Button button = new Button(this);
  19. button.setText("Click!");
  20. button.setOnClickListener(new View.OnClickListener() {
  21. @Override
  22. public void onClick(View v) {
  23. new MyAsyncTask().execute(tv);
  24. }
  25. });
  26. layout.addView(tv);
  27. layout.addView(button);
  28. setContentView(layout);
  29. }
  30. }

如果你运行它,并点击按钮,你的应用程序会按预期的方式停止,你会在logcat中找到以下堆栈跟踪:

11:21:36.630: E/AndroidRuntime(23922): FATAL EXCEPTION: AsyncTask #1

11:21:36.630: E/AndroidRuntime(23922): java.lang.RuntimeException: An error occured while executing doInBackground()

11:21:36.630: E/AndroidRuntime(23922): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
11:21:36.630: E/AndroidRuntime(23922): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)

到现在为止还挺好.

现在我更改了onCreate()来立即执行AsyncTask,而不是等待按钮点击.

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. // same as above...
  5. new MyAsyncTask().execute(tv);
  6. }

应用程序没有关闭,日志中没有,TextView现在显示“Boom!”屏幕上.哇.没想到这个.

也许活动生命周期过早?让我们把执行移到onResume().

  1. @Override
  2. protected void onResume() {
  3. super.onResume();
  4. new MyAsyncTask().execute(tv);
  5. }

与上述相同的行为.

好的,我们把它放在处理程序上.

  1. @Override
  2. protected void onResume() {
  3. super.onResume();
  4. Handler handler = new Handler();
  5. handler.post(new Runnable() {
  6. @Override
  7. public void run() {
  8. new MyAsyncTask().execute(tv);
  9. }
  10. });
  11. }

同样的行为.我用完了想法,并尝试延迟1秒的postDelayed():

  1. @Override
  2. protected void onResume() {
  3. super.onResume();
  4. Handler handler = new Handler();
  5. handler.postDelayed(new Runnable() {
  6. @Override
  7. public void run() {
  8. new MyAsyncTask().execute(tv);
  9. }
  10. },1000);
  11. }

最后!预期的例外:

11:21:36.630: E/AndroidRuntime(23922): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

哇,这是与时间有关吗?

我尝试不同的延迟,似乎对于这个特定的测试运行,在这个特定的设备(Nexus 4,运行5.1)上,魔术数字是60ms,即有时会抛出异常,有时它更新TextView,就好像没有发生任何事情.

我认为当在AsyncTask被修改的时候,没有完全创建视图层次结构时,会发生这种情况.它是否正确?有更好的解释吗? Activity上有一个回调,可以用来确保视图hierachy已经被完全创建了吗?与时间有关的问题是可怕的.

我在这里发现了一个类似的问题Altering UI thread’s Views in AsyncTask in doInBackground,CalledFromWrongThreadException not always thrown,但没有解释.

更新:

由于在评论和建议的答案中的请求,我添加了一些调试日志来确定事件链…

  1. public class MainActivity extends Activity {
  2. private TextView tv;
  3.  
  4. public class MyAsyncTask extends AsyncTask<TextView,Void> {
  5. @Override
  6. protected Void doInBackground(TextView... params) {
  7. Log.d("MyAsyncTask","before setText");
  8. params[0].setText("Boom!");
  9. Log.d("MyAsyncTask","after setText");
  10. return null;
  11. }
  12. }
  13.  
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. LinearLayout layout = new LinearLayout(this);
  18. tv = new TextView(this);
  19. tv.setText("Hello world!");
  20. layout.addView(tv);
  21. Log.d("MainActivity","before setContentView");
  22. setContentView(layout);
  23. Log.d("MainActivity","after setContentView,before execute");
  24. new MyAsyncTask().execute(tv);
  25. Log.d("MainActivity","after execute");
  26. }
  27. }

输出

10:01:33.126: D/MainActivity(18386): before setContentView
10:01:33.137: D/MainActivity(18386): after setContentView,before execute
10:01:33.148: D/MainActivity(18386): after execute
10:01:33.153: D/MyAsyncTask(18386): before setText
10:01:33.153: D/MyAsyncTask(18386): after setText

一切如预期的,这里没有什么不寻常的,setContentView()在执行execute()之前完成,这又在doInBackground()中调用setText()之前完成.所以不是这样.

更新:

另一个例子:

  1. public class MainActivity extends Activity {
  2. private LinearLayout layout;
  3. private TextView tv;
  4.  
  5. public class MyAsyncTask extends AsyncTask<Void,Void> {
  6. @Override
  7. protected Void doInBackground(Void... params) {
  8. tv.setText("Boom!");
  9. return null;
  10. }
  11. }
  12.  
  13. @Override
  14. protected void onCreate(Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. layout = new LinearLayout(this);
  17. Button button = new Button(this);
  18. button.setText("Click!");
  19. button.setOnClickListener(new View.OnClickListener() {
  20. @Override
  21. public void onClick(View v) {
  22. tv = new TextView(MainActivity5.this);
  23. tv.setText("Hello world!");
  24. layout.addView(tv);
  25. new MyAsyncTask().execute();
  26. }
  27. });
  28. layout.addView(button);
  29. setContentView(layout);
  30. }
  31. }

这一次,我在AsyncTask上调用execute()之前,立即在Button的onClick()中添加TextView.在这个阶段,初始布局(没有TextView)已经正确显示(即我可以看到按钮并点击它).再次,没有抛出异常.

和柜台的例子,如果我添加Thread.sleep(100);在doInBackground()中的setText()之前的execute()中抛出通常的异常.

我刚刚注意到的另外一件事就是,在异常抛出之前,TextView的文本实际上已经被更新了,只要一秒钟就可以正常显示,直到应用程序自动关闭.

我想有事情必须发生(异步地,即从任何生命周期方法/回调分离)到我的TextView,以某种方式“附加”到ViewRootImpl,这使得后者抛出异常.有人有任何解释或指针,进一步的文件说明什么是“某事”?

解决方法

ViewRootImpl.java的checkThread()方法负责抛出此异常.
这个检查使用成员mHandlingLayoutInLayoutRequest进行抑制,直到performLayout(),即所有的初始绘图遍历都完成.

因此只有当我们使用延迟时才会抛出异常.

不知道这是否是android或故意的bug?

猜你在找的Android相关文章