Android_Handler
今天无意间翻出来从前有关Handler的笔记,突然感觉到有点懵逼,不过现在明白了,还是记录下来,防止以后再次遇见这种情况,同时跟大家分享一下,如果有什么错误的,请各位大神指正。以下的都是本人的理解,希望指正。谢谢。
我想在开始今天的学习之前先跟大家讲一下有关线程的知识。
线程
线程有主次之分,每个正在执行的程序都存在主线程,可能存在若干个分支线程(子线程)
当正在执行的程序存在多个线程时,表现为多个线程同时工作
当正在执行的程序存在多个线程时,这些线程会交替运行,交替的间隔时间和每次每个线程的执行时间都是不可以确定的
创建线程的方法:
1) 自定义类继承java.lang.Thread类,并重写public void run()方法,当需要启动线程时,初始化该线程类的对象,并调用start()方法开启线程
2) 自定义类实现java.lang.Runnable接口,并重写抽象的public void run()方法,当需要启动线程时,初始化Thread类的对象,并在创建对象时使用自定义的Runnable实现类对象作为其构造方法的参数,最后,调用Thread类对象的start()方法开启线程
线程控制
Thread.sleep()方法可以让线程休眠,即多少毫秒之内不会工作
ANDROID的UI线程模型
UI:User Interface,即:(软件与)用户(的)接口
在ANDROID系统中,主线程也叫作UI线程
所有的显示控件的初始化都是在主线程中运行的,ANDROID系统约定:只有创建控件的线程(主线程)才可以控制这些控件!即:子线程不允许控制各个控件
我们在编写Android应用程序的时候,有的Activity中不仅仅只有一个主线程在执行操作,像一些网路请求这种耗时操作,我们应该交给子线程去执行,如果不交给子线程去执行那么你就会遇见 ANR错误。
ANR错误
ANR(Application Not Respoding)表示“应用程序没有响应”,出现该错误的原因在于执行时间过长,例如Activity的执行时间超过5s,或者广播接收者的执行时间超过10s
以上错误的出现原因针对主线程,子线程的执行时间不受限制
既然我们已经知道了在Android中需要用到子线程去执行一些耗时操作,那么问题来了,子线程只能去执行操作,但是不能更改UI,那么主线程和子线程是如何通信的呢?这就需要我们学习今天的知识 Handler机制。我们在学习Handler机制之前需要有以下知识做铺垫。
1、Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。通常我们会使用到的几个属性:message.what->用于标识是哪一个线程发送出来的message。what属性的值应该使用静态常量表示。message.arg1和arg2->用于携带 int值,message.obj携带的是一个Object对象。
2、Handler
Handler主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列的辗转处理后,追中会传到Handler的handlerMessage()方法中。
3、MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发动的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
4、Looper
Loop是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,饭后每当发现MessageQueue中存在一条消息,就会将它取出来,并传递到Handler的handlerMessage()方法中。每个线程中也只会有一个Looper对象。
为了让大家能够更好的理解Handler消息机制的工作原理,请看下图:
通过以上的知识了解,相信大家对Handler消息机制大概有了一定的了解,那么我们就来实践一下吧。以下的例子我是使用了一个Clock例子,该例子就是启动程序,主界面上实时显示当前的时间。
1、我的MainActivity所对应的activity_main.xml布局文件很简单就只有一个TextView控件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<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="com.example.clockthreadtest.MainActivity">
<TextView
android:id="@+id/tv_Clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello World!"
android:textSize="32sp"
/>
</RelativeLayout>
2、我在MainActivity中使用了大家平时都使用的Handler机制来发送消息和处理消息,代码很基础,我就不解释了。
public class MainActivity extends Activity {
private TextView tvClock;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
tvClock = (TextView) findViewById(R.id.tv_Clock);
handler =new InnerHandler();
new InnerThread().start();
}
class InnerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String data= (String) msg.obj;
tvClock.setText(data);
}
}
class InnerThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1200; i++) {
Message message =new Message();
message.obj =new Date().toString();
handler.sendMessage(message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
其实对于Handler机制,要是按照上面的写法完全没有什么错误,但是Handler其实还有其他的写法,就让我们来了解一下,开阔一下视野吧。
使用Message.obtain创建message
我新建了一个Activity名叫ClockActivityTest,它的布局文件的代码和activity_main代码相同,让我们来看一下ClockActivityTest的代码吧:
对了,要记着在AndroidManifest.xml文件中将ClockActivityTest设置为启动界面。
public class ClockActivity extends Activity {
private TextView tvClock;
private static final int UPDATA_MESSAGE_TIME = 1000;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_clock);
tvClock = (TextView) findViewById(R.id.ClockActivity_tv_clock);
handler = new Handler();
new InnerThread().start();
}
//自定义类继承Runnable,这个类的主要做用是处理消息,下面的
//Message.obtion(handler,Runnable)中的Runnable就是该InnerRunner
public class InnerRunner implements Runnable {
@Override
public void run() {
tvClock.setText(new Date().toString());
}
}
public class InnerThread extends Thread {
@Override
public void run() {
while (true) {
//Message类有公有的无参数的构造方法,开发者可以随 意创建Message对象,但是,
// Message对象大多是被处理后就没有存在的意义了,所以反复创建Message对象会无谓的消耗内存
//推荐使用Message.obtain() 方法获取消息的对象,Message类自身使用了“Message池”对消息对象进行管理,
//从而可以控制产生的Message对象的数量.如果使用的Message.obtain() 方法中,
// 使用到了Handler做为参数,则需要使用Message对象的sendToTarget() 发送消息,
//而不再使用Handler的sendMessage() 系列方法
//Message的what属性用于标识消息的类型(因为子线程可能向主线程发送若干种不同的消息),
// 不应该用于封装有具体数值含义的数据,通常,what属性的值应该使用静态常量表示,
// 例如msg.what = MESSAGE_UPDATE_CLOCK;
//通过Handler对象的obtainMessage() 系列方法也可以获取Message对象,
// 其实现效果与Message类的obtain() 方法是等效的
Message message = Message.obtain(handler, new InnerRunner());
message.what = UPDATA_MESSAGE_TIME;
message.sendToTarget();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
使用CallBack处理消息
我自己又新建了一个Activity名叫ClockActivityTestTwo,它的布局文件的代码和activity_main代码相同,让我们来看一下ClockActivityTestTwo的代码吧:
对了,要记着在AndroidManifest.xml文件中将ClockActivityTestTwo设置为启动界面。
public class ClockActivityTwo extends AppCompatActivity {
private TextView tvClock;
private Handler handler;
private String dataString;
private Date date =new Date();
private SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_clock_activity_two);
tvClock = (TextView) findViewById(R.id.ClockActivityTwo_tv_clock);
//使用Handler.Callback接口的实现类对象,做为Handler构造方法的参数,
// 也可以实现对消息的处理【推荐】
handler =new InnerHandler( new InnerCallBack());
new InnerThread().start();
}
//Handler.Callback定义了public boolean handleMessage(Message msg)方法,
// 其中,返回值(boolean类型)表示“是否已经完全处理了消息”,
// 当返回值为false时,如果存在其它的消息处理方法,则对应的代码也会被执行,
// 当返回值为true时,表示“已经完全处理了消息”,则其它的处理消息的代码不会运行
class InnerCallBack implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
tvClock.setText(dataString);
Log.i("ClockActivityTwo", "HandlerCallBack");
return true;
}
}
class InnerHandler extends Handler {
public InnerHandler(Callback callback) {
super(callback);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tvClock.setText(dataString);
Log.i("ClockActivityTwo", "HandlerMessage");
}
}
class InnerThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1200; i++) {
Message message =Message.obtain();
date.setTime(System.currentTimeMillis());
dataString =sdf.format(date);
handler.sendMessage(message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这里我分别使用了callBack和handlerMessage()方法处理消息,当我在public boolean handleMessage(Message msg) 这是返回值为true时,那么handlerMessage就不会处理该消息了。可以看一下log日志:
06-05 23:19:04.489 24761-24761/com.example.clockthreadtest I/ClockActivityTwo: HandlerCallBack
06-05 23:19:05.489 24761-24761/com.example.clockthreadtest I/ClockActivityTwo: HandlerCallBack
06-05 23:19:06.489 24761-24761/com.example.clockthreadtest I/ClockActivityTwo: HandlerCallBack
当我将callBack中的public boolean handleMessage(Message msg) 这是返回值为false时,handlerMessage也会执行。让我们来看一下log日志:
06-05 23:19:55.489 25109-25109/com.example.clockthreadtest I/ClockActivityTwo: HandlerCallBack
06-05 23:19:55.489 25109-25109/com.example.clockthreadtest I/ClockActivityTwo: HandlerMessage
06-05 23:19:56.489 25109-25109/com.example.clockthreadtest I/ClockActivityTwo: HandlerCallBack
06-05 23:19:56.489 25109-25109/com.example.clockthreadtest I/ClockActivityTwo: HandlerMessage
本篇博客我给大家列举了三种不同的Handler的处理方法,至于你要使用哪一种方式,那就要看你自己的习惯了。下面我来总结一下这三种方式:
关于处理消息的方式
1) 使用Message.obtain(Handler h, Runnable callback)中的Runnable对象处理消息
2) 使用Handler.Callback处理消息
3) 重写Handler类的handleMessage()处理消息
以上3种方式可以并存,优先级为1 -> 2 -> 3
当存在第1种方式时,消息将直接被处理,并且不会向后传递
当第2种方式存在时,其public boolean handleMessage(Message msg)方法的返回值决定消息是否向后传递