一 前言
Handler的由来
(1)首先为什么需要Handler呢? 因为UI更新只能在UI线程。
(2)那为什么只能在UI线程更新UI呢? 因为Android是单线程模型。
(3)那为什么Android是单线程模型? 因为如果任意线程都可以更新UI的话,线程安全问题处理起来会相当麻烦复杂,所以就规定了Android是单线程模型,只允许在UI线程更新UI操作。
二 异步消息的方式与Demo
1 handler+Message+Looper
Android在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新UI信息,就会抛出异常。 Handler就是是Android提供的用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息。
(1) 相关介绍
Handler:
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler 的 sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到 Handler 的 handlerMessage()方法中。
Message:
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。通常使用 Message 的 what 字段携带命令,除此之外还可以使用 arg1 和arg2 字段来携带一些整形数据,使用 obj 字段携带一个 Object 对象。
MessageQueue:
MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue 对象。
Looper:
Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop() 方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取出,并传递到 Handler 的 handleMessage() 方法中。每个线程中也只会有一个 Looper 对象。
(2) 运行机制流程
首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
然后当子线程中需要进行UI操作的时,就创建一个Message对象,并通过Handler将这条消息发送出去。
之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MesssageQueue中取出待处理消息,最后发回Handler的HandleMessage()方法中。
由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码页会在主线程中运行,于是我们在这里就可以进行UI操作了。
(3) Demo
package com.demo.handler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
protected static final int UPDATE_TEXT = 1;
private Button button;
private TextView textView;
//创建handler对象,并重写父类handleMessage方法
private Handler handler = new Handler(){
//处理传递过来的消息
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
//在主线程里更新UI操作
textView.setText("我要更新了");
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.btn);
textView = (TextView) findViewById(R.id.text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);//将message对象发送出去
}
}).start();
break;
}
}
}
<LinearLayout 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"
android:orientation="vertical" >
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点击修改内容" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="我要被改动了" />
</LinearLayout>
(4) 其它
另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:
Handler的post(Runnable)方法
View的post(Runnable)方法
Activity的runOnUiThread()方法
2 AsyncTask
(1) AsyncTask介绍
AsyncTask是由线程池 + 工作线程 + Handler进行构建的一个异步任务工具类。
AsyncTask本质上是一个封装了线程池、工作线程和Handler的异步框架,主要是执行异步任务,由于AsyncTask里面封装了Handler,所以它可以在主线程和子线程之间灵活的切换。
AsyncTask 背后的实现原理也是基于异步消息处理机制的,只是 Android 做了很好的封装而已。我们并不需要去考虑异步消息处理机制,也不需要专门使用一个 Handler 来发送和接收消息,只需要调用一下 publishProgress()方法就可以轻松地从子线程切换到 UI 线程了。
(2) AsyncTask的机制原理
1) 线程池:
为了在处理多个任务时,避免创建多个线程带来内存开销,内部维护了一个线程池,管理每个线程。内部的线程池通过ThreadPoolExecutor来动态分配固定大小的核心工作线程。这样所有的异步任务都会放到线程池,并维护每个工作线程。
2) 工作线程:
该线程用于执行一个耗时的操作,内部通过FutureTask接收一个WorkerRunnable接口,并在接口的call方法中,执行异步任务,此时调用AsyncTask类的doInBackground()方法,做耗时操作。由于FutureTask类实现了FutureRunnable接口,通过这个接口,在它的run方法中,执行任务。
3) Handler消息机制更新UI:
当FutureTask工作线程任务执行完成以后,把结果交给Handler,Handler通过发送消息,在主线程中接收消息,并调用AsyncTask类的onPostExecute(Result result)方法,来处理结果。
(3) AsyncTask 的基本用法
首先来看一下 ,由于 AsyncTask 是一个抽象类,所以如果我们想使用它,就必须创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数。
三个参数
Params:启动任务执行的输入参数,比如HTTP请求的URL。
Progress:后台任务执行的百分比。
Result:后台任务执行最终返回的结果,比如String,Integer等。
/**
* AsyncTask泛型参数解释
* Void 表示在执行AsyncTask的时候不需要传入参数给后台任务
* Integer 表示使用整形数据来作为进度显示单位
* Boolean 表示使用布尔类型数据来反馈执行结果
*/
class MyTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
return null;
}
}
四个方法
onPreExecute():一般会在 UI Thread 中执行。用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
doInBackground(Params…):这个方法中的所有代码都会在子线程 Worker Thread 中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用下面这个 publishProgress(Progress...) 方法来完成。
onProgressUpdate(Progress…):当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
onPostExecute(Result):在 UI Thread 中执行。当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如弹出Toast提醒任务执行的结果,以及关闭掉进度条对话框等。
可以知道,使用 AsyncTask ,则可以在onPreExecute()进行一些界面上的初始化操作,在 doInBackground() 方法中去执行具体的耗时任务,在 onProgressUpdate() 方法中进行 UI 操作,在 onPostExecute()方法中执行一些任务的收尾工作。
而如果想要启动这个任务,则只需要 new MyTask.execute();
(4) AsyncTask注意事项
1)内存泄漏(类似handler,只是AsyncTask销毁方法为cancel())
2)生命周期(不会随着Activity的销毁而销毁,Activity销毁前若doInBackground()中耗时未进行完会出现问题)
3)并行or串行(一般做串行,并行不稳定)
4)但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池
5)AsyncTask的实例必须在UI thread中创建,启动实例的execute方法必须在UI thread中调用
6)一个AsyncTask实例只能被执行一次,多次调用时将会出现异常
(5) Demo
public class MyActivity extends Activity
{
private Button btn;
private TextView tv;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button) findViewById(R.id.start_btn);
tv = (TextView) findViewById(R.id.content);
btn.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v) {
update();
}
});
}
private void update(){
UpdateTextTask updateTextTask = new UpdateTextTask(this);
updateTextTask.execute();
}
class UpdateTextTask extends AsyncTask<Void,Integer,Integer>{
private Context context;
UpdateTextTask(Context context) {
this.context = context;
}
/**
* 运行在UI线程中,在调用doInBackground()之前执行
*/
@Override
protected void onPreExecute() {
Toast.makeText(context,"开始执行",Toast.LENGTH_SHORT).show();
}
/**
* 后台运行的方法,可以运行非UI线程,可以执行耗时的方法
*/
@Override
protected Integer doInBackground(Void... params) {
int i=0;
while(i<10){
i++;
publishProgress(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
return null;
}
/**
* 运行在ui线程中,在doInBackground()执行完毕后执行
*/
@Override
protected void onPostExecute(Integer integer) {
Toast.makeText(context,"执行完毕",Toast.LENGTH_SHORT).show();
}
/**
* 在publishProgress()被调用以后执行,publishProgress()用于更新进度
*/
@Override
protected void onProgressUpdate(Integer... values) {
tv.setText(""+values[0]);
}
}
}
3 HandlerThread
(1) HandlerThread 介绍
HandlerThread是Google帮我们封装好的框架,可以用来执行多个耗时操作,而不需要多次开启线程。其内部实现为:
Handle + Thread + Loop
HandlerThread 继承于 Thread,所以它本质就是个 Thread。与普通的 Thread 的差别就在于,它有个 Looper 成员变量。这个 Looper 其实就是对消息队列以及队列处理逻辑的封装。与普通线程不同的地方就是内部有loop。因为我们如果想在子线程中使用Handle,就必须在子线程中创建loop对象。 使用Looper.prepare();
来创建当前线程的loop,接着使用 Looper.loop();
来开启循环,这样我们才能在线程中创建Handle。而Handle的内部就是这样实现的。
HandlerThread集合了Thread和Handler,集成了Looper和MessageQueue。当启动HandlerThread时,会同时生成Looper和MessageQueue,然后等待消息处理。它只是帮你调用了 Looper.prepare()
方法和 Looper.loop()
方法而已。也就是说如果你一个类继承了 HandlerThread,你可以像在主线程那样使用 Handler。
(2) HandleThread 使用
1) 创建HandlerThread的实例对象
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
2) 启动我们创建的HandlerThread线程
handlerThread.start();
3) 将我们的handlerThread与Handler绑定在一起,就是将线程的looper与Handler绑定在一起,代码如下:
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdate){
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};
(3) HandlerThread 使用场景
HandlerThread 所做的就是在新开的子线程中创建了 Looper,那它的使用场景就是 Thread + Looper 使用场景的结合,即:在子线程中执行耗时的、可能有多个任务的操作。比如说多个网络请求操作,或者多文件 I/O 等等。使用 HandlerThread 的典型例子就是 IntentService
。
当我们需要一个工作线程,而不是把它当作一次性消耗品,用过即废的话,就可以使用它。
(4) HandlerThread
特点
1)它就是一个帮我们创建 Looper 的线程,让我们可以直接在线程中使用 Handler 来处理异步任务。HandlerThread 继承自Thread,内部封装了Looper。
2)首先Handler和HandlerThread的主要区别是Handler与Activity在同一个线程中,HandlerThread与Activity不在同一个线程,而是别外新的线程中(Handler中不能做耗时的操作)。
3)通过将HandleThread的Loop对象传递给Handle对象,可以在handleMessage()中执行异步任务。
4)与线程池并发不同,HandleThread内部只有一个线程,是一个串行的队列。
5)优点是不会堵塞,减少了对性能的消耗。缺点是不能同时进行多任务处理,需等待处理,效率较低。
(5) Demo
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
/**
* 在主线程main中创建handler对象,创建消息,并用handler向工作线程发送消息,在工作线程中处理消息;
* 应用环境:比如下载;
* @author gaohong
*
*/
public class HandlerThreadActivity extends Activity {
HandlerThread thread;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//创建工作线程并启动(工作线程带消息队列)
thread=new HandlerThread("workThread");
thread.start();
Looper looper=thread.getLooper();
//创建Handler对象并与工作线程的消息队列关联
Handler handler=new Handler(looper){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
System.out.println("在"+Thread.currentThread().getName()+"中处理消息");
System.out.println("msg.obj="+msg.obj);
}
};
//创建Message对象
Message msg=Message.obtain();
msg.obj="在"+Thread.currentThread().getName()+"线程中发送消息";
handler.sendMessage(msg);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
thread.quit();
}
}
4 IntentService
(1) IntentService介绍
Service是作为后台服务运行再程序中的。但是Service他依然是运行在主线程中的,所以我们依然不能在Service中进行耗时操作。所以当我们在Service处理时,我们需要在Service中开启一个子线程,并且在子线程中运行。当然为了简化我们的操作,在Android中为我们提供了IntentService来进行这一处理。
IntentService具有Service一样的生命周期,也提供了后台线程中的异步处理机制。IntentService可以看做是Service和HandlerThread的结合,完成任务后自动停止,适合需要在工作线程处理ui无关任务的场景。
IntentServie继承自Service并处理异步请求的抽象类,用于执行后台耗时任务,优先级比单纯的线程高很多,适合执行一些高优先级任务,不容易被系统杀死,内部封装了HanlerThread和Handler。 封装的一个work-thread,处理接收到的Intent,它可以处理多个请求,但一次只能处理一个。如果启动IntentService多次,每一个耗时操作会以工作队列的方式在inentService的onHandleIntent回调方法中执行,依次执行,使用串行方式,执行完自动结束。
(2) IntentServie特点
1) IntentService会自动启动一个WorkThread来处理后台耗时任务,它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止。
2) 后台耗时任务处理完成以后,会自动关闭Service(这样只需要启动IntentService即可,后面就不用关注IntentService的关闭了)
3) 拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务
4) 内部通过HandlerThread和Handler实现异步操作创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作。
5) 当然IntentService也有其限制,多个任务会放到任务队列,顺序执行。所以任务会有先后顺序的限制,如果要几个任务同时并发处理,就不适合使用IntentService了
(3) Demo
public class MyIntentService extends IntentService {
private static final String TAG = MyIntentService.class.getSimpleName();
private boolean isRunning;
private int count;
public MyIntentService() {
super(TAG);
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String action = intent.getStringExtra("task_action");
Log.e(TAG, "onHandleIntent: " + action);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (action.equals("task_action111")) {
Log.e(TAG, "onHandleIntent: " + action);
}
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy: ");
super.onDestroy();
}
}
三 总结
(1) 它们之间的关系
(2) 它们之间的区别
Handler: 它主要有两个用途,一是为了在将来某个时间点处理一个消息或者执行一个任务;二是将一个任务放入队列,以便它可以在另外的线程中执行。
AsyncTask: 封装了线程池和Handler,为 UI 线程与工作线程之间进行快速切换提供一种便捷机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。这个类的设计目的很明确,就是为了“执行一个较为耗时的异步任务(最多几秒钟),然后更新界面”。
Handler + 线程池 + 工作线程
HandlerThread: 一个已经拥有了Looper的线程类,内部可以直接使用Handler。为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。使用场景就是 Thread + Looper 使用场景的结合,即在子线程中执行耗时的、可能有多个任务的操作。比如说多个网络请求操作,或者多文件 I/O 等等。
Handler + Thread + Loop
IntentService: 适合于执行由 UI 触发的后台 Service 任务,并可以把后台任务执行的情况通过一定的机制反馈给 UI。提供了后台线程中的异步处理机制,IntentService可以看做是Service和HandlerThread的结合,完成任务后自动停止,适合需要在工作线程处理ui无关任务的场景。
HandlerThread+ Service