热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Android】Handler之线程间通信

Android多线程之间的消息传递是通过Handler来实现的。不同的线程之间又分为两类:即MainThread(主线程)和WorkerThread(”苦力“线程)。MainTh

Android多线程之间的消息传递是通过Handler来实现的。不同的线程之间又分为两类:即Main Thread(主线程)和Worker Thread(”苦力“线程) 。


Main Thread又称为UI Thread,意思是主线程负责Android App的界面管理与显示和与用户之间的交互,是最重要的也是最核心的Thread,所以我们要对它细心呵护,如果处理不当使UI Thread阻塞,跳出了不友善的窗口,这样是非常影响用户体验的,也是无法容忍的。


而在Andrioid多线程开发中有两点是我们必须要注意的:

1.原则上Worker线程不允许修改UI线程(即主线程)的内容或控件的,但是某些特殊的控件允许被修改,如ProgressBar等。


2.在一个应用程序中,主线程通常用于接收用户的输入,以及将运算的结果反馈给用户,所以说对于一些可能会产生阻塞的操作,如连接远程服务器下载数据信息、复杂的   数据计算等必须放置在Worker Thread中。


Main Thread还是比较好理解的,那么这个Worker Thread又该如何理解呢?相信大家也看到了我上面对它的翻译,即“苦力”线程。我们可以把一个Android App比作一个公司的Project,Thread比作一个单独的Worker,Main Thread就是一个Leader。和用户接洽这种抛头露面的事(UI)和与上级的沟通由Leader来做,苦差事都是由Worker来做,比如跑腿,搬东西,订外卖,取快递,同时不能反对Leader的意思,然后Worker把工作的结果汇报给LeaderLeader进行汇总处理。这样来类比着是不是更好理解呢。


接下来介绍一下我们今天的主角Handler,handle的字面意思是处理、解决,加上er后缀意思大概就是处理事务的东西。

作为一个Android开发菜鸟,时常听到前辈大牛们对Handler是赞不绝口,赞赏Handle设计的精妙,简洁,强大。所以我们有必要了解一下Handler工作机理,和它一起搭配干活的还有和Looper(循环器)以及MessageQueue(消息队列)。俗话说的好,没图你说个J8,哈哈。我已经准备了高清五码给大家呦,走你。


上图就能够很清晰明了的向我们描述Handler的工作流程。椭圆形代表着Handler,长方形代表MessageQueue,而正六边形就是Looper。Handler可以通过obtainMessage()方法创建许多个消息Message,然后把Message发送到MessageQueue中,(在这里提一句,MessageQueue是一个队列,有先进先出(FIFO)的特性,相信学过数据结构的小伙伴都不会陌生,如果对它还不熟的话赶快去恶补数据结构吧),然后按照先进先出顺序由Looper调用Handler的HandleMessage()方法处理(如果此刻没有消息传递过来那么Looper就会阻塞掉),注意:HandleMessage()方法是你自己需要重写的,具体实现的功能由你的实现代码决定。


以上就是今天的预备知识,理解了以上内容之后,让我们通过一个例子来看看Android是如何通过Handler来实现多线程之间通信的。


首先打开我们的Android Studio,创建一个名为TestMultiThreadsCommunicateDemo(卧槽好长)的Blank Project,一路next下去就好了。


然后在activity_Main.xml中创建如下布局:

我们的主要业务逻辑就是点击一下按钮,然后从远程服务器传过来的消息显示在TextView中,替换掉原来的内容(Message)


布局文件代码如下:

    xmlns:tools="http://schemas.android.com/tools" android:layout_
android:layout_ android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:cOntext=".MainActivity">

android:id="@+id/textview"
android:text="Message"
android:background="#A6D4F2"
android:textSize="20sp"
android:textColor="#000000"
android:paddingLeft="20dp"
android:gravity="center_vertical"
android:layout_
android:layout_ />
            android:text="点击"
android:id="@+id/button"
android:layout_below="@id/textview"
android:layout_marginTop="20dp"
android:layout_
android:layout_ />


然后打开MainActivity.java,定义各控件的对象,获取TextView和Button控件,给button设置监听器(这里我们采用我比较习惯的匿名内部类的方式),很简单我就不解释了,代码在下面

public class MainActivity extends ActionBarActivity {
private Button mButton;
private TextView mTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button)findViewById(R.id.button);
mTextView = (TextView)findViewById(R.id.textview);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

}
});

}


接下来我们自己定义一个叫做NetworkThread的Worker Thread,顾名思义,就是模拟Worker线程访问网络下载数据,代码如下:

public class NetWorkThread extends Thread{
@Override
public void run() {
//模拟访问网络,所以当线程运行时,会先休眠1秒钟,处理数据。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//变量s模拟从网络中获取的数据。
//textview.setText(s); 这样的做法是错误的,因为在Android系统当中只有Main Thread才能操作UI。
String s = "Message from the server!!!";


}
}

NetWorkThread继承了Thread类,那么就必须要重写run()方法,我相信聪明的小伙伴们都是很清楚的。主要功能就是模拟在服务器端下载数据,其实就是字符串s而已,中间的sleep休眠1000毫秒代表取数据消耗的时间,需要注意的是,在sleep()方法调用的时候要try catch一下,防止抛出InterruptedException。还有就是千万不要像注释里的

  //变量s模拟从网络中获取的数据。
//textview.setText(s); 这样的做法是错误的,因为在Android系统当中只有Main Thread才能操作UI。
一样,这样你就违背了我上面提到的第一点:

1.原则上Worker线程不允许修改UI线程(即主线程)的内容或控件的,但是某些特殊的控件允许被修改,如ProgressBar等。


接下来我们来定义一个我们自己的Handler类MyHandler

public class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) { //子类必须覆盖这个方法来接收消息
String string = (String)msg.obj;
mTextView.setText(string);
}
}

这里我们重写了handleMessage()方法,还记得handleMessage方法的作用么?对了就是Handler提供给Looper来调用处理消息的方法。msg是传递过来的消息对象,注意这里的obj,它是消息msg的一个属性,代表你传递的信息,可以使任意类型的数据,最后再调用TextView的setText()方法改变TextView显示的文本。


之后我们就要实现MyHandler类了:

 private Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button)findViewById(R.id.button);
mTextView = (TextView)findViewById(R.id.textview);handler = new MyHandler(); mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread thread = new NetWorkThread();
thread.start();
}
});

}
 
 
 
 定义一个Handler对象handler,new一个MyHandler出来,然后在监听器中定义我们的“苦力线程”,调用start()方法,开启Worker线程。 
 


最重要的来了,我们到底是怎样实现不同线程间的通信的呢?

先看代码:

public class NetWorkThread extends Thread{
@Override
public void run() {
//模拟访问网络,所以当线程运行时,会先休眠1秒钟,处理数据。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//变量s模拟从网络中获取的数据。
//textview.setText(s); 这样的做法是错误的,因为在Android系统当中只有Main Thread才能操作UI。 String s = "Message from the server!!!"; Message msg = handler.obtainMessage();
//handler.sendMessage(msg);
msg.obj = s;
//sendMessage()方法,无论是在主线程当中发送,还是在Worker Thread当中发送都是可以的
handler.sendMessage(msg);
}
}
 
 
 
 
我们定义了一个Message消息对象msg,然后把下载过来的数据,附加在msg的obj属性上,然后通过handler的sendMessage()方法,发送给MessageQueue,然后Looper就会自动调用Handler的handleMessage()方法:
public void handleMessage(Message msg) {   //子类必须覆盖这个方法来接收消息
String string = (String)msg.obj;
mTextView.setText(string);
}

然后你点击按钮的时候,神奇的事情就发生了。



好了,最后把activity的代码汇总一下给大家,感谢。


package com.example.chad.testworkerthreadcommunicate;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity {
private Button mButton;
private TextView mTextView;
private Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button)findViewById(R.id.button);
mTextView = (TextView)findViewById(R.id.textview);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread thread = new NetWorkThread();
thread.start();
}
});
handler = new MyHandler();
}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

public class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) { //子类必须覆盖这个方法来接收消息
String string = (String)msg.obj;
mTextView.setText(string);
}
}

public class NetWorkThread extends Thread{
@Override
public void run() {
//模拟访问网络,所以当线程运行时,会先休眠1秒钟,处理数据。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//变量s模拟从网络中获取的数据。
//textview.setText(s); 这样的做法是错误的,因为在Android系统当中只有Main Thread才能操作UI。
String s = "Message from the server!!!";

Message msg = handler.obtainMessage();
//handler.sendMessage(msg);
msg.obj = s;
//sendMessage()方法,无论是在主线程当中发送,还是在Worker Thread当中发送都是可以的
handler.sendMessage(msg);
}
}
}







推荐阅读
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 开发笔记:(002)spring容器中bean初始化销毁时执行的方法及其3种实现方式
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了(002)spring容器中bean初始化销毁时执行的方法及其3种实现方式相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 一、死锁现象与递归锁进程也是有死锁的所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作 ... [详细]
  • 2021年最详细的Android屏幕适配方案汇总
    1Android屏幕适配的度量单位和相关概念建议在阅读本文章之前,可以先阅读快乐李同学写的文章《Android屏幕适配的度量单位和相关概念》,这篇文章 ... [详细]
  • 这两天用到了ListView,写下遇到的一些问题。首先是ListView本身与子控件的焦点问题,比如我这里子控件用到了Button,在需要ListView中的根布局属性上加上下面的这一个属性:and ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 今天终于成功使用ReactNative打包APK成功,IOS暂时没有 ... [详细]
author-avatar
tomodachitch
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有