編輯:關於Android編程
提到Handler大家並不陌生,Handler是android給我們提供用來更新UI的一套機制,也是一套消息處理機制,我們可以通過它發送消息,也可以通過它處理消息,它既可以發送消息也可以接收消息。當我們在子線程中對UI進行更改的操作的時候,應用會崩潰,系統提示我們不能在子線程中進行更新UI的操作。這時候Handler就可以派上用場了。為什麼要使用Handler呢,其實谷歌工程師估計考慮到程序員對於多線程的苦惱,所以android在設計的時候就封裝了一套消息創建、傳遞、處理機制,如果不遵循這樣的機制就沒有辦法更新UI信息的,就會拋出異常信息。這就是Handler。
在Android中我們對Handler的最基本用法是子線程和主線程之間的通訊,將耗時的操作放在子線程中進行,將操作後的結果或者數據通過Handler傳遞給UI線程,UI再通過這些數據更新UI和進行相應的用戶操作。
比如我們有如下代碼:
public class MainActivity extends Activity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView =(TextView) findViewById(R.id.textView); new Thread(){ public void run() { try { Thread.sleep(1000); textView.setText("Hanlder"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } }這是用子線程的sleep模擬耗時操作,然後在子線程中去更新textView,運行之後程序崩潰。日志中記錄:Only the original thread that created a view hierarchy can touch its views.意思就是,只有創建了View的原始現成能夠處理操作View,通俗的就是只有UI現成能夠更新UI。
那我們的代碼更改為下面:
public class MainActivity extends Activity { private TextView textView; private Handler handler = new Handler(){ public void handleMessage(android.os.Message msg) { textView.setText("Hanlder"); }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView =(TextView) findViewById(R.id.textView); new Thread(){ public void run() { try { Thread.sleep(1000); handler.sendEmptyMessage(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } }這樣就可以實現UI的更新了。這是Handler最基本也是最簡單的用法了,實現子線程和主線程之間的通訊。
public class MainActivity extends Activity { private TextView textView; private int i=0; private boolean flag =false; private Runnable runnable = new Runnable() { @Override public void run() { i++; textView.setText(i+""); handler.postDelayed(runnable, 500); } }; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView =(TextView) findViewById(R.id.textView); handler.postDelayed(runnable, 500); } //button的點擊事件的回調方法 public void remove(View view){ if(!flag){ flag =true; handler.removeCallbacks(runnable); }else{ flag =false; handler.postDelayed(runnable, 500); } } }界面的簡單實現效果就是在TextView上面顯示i的值,這裡的i的值是循環++的,我們在onCreate中使用handler來延遲500毫秒執行runnable對象,使i++,在runnable的run方法中我們也發送一個runnable對象,所有就可以循環執行i++的操作了額。方法remove根據flag的標志可以隨時停止和開始執行i++的循環操作,是不是感覺有點屌屌哒呢。這樣也可以實現簡單的循環操作,而且狀態自己隨時可以控制。 3.Handler的相關方法的使用。 a.public Handler(Callback callback)構造函數 大家應該在使用Handler的時候,都會使用它的默認的無參數的構造函數,然後重寫其handleMessage方法,進行相應的邏輯操作。Handler還有這樣一個構造函數public Handler(Callback callback)。先看下面的代碼
public class MainActivity extends Activity { private TextView textView; private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Toast.makeText(getApplicationContext(), "callback handlemessage", 1000).show(); //代碼1 return true; //這裡返回值需要注意 // 代碼3 } }){ public void handleMessage(Message msg) { Toast.makeText(getApplicationContext(), "handler handlemessage", 1000).show(); //代碼2 }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView =(TextView) findViewById(R.id.textView); } public void show(View view){ handler.sendEmptyMessage(1); } }上面的代碼中使用了public Handler(Callback callback)的構造函數。我們這裡可以進行消息傳遞的攔截。當我們的“代碼3”中return false的時候 “代碼1” “代碼2”會依次執行,當“代碼3”中return true的時候“代碼1”會先執行 ,但是“代碼2”不會執行,此時有點類似事件傳遞中返回true事件消費,false繼續向上傳遞的意思。我們可以使用Handler的這個構造方法,來進行消息傳遞的攔截。 b.Handler與Looper,MessageQueue的關系 學習Hanlder必須要弄清楚Hanlder與Looper,MessageQueue之間的關系。 Hanlder:在Android中主要是封裝了消息的發送。 Looper:類似一個“消息泵”,產生動力,接收Handler發送過來的消息,並且在Looper中存在一個loop方法和一個MessageQueue對象,通過loop方法一直輪詢,從MessageQueue中取出消息,並回傳給Hanlder自己 MessageQueue:就是一個存儲消息的容器。 下面我們可以從源代碼的角度來認識一下Handler和這兩個類之間的關系以及Handler中消息的處理邏輯。在我們一個引用創建的時候,其實也是通過一個主線程中的main方法執行的,這個“主線程(其實並不是一個線程,就是一個普通java類,但是有入口函數main)也即是ActivityThread。看ActivityThread的main函數怎麼寫的
public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Process.setArgV0("前面的一些代碼就 不詳細講解了 ,在Acitivity的啟動過程中有講解到。我們看代碼的11行Looper.prepareMainLooper();方法,跟蹤進去,會發現這裡其實就是為我們整個應用程序關聯一個Looper對象,這個Looper其實就是我們UI線程關聯的Looper對象,當我們在主線程中穿件Handler對象的時候,其實關聯的也是這個Looper對象。 代碼24行,也就是調用Looper的輪詢方法。輪詢消息,開始的時候可能消息隊列中沒有消息。 當我們在主線程中創建自己的Hanlder對象的時候,我們一般的入口就是他的無參數構造函數"); Looper.prepareMainLooper(); if (sMainThreadHandler == null) { sMainThreadHandler = new Handler(); } ActivityThread thread = new ActivityThread(); thread.attach(false); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
public Handler() { if (FIND_POTENTIAL_LEAKS) { final Class klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }從構造函數中,可以看出,handler中的Loopder對象通過Looper.myLooper()方法賦值,實質也是從主線程的ThreadLocal中取出Looper並且賦值,然後初始化MessageQueue對象來存放消息。 當我們Handler和Looper,MessageQueue初始化完畢之後,就看看消息的發送了,我們以一般的SendMessage(Message)來講解,跟蹤此方法可以看出,最後調用的是sendMessageAtTime方法,來看看這個方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; sent = queue.enqueueMessage(msg, uptimeMillis); } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }在這個函數中首先有一句代碼msg.target=this;這個東西後面有用,target指回handler自己,這就是我們上面所說的Looper接受handler發送的消息,並且將消息回傳給Handler自己。 代碼07行,就是將消息塞入MessageQueue中,此時我們的Looper有了,消息隊列MessageQueue中也有消息了。上面說過我們Looper.loop其實是一個死循環一直輪詢消息隊列中的消息那我們來具體看看loop方法
/ public static void loop() { Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); while (true) { Message msg = queue.next(); // might block if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } long wallStart = 0; long threadStart = 0; // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); wallStart = SystemClock.currentTimeMicro(); threadStart = SystemClock.currentThreadTimeMicro(); } msg.target.dispatchMessage(msg); if (logging != null) { long wallTime = SystemClock.currentTimeMicro() - wallStart; long threadTime = SystemClock.currentThreadTimeMicro() - threadStart; logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); if (logging instanceof Profiler) { ((Profiler) logging).profile(msg, wallStart, wallTime, threadStart, threadTime); } } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } } }的確有一個while(true)的死循環一直在輪詢消息隊列。 代碼34行有通過msg.target.dispatchMessage(msg),這就是上面所說的target的作用回傳給Handler自己來處理消息,來看dispatchMessage函數
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }這裡才是真正的處理消息的方法,裡面調用了我們復寫的handleMessage方法。從函數的處理邏輯看,首先檢查msg的callback(其實就是上面用到的一個runnable對象)是否為null,如果不為null執行,就執行他的run方法,如果為null,檢查我們的Handler的callback是否為空,不為空的時候,這就要涉及到我們上面的消息攔截的處理邏輯了。mCallback.handleMessage(msg)方法有一個返回值,放返回true的時候就不在執行我們handler的handleMessage方法了,只有返回false的時候才會執行handler的handleMessage方法。處理我們更新UI的邏輯操作了。這裡handler的發送消息的邏輯差不多跟著源代碼讀了一遍,其實也不難哈。 c.自定義與線程相關的Handler之引出HandlerThread的用法。
先看下面的一段代碼:
public class MainActivity extends Activity { private Handler handler; class MyThread extends Thread{ public Looper looper; @Override public void run() { looper.prepare(); System.out.println("current thread"+Thread.currentThread()); looper.loop(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyThread thread = new MyThread(); thread.start(); Handler handler = new Handler(thread.looper){ @Override public void handleMessage(Message msg) { System.out.println("handle message"); } }; } }我們創建自己的handler的時候,可以傳遞一個Looper對象,正如上面的實例而言,但是運行代碼我們會發現,會報空指針異常,log提示在代碼的21行出現空指針,thread不可能為空,因為thread的run方法執行了,那只有looper為空了,這就是多線程造成的困擾,有可能是當我們的handle在new出來的時候,子線程並沒有執行完成,looper就沒有成功生成,就會報空指針異常。
HandlerThread的用法:
package com.example.handlertest; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; public class MainActivity extends Activity { private Handler handler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); HandlerThread thread = new HandlerThread("Handler Thread"); thread.start(); Handler handler = new Handler(thread.getLooper()){ @Override public void handleMessage(Message msg) { System.out.println("handle message"); } }; handler.sendEmptyMessage(1); } }這樣就不會出現上面所說的空指針異常了。我們查看源代碼發現HandlerThread繼承Thread.
public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }但是在HandlerThread的getLooper方法中其實是做了同步線程保護的處理的,只要looper為空,線程就處於等待狀態,再看看handlerThread的run方法
@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }會調用notifyAll,喚醒所有的線程。
1.在Activity中調用runOnUiThread(runnable)方法,裡面傳遞一個Runnable對象,在他的run方法之後中進行更新UI的操作。其實這最終還是調用handler.post(runnable)方法
2.handler.post(runnable),
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
private final Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }看了上面2段代碼片段其實質還是講runnable對象作為Message的callbakc對象。最後還是調用Handler的消息處理邏輯
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }開始是檢查msg.callback是否為空,不為空就調用handleCallback(msg)方法。
private final void handleCallback(Message message) { message.callback.run(); }其實就是調用Runnable對象的run方法。
3.handler的sendMessage方法,此方法我們經常使用,上面也講解到,所有不在多說了。
4,view.post(runnable)方法。舉例子TextView.post方法
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }其實質還是handler的post方法。
非UI線程真的不能更新UI 嗎?
先看下列2段代碼:
import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends Activity { private TextView textview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textview = (TextView) findViewById(R.id.textView); new Thread(){ public void run() { textview.setText("ok"); }; }.start(); } }這個方法的確可以是textview上面顯示ok,不相信大家可以測試一下。
package com.example.handlertest; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends Activity { private TextView textview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textview = (TextView) findViewById(R.id.textView); new Thread(){ public void run() { try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } textview.setText("ok"); }; }.start(); } }而這段代碼執行的時候會使應用崩潰。提示“Only the original thread that created a view hierarchy can touch its views.”也就是我們通常所說的非UI線程不能更新UI。我們知道其實在textview最終更新內容的時候會調用其invilidate方法。最後其實調用viewParent的invidateChild,而Viewparent是個抽象類,他的實現了是ViewRootImpl。查看ViewRootImpl的invidateChild的方法
public void invalidateChild(View child, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); if (dirty == null) { // Fast invalidation for GL-enabled applications; GL must redraw everything invalidate(); return; } if (mCurScrollY != 0 || mTranslator != null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY != 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } if (!mDirty.isEmpty() && !mDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } mDirty.union(dirty); if (!mWillDrawSoon) { scheduleTraversals(); } }第一句就是checkThread方法。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }看到所拋出的異常是否非常之熟悉呢,哈哈,就是上面所說的非UI線程不能更新UI。這裡是調用了ViewRootImpl中的invilidateChild方法,才會拋出這個異常,而我們的ViewRootImpl的初始化操作是在Activity中的onResume方法中進行的。我們這裡可以自己去跟蹤Activity的onResume方法的執行邏輯,看看HandleResumeActivity方法,再仔細跟蹤閱讀。在我的Acitivity源碼解讀藜麥也有講到。其實最後是調用了WindowManager的實現類WindowManagerImpl的addView方法中初始化了ViewRootImpl。
感覺這裡可以在面試的時候和面試官裝逼使用。運用得當可以加很大的印象分額。
0. 前言今天這篇文章主要描述二維碼的生成與掃描,使用目前流行的Zxing,為什麼要講二維碼,因為二維碼太普遍了,隨便一個Android APP都會有二維碼掃描。本篇旨在
本文實例講述了Android編程之繪制文本(FontMetrics)實現方法。分享給大家供大家參考,具體如下:Canvas 作為繪制文本時,使用FontMetrics對象
在前面的Linux文件系統Inotify機制 中介紹了Linux對文件變更監控過程。Android系統在此基礎上封裝了一個FileObserver類來方便使用Inotif
雖然自己已經完成了百度地圖的調用,但是在使用過程中產生很多的疑問,在不斷的百度各種大神的作品後才慢慢的調試出來,所以覺得作為新手自己應該把這個過程記錄下來。尤其是自己在找