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

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

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

例如:

public class MainActivity extends Activity {
    private TextView tv;

    public class MyAsyncTask extends AsyncTask<TextView,Void,Void> {
        @Override
        protected Void doInBackground(TextView... params) {
            params[0].setText("Boom!");
            return null;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout = new LinearLayout(this);
        tv = new TextView(this);
        tv.setText("Hello world!");
        Button button = new Button(this);
        button.setText("Click!");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new MyAsyncTask().execute(tv);
            }
        });
        layout.addView(tv);
        layout.addView(button);
        setContentView(layout);
    }
}

如果你运行它,并点击按钮,你的应用程序会按预期的方式停止,你会在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,而不是等待按钮点击.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // same as above...
    new MyAsyncTask().execute(tv);
}

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

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

@Override
protected void onResume() {
    super.onResume();
    new MyAsyncTask().execute(tv);
}

与上述相同的行为.

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

@Override
protected void onResume() {
    super.onResume();
    Handler handler = new Handler();
    handler.post(new Runnable() {
        @Override
        public void run() {
            new MyAsyncTask().execute(tv);
        }
    });
}

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

@Override
protected void onResume() {
    super.onResume();
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            new MyAsyncTask().execute(tv);
        }
    },1000);
}

最后!预期的例外:

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,但没有解释.

更新:

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

public class MainActivity extends Activity {
    private TextView tv;

    public class MyAsyncTask extends AsyncTask<TextView,Void> {
        @Override
        protected Void doInBackground(TextView... params) {
            Log.d("MyAsyncTask","before setText");
            params[0].setText("Boom!");
            Log.d("MyAsyncTask","after setText");
            return null;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout = new LinearLayout(this);
        tv = new TextView(this);
        tv.setText("Hello world!");
        layout.addView(tv);
        Log.d("MainActivity","before setContentView");
        setContentView(layout);
        Log.d("MainActivity","after setContentView,before execute");
        new MyAsyncTask().execute(tv);
        Log.d("MainActivity","after execute");
    }
}

输出

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()之前完成.所以不是这样.

更新:

另一个例子:

public class MainActivity extends Activity {
    private LinearLayout layout;
    private TextView tv;

    public class MyAsyncTask extends AsyncTask<Void,Void> {
        @Override
        protected Void doInBackground(Void... params) {
            tv.setText("Boom!");
            return null;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        layout = new LinearLayout(this);
        Button button = new Button(this);
        button.setText("Click!");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tv = new TextView(MainActivity5.this);
                tv.setText("Hello world!");
                layout.addView(tv);
                new MyAsyncTask().execute();
            }
        });
        layout.addView(button);
        setContentView(layout);
    }
}

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

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

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

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

解决方法

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

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

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

猜你在找的Android相关文章