android如何在子线程中更新UI?
工具/原料
android环境
一.基本概念
1、当启动一个APP的时候,就会有一个进程(一般情况下)process,一般情况下一个APP只有一个进程。
2、每个进程中,都有一个虚拟机实例,所以说,一般情况下,每个APP都有一个独立的虚拟机,虽然实际物理上只有一个。
3、正因为Dalvik虚拟机允许多个instance的存在,所以每个APP都有一个单独的进程,有一个单独的虚拟机,所以这个进程也可以叫DVM进程,也叫LINUX进程。也正因此,APP和APP之间基本上是互不干扰的,一个APP有问题,比如内存泄漏等,只会影响到他自己,即使他的虚拟机崩溃了,顶多也就是这个APP崩溃了,不会影响到其他的APP
4、当然,第3点说的也不是绝对的,如果有一些APP权限较高之类的,他可以使用一些系统资源,也可能会导致系统重启之类的。所以,只是一般情况下,APP和APP之间是互相独立的。
5、每个进程中,会有一个主线程MainThread,通常情况下,一个应用所有的组件都运行在这个线程中
6、线程中许多的逻辑(系统事件处理、户输入事件处理)、用户自己写的代码等等,如果我们写的代码特别耗时,比如网络请求、延迟等,会直接阻塞主线程的运行,那么界面就会卡顿,点击按钮等什么都操作不了。当卡顿时间超过5S,系统就会提示“该应用无响应”,会报ANR错误。
7、所以,一些耗时的操作,会拿到子线程去操作,不影响主线程的运转。但是,android的UI更新不是线程安全的,换句话说就是不允许在子线程中进行UI的更新,如果想进行UI的更新,就必须在主线程中进行。
8、OK,说到这里问题有来了,如何在子线程中进行更新UI。具体如何创建子线程,可以看我之前的经验。
二.异步消息处理机制
1、这里简单说下异步消息处理机制。这里可能你会看的比较迷茫,但看到后面2部分后,就会理解了。
2、Android中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue和Looper。
3、1)Message这个很容易理解,就是消息,他可以携带少量的消息,比如你在子线程中去更新UI,就将要更新的内容放到message中,然后将message发送到主线程中,由主线程去做更新UI的操作。也就是说,messge可以用于不同线程中进行数据交换。
4、2)Handler这个就是处理者的意思。你可以在线程中调用Handler的sendMessage方法去发送消息,然后在主线程中,通过handleMessage去接受这个message消息,然后去处理。
5、3)Me衡痕贤伎ssageQueue消息队列,Android主线程包含一个消息队列,它主要用于存放所有通过Handler发送的消息,可以是Mes霜杼厮贿sage,也可以是Runnable。主线程在创建的时候会默认创建消息队列,子线程在创建的时候默认不会创建消息队列,但是可以手动创建。当在主线程创建一个Handler,这个Handler会自动绑定主线程以及主线程的消息队列。然后在子线程通过这个Handler发送消息的时候,这个消息就会被加入到主线程的消息队列了。
6、4)Loo圬桦孰礅per那么,通过Handler的sendMessage可以发送消息加入到主线程的消息队列中。那么,主线程在什么情况下,从茇坍酮踪消息队列中取出这些消息进行处理呢?就需要Looper了。Looper是每个线程中的消息队列的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,每当发现消息队列中有消息,就会取出来,然后传递到Handler的handleMessage()方法中。所以,我们只需要在主线程中完成handleMessage()方法的实现,剩下的就不需要你操心了,Looper会帮我们完成其他所有的事情的。Looper和消息队列一样,主线程会自动创建,而且每个线程只有一个消息队列和一个Looper
7、总结一下:1)主线程在创建时拥有一个消息队列,和一个管家Looper;
8、2)在主线程中创建一个Handler,这个handler就会绑定这个主线程和消息队列;
9、3)在主线程中实现Handler的handleMessage()方法,用于处理收到的消息;
10、4)在子线程中调用Handler的sendMessage()方法,就可以将数据传递到主线程的消息队列中;
11、5)Looper发现消息队列中有数据,就将数据取出,然后调用之前写的handleMessage()方法,并传递这个数据。
三.Handler--sendMessage方式
1、首先,在布局文竭惮蚕斗件中增加一个TextView,默认值为hello<TextViewandroid:id租涫疼迟="@+id/view1"android:layout_height="wrap_content"android:layout_width="match_parent"android:text="hello"/>
2、创建一个Handler实例,并实现handleMessage方法privateHand盟敢势袂lerhandler=new惺绅寨瞀Handler(){ publicvoidhandleMessage(Messagemsg){ switch(msg.what){ case1: //在这里可以进行UI操作 text.setText("你好"); break; default: break; } } };
3、创建一个子线程,并在子线程中发送消息classMyThread2implements咯悝滩镞Runnable{ @Override publicvoi颊俄岿髭drun(){ Messagemessage=newMessage(); message.what=1; handler.sendMessage(message); } }
4、在onCreate()中,启动线程text=(TextView)findViewById(R.id.view1);MyThread2myThread2=newMyThread2();newThread(myThread2).start();
5、可以看到,本来默认的值为"nihao",现在更改为“你好”,说明在子线程中更改UI成功了
四.Handler--Post方式
1、Post允许把一个Runnable对象入队到消息队列中。常用的有post(Runnable)、postDelayed(Runnable,long)
2、Post方式来说,它会传递一个Runnable对象到消息队列中。所以,我们不需要实现handleMessage()方法了,因为他也不是一个message,是一个Runnable。发送Runnable对象到消息队列中后,Looper会将其取出,然后被执行。所以,可以直接在这个Runnable中进行UI的更新
3、首先,创建一个Handler对象。因为不需要实现handleMessage()方法,所以直接HandlermhHandler=newHandler();就可以了
4、然后在之前写的MyThread2子线程中,更改run()方法中的内容class咯悝滩镞MyThread2implementsRunnable{ @Over鸡堕樱陨ride publicvoidrun(){ mhHandler.post(newRunnable(){@Overridepublicvoidrun(){ text.setText("你好");}}); } }在这里,通过mhHandler.post直接更改UI内容
5、可以看到,也修改成功了。
6、那么什么时候用post,什么时候用mes衡痕贤伎sage呢?通过代码结构来看,如果有大量的更改UI代码的地方,那么所有修改UI的代码全部都写在了茑霁酌绡handleMessage方法中,很容易管理,但相对于post更繁琐了些。所以,如果只有一个地方需要处理,那么用post更好些,如果有很多地方需要更新UI,用message更好些。当然,还要根据实际情况做实际处理。
7、postDelayed(Runnable,long)就是做一个延迟。我这里写了8000,就是8S的意思
8、到这里,就简单介绍完了。