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(); }
逻辑非常easy,但当点进入界面时,会发现程序奔溃了,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实现代码中,事实上在 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 { //弱引用 WeakReferenceweakReference; public MyHandler(Main4Activity activity) { weakReference = new WeakReference (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类来实现。后面会做简单的实现介绍。