【Android】使用Handler做多个线程之间的通信
一:两个线程之间的通信
private void startHandler(){new Thread(new Runnable() {@Overridepublic void run() {
// //准备当前线程的looper
// Looper.prepare();
// //获取当前线程的实例
// Looper looper = Looper.myLooper();
//
// //在子线程中创建Handler,如果不传looper,默认是和子线程关联,子线程中去更新UI就会报错
// Handler handler1 = new Handler();
//
// Looper.loop();//开始循环发送消息// 有传Looper.getMainLooper(),表示和主线程关联Looper mainLooper = Looper.getMainLooper();Handler handler1 = new Handler(mainLooper);String id = etUserId.getText().toString();String urlAddress = "http://titok.fzqq.fun/addons/cms/api.user/userInfo?user_id=" + id + "&type=archives";try {URL url = new URL(urlAddress);HttpURLConnection connection = (HttpURLConnection)url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(8000);connection.setReadTimeout(8000);InputStream inputStream = connection.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));StringBuilder builder = new StringBuilder();String line;while((line = reader.readLine() ) != null){builder.append(line);}//返回主线程更新UIhandler1.post(new Runnable() {@Overridepublic void run() {String result = builder.toString();Log.i(TAG, "run: 网络访问的结果是:" + result);}});connection.disconnect();} catch (MalformedURLException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}}).start();}
四种情况
- Handler在哪个线程中创建,它就属于哪个线程
1:利用主线程Handler更新UI
之前更新UI信息是调用runOnUiThread()方法,今天学习到第二种方法,主线程中创建Handler(这里是成员变量),再在子线程中利用handler.post()方法更新UI
2:子线程中的Handler不传Looper
在子线程中创建Handler,进行UI更新就会报错;
因为这里的handler.post()是post给子线程了,子线程不可以更新UI的,所以报错
3:子线程中的Handler设置Looper
非要在子线程中进行post请求,就需要去获取子线程的looper
调整指定在子线程中进行handler的post请求,成功,没有日志打印(正常)
4:子线程利用主线程的Looper
子线程的Handler中传入主线程的looper也是可以正常在主线程中进行UI更新的
二:一些细节
1:Looper.prepare
作用:为当前线程创建 Looper
并初始化消息循环相关的基础设施 。在 Android 中,主线程(UI 线程)的 Looper
是系统自动初始化好的,但子线程若要使用 Handler
进行消息循环,就必须手动调用 Looper.prepare()
。
内部逻辑:它会在当前线程中创建一个 Looper
对象,同时该对象内部会维护一个 MessageQueue
(消息队列),用于存储后续发送过来的 Message
(消息) 。如果在已经存在 Looper
的线程中再次调用 Looper.prepare()
,会抛出 RuntimeException
,保证一个线程最多只有一个 Looper
。
2:Looper.myLooper();
作用:获取当前线程关联的 Looper
对象 。如果当前线程还未通过 Looper.prepare()
初始化 Looper
,那么调用此方法会返回 null
。一般在需要明确拿到当前线程 Looper
,用于一些和 Looper
关联的操作(比如给 Handler
构造方法传特定 Looper
等场景 )时使用,不过很多时候如果只是在当前线程创建 Handler
,Handler
内部会自动去获取当前线程的 Looper
,不一定需要显式调用这个方法来获取。
拓展:Looper.myLooper.quit()方法是退出线程,也有.quitSafely()安全退出这一说
3:Looper.loop()
作用:开启 Looper
的消息循环,让当前线程进入一个不断从 MessageQueue
中取出消息并处理的循环过程 。
总结:Looper相当于可以往容器里放Message的工具,Handler相当于一个放Message的容器,.loop方法不断从队列里取出消息交给对应的Handler,Handler调用handleMessage方法处理消息
- 普通线程执行完 run () 方法后就会终止,无法再次使用
- 加入 Looper 后,Looper.loop () 会启动一个无限循环,让线程一直运行
- 这个循环会不断从 MessageQueue 中取出消息并处理
- 当你需要使用这个线程时,只需通过 Handler 发送消息即可
- 线程会一直处于等待 - 处理消息的状态,直到调用 quit () 方法才会真正结束
三:多个线程间的通信
场景:(多对一)多个线程发送消息,一个线程来接收
1:延迟发送消息
private Handler handler = new Handler(Looper.getMainLooper());
else if (v.getId() == R.id.btn_post_delayed) {delayToast();
private void delayToast() {handler.postDelayed(new Runnable() {@Overridepublic void run() {Toast.makeText(HandlerActivity.this, "我他喵延迟1s来啦", Toast.LENGTH_SHORT).show();}},1000);}
效果如下
2:处理消息队列
如果有多个子线程和主线程需要处理同一件事情的话,我们可以利用同一个Handler,这样的代码也会更加直观和清爽,对底层的性能消耗也会更小一点
(1)写法一
一般要去指定它是一个主线程,不传参的写法是已经过时的写法;
如果还需要处理一些其他的事情,只有一个handleMessage不够,那就这种写法
/*** 第一种处理接收消息的写法* 记得要传参,不传参的过时了*/private Handler sendHandler = new Handler(Looper.getMainLooper()){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);}};
(2)写法二
new 一个Handle.Callback()接口,重写handlerMessage方法
接收消息的一方
private Handler sendHandler1 = new Handler(new Handler.Callback(){@Overridepublic boolean handleMessage(@NonNull Message msg) {switch(msg.what){case 123:String data = (String) msg.obj;Toast.makeText(HandlerActivity.this, data, Toast.LENGTH_SHORT).show();return true;//回收Messagecase 234:int num = (int) msg.obj;Toast.makeText(HandlerActivity.this, num+"", Toast.LENGTH_SHORT).show();return true;}return false;}});
返回ture底层会判断出当前消息已经被消费过了,就可以被回收掉了
发送消息的一方
1:Message详解
通信的桥梁就是Message
- message.what 标记消息是从哪里被发送过来的,类似于请求码
- message.obj Message中的obj,就是Object,海纳百川有容乃大,传什么类型都可以接受,再把这些数据发送出去
startTaskA和B两种发送消息的方式最大的不同就在于,创建Message的方式;
区别:new Message()是每次都去创建一个Message,代价太大;
obtatin(obtain 英 [əbˈteɪn] 得到)是从消息池中获取一个Message对象,可以减小创建对象的开销,更适合性能要求高的地方
Message message = new Message();
Message message = sendHandler1.obtainMessage();
以下是详细代码
private void startTaskA(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}String data = "我是服务器返回的数据:{name = 我他喵莱纳}";//创建一个Message,通过sendHandler1对象发送消息Message message = new Message();message.what = 123;message.obj = data;sendHandler1.sendMessage(message);}}).start();}
private void startTaskB(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}Message message = sendHandler1.obtainMessage();message.what = 234;message.obj = 520;sendHandler1.sendMessage(message);}}).start();}
四:post通信和Message通信对比
在HandlerMessage中处理通过sendMessage方法发送的消息,可以集中处理多个线程发来的消息;有很多任务需要同时去处理,需要把它写在一起,就用what进行区分
post更适合一对一线程之间的通信,代码逻辑更简单,不需要数据的传递;