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使用浅析的更多相关文章

  1. Android中Handler 、Thread和Runnable之间的关系

    在多线程编程的时候,我们经常会用到Handler,Thread和Runnable这三个类,我们来看看这三个类之间是怎么样的关系? 首先说明Android的CPU分配的最小单元是线程,Handler一般 ...

  2. Android 中 Handler 引起的内存泄露

    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.其实这可能导致内存泄露,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下.http://w ...

  3. android中handler用法总结

    一.Handler的定义: Handler主要接收子线程发送的数据, 并用此数据配合主线程更新UI,用来跟UI主线程交互用.比如可以用handler发送一个message,然后在handler的线程中 ...

  4. Android中Handler作用

    在Android的UI开发中,我们经常会使用Handler来控制主UI程序的界面变化.有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过接收到的消息更新主UI线程的内容 ...

  5. Android中Handler的消息处理机制以及源码分析

    在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui.我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui.而一些耗时操作,比如数据库,网络请 ...

  6. Android中Handler引起的内存泄露

    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. 1 2 3 4 5 6 7 8 9 public class SampleActivit ...

  7. Android中Handler的使用

    当我们在处理下载或是其他需要长时间执行的任务时,如果直接把处理函数放Activity的OnCreate或是OnStart中,会导致执行过程中整个Activity无响应,如果时间过长,程序还会挂掉.Ha ...

  8. Android中Handler的使用方法及实例(基础回顾)

    Handler使用例1 这个例子是最简单的介绍handler使用的,是将handler绑定到它所建立的线程中.本次实验完成的功能是:单击Start按钮,程序会开始启动线程,并且线程程序完成后延时1s会 ...

  9. Android中Handler原理

    Handler主要是主线程和子线程通信.一般子线程中做一些耗时操作做完之后通知主线程来改动UI. 实际上android系统在Activity启动或者状态变化等都是通过Handler机制实现的. 首先进 ...

随机推荐

  1. 利用JS做到隐藏div和显示div

    div的visibility可以控制div的显示和隐藏,但是隐藏后页面显示空白 style="visibility: none;" document.getElementById( ...

  2. DELPHI中MessageBox的用法

    MessageBox对话框 输入控件的   ImeName属性把输入法去掉就默认为英文输入了 MessageBox对话框是比较常用的一个信息对话框,其不仅能够定义显示的信息内容.信息提示图标,而且可以 ...

  3. 二叉树最大路径和-Binary Tree Maximum Path Sum

    Given a binary tree, find the maximum path sum. For this problem, a path is defined as any sequence ...

  4. Delphi 常用API 函数

    Delphi 常用API 函数 AdjustWindowRect 给定一种窗口样式,计算获得目标客户区矩形所需的窗口大小 AnyPopup 判断屏幕上是否存在任何弹出式窗口 ArrangeIconic ...

  5. thinkphp CURD 1

    二.ThinkPHP 3 读取数据    (重点)    对数据的读取 Read    $m=new Model('User');    $m=M('User'); select    $m-> ...

  6. LIS算法

    LIS(Longest Increasing Subsequence)最长上升(不下降)子序列. 1. O(n^2) #include<cstdio> #include<algori ...

  7. WebSocket协议再认识

    WebSocket出现之前 在线聊天室.在线客服系统.评论系统.WebIM等这些应用有一个共同点,就是用户不需要去刷新浏览器就能够从服务器获得最新的数据,这就用到了推送技术. WebSocket出现之 ...

  8. Android离线缓存

    android做到一定程度,需要考虑缓存的问题,不信可以掏出手机看看淘宝等一些app是否无网的情况下还可以浏览,不过大部分app并没有考虑到这些问题,解决Android的缓存有哪些方法呢 1.IO流读 ...

  9. 【The Most Important】浅谈JSP表单Post方式中文乱码问题

    首先祝大家鸡年吉祥!在这里我要说下这两天里这个问题困扰着我,大过年的心情都被烦扰的不好了,所以我带着兴奋的心情来赶快完成这篇博客,解决大家的问题.我的问题是post方式传递表单数据,Tomcat服务器 ...

  10. .net core中加载lua脚本的类库: MoonSharp

    前言 MoonSharp是一个支持C#调用lua脚本的类库,支持.net, .net core, mono, unity,因此在.net core中也能够使用,而且加载和调用lua也很方便简单: 官网 ...