深入理解Handler及其应用——下载功能
1.概述
Handler
A handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue.
handler 允许发送和处理与线程的MessageQueue关联的Message和Runnable对象。
Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper’s message queue and execute them on that Looper’s thread.
每个Handler实例都与一个线程和该线程的消息队列关联。 创建新的处理程序时,它将绑定到Looper。 它将消息和可运行对象传递到该Looper的消息队列,并在该Looper的线程上执行它们。
There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed at some point in the future;
安排消息和可运行对象在将来的某个时刻执行;(定时任务)
(2) to enqueue an action to be performed on a different thread than your own.
使要在不同于你自己的线程上执行的操作排队。(实现线程之间的通信)
Looper 循环者
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
用于为线程运行消息循环的类。 默认情况下,线程没有与之关联的消息循环。 要创建一个,在运行循环的线程中调用prepare(),然后使用loop()使其处理消息,直到循环停止为止。
Message 消息
Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
定义一条消息,其中包含可以发送到处理程序的描述和任意数据对象。 该对象包含两个额外的int字段和一个额外的object字段,使您在许多情况下不进行分配。
MessageQueue 消息队列
Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.
包含要由Looper调度的消息列表的低级类。 消息不是直接添加到MessageQueue,而是通过与Looper关联的Handler对象添加。
————源自 开发文档
子线程中使用Handler
默认情况下,线程没有与之关联的消息循环。 要创建一个,在运行循环的线程中调用prepare(),然后使用loop()使其处理消息,直到循环停止为止。
由于主线程中已经为我们建好了loop(),并且已经调用好了prepare(),所以我们在主线程中我们不用 考虑loop的问题。
而在子线程中使用Handler,首先需要prepare()
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
//子线程中使用Handler
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}).start();
}
});
2.Handler 的简单实现
代码实现
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
{
...
//创建Handler
handler= new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//主线程:接到子线程发来的消息,处理消息
if(msg.what==1001){
contextTxt.setText(result);
}
}
};
}
//按钮点击可开启子线程
//子线程一般处理比较耗时的操作,如网络请求等
new Thread(new Runnable() {
@Override
public void run() {
...
//子线程中通知UI更新
handler.sendEmptyMessage(1001);
}
}).start();
常用方法
1.sendMessage()方法,参数为Message对象
//使用 Message.obtain()创建Message,因为其中存在缓存机制
Message message = Message.obtain();
message.what = 1002;
message.arg1 = 1;
message.arg2 = 50;
message.obj = MainActivity.this;
//1.sendMessage()方法,参数为Message对象
handler.sendMessage(message);
2.sendMessageAtTime()方法:定时发送信息
参数1:Message对象 参数2:定时时间 uptimeMillis
handler.sendMessageAtTime(message,SystemClock.uptimeMillis()+3000);
3.sendMessageDelayed()方法:延时发送信息
参数1:Message对象 参数2:延时时间 delayedtimeMillis
handler.sendMessageDelayed(message,2000);
4.post()方法:添加Runnable对象到消息队列中
参数为Runnable对象
handler.post(new Runnable() {
@Override
public void run() {
...
}
});
3.实例——下载文件并更新进度条
Step 1 创建布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DownloadHandlerActivity">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
android:layout_marginBottom="10dp"
tools:layout_editor_absoluteX="179dp"
tools:layout_editor_absoluteY="96dp" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
tools:layout_editor_absoluteX="208dp"
tools:layout_editor_absoluteY="201dp" />
</LinearLayout>
Step 2 主线程 点击按键 发起下载
button = findViewById(R.id.button);
//主线程:点击按键
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
url = "http:download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk";
new Thread(new Runnable() {
@Override
public void run() {
download(url);
}
}).start();
}
});
private void download(String appUrl) {
try {
URL url = new URL(appUrl);
URLConnection urlConnection = url.openConnection();
//创建输入流
InputStream inputStream = urlConnection.getInputStream();
//获取文件的总长度
int contentLength = urlConnection.getContentLength();
//设置存储目录
String downloadFileName = getExternalFilesDir(null)
+ File.separator + "imooc" +File.separator;
Log.d(TAG,downloadFileName);
File file = new File(downloadFileName);
if(!file.exists()){
file.mkdir();
}
String fileName = downloadFileName+"imooc.apk";
//创建apkFile
File apkFile = new File(fileName);
if(apkFile.exists()){
apkFile.delete();
}
//获取下载时的长度
int downloadSize = 0;
//创建字节数组
byte[] bytes = new byte[1024];
int length = 0;
//创建输出流
OutputStream outputStream = new FileOutputStream(fileName);
while ( (length= inputStream.read(bytes))!= -1){
//将bytes写入outputStream
outputStream.write(bytes,0,length);
downloadSize +=length;
/**
* 更新UI
* */
...
}
//关闭输入输出流
inputStream.close();
outputStream.close();
} catch (MalformedURLException e) {
Message message =Message.obtain();
message.obj = "下载失败!";
message.what = DOWNLOAD_MESSAGE_FAIL_CODE;
handler.sendMessage(message);
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Step 3 下载过程中通知更新UI
//子线程通知
/**
* 更新UI
* */
Message message =Message.obtain();
message.obj = downloadSize*100/contentLength;
message.what = DOWNLOAD_MESSAGE_CODE;
handler.sendMessage(message);
Step 4 主线程更新进度条
progressBar=findViewById(R.id.progressBar);
//创建Handler对象
handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case DOWNLOAD_MESSAGE_CODE:
progressBar.setProgress((Integer) msg.obj);
break;
case DOWNLOAD_MESSAGE_FAIL_CODE:
Toast.makeText(DownloadHandlerActivity.this,"下载失败!",Toast.LENGTH_SHORT).show();
}
}
};