Android 基础之 Handler
Android 是单线程模型,但是仅仅靠主线程是不够的,比如在主线程中进行网络请求、下载图片、下载视频等耗时操作时,就会造成阻塞,用户用起来会很不舒服。所以,除了 UI 的更新外,一些耗时的操作需开启子线程来处理。主线程和子线程之间需要数据交换等通信,子线程和子线程之间同样也需要通信。
目前线程中的通信是借助 Handler 实现的,但 Handler 的作用不仅限于线程间通信,还有延时启动 Runnable,有一点需要说明:一个线程 (Thread)对应一个 Looper,一个 Looper 对应一个消息队列 (MessageQueue) ,一个消息队列(MessageQueue )中可以包含多条 Message 消息,一个线程中可以有多个 Handler 。
Handler
Handler 是一个消息分发对象。当创建一个 Handle r时,会与创建他的线程和线程的消息队列绑定,从这时候开始,Handler 会向消息队列发送 Message ,并且在 Message 从消息队列出来时对Message 进行处理。
Handler 类包含如下方法用于发送、处理消息:
- handleMessage(Message msg):处理消息的方法。
- obtainMessage():获取消息。
- sendEmptyMessage(int what):发送空消息。
- sendMessage(Message msg):立即发送消息。
- sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后发送消息。
- sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后发送空消息。
Message
Handler接收和处理的消息对象。
- 2个整型数值:轻量级存储int类型的数据。
- 1个Object:任意对象。
- replyTo:线程通信时使用。
- what:用户自定义的消息码,让接收者识别消息。
MessageQueue
Message的队列
- 采用先进先出的方式管理Message。
- 每一个线程最多可以拥有一个。
Looper
消息泵,是 MessageQueue 的管理者,会不断从 MessageQueue 中取出消息,并将消息分给对应的 Handler 处理。
- 每个线程只有一个 Looper。
- Looper.prepare():为当前线程创建 Looper对象。
- Looper.myLooper():可以获得当前线程的 Looper对象。
- Handler:能把消息发送给 MessageQueue,并负责处理 Looper 分给它的消息。
Android 消息机制的运行流程
在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断地从线程池中读取消息,即调用MessageQueue.next,然后调用目标Handler(即发送该消息的Handler)的dispatchMessage方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。
MessageQueue,Handler和Looper三者之间的关系
每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。 Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。
Handler 如何去实现发送和处理消息
1. 使用 Thread 发送消息
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1001) {
mTextView.setText("Star");
}
}
};
private TextView mTextView;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv_text);
mButton = findViewById(R.id.btn_send);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(1001);
}
}).start();
}
});
}
}
2. 使用Handler下载文件并更新进度条
- 添加权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- 实现代码
public class MainActivity extends AppCompatActivity {
private static final int DOWNLOAD_FILE_CODE = 100001 ;
private static final String DOWNLOAD_URL ="http://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk" ;
private static final int DOWNLOAD_FILE_FAILE_CODE = 100002;
private Button StartDownload;
private TextView percent;
private ProgressBar progressBar;
private Handler handler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
StartDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//执行下载程序
new Thread(new Runnable() {
@Override
public void run() {
download(DOWNLOAD_URL);
}
}).start();
}
});
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 100001:
//更新进度条
progressBar.setProgress((Integer) msg.obj);
percent.setText(String.valueOf(msg.arg1)+"%");
if (progressBar.getProgress()==100){
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}
break;
case 100002:
Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
break;
}
}
};
}
private void download(String AppUrl) {
//实例化URL对象
try {
URL url = new URL(AppUrl);
//实例化一个URLConnection对象
URLConnection conn = url.openConnection();
//获取下载路径
String path = Environment.getExternalStorageDirectory()
+ File.separator + "ZerGen" + File.separator;
//创建目录
File PathName = new File(path);
//如果PathName不存在的话 就创建这个目录
if(!PathName.exists()){
PathName.mkdir();
}
//有了目录之后,就需要一个文件名
String ApkName = path+"ZerGen.apk";
//判断一下这个文件是否已经存在,存在的话就删除它
File ApkFile = new File(ApkName);
if(ApkFile.exists()){
ApkFile.delete();
}
//获取文件的总长度
int ContentLength = conn.getContentLength();
//获取输入流
InputStream in = conn.getInputStream();
byte[] b = new byte[1024];
int DownloadLength = 0; //用于保存实时下载长度
int len =0;
OutputStream out = new FileOutputStream(ApkName);
while((len = in.read(b))>-1){
out.write(b,0,len);
DownloadLength += len;
//将实时的下载长度传给UI线程
Message message=handler.obtainMessage();
message.what =DOWNLOAD_FILE_CODE;
message.obj = DownloadLength*100/ContentLength;
message.arg1 = DownloadLength*100/ContentLength;
handler.sendMessage(message);
}
} catch (java.io.IOException e) {
downloadfail();
e.printStackTrace();
}
}
private void downloadfail() {
Message message=handler.obtainMessage();
message.what =DOWNLOAD_FILE_FAILE_CODE;
handler.sendMessage(message);
}
private void initView() {
StartDownload = (Button) findViewById(R.id.btn_start);
percent = (TextView) findViewById(R.id.percent);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
}
}
3. 使用 handler实现倒计时功能
public class SplashActivity extends AppCompatActivity {
private TextView mTime;
private static int TIME = 5;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
TIME--;
mTime.setText(TIME + "s");
if (TIME > 0) {
Message message = mHandler.obtainMessage(1);
mHandler.sendMessageDelayed(message, 1000); // send message
} else {
//跳转到主界面
goHome();
}
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
mTime = findViewById(R.id.textView);
Message message = mHandler.obtainMessage(1);
mHandler.sendMessageDelayed(message,1000);
}
private void goHome() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
}