场景:
项目开发中会涉及到一些耗时操作,这个时候就会开启一个子线程,将耗时操作放到子线程中取操作,操作完毕后,往往还要同步更新ui,这个时候如果直接在子线程中更新ui,将会导致程序的闪退,同时还会看到
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
的错误日志,意思是:只有创建视图层次结构的原始线程才能触及它的视图。说白了,就是子线程中不能更新ui操作,那么为什么子线程中不能更新ui呢?
每个view在ui更新的时候都会去检测线程,最终就会去调用ViewRootImpl里面的checkThread()方法
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
mThread是在ViewRootImpl的构造方法中实例化的,在调用checkThread()方法的时候就会去判断当前线程是否是主线程,如果不等于主线程,就会抛异常导致程序闪退。如果要在子线程中更新ui,其实是可以的,这里说的可不是直接在子线程中更新ui,而是通过其他的途径在子线程中更新ui;
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv= (TextView) findViewById(R.id.tv);
//开启子线程
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(3000);
}
}).start();
}
}
开启一个子线程,睡眠3000毫秒后更新ui;
方式一:runOnUiThread
private void updataFist(){
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
tv.setText("更新ui啦 updataFist");
}
});
}
runOnUiThread是Activity里面的方法,其实会发现runOnUiThread里面还是进行了线程的判断;
/**
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
//当前线程是否等于UI线程
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
如果当前线程是UI线程就直接更新;如果当前线程不是ui线程,就调用Handler里面的post();方法进行更新。
方式二:Handler
private void updataSecond(){
Message message = handler.obtainMessage();
message.what=0;
handler.sendMessage(message);
}
在子线程里面调用updataSecond();利用handler发送一个Message ,然后去接收该Message;
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
if(what==0){
tv.setText("更新ui啦 updataSecond");
}
}
};
在使用Handler的时候,特别是想无限轮播广告图等效果中涉及到使用Handler时,要注意Handler造成的内存泄漏,在该界面不可视时可以调用removeMessages();或者removeCallbacksAndMessages();方法,在界面可视的时候再调用相应方法发送消息。
方式三:view.post();
private void updataThrid(){
tv.post(new Runnable() {
@Override
public void run() {
tv.setText("更新ui啦 updataThrid");
}
});
}
方式四:Handler.post();
private void updataFour(){
Handler handle=new Handler(Looper.getMainLooper());
handle.post(new Runnable() {
@Override
public void run() {
tv.setText("更新ui啦 updataFour");
}
});
}
方式五:AsyncTask
private class MyAsyncTask extends AsyncTask<String, Integer, String>{
/**
* 子线程中执行,执行一些耗时操作,关键方法
* @param params
* @return
*/
@Override
protected String doInBackground(String... params) {
Log.e("TAG","doInBackground--->");
return "更新ui啦 MyAsyncTask";
}
/**
* 主线程中执行
* 在execute()被调用后首先执行
* 一般用来在执行后台任务前对UI做一些标记
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.e("TAG","onPreExecute--->");
}
/**
* 主线程中执行
* 在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件中
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e("TAG","onProgressUpdate--->");
}
/**
* 在主线程中,当后台操作结束时,此方法将会被调用
* 计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
* @param s
*/
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.e("TAG","onPostExecute--->");
tv.setText(s);
}
/**
* 主线程中执行
* 当异步任务取消后的,会回调该函数。在该方法内可以更新UI
*/
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onCancelled(String s) {
super.onCancelled(s);
}
}