面试回答:子线程不可以更新UI。
如果面试官问得比较深,那就这样回答他:一般来说子线程是不可以更新UI的,但是非要用子线程更新UI,那也可以做到。
在讨论这个问题之前先普及几个常识,即:什么叫更新UI?
什么叫线程?
什么是主线程?
什么是子线程?
更新UI - - 就是改变页面效果,视觉上可以看到的变化。
线程 - - - 一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行 流就是一个线程。(借鉴《疯狂Java讲义》)
(这种解释太官方了是吧,简言之,线程就是一个小程序,可以实现一定的功能)
主线程 - - 当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread)。需要单独创建的线程都是这个主线程的子线程。(借鉴百度)
子线程 - - 主线程之外的线程都是子线程。
直白来讲,所有New 类名 (){......},省略号部分的方法都是子线程。
下面讨论一下子线程可不可以更新UI
1、Android规定子线程是不可以更新UI界面的,但是也有例外,
如以下代码:
public class MainActivity extends Activity {
private TextView tv;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
new Thread(){
public void run() {
tv.setText("你好!!!");
};
}.start();
} }
这样写,是可以更新UI的,但是仅限于第一次开启程序,线程之间的执行机会是平等的,其实严格意义上讲,UI界面并不是被子线程改掉了,而是程序运行的时候,先走的子线程这段代码,直接就让TextView显示的“你好!!!”,没有给“helloworld”显示的机会。
2、如果给上面这个子线程一个休眠时间,也就是说让其在主线程之后执行,就不能更新UI界面了。
代码如下:
public class MainActivity extends Activity {
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(){
public void run() {
try {
sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tv.setText("你好!!!");
};
}.start();
} }
这是因为,程序进入之后主线程先执行,子线程有休眠时间限制,所以晚于主线程被执行,这样它就是正在意义上的“子线程”,不能更新UI。
3、有一个方法是可以在子线程更新UI的,runOnUiThread()方法。
代码如下:
public class MainActivity extends Activity {
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(){
public void run() {
runOnUiThread(new Runnable() {
public void run() {
tv.setText("你好!!!");
}
});
};
}.start();
}
先看一下runOnUiThread()这个方法的原理:
利用Activity.runOnUiThread(Runnable)把更新UI的代码创建在Runnable中,然后在需要更新UI时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。
Runnable对像就能在UI程序中被调用。如果当前线程是UI线程(主线程),那么行动是立即执行。如果当前线程不是UI线程,操作就要发布到事件队列的UI线程(即,程序会判断更新UI操作是不是在主线程,如果是,那就直接更新;如果不是,那就把信息传递给主线程,最终还是由主线程操作,可以看一下这个方法的底层代码)。
runOnUiThread()这个方法的底层代码:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
总结:
目前来看,子线程想更新UI就两种方法:
1、runOnUiThread()方法
2、通过Handler把信息传递给主线程,让主线程去做更新操作。
注意--如果在服务(Service)里,子线程做更新UI操作,就必须通过Handler将信息传递给主线程来完成,因为服务里,没有runOnUiThread()这个方法,它是Activity的方法。
严格意义上讲,子线程不可以直接更新UI,无论哪种子线程更新UI,其实都是想方设法地把信息交给主线程来做更新。
既然子线程不可以更新UI,那为什么又有“TimerTask中run方法中的语句相当于在子线程执行”、“进度条可以在子线程更新UI”这样的说法呢?
首先,针对“TimerTask中run方法中的语句相当于在子线程执行”,这种说法对就对在了“相当于”三个字上,因为TimerTask是通过new得到的。
其次,“进度条可以在子线程更新UI”,是因为setProgress()这个方法会自动交给主线程去操作,也就是说对于setProgress()这个方法,runOnUiThread()可有可无,因为两者目的都是将子线程的信息发送到主线程去操作。这就要区别于更新控件显示内容的setText()方法,此方法如果放到子线程就要报错了。