Android(java)学习笔记145:Handler消息机制的原理和实现
联合学习 Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
1. 首先我们通过一个实例案例来引出一个异常:
(1)布局文件activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" > <TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" /> </RelativeLayout>
(2)MainActivity.java:
package com.itheima.testthread; import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.EditText;
import android.widget.TextView; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println(Thread.currentThread().getName());
final TextView tv = (TextView) findViewById(R.id.tv);
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
tv.setText("哈哈哈"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
} }
(3)布署程序到模拟器上,出现如下效果,程序直接stop;并且直接报出异常错误:
报错:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
意思:从错误线程调用的异常。谁创建的view,谁才能修改编辑view,只有主线程才可以修改view(所有的View都是主线程创建的)。
上面的报错是ViewRootImpl对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法来完成的,如下所示:
void checkThread() {
if(mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(" Only the
original thread that created a view hierarchy can touch its views.");
}
}
总结:上面报这个CalledFromWrongThreadException错误,是因为只有主线程才能修改View更新UI,但是我们这里却自己新建一个线程new Thread()其中run()的内容涉及到更新UI,这是不允许的,所有才会出现这样的错误警告。
但是往往我们编写程序的时候设计多个线程需要控制UI显示,但是我们上面已经说过了UI更新只能交给主线程,为了解决这个矛盾,解放程序员,google开发出来消息机制,用来子线程和主线程通信,子线程要更新UI,就会发送消息数据给主线程,这样主线程就会更新相应的UI界面,从而解决这个矛盾。
(4)系统为什么不允许在子线程中访问UI呢 ?
这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那为什么系统不对UI控件的访问加锁呢? 缺点有两个:
首先加上锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,这是因为锁机制会阻塞某些线程的执行。
鉴于这两个缺点,最简单且高效的方法就是采用单线程模型去处理UI操作,对于开发者来说也不是很麻烦,只需要通过Handler切换一下UI访问的执行线程即可。
2. 消息机制的原理和实现
消息机制实现逻辑图:
这里handler负责把外面的子线程的消息发到message queue消息队列之中,然后looper会以无限循环的形式查看message queue里面是不是有新消息,如果有的话,looper就把消息发送到handler,交给handler的handlemessage根据信息,更新UI,否则就一直等待。
Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal不是线程,它的作用是可以在每个线程中存储数据。我们知道,Handler创建的时候会采用当前的线程的Looper来构造消息循环系统,那么Handler内部如何获取当前线程的Looper呢 ? 这就是要使用ThreadLocal了,ThreadLocal可以在不同线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程默认是没有Looper,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时候就会初始化Looper,这也就是主线程中默认可以使用Handler的原因。
使用消息机制目的:
Android系统的主线程安全的系统,别的线程不可以修改ui线程的界面,如果子线程里面想要更新UI,就必须采用消息机制处理。
使用handler消息处理器编写代码步骤:
(1)在主线程里面声明消息处理器handler;
private Handler handler = new Handler() { };
(2)子线程想要更新UI,利用消息机制
Message msg = new Message();
msg.what 消息类型
msg.obj 具体消息携带的数据
handler.sendMessage(msg);
(3)系统内部有消息队列message queue和消息looper,轮询到消息,交给handler去处理:
(4)重写handler的方法处理消息:
public void handleMessage (Message msg) {
//运行在主线程,更新UI };
接下来我们就修改上面的案例,就更好理解这个消息机制:
MainActivity.java:
package com.itheima.testthread; import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.EditText;
import android.widget.TextView; public class MainActivity extends Activity {
private TextView tv;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
// 运行在主线程,更新ui
String str = (String) msg.obj;
tv.setText(str);
EditText et; };
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println(Thread.currentThread().getName());
tv = (TextView) findViewById(R.id.tv);
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
// tv.setText("哈哈哈"+i);
Message msg = new Message();
msg.obj = "哈哈哈" + i;
handler.sendMessage(msg);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
} }
acitivity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" /> </RelativeLayout>
3. Hanlder之 --- 传递Message --- 传递Runnable对象
(1)简介:
一般来说在工作线程中执行耗时任务,当任务完成时,会返回UI线程,一般是更新UI。这时有两种方法可以达到目的。
一种是:handler.sendMessage。发一个消息,,它是一个Message,再根据消息,执行相关任务代码。
另一种是:handler.post(r)。r是要执行的任务代码,r是Runnable对象。意思就是说r的代码实际是在UI线程执行的。可以写更新UI的代码。(工作线程是不能更新UI的)
(2) 传递Message。用于接受子线程发送的数据, 并用此数据配合主线程更新UI。
在Android中,对于UI的操作通常需要放在主线程中进行操作。如果在子线程中有关于UI的操作,那么就需要把数据消息作为一个Message对象发送到Handler的消息队列中,然后,有Handler中的handlerMessge方法处理传过来的数据信息,并操作UI。当然,Handler对象是在主线程中初始化的,以为它需要绑定在主线程的消息队列中。
类sendMessage(Message msg)方法实现发送消息的操作。在初始化Handler对象时重写的handleMessage方法来接收Messgae并进行相关操作:
//Handler处理子线程消息代码示例:
public class Activity01 extends Activity
{
//声明ProgressBar对象
private ProgressBar m_ProgressBar;
private ProgressBar m_ProgressBar2;
private Button mButton01;
protected static final int GUI_STOP_NOTIFIER = 0x108;
protected static final int GUI_THREADING_NOTIFIER = 0x109; public int intCounter=0; @Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//设置窗口模式,,因为需要显示进度条在标题栏
requestWindowFeature(Window.FEATURE_PROGRESS);
setProgressBarVisibility(true);
setContentView(R.layout.main);
//取得ProgressBar
m_ProgressBar = (ProgressBar) findViewById(R.id.ProgressBar01);
m_ProgressBar2= (ProgressBar) findViewById(R.id.ProgressBar02);
mButton01 = (Button)findViewById(R.id.Button01);
m_ProgressBar.setIndeterminate(false);
m_ProgressBar2.setIndeterminate(false);
//当按钮按下时开始执行,
mButton01.setOnClickListener(new Button.OnClickListener()
{
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
//设置ProgressBar为可见状态
m_ProgressBar.setVisibility(View.VISIBLE);
m_ProgressBar2.setVisibility(View.VISIBLE);
//设置ProgressBar的最大值
m_ProgressBar.setMax(100);
//设置ProgressBar当前值
m_ProgressBar.setProgress(0);
m_ProgressBar2.setProgress(0);
//通过线程来改变ProgressBar的值
new Thread(new Runnable() {
public void run()
{
for (int i = 0; i < 10; i++)
{
try
{
intCounter = (i + 1) * 20;
Thread.sleep(1000);
if (i == 4)
{
Message m = new Message();
m.what = Activity01.GUI_STOP_NOTIFIER;
Activity01.this.myMessageHandler.sendMessage(m);
//将message发送到消息队列
break;
}
else
{
Message m = new Message();
m.what = Activity01.GUI_THREADING_NOTIFIER;
Activity01.this.myMessageHandler.sendMessage(m);
//将message发送到消息队列
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}).start();
}
});
} //通过匿名类复写Handler类中的handleMessage方法,用于接收传递到消息队列中的Message,并进行UI操作。
Handler myMessageHandler = new Handler()
{
// @Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
//ProgressBar已经是对大值
case Activity01.GUI_STOP_NOTIFIER:
m_ProgressBar.setVisibility(View.GONE);
m_ProgressBar2.setVisibility(View.GONE);
Thread.currentThread().interrupt();
break;
case Activity01.GUI_THREADING_NOTIFIER:
if (!Thread.currentThread().isInterrupted())
{
// 改变ProgressBar的当前值
m_ProgressBar.setProgress(intCounter);
m_ProgressBar2.setProgress(intCounter);
// 设置标题栏中前景的一个进度条进度值
setProgress(intCounter*100);
// 设置标题栏中后面的一个进度条进度值
setSecondaryProgress(intCounter*100);//
}
break;
}
super.handleMessage(msg);
}
};
}
以上的例子中,子线程只是对进度条的参数进行了变更,并将结果以message形式发送到消息队列中去,子线程的内部并未进行UI操作,而是在重写的Handler的handlerMessage方法中操作了UI界面。
(3)传递Runnable对象。用于通过Handler绑定的消息队列,安排不同操作的执行顺序。
Handler对象在进行初始化的时候,会默认的自动绑定消息队列。利用类post方法,可以将Runnable对象发送到主线程的消息队列中,按照队列的机制按顺序执行不同的Runnable对象中的run方法。
public class HandlerActivity extends Activity {
//声明两个按钮控件
private Button startButton = null;
private Button endButton = null; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//根据控件的ID得到代表控件的对象,并未这两个按钮设置相应的监听器
startButton = (Button)findViewById(R.id.startButton);
startButton.setOnClickListener(new StartButtonListener());
endButton = (Button)findViewById(R.id.endButton);
endButton.setOnClickListener(new EndButtonListener());
} class StartButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
//调用Handler的post方法,将要执行的线程对象添加到队列当中
handler.post(updateThread);
}
} class EndButtonListener implements OnClickListener{
@Override
public void onClick(View v) {
handler.removeCallbacks(updateThread);
}
} //创建一个Handler对象
Handler handler = new Handler();
//将要执行的操作写在线程对象的run方法当中
Runnable updateThread = new Runnable(){
@Override
public void run() {
System.out.println("UpdateThread");
//在run方法内部,执行postDelayed或者是post方法
handler.postDelayed(updateThread, 3000);
}
};
}
程序的运行结果就是每隔3秒钟,就会在控制台打印一行UpdateTread。这是因为实现了Runnable接口的updateThread对象进入了空的消息队列即被立即执行run方法,而在run方法的内部,又在3000ms之后将其再次发送进入消息队列中。
注意:
post方法虽然发送的是一个实现了Runnable接口的类对象,但是它并非创建了一个新线程,而是执行了该对象中的run方法。也就是说,整个run中的操作和主线程处于同一个线程。
(4)多线程中传递Runnable对象
这样对于那些简单的操作,似乎并不会影响。但是对于耗时较长的操作,当它被加入到消息队列中之后执行会占用很长的时间,以至于处于同一线程的其他操作无法继续执行,就会出现“假死”。为了解决这个问题,就需要使得handler绑定到一个新开启线程的消息队列上,在这个处于另外线程的上的消息队列中处理传过来的Runnable对象和消息。
具体操作方法如下:
public class HandlerTest2 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//打印了当前线程的ID
System.out.println("Activity-->" + Thread.currentThread().getId()); //生成一个HandlerThread对象
HandlerThread handlerThread = new HandlerThread("handler_thread"); //在使用HandlerThread的getLooper()方法之前,必须先调用该类的start(),同时开启一个新线程;
handlerThread.start(); //将由HandlerThread获取的Looper传递给Handler对象,即由处于另外线程的Looper代替handler初始化时默认绑定的消息队列来处理消息。——关键是这句
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
Message msg = myHandler.obtainMessage(); //将msg发送到目标对象,所谓的目标对象,就是生成该msg对象的handler对象
Bundle b = new Bundle();
b.putInt("age", 20);
b.putString("name", "Jhon");
msg.setData(b);
msg.sendToTarget(); //将msg发送到myHandler
} //定义类,这里只是定义MyHandler一个类,并未和新线程相关起来
class MyHandler extends Handler{
public MyHandler(){
}
public MyHandler(Looper looper){
super(looper);
} @Override
public void handleMessage(Message msg) {
Bundle b = msg.getData();
int age = b.getInt("age");
String name = b.getString("name");
System.out.println("age is " + age + ", name is" + name);
System.out.println("Handler--->" + Thread.currentThread().getId());
System.out.println("handlerMessage");
}
}
}
这样,当使用sendMessage方法传递消息或者使用post方法传递Runnable对象时,就会把它们传递到与handler对象绑定的处于另外一个线程的消息队列中,它们将在另外的消息队列中被处理。而主线程还会在发送操作完成时候继续进行,不会影响当前的操作。
这里需要注意,这里用到的多线程并非由Runnable对象开启的,而是ThreadHandler对象开启的。Runnable对象只是作为一个封装了操作的对象被传递,并未产生新线程。
Android(java)学习笔记145:Handler消息机制的原理和实现的更多相关文章
- Android:日常学习笔记(9)———探究广播机制
Android:日常学习笔记(9)———探究广播机制 引入广播机制 Andorid广播机制 广播是任何应用均可接收的消息.系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播.通过将 In ...
- Android(java)学习笔记202:Handler消息机制的原理和实现
联合学习 Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系 1. 首先我们通过一个实例案例来引出一个异常: (1)布局文件activity_m ...
- Android开发学习之路-Handler消息派发机制源码分析
注:这里只是说一下sendmessage的一个过程,post就类似的 如果我们需要发送消息,会调用sendMessage方法 public final boolean sendMessage(Mess ...
- android菜鸟学习笔记26----Android广播消息及BroadcastReceiver
1.广播类型: Android中的广播有两种类型:标准广播和有序广播.其中,标准广播是完全异步发送的广播,发出之后,几乎所有的广播接收者都会在同一时刻收到这条广播消息,因而,这种类型的广播消息是不可拦 ...
- java学习笔记13--反射机制与动态代理
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址. Java的反射机制 在Java运行时环境中,对于任意 ...
- Android全面解析之由浅及深Handler消息机制
前言 很高兴遇见你~ 欢迎阅读我的文章. 关于Handler的博客可谓是俯拾皆是,而这也是一个老生常谈的话题,可见的他非常基础,也非常重要.但很多的博客,却很少有从入门开始介绍,这在我一开始学习的时候 ...
- Android消息传递之Handler消息机制
前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...
- Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...
- Android Handler 消息机制原理解析
前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...
随机推荐
- 3.4-3.6 Hive Storage Format
一.file format ORCFile在HDP 2:更好的压缩,更好的性能: https://zh.hortonworks.com/blog/orcfile-in-hdp-2-better-com ...
- UVa 12333 Revenge of Fibonacci (字典树+大数)
题意:给定一个长度小于40的序列,问你那是Fib数列的哪一项的前缀. 析:首先用大数把Fib数列的前100000-1项算出来,注意,一定不能是100000,要不然会WA的,然后每个数取前40位,不足4 ...
- golang http/transport 代码分析
请结合源码阅读,本文只是总结一下,源码里有详细的注释.基于:go1.12.4 http.Client 表示一个http client端,用来处理HTTP相关的工作,例如cookies, redirec ...
- sql查询的时候,等于这两个的值得全部取出来
sql查询的时候 用or连接 ad.jqtype='人文历史' or ad.jqtype='名胜古迹'
- 基于ndk_r7_windows编译实现ndk项目,不需要cygwin
下面就介绍下Android NDK的入门学习过程: 入门的最好办法就是学习Android自带的例子, 这里就通过学习Android的NDK自带的demo程序:hello-jni来达到这个目的. 一. ...
- uoj#79. 一般图最大匹配(带花树)
传送门 带花树 不加证明的说一下过程好了:每次从一个未匹配点\(S\)出发bfs,设\(S\)为\(1\)类点,如果当前点\(v\)在本次bfs中未经过,分为以下两种情况 1.\(v\)是未匹配点,那 ...
- .netcore--Controller后台实现企业微信发送消息
一.获得企业微信管理端权限,登录企业企业微信管理端界面,并创建应用,如下图中的[网站消息推送] 二.参见企业微信API文献,根据corpid=ID&corpsecret=SECRET(其中企业 ...
- OFFICE 365 A1 Plus账号注册
OFFICE365 A1 Plus账号注册 Office2019与Office365专业增强版之间的区别: Office2019是一次性购买,不会在购买后接收功能更新,但会根据需要接收质量和安全修补程 ...
- 最短路之Floyd(多源)HDU 1874
#include <iostream> #include <cstdio> #include <cstring> using namespace std; #def ...
- __str__,__repr__,__format__
__str__,__repr__ __str__:控制返回值,并且返回值必须是str类型,否则报错 __repr__:控制返回值并且返回值必须是str类型,否则报错 __repr__是__str__的 ...