1. Handler使用引出
創(chuàng)新互聯(lián)公司2013年開創(chuàng)至今,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目做網(wǎng)站、網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元康縣做網(wǎng)站,已為上家服務(wù),為康縣各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:13518219792
現(xiàn)在作為客戶,有這樣一個(gè)需求,當(dāng)打開Activity界面時(shí),開始倒計(jì)時(shí),倒計(jì)時(shí)結(jié)束后跳轉(zhuǎn)新的界面(思維活躍的朋友可能立馬想到如果打開后自動(dòng)倒計(jì)時(shí),就類似于各個(gè)APP的歡迎閃屏頁面),如下圖:
作為初學(xué)者,可能覺得直接開啟一個(gè)包含倒序循環(huán)的子線程就ok了,具體實(shí)現(xiàn)如下:
1.1 Layout界面代碼如下:
<?xml version="1.0" encoding="utf-8"?>
1.2 java實(shí)現(xià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(); } } //計(jì)時(shí)結(jié)束后跳轉(zhuǎn)到其他界面 startActivity(new Intent(Main2Activity.this,Main3Activity.class)); //添加finish方法在任務(wù)棧中銷毀倒計(jì)時(shí)界面,使新開界面在回退時(shí)直接退出而不是再次返回該界面 finish(); } }).start(); }
邏輯很簡單,但當(dāng)點(diǎn)進(jìn)入界面時(shí),會(huì)發(fā)現(xiàn)程序奔潰了,logcat中錯(cuò)誤日志如下(只有UI線程可以更改UI界面):
由此我們發(fā)現(xiàn)在安卓開發(fā)中,例如上面的示例,我們常常通過一個(gè)線程來完成某些操作,然后同步顯示對(duì)應(yīng)的視圖控件UI上,通過上面的例子我們也知道了安卓中無法直接通過子線程來進(jìn)行UI更新操作,對(duì)于這種情況,Android提供了一套異步消息處理機(jī)制Handler。
2. Handler實(shí)現(xiàn)方法
使用handler實(shí)現(xiàn),修改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 處理的消息對(duì)象就是Message,理解為要傳遞的消息數(shù)據(jù)的封裝對(duì)象 * Message what : 標(biāo)記,用來區(qū)分多個(gè)消息 * Message arg1,arg2 : 用來傳遞int類型的數(shù)據(jù) * Message obj : 可以傳遞任何類型的對(duì)象(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();//開啟倒計(jì)時(shí)并跳轉(zhuǎn)頁面的方法 } //消息處理者,創(chuàng)建一個(gè)Handler的子類對(duì)象,目的是重寫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); } //計(jì)時(shí)結(jié)束后跳轉(zhuǎn)到其他界面 startActivity(new Intent(Main2Activity.this,Main3Activity.class)); //添加finish方法在任務(wù)棧中銷毀倒計(jì)時(shí)界面,使新開界面在回退時(shí)直接退出而不是再次返回該界面 finish(); } }).start(); } @Override protected void onDestroy() { super.onDestroy(); //log打印用于測試activity銷毀 Log.i("tag","destory"); } }
3. Handler實(shí)現(xiàn)原理
使用Handler方式進(jìn)行異步消息處理主要由Message,Handler,MessageQueue,Looper四部分組成:
(1)Message,線程之間傳遞的消息,用于不同線程之間的數(shù)據(jù)交互。Message中的what字段用來標(biāo)記區(qū)分多個(gè)消息,arg1、arg2 字段用來傳遞int類型的數(shù)據(jù),obj可以傳遞任意類型的字段。
(2)Handler,用于發(fā)送和處理消息。其中的sendMessage()用來發(fā)送消息,handleMessage()用于消息處理,進(jìn)行相應(yīng)的UI操作。
(3)MessageQueue,消息隊(duì)列(先進(jìn)先出),用于存放Handler發(fā)送的消息,一個(gè)線程只有一個(gè)消息隊(duì)列。
(4)Looper,可以理解為消息隊(duì)列的管理者,當(dāng)發(fā)現(xiàn)MessageQueue中存在消息,Looper就會(huì)將消息傳遞到handleMessage()方法中,同樣,一個(gè)線程只有一個(gè)Looper。
Handler實(shí)現(xiàn)原理如下圖:
結(jié)合上文的的代碼示例以及上圖的實(shí)現(xiàn)流程,要使用Handler實(shí)現(xiàn)異步消息處理,首先我們需要在主線程中創(chuàng)建Handler對(duì)象并重寫handleMessage()方法,然后當(dāng)子線程中需要進(jìn)行UI操作時(shí),就創(chuàng)建一個(gè)Message對(duì)象,并通過Handlerr將這條消息發(fā)送出去。之后這條消息會(huì)被添加到MessageQueue的隊(duì)列中等待被處理,而Looper則會(huì)一直嘗試從MessageQueue中取出待處理消息,最后分發(fā)回Handler的handleMessage()方法中。由于Halldler是在主線程中創(chuàng)建的,所以此時(shí)handleMessage()方法中的代碼也會(huì)在主線程中運(yùn)行,從而實(shí)現(xiàn)子線程通過Handler機(jī)制實(shí)現(xiàn)UI線程操作的目的。
4. Handler內(nèi)存泄漏分析
4.1 Handler內(nèi)存泄漏問題的引出:
上面的Handler實(shí)現(xiàn)代碼中,其實(shí)在Android Studio中會(huì)提示以下問題:
大致意思就是應(yīng)該讓Handler類為靜態(tài)的,不然就會(huì)產(chǎn)生內(nèi)存泄漏。 原因也說的很清楚,Handler被聲明為一個(gè)非靜態(tài)內(nèi)部類或者匿名類可能會(huì)阻止外部類的垃圾回收(大家可以了解下Android的gc回收機(jī)制)。過多的內(nèi)存泄漏使程序占用的內(nèi)存超出系統(tǒng)限制,導(dǎo)致OOM(內(nèi)存溢出),程序出錯(cuò)。
4.2 防止Handler引起內(nèi)存泄漏:
方法一:通過程序邏輯進(jìn)行保護(hù):
(1)在關(guān)閉Activity時(shí)停掉對(duì)應(yīng)的后臺(tái)線程。線程停止就相當(dāng)于切斷了Handle和外部鏈接的線,Activity自然會(huì)在合適的時(shí)候被回收。
(2)如果Handler是被delay的Message持有了引用,那就使用Handler的removeCallbacks()方法將消息對(duì)象從消息隊(duì)列移除即可。
方法二:將Handler聲明為靜態(tài)類,靜態(tài)類不持有外部類的對(duì)象,所以Activity可以被隨意回收。此處使用了弱引用WeakReference,也就是說當(dāng)在內(nèi)存不足時(shí),系統(tǒng)會(huì)銷毀弱/回收引用引用的對(duì)象,從而達(dá)到優(yōu)化內(nèi)存的目的。優(yōu)化后代碼如下:
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();//開啟倒計(jì)時(shí)并跳轉(zhuǎn)頁面的方法 } //Handler靜態(tài)內(nèi)部類 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); } //計(jì)時(shí)結(jié)束后跳轉(zhuǎn)到其他界面 startActivity(new Intent(Main4Activity.this, Main3Activity.class)); //添加finish方法在任務(wù)棧中銷毀倒計(jì)時(shí)界面,使新開界面在回退時(shí)直接退出而不是再次返回該界面 finish(); } }).start(); } @Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacksAndMessages(null); Log.i("tag", "destory"); } }
5.小結(jié)
本次使用handler實(shí)現(xiàn)倒計(jì)時(shí)頁面跳轉(zhuǎn)的效果實(shí)現(xiàn),只是向大家簡單介紹handler的使用方法以及注意事項(xiàng),但依然存在bug,如果倒計(jì)時(shí)未完成時(shí)退出activity,子線程依然會(huì)在后臺(tái)運(yùn)行直至完成跳轉(zhuǎn),效果以及l(fā)og日志如下:
針對(duì)這種情況,我處理起來比較麻煩,需要在銷毀倒計(jì)時(shí)activity時(shí),同時(shí)終止線程,我試了很多方法,未能實(shí)現(xiàn)。
其實(shí)要實(shí)現(xiàn)倒計(jì)時(shí)閃屏效果,可以使用Android中有個(gè)countDownTimer類來實(shí)現(xiàn),后面會(huì)做簡單的實(shí)現(xiàn)介紹。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持創(chuàng)新互聯(lián)!