Android主线程简介:
当Android应用程序启动后,系统会创建一个叫做“main”的线程。它就是主线程,也叫UI线程,非常重要。在Android系统中,主线程主要负责执行四大组件的执行。负责分发事件给构建,包括绘制事件。
Android中规定访问UI只能在主线程进行,如果在子线程中访问UI,那么程序就会抛出异常。
那么为什么安卓系统不允许在子线程中访问UI呢?这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。如果在UI控件的访问加上锁机制的话,由于锁机制会阻塞线程的执行, 会降低UI运行效率。
主线程的主要责任:
- 快速的处理UI事件。Android希望UI线程能快速响应用户操作,如果UI线程花太多时间处理后台的工作,会让用户有非常糟糕的体验。当UI事件发生时,让用户等待时间超过5秒而未处理,Android系统就会给用户显示APP未响应提示信息。
- 快速的处理Broadcast消息。在BroadcastReceiver的onReceive()函数中,不宜占用太长的时间,否则会导致主线程无法处理其它的Broadcast消息或UI事件。如果占用时间超过10秒, Android系统就会给用户显示APP未响应提示信息。
为了更清楚的讲述UI线程的特点, 先提出一个贯穿全文的需求, 需求的大致要求是:
实现一个倒计时器, 获取用户输入的倒计时数, 点击开始按钮开始计时, 计时完毕显示完成.
需求十分的简单, 功能就是一个计时, 心中瞬间就有了思路, 不就是设置一个变量接收用户输入, 然后循环改变界面的输出嘛(年轻的我), 思路有了那就开始撸码吧.
附上源码(错误示范):
UI
页面逻辑
package example.senior0602;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity
{
final String TAG = "测试";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 点击'开始倒计时'
public void startCount( View view)
{
long timeStart = System.currentTimeMillis();
TextView textView = (TextView)findViewById(R.id.id_display);
int number = 4;
while (number > 0)
{
String str = String.valueOf(number);
textView.setText(str);
number --;
Log.w(TAG, "倒计时: " + str);
// 等1秒
try{
Thread.sleep(1000);
}catch (Exception e){}
}
textView.setText("完成");
Log.w(TAG, "倒计时结束");
long timeEnd = System.currentTimeMillis();
Log.w(TAG, "耗时: " + (timeEnd- timeStart) + " 毫秒");
}
}
完成了, 看起来没有什么毛病, 编译运行
直接出现了完成... 我的倒计时效果呢?? 查看后台输出后发现, setText() 确确实实被调用了五次, 那么就说明倒计时的逻辑没有错, 但是UI怎么不会按照代码执行来改变呢, 经过查阅文档后了解到原来UI的改变并不是实时的, 中间还有一层 消息循环做中间层, 简单的说就是只有按钮的监听回调结束后, TextView 才会更新显示.
直接的文字可能会难以理解,下面用伪代码描述一下这个线程做了什么:
void run()
{
msgQueue: 消息队列
while(true)
{
msg: 从msgQueue取得一个消息
switch(msg.type)
{
case 点击了Button:
调用startCount();
break;
case 重新绘制TextView:
重新绘制TextView;
break;
}
...
}
}
显然,只有在退出startCount()之后,才有机会重绘TextView。那么TextView的setText()做了什么呢?
class TextView
{
void setText (String text )
{
// 保存text的值
// 请求重绘: 内部发一个msg到msgQueue
}
}
也就是说,TextView只是保存了一个新的text,创建了一个要求绘制的消息 .
了解了UI更新机制后我们来重构我们的代码.
package example.countdown;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
final String TAG = "测试: ";
MyHandler handler = new MyHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 点击事件, UI线程应该尽快完成,
// 耗时超过0.3s界面将会出现卡顿,
// 耗时的操作应该分发到其它线程处理
public void StartCount(View view) {
MyCountThread myTask = new MyCountThread();
myTask.start();
}
// 工作线程
private class MyCountThread extends Thread {
@Override
public void run() {
EditText editText = (EditText) findViewById(R.id.id_editText_count);
int number = Integer.valueOf(editText.getText().toString());
while (number > 0) {
String str = String.valueOf(number);
Log.w(TAG, "倒计时: " + str);
// 更新 UI 显示
Message msgCount = new Message();
msgCount.what = 1;
msgCount.arg1 = number;
if (number == 1) {
msgCount.obj = "完成";
}
handler.sendMessage(msgCount);
number--;
try {
Thread.sleep(1000);
}catch (Exception e ) { }
}
// 倒计时结束
Message msgFinish = new Message();
msgFinish.what = 2;
msgFinish.obj = "完成!";
handler.sendMessage(msgFinish);
Log.w(TAG, "倒计时结束");
}
}
// 消息处理器, 用来更新界面
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg)
{
TextView textView = (TextView) findViewById(R.id.id_textView_count);
switch (msg.what) {
case 1:
int number = msg.arg1;
textView.setText(String.valueOf(number));
break;
case 2:
String value = (String) msg.obj;
textView.setText(value);
break;
}
}
}
}
计时的逻辑还是利用循环, 不过循环中并没有更新页面的代码, 而是利用一个 MyHandler 对象给UI线程发送了一个消息, MyHandler 继承了 Handler他是一个UI线程的消息队列, 每循环一次就会有一个消息加入到队列中, 这样界面卡死的问题就解决啦.