Android中Handler使用淺析,androidhandler
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類來實現,後面會做簡單的實現介紹。