1. Handler使用引出

現(xiàn)在作為客戶,有這樣一個需求,當(dāng)打開Activity界面時,開始倒計時,倒計時結(jié)束后跳轉(zhuǎn)新的界面(思維活躍的朋友可能立馬想到如果打開后自動倒計時,就類似于各個APP的歡迎閃屏頁面),如下圖:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

作為初學(xué)者,可能覺得直接開啟一個包含倒序循環(huán)的子線程就ok了,具體實現(xiàn)如下:

1.1 Layout界面代碼如下:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

<?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>

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

1.2 java實現(xiàn)代碼如下:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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();
                    }
                }                //計時結(jié)束后跳轉(zhuǎn)到其他界面
                startActivity(new Intent(Main2Activity.this,Main3Activity.class));                //添加finish方法在任務(wù)棧中銷毀倒計時界面,使新開界面在回退時直接退出而不是再次返回該界面                finish();
            }
        }).start();
    }

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

邏輯很簡單,但當(dāng)點進入界面時,會發(fā)現(xiàn)程序奔潰了,logcat中錯誤日志如下(只有UI線程可以更改UI界面):

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

由此我們發(fā)現(xiàn)在安卓開發(fā)中,例如上面的示例,我們常常通過一個線程來完成某些操作,然后同步顯示對應(yīng)的視圖控件UI上,通過上面的例子我們也知道了安卓中無法直接通過子線程來進行UI更新操作,對于這種情況,Android提供了一套異步消息處理機制Handler。

2. Handler實現(xiàn)方法

使用handler實現(xiàn),修改java代碼Main2Activity.java如下:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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,理解為要傳遞的消息數(shù)據(jù)的封裝對象
 * Message what : 標(biāo)記,用來區(qū)分多個消息
 * Message arg1,arg2 : 用來傳遞int類型的數(shù)據(jù)
 * 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();//開啟倒計時并跳轉(zhuǎn)頁面的方法    }    //消息處理者,創(chuàng)建一個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);
                }                //計時結(jié)束后跳轉(zhuǎn)到其他界面
                startActivity(new Intent(Main2Activity.this,Main3Activity.class));                //添加finish方法在任務(wù)棧中銷毀倒計時界面,使新開界面在回退時直接退出而不是再次返回該界面                finish();
            }
        }).start();
    }

    @Override    protected void onDestroy() {        super.onDestroy();        //log打印用于測試activity銷毀
        Log.i("tag","destory");
    }
}

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

3. Handler實現(xiàn)原理

使用Handler方式進行異步消息處理主要由Message,Handler,MessageQueue,Looper四部分組成:

(1)Message,線程之間傳遞的消息,用于不同線程之間的數(shù)據(jù)交互。Message中的what字段用來標(biāo)記區(qū)分多個消息,arg1、arg2 字段用來傳遞int類型的數(shù)據(jù),obj可以傳遞任意類型的字段。

(2)Handler,用于發(fā)送和處理消息。其中的sendMessage()用來發(fā)送消息,handleMessage()用于消息處理,進行相應(yīng)的UI操作。

(3)MessageQueue,消息隊列(先進先出),用于存放Handler發(fā)送的消息,一個線程只有一個消息隊列。

(4)Looper,可以理解為消息隊列的管理者,當(dāng)發(fā)現(xiàn)MessageQueue中存在消息,Looper就會將消息傳遞到handleMessage()方法中,同樣,一個線程只有一個Looper。

Handler實現(xiàn)原理如下圖:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

結(jié)合上文的的代碼示例以及上圖的實現(xiàn)流程,要使用Handler實現(xiàn)異步消息處理,首先我們需要在主線程中創(chuàng)建Handler對象并重寫handleMessage()方法,然后當(dāng)子線程中需要進行UI操作時,就創(chuàng)建一個Message對象,并通過Handlerr將這條消息發(fā)送出去。之后這條消息會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理消息,最后分發(fā)回Handler的handleMessage()方法中。由于Halldler是在主線程中創(chuàng)建的,所以此時handleMessage()方法中的代碼也會在主線程中運行,從而實現(xiàn)子線程通過Handler機制實現(xiàn)UI線程操作的目的。

4. Handler內(nèi)存泄漏分析

4.1 Handler內(nèi)存泄漏問題的引出:

上面的Handler實現(xiàn)代碼中,其實在Android Studio中會提示以下問題:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

大致意思就是應(yīng)該讓Handler類為靜態(tài)的,不然就會產(chǎn)生內(nèi)存泄漏。 原因也說的很清楚,Handler被聲明為一個非靜態(tài)內(nèi)部類或者匿名類可能會阻止外部類的垃圾回收(大家可以了解下Android的gc回收機制)。過多的內(nèi)存泄漏使程序占用的內(nèi)存超出系統(tǒng)限制,導(dǎo)致OOM(內(nèi)存溢出),程序出錯。

4.2 防止Handler引起內(nèi)存泄漏:

方法一:通過程序邏輯進行保護:

(1)在關(guān)閉Activity時停掉對應(yīng)的后臺線程。線程停止就相當(dāng)于切斷了Handle和外部鏈接的線,Activity自然會在合適的時候被回收。

(2)如果Handler是被delay的Message持有了引用,那就使用Handler的removeCallbacks()方法將消息對象從消息隊列移除即可。

方法二:將Handler聲明為靜態(tài)類,靜態(tài)類不持有外部類的對象,所以Activity可以被隨意回收。此處使用了弱引用WeakReference,也就是說當(dāng)在內(nèi)存不足時,系統(tǒng)會銷毀弱/回收引用引用的對象,從而達到優(yōu)化內(nèi)存的目的。優(yōu)化后代碼如下:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

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();//開啟倒計時并跳轉(zhuǎn)頁面的方法    }    //Handler靜態(tài)內(nèi)部類
    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);
                    }                    //計時結(jié)束后跳轉(zhuǎn)到其他界面
                    startActivity(new Intent(Main4Activity.this, Main3Activity.class));                    //添加finish方法在任務(wù)棧中銷毀倒計時界面,使新開界面在回退時直接退出而不是再次返回該界面                    finish();
                }
        }).start();
    }
    @Override    protected void onDestroy() {        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
        Log.i("tag", "destory");
    }
}

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

5.小結(jié)

本次使用handler實現(xiàn)倒計時頁面跳轉(zhuǎn)的效果實現(xiàn),只是向大家簡單介紹handler的使用方法以及注意事項,但依然存在bug,如果倒計時未完成時退出activity,子線程依然會在后臺運行直至完成跳轉(zhuǎn),效果以及l(fā)og日志如下:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營銷培訓(xùn)

針對這種情況,我處理起來比較麻煩,需要在銷毀倒計時activity時,同時終止線程,我試了很多方法,未能實現(xiàn)。

其實要實現(xiàn)倒計時閃屏效果,可以使用Android中有個countDownTimer類來實現(xiàn),后面會做簡單的實現(xiàn)介紹。

分類: Android,技術(shù)分享