Android中Handler使用浅析
1. Handler使用引出
现在作为客户,有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个APP的欢迎闪屏页面),如下图:

作为初学者,可能觉得直接开启一个包含倒序循环的子线程就ok了,具体实现如下:
1.1 Layout界面代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.mly.panhouye.handlerdemo.Main2Activity">
<TextView
android:gravity="center"
android:textSize="30sp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="NO DATA"
android:id="@+id/tv"/>
</LinearLayout>
1.2 java实现代码如下:
public class Main2Activity extends AppCompatActivity {
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
tv = (TextView) findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
for (int i=5;i>0;i--){
tv.setText(String.valueOf(i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//计时结束后跳转到其他界面
startActivity(new Intent(Main2Activity.this,Main3Activity.class));
//添加finish方法在任务栈中销毁倒计时界面,使新开界面在回退时直接退出而不是再次返回该界面
finish();
}
}).start();
}
逻辑很简单,但当点进入界面时,会发现程序奔溃了,logcat中错误日志如下(只有UI线程可以更改UI界面):

由此我们发现在安卓开发中,例如上面的示例,我们常常通过一个线程来完成某些操作,然后同步显示对应的视图控件UI上,通过上面的例子我们也知道了安卓中无法直接通过子线程来进行UI更新操作,对于这种情况,Android提供了一套异步消息处理机制Handler。
2. Handler实现方法
使用handler实现,修改java代码Main2Activity.java如下:
package com.mly.panhouye.handlerdemo; import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
/**
* Handler:
* 1 处理的消息对象就是Message,理解为要传递的消息数据的封装对象
* Message what : 标记,用来区分多个消息
* Message arg1,arg2 : 用来传递int类型的数据
* Message obj : 可以传递任何类型的对象(Object)
*/
public class Main2Activity extends AppCompatActivity {
public static final int UPDATE = 0x1;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
tv = (TextView) findViewById(R.id.tv);
begin();//开启倒计时并跳转页面的方法
}
//消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage())
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE:
tv.setText(String.valueOf(msg.arg1));
break;
}
}
}; public void begin(){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=5;i>0;i--){
Message msg = new Message();
msg.what = UPDATE;
msg.arg1 = i;
handler.sendMessage(msg);
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印log
Log.i("tag",Main2Activity.this+"-"+ i);
}
//计时结束后跳转到其他界面
startActivity(new Intent(Main2Activity.this,Main3Activity.class));
//添加finish方法在任务栈中销毁倒计时界面,使新开界面在回退时直接退出而不是再次返回该界面
finish();
}
}).start();
} @Override
protected void onDestroy() {
super.onDestroy();
//log打印用于测试activity销毁
Log.i("tag","destory");
}
}
3. Handler实现原理
使用Handler方式进行异步消息处理主要由Message,Handler,MessageQueue,Looper四部分组成:
(1)Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。
(2)Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。
(3)MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。
(4)Looper,可以理解为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。
Handler实现原理如下图:

结合上文的的代码示例以及上图的实现流程,要使用Handler实现异步消息处理,首先我们需要在主线程中创建Handler对象并重写handleMessage()方法,然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handlerr将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。由于Halldler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,从而实现子线程通过Handler机制实现UI线程操作的目的。
4. Handler内存泄漏分析
4.1 Handler内存泄漏问题的引出:
上面的Handler实现代码中,其实在Android Studio中会提示以下问题:

大致意思就是应该让Handler类为静态的,不然就会产生内存泄漏。 原因也说的很清楚,Handler被声明为一个非静态内部类或者匿名类可能会阻止外部类的垃圾回收(大家可以了解下Android的gc回收机制)。过多的内存泄漏使程序占用的内存超出系统限制,导致OOM(内存溢出),程序出错。
4.2 防止Handler引起内存泄漏:
方法一:通过程序逻辑进行保护:
(1)在关闭Activity时停掉对应的后台线程。线程停止就相当于切断了Handle和外部链接的线,Activity自然会在合适的时候被回收。
(2)如果Handler是被delay的Message持有了引用,那就使用Handler的removeCallbacks()方法将消息对象从消息队列移除即可。
方法二:将Handler声明为静态类,静态类不持有外部类的对象,所以Activity可以被随意回收。此处使用了弱引用WeakReference,也就是说当在内存不足时,系统会销毁弱/回收引用引用的对象,从而达到优化内存的目的。优化后代码如下:
package com.mly.panhouye.handlerdemo; import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import java.lang.ref.WeakReference; public class Main4Activity extends AppCompatActivity {
public static final int UPDATE = 0x1;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
tv = (TextView) findViewById(R.id.tv);
begin();//开启倒计时并跳转页面的方法
}
//Handler静态内部类
private static class MyHandler extends Handler {
//弱引用
WeakReference<Main4Activity> weakReference;
public MyHandler(Main4Activity activity) {
weakReference = new WeakReference<Main4Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Main4Activity activity = weakReference.get();
if (activity != null) {
activity.tv.setText(String.valueOf(msg.arg1));
}
}
}
private MyHandler handler = new MyHandler(this);
public void begin() {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 5; i > 0; i--) {
Message msg = new Message();
msg.what = UPDATE;
msg.arg1 = i;
handler.sendMessage(msg);
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("tag", Main4Activity.this + "-" + i);
}
//计时结束后跳转到其他界面
startActivity(new Intent(Main4Activity.this, Main3Activity.class));
//添加finish方法在任务栈中销毁倒计时界面,使新开界面在回退时直接退出而不是再次返回该界面
finish();
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
Log.i("tag", "destory");
}
}
5.小结
本次使用handler实现倒计时页面跳转的效果实现,只是向大家简单介绍handler的使用方法以及注意事项,但依然存在bug,如果倒计时未完成时退出activity,子线程依然会在后台运行直至完成跳转,效果以及log日志如下:


针对这种情况,我处理起来比较麻烦,需要在销毁倒计时activity时,同时终止线程,我试了很多方法,未能实现。
其实要实现倒计时闪屏效果,可以使用Android中有个countDownTimer类来实现,后面会做简单的实现介绍。
Android中Handler使用浅析的更多相关文章
- Android中Handler 、Thread和Runnable之间的关系
在多线程编程的时候,我们经常会用到Handler,Thread和Runnable这三个类,我们来看看这三个类之间是怎么样的关系? 首先说明Android的CPU分配的最小单元是线程,Handler一般 ...
- Android 中 Handler 引起的内存泄露
在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.其实这可能导致内存泄露,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下.http://w ...
- android中handler用法总结
一.Handler的定义: Handler主要接收子线程发送的数据, 并用此数据配合主线程更新UI,用来跟UI主线程交互用.比如可以用handler发送一个message,然后在handler的线程中 ...
- Android中Handler作用
在Android的UI开发中,我们经常会使用Handler来控制主UI程序的界面变化.有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过接收到的消息更新主UI线程的内容 ...
- Android中Handler的消息处理机制以及源码分析
在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui.我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui.而一些耗时操作,比如数据库,网络请 ...
- Android中Handler引起的内存泄露
在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. 1 2 3 4 5 6 7 8 9 public class SampleActivit ...
- Android中Handler的使用
当我们在处理下载或是其他需要长时间执行的任务时,如果直接把处理函数放Activity的OnCreate或是OnStart中,会导致执行过程中整个Activity无响应,如果时间过长,程序还会挂掉.Ha ...
- Android中Handler的使用方法及实例(基础回顾)
Handler使用例1 这个例子是最简单的介绍handler使用的,是将handler绑定到它所建立的线程中.本次实验完成的功能是:单击Start按钮,程序会开始启动线程,并且线程程序完成后延时1s会 ...
- Android中Handler原理
Handler主要是主线程和子线程通信.一般子线程中做一些耗时操作做完之后通知主线程来改动UI. 实际上android系统在Activity启动或者状态变化等都是通过Handler机制实现的. 首先进 ...
随机推荐
- Mysql 多列形成主键(复合主键 )
什么是数据表的复合主键 所谓的复合主键 就是指你表的主键含有一个以上的字段组成 比如 create table test ( name varchar(19), id number, ...
- 7 款华丽的 HTML5 Loading 动画特效
我们在进行大数据的传输或者复杂操作的等待时,最好能有一个Loading等待的小动画提示用户.本文将为大家分享一些超华丽的基于HTML5的Loading加载动画特效,希望你会喜欢. 1.HTML5 Ca ...
- iOS 之 CALayer与UIView的区别
最大区别:CALayer (图层)不会直接渲染到屏幕上. UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它.它本身完全是由CoreAnimation来实现的. 真正的绘图部分,是由一 ...
- PHP中PDO错误/异常(PDOException)处理
PDO 提供了三种不同的错误处理模式,以满足不同风格的应用开发: PDO::ERRMODE_SILENT 此为默认模式. PDO 将只简单地设置错误码,可使用 PDO::errorCode() 和 P ...
- check_arp
检查arp表是否满 #!/bin/bash LANG=C test -e /bin/date && timestamp=`/bin/date +%s` ARP=`which arp` ...
- docker的资源限制cpuset cpuquota memory
总结 目前,公司7u已经不再使用lxc,转而使用libcontainer 即native docker对cpuquota的支持目前是有问题的,一般大家使用docker的时候,主要是对memory,cp ...
- Cookie的一些用法
Cookie的一些用法: package com.stono.servlet.listenerorder; import java.io.IOException; import java.io.Pri ...
- V8引擎嵌入指南
如果已读过V8编程入门那你已经熟悉了如句柄(handle).作用域(scope)和上下文(context)之类的关键概念,以及如何将V8引擎作为一个独立的虚拟机来使用.本文将进一步讨论这些概念,并介绍 ...
- C++中的RAII技法
Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life ...
- 新手Axis2 发布Web Service之路
由于公司的需求,需要写几个银行接口写模拟器(Mock Server),此次接口需要发布成一个WEB Service. 一开始,我以为只要负责写接口的业务层就行了,具体的框架或是环境搭建可以不用管.在与 ...