一. Handler 的使用方法
1. Handler 作用
在开发中,我们经常会需要做一些耗时的操作:比如下载图片、打开网页、下载视频等。如果将这些耗时的操作放在主线程(UI线程),长时间的阻塞导致应用ANR。必然应该将这些操作放在子线程中处理,这些操作处理过程中,我们需要更新UI界面以告知用户现在具体的进度、状态等信息。
所以:在多线程的应用场景中,将工作线程中需更新UI
的操作信息 传递到 UI
主线程,从而实现 工作线程对UI
的更新处理,最终实现异步消息的处理
但是,多个线程并发执行UI 主线程,会导致数据更新异常,使用 Handler 的作用时: 多个线程并发更新UI的同时 保证线程安全
2. Handler 相关的名词
Handler 作为主线程和 工作线程的一个媒介
Handler
、Message
、Message Queue
、Looper
3. 使用方法
在子线程中Handler将消息发送到MessageQueue中,然后Looper不断的从MessageQueue中读取消息,并调用Handler的dispatchMessage发送消息,最后再Handler来处理消息。为了更好的帮助大家一起理解,我画了一个Handler机制的原理图:
使用方式分为2 种:使用Handler.sendMessage()
、使用Handler.post()
3.1 Handler.sendMessage()
使用步骤:
1. 自定义Handler子类(继承Handler类) & 复写handleMessage()方法
@Override
public void handleMessage(Message msg) { ...
switch(msg.what){} // 根据不同工作线程,执行不同操作
// 需执行的UI操作 }
2. 在 UI 主线程中, 创建Handler实例
private Handler mhandler = new mHandler();
3. 在工作线程中 , 创建所需的消息对象
Message msg = Message.obtain(); // 实例化消息对象 ,推荐使用这种方法。而不是 Message msg = new Message();
msg.what = 1; //
消息标识 msg.obj = "AA"; // 消息内容存放
4. 在工作线程中, 通过Handler发送消息到消息队列中
mHandler.sendMessage(msg);
5. 在监听事件中,开启工作线程
例子:模拟点击按钮,进行下载的小栗子
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements Button.OnClickListener {
private TextView statusTextView = null;
private Handler uiHandler;
class Mhandler extends Handler {
//步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
@Override
public void handleMessage(Message msg) {
// 工作线程发过来的信息
switch (msg.what){
case 1:
System.out.println("handleMessage thread id " + Thread.currentThread().getId());
System.out.println("msg.arg1:" + msg.arg1);
System.out.println("msg.arg2:" + msg.arg2);
MainActivity.this.statusTextView.setText("文件下载完成");
// 获取传入的参数
Bundle bundle = msg.getData();
String value = bundle.getString("list");
textView.setText(value);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 步骤2:在主线程中创建Handler实例
uiHandler = new Handler()
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
// 工作线程
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("DownloadThread id " + Thread.currentThread().getId());
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
// 步骤3:在工作线程中 创建所需的消息对象
Message msg = new Message();
//msg = Message.obtain(); // 最好使用这种方法
//what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
//出不同的Message,以便我们做出不同的处理操作
msg.what = 1;
//我们可以通过arg1和arg2给Message传入简单的数据
msg.arg1 = 123;
msg.arg2 = 321;
//我们也可以通过给obj赋值Object类型传递向Message传入任意数据
//msg.obj = null;
//我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
//msg.setData(null);
//Bundle data = msg.getData();
// 传入更多的参数
Bundle bundle = new Bundle();
bundle.putString("list", "this is mesage");
bundle.putString("list2", "this is list2");
msg.setData(bundle);
//步骤4:在工作线程中 通过Handler发送消息到消息队列中
uiHandler.sendMessage(msg);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
3.2 Handler.post()使用方法:较为简单,但是底层还是 调用Handler.sendMessage()
package ispring.com.testhandler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主线程中创建,所以自动绑定主线程
private Handler uiHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("DownloadThread id " + Thread.currentThread().getId());
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Runnable thread id " + Thread.currentThread().getId());
MainActivity.this.statusTextView.setText("文件下载完成");
}
};
// 这里使用 post
uiHandler.post(runnable);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
二、AsyncTask 异步任务
Android 提供了一个轻量级的用于处理异步任务的类 AsyncTask,对Thread 和 Handler 进行了封装,用于多线程通信。
我们一般是继承 AsyncTask
,然后在类中实现异步操作,再将异步执行的进度,反馈给 UI 主线程
AsyncTask
是一个抽象类,一般我们都会定义一个类继承 AsyncTask 然后重写相关方法
AsyncTask<Params, Progress, Result>
三个参数说明:
参数 | 说明 |
---|---|
Params | 启动任务执行的是如参数,比如一个网络请求的 URL |
Progress | 后台任务执行的百分比 |
Result | 后台任务执行完毕后返回的结果 |
如果不需要一些参数,可以使用
void
代替
需要重写的方法,不能直接调用
方法 | 说明 |
---|---|
onPreExecute() | 在执行后台耗时操作前调用,通常用于一些初始化操作,比如显示进度条 |
doInBackground(params...) | 在 onPreExecute() 方法执行完毕后立即执行,该方法运行于后台,主要负责执行耗时的后台处理工作,可调用 publishProgress(progress) 来更新时时的任务进度 |
onPostExecute(Result) | 在 doInBackground(params...) 执行完毕后,该方法会被 UI 线程调用,后台任务的运行结果将通过该方法传递到 UI 线程,然后展示给用户 |
onProgressUpdate(progress) | 在 publishProgress(progress...) 被调用后,接收publishProgress传来的参数, UI 线程将调用该方法在界面上展示任务的进度,比如更新进度条 |
onCancelled() | 用户取消线程操作的时候调用,也就是在主线程调用 doInBackground方法中调用cancel时会触发该方法 |
可以直接调用的方法
方法 | 说明 |
---|---|
execute | 开始执行异步处理任务。params参数对应execute方法的输入参数 |
executeOnExecutor | 以指定线程池模式开始执行任务。THREAD_POOL_EXECUTOR表示异步线程池, SERIAL_EXECUTOR表示同步线程池。默认是SERIAL_EXECUTOR。 |
publishProgress | 更新进度。该方法只能在doInBackground方法中调用,调用后会触发onProgressUpdate方法。 |
cancel | 取消任务。该方法调用后,doInBackground的处理立即停止,并且接着调用onCancelled方法,而不会调用onPostExecute方法。 |
get | 获取处理结果。 |
getStatus | 获取任务状态。PENDING表示还未执行,RUNNING表示正在执行,FINISHED表示执行完毕 |
isCancelled | 判断该任务是否取消。true表示取消,false表示未取消 |
使用 AsyncTask 几点注意事项
- Task 的实例必须在 UI 线程中创建
execute()
方法必须在 UI 线程中调用- 不要手动调用
onPreExecute()
、doInBackground(params...)
、onPostExecute
、onCancelled()
这几个方法 - Task 只能执行一次,运行多次会出现异常
- execute() 开启任务入口,应该在UI 线程中运行
模拟下载的例子:
1. activity_main.xml
ui文件:
<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">
<TextView
android:id="@+id/txttitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--设置一个进度条,并且设置为水平方向-->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/pgbar"
style="?android:attr/progressBarStyleHorizontal"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_update"
android:text="开始下载"/>
</LinearLayout>
2. 创建 Download.java
用于模拟耗时操作
package cn.twle.android.asynctask;
public class Download {