现在作为客户,有这样一个需求,当打开 Activity 界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个 APP 的欢迎闪屏页面),如下图:
作为初学者,可能觉得直接开启一个包含倒序循环的子线程就 ok 了,具体实现如下:
- 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"/>
- 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。
使用 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");
- }
- }
使用 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 线程操作的目的。
上面的 Handler 实现代码中,其实在 Studio 中会提示以下问题:
大致意思就是应该让 Handler 类为静态的,不然就会产生内存泄漏。 原因也说的很清楚,Handler 被声明为一个非静态内部类或者匿名类可能会阻止外部类的垃圾回收(大家可以了解下 Android 的 gc 回收机制)。过多的内存泄漏使程序占用的内存超出系统限制,导致 OOM(内存溢出),程序出错。
方法一:通过程序逻辑进行保护:
(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 weakReference;
- 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");
- }
- }
本次使用 handler 实现倒计时页面跳转的效果实现,只是向大家简单介绍 handler 的使用方法以及注意事项,但依然存在 bug,如果倒计时未完成时退出 activity,子线程依然会在后台运行直至完成跳转,效果以及 log 日志如下:
针对这种情况,我处理起来比较麻烦,需要在销毁倒计时 activity 时,同时终止线程,我试了很多方法,未能实现。
其实要实现倒计时闪屏效果,可以使用 Android 中有个 countDownTimer 类来实现,后面会做简单的实现介绍。
来源: http://www.cnblogs.com/panhouye/p/6494753.html