关于Handler的理解
链接: NewOrin_CSDN.
链接:yxb_yingu_CSDN.
链接:songzi1228_CSDN.
这里强调一个问题,子线程其实也是可以更新UI操作的,只是不建议(不允许)日常使用(文章下面有解释)。
一、 对于handler的理解:
对于Android界面内容的更新只能由主线程来完成,然而主线程做耗时操作轻则造成界面的卡顿,严重会导致ANR(字面意思就是应用无响应,主线程没有在规定的时间内完成相应的工作),所以通常我们把耗时操作放在子线程中去进行,当我们创建的子线程想要更新界面,就需要子线程和主线程存在某种联系,而这个存在它就是handler。
二、 基本使用
- 创建handler对象,重写handleMessge函数
- 创建线程实现Runnable接口
- 创建Message对象(3中创建方式)
- 在线程中用handler对象调用sendMessage函数把内容发送到MessageQueu(消息队列)中
- 主线程将消息取出,更新界面
public class MainActivity extends AppCompatActivity {
private TextView tvAge;
private TextView tvName;
private A mMan;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {//重写处理消息的方法
super.handleMessage(msg);
String name = Thread.currentThread().getName();
Log.i("thread", "当前线程2: "+name);
switch (msg.what) {
case 100:
A man = (A) msg.obj;//强制转型
tvAge.setText(man.getAge()+"");
tvName.setText(man.getName());
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
tvAge = (TextView) findViewById(R.id.tv_age);
tvName = (TextView) findViewById(R.id.tv_name);
new Thread(new Runnable() {
@Override
public void run() {
mMan = new A(26, "牧区叔叔");
String name = Thread.currentThread().getName();
Log.i("thread", "当前线程1: "+name);
Message msg = Message.obtain();
msg.what = 100;
msg.obj = mMan;
mHandler.sendMessage(msg);
}
}).start();
}
}
2020-10-18 19:02:20.127 11290-11356/com.example.myhelloword401 I/thread: 当前线程1: Thread-2
2020-10-18 19:02:20.188 11290-11290/com.example.myhelloword401 I/thread: 当前线程2: main
更新内容的传递路径:子线程→消息队列→主线程→界面
补充说明:
- 系统在创建主线程时候会自动初始化Looper对象,同时会创建一个与其关联的MessageQueue(子线程也可以创建handler对象的,但是必须创建Looper才能使用,当前线程只能存在一个Looper)(下面会讲到)
- Handler:的作用就是发送与处理消息(前提是当前线程必须有Looper)
- Message(消息):Handler接收与处理的消息对象
- MessageQueue(消息队列):先进先出,管理Message
- Looper(消息循环者):每个线程中只能有一个Looper,管理着MessageQueue,不断的从其中取出Message分发给对应的Handler处理。
三、 Handler的作用
- 更新UI
- 发送消息
- 处理消息
四、 原理分析
那么Handler是怎么工作的呢?首先我们要认识三个类,分别是Message、MessageQueue、Looper。
-
Message:创建一个Message,包含必要的描述和属性数据,并且此对象可以发送给Handler处理(handler.sendMessage();)。
Message对象有3种创建方式
1、Message msg = new Message();//直接创建一个Message对象
2、Message msg = Message.obtain();//从整个Messge池中返回一个新的Message实例,通过obtainMessage能避免重复Message创建对象。
3、Message msg = handler.obtainMessage();//跟第二个一样
这里推荐使用2或3种方式属性字段:
arg1、arg2:用来存放整形数据;
what:消息表示;
obj:Object类型的任意对象;
replyTo:消息管理器,关联到handler,handler处理其中的消息; -
MessageQueue:消息队列,内部存储了一组消息,以队列的形式对外提供插入和删除(先进先出)。虽然称之为消息队列,其实内部存储结构是采用单链表的数据结构来存储消息,仅仅是存储消息,而不能处理消息,所以Looper填补了这个功能。
-
Looper:在MessageQueue存了消息之后Looper就会以无限循环的形式去查是否有新消息,如果有的话就去处理消息,否则就是一直等待着。Looper中还有一个特殊的概念就是ThreadLocal,ThreadLocal并不是线程,它是用来在每个线程中存储数据。Handler创建的时候就会采用ThreadLocal获取当前线程的Looper构造消息循环系统。还有一点非常要注意的是,工作线程是默认是没有Looper的,若要在线程中使用就需要创建Looper,否则就会抛出Can’t create handler inside thread that has not called Looper.prepare(),意思是不能创建Handler因为Looper没有执行Looper.prepare()方法,尚未初始化。而UI线程即主线程中,是默认初始化了Looper的,所以不需要再UI线程再次执行Looper.prepare()方法了。
Looper.loop():这个方法就是一个死循环,不断地从MessageQueue取消息,如有消息就处理,没有就阻塞;
五、 工作流程
Handler的主要工作包含消息的发送与接收过程。消息的发送通过post和send的一系列方法来实现,其实post的一系列方法最终还是通过send来实现的。当Handler发送了消息之后,MessageQueue里就插入了一条消息,然后MessageQueue就会返回这条消息给Looper,Looper接收到消息之后就开始处理了,最终消息由Looper交给Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段,最后经过一系列的判断之后,就会调用handleMessage()方法了。
handler在哪里创建是没有关系的,重要的是handler发送的message到达了那个looper的消息队列,不管通过什么方法,只要让handler发送的消息到达UI线程Looper的消息队列,就能实现对UI线程的修改!
前方高能!:
- 一个普通Thread不能创建handler的,需要在该线程的run()方法中执行Looper.papare(),将该线程变为looperThread。
那么我们会问为什么在UI线程创建Handler时候没有执行Looper.papare()呢?
其实UI线程自动执行了looper.papare和looper.loop()方法,所以UI线程本身就是looper线程 - 一个looperThread只能有一个looper,一个looper绑定一个messageQeue;
- 一个looperThread可以用多个handler;
Handler跟Thread没有关系,它跟looper绑定,具体绑定那个looper,就要看用的那个构造方法,如果用的是new Handler()这个构造方法,会默认绑定它所在线程的looper和消息队列,如果用的是new Handler(Looper looper)的构造方法,就会与传进去的那个looper绑定。
六、 Android为什么要设计只能通过Handler机制更新UI呢?
最根本的目的是解决多线程迸发问题。
假如在一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样子的问题?
答案是:更新界面错乱。
如果对更新UI的操作都进行加锁处理的话又会产生什么样子的问题?
答案是:性能下降。
出于对以上问题的考虑,android给我们提供了一套更新UI的机制,我们只需要遵循这样的机制就行了,而根本不用去关心多线程问题,所有的更新UI的操作,都是在主线程的消息队列当中去轮询处理的。
===== ** 子线程操作UI就一定会报错崩溃吗?** ====
不!首先我们要了解系统为什么会崩毁并报错,是系统本身有一个判断机制,当执行到layoutRequset 时,只有判断到parentLayout != null系统才会判断并抛出异常,导致错误;
其实,在oncreate()方法中执行对UI控件值的修改是可以的,因为此时activity的生命周期才刚开始,parentLayout还没创建和绘制,其值是null,所以“监控失效”,所以不会抛错和崩溃,并且控件的属性值会被正常修改。
网友评论