1. 程式人生 > >Android Handler 訊息機制(解惑篇)

Android Handler 訊息機制(解惑篇)

Android中的訊息處理機制概述

大家對於Android中的訊息處理機制的用法一定都比較熟悉,至於工作原理估計不少人有研究。就像我們自己寫的類我們用起來比較熟悉一樣,如果我們熟悉了訊息處理機制的具體實現,那麼我們用起來肯定也會事半功倍。

博主之前只是稍有涉獵,對其中一些地方也還心存疑慮,比如既然Looper.loop()裡是一個死迴圈,那它會不會很消耗CPU呢?死迴圈阻塞了執行緒,那我們其他的事務是如何被處理的呢?Android的UI執行緒是在哪裡被初始化的呢?等等。索性今天就把他們放到一起,說道說道。

Android中執行緒的分類

  • 帶有訊息佇列,用來執行迴圈性任務(例如主執行緒、android.os.HandlerThread)

    • 有訊息時就處理

    • 沒有訊息時就睡眠

  • 沒有訊息佇列,用來執行一次性任務(例如java.lang.Thread)

    • 任務一旦執行完成便退出

帶有訊息佇列執行緒概述

四要素

  • Message(訊息)

  • MessageQueue(訊息佇列)

  • Looper(訊息迴圈)

  • Handler(訊息傳送和處理)

四要素的互動過程

具體工作過程

  • 訊息佇列的建立

  • 訊息迴圈

  • 訊息的傳送

    最基本的兩個API

    • Handler.sendMessage

      • 帶一個Message引數,用來描述訊息的內容
    • Handler.post

      • 帶一個Runnable引數,會被轉換為一個Message引數
  • 訊息的處理

基於訊息的非同步任務介面

  • android.os.HandlerThread

    • 適合用來處於不需要更新UI的後臺任務
  • android.os.AyncTask

    • 適合用來處於需要更新UI的後臺任務

帶有訊息佇列執行緒的具體實現

ThreadLocal

ThreadLocal並不是一個Thread,而是Thread的區域性變數。當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。

Looper

用於在指定執行緒中執行一個訊息迴圈,一旦有新任務則執行,執行完繼續等待下一個任務,即變成Looper執行緒。Looper類的註釋裡有這樣一個例子:

class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {

        //將當前執行緒初始化為Looper執行緒
        Looper.prepare();

        // ...其他處理,如例項化handler
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };

        // 開始迴圈處理訊息佇列
        Looper.loop();
    }
}

其實核心程式碼就兩行,我們先來看下Looper.prepare()方法的具體實現

public final class Looper {

    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    //Looper內的訊息佇列
    final MessageQueue mQueue;
    // 當前執行緒
    final Thread mThread;

    private Printer mLogging;

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //試圖在有Looper的執行緒中再次建立Looper將丟擲異常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    //~省略部分無關程式碼~
}

從中我們可以看到以下幾點:

  • prepare()其核心就是將looper物件定義為ThreadLocal
  • 一個Thread只能有一個Looper物件
  • prepare()方法會呼叫Looper的構造方法,初始化一個訊息佇列,並且指定當前執行緒
  • 在呼叫Looper.loop()方法之前,確保已經呼叫了prepare(boolean quitAllowed)方法,並且我們可以呼叫quite方法結束迴圈

說到初始化MessageQueue,我們來看下它是幹什麼的

/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
*

You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/

它是一個低等級的持有Messages集合的類,被Looper分發。Messages並不是直接加到MessageQueue的,而是通過Handler物件和Looper關聯到一起。我們可以通過Looper.myQueue()方法來檢索當前執行緒的MessageQueue。

接下來再看看Looper.loop()

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    //得到當前執行緒Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //得到當前looper的MessageQueue
    final 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();

    //開始迴圈
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            //沒有訊息表示訊息佇列正在退出
            return;
        }

        // 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);
        }

        //將真正的處理工作交給message的target,即handler
        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // 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.recycleUnchecked();
    }
}

通過這段程式碼可知,呼叫loop方法後,Looper執行緒就開始真正工作了,它不斷從自己的MessageQueue中取出隊頭的訊息(或者說是任務)執行

除了prepare()和loop()方法,Looper類還有一些比較有用的方法,比如

  • Looper.myLooper()得到當前執行緒looper物件

  • getThread()得到looper物件所屬執行緒

  • quit()方法結束looper迴圈

    這裡需要注意的一點是,quit()方法其實呼叫的是MessageWueue的quite(boolean safe)方法。

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
    
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
    
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
    
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
    

    我們看到其實主執行緒是不能呼叫這個方法退出訊息佇列的。至於mQuitAllowed引數是在Looper初始化的時候初始化的,主執行緒初始化呼叫的是Looper.prepareMainLooper()方法,這個方法把引數設定為false。

Message

在整個訊息處理機制中,message又叫task,封裝了任務攜帶的資訊和處理該任務的handler。我們看下這個類的註釋

/**
*
* Defines a message containing a description and arbitrary data object that can be
* sent to a {@link Handler}. This object contains two extra int fields and an
* extra object field that allow you to not do allocations in many cases.
*
*

While the constructor of Message is public, the best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
* them from a pool of recycled objects.


*/

這個類定義了一個包含描述和一個任意型別物件的物件,它可以被髮送給Handler。

從註釋裡我們還可以瞭解到以下幾點:

  • 儘管Message有public的預設構造方法,但是你應該通過Message.obtain()來從訊息池中獲得空訊息物件,以節省資源。

  • 如果你的message只需要攜帶簡單的int資訊,請優先使用Message.arg1和Message.arg2來傳遞資訊,這比用Bundle更省記憶體

  • 用message.what來標識資訊,以便用不同方式處理message。

Handler

從MessageQueue的註釋中,我們知道新增訊息到訊息佇列是通過Handler來操作的。我們通過原始碼來看下具體是怎麼實現的

/**
* A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread’s {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread’s message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it – from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
*
*

There are two main uses for a Handler: (1) to schedule messages and
* runnables to be executed as some point in the future; and (2) to enqueue
* an action to be performed on a different thread than your own.
*
*/

註釋比較簡單,這裡就不過多翻譯了,主要內容是:每一個Handler例項關聯了一個單一的thread和這個thread的messagequeue,當Handler的例項被建立的時候它就被繫結到了建立它的thread。它用來排程message和runnables在未來某個時間點的執行,還可以排列其他執行緒裡執行的操作。

public class Handler {

    //~省略部分無關程式碼~

    final MessageQueue mQueue;
    final Looper mLooper;

    public Handler() {
        this(null, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> 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 = callback;
        mAsynchronous = async;
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    //~省略部分無關程式碼~
}

先看構造方法,其實裡邊的重點是初始化了兩個變數,把關聯looper的MessageQueue作為自己的MessageQueue,因此它的訊息將傳送到關聯looper的MessageQueue上

有了handler之後,我們就可以使用Handler提供的post和send系列方法向MessageQueue上傳送訊息了。其實post發出的Runnable物件最後都被封裝成message物件

接下來我們看一下handler是如何傳送訊息的

/**
 * Causes the Runnable r to be added to the message queue.
 * The runnable will be run on the thread to which this handler is 
 * attached. 
 *  
 * @param r The Runnable that will be executed.
 * 
 * @return Returns true if the Runnable was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

/**
 * Enqueue a message into the message queue after all pending messages
 * before (current time + delayMillis). You will receive it in
 * {@link #handleMessage}, in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

/**
 * Enqueue a message into the message queue after all pending messages
 * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
 * Time spent in deep sleep will add an additional delay to execution.
 * You will receive it in {@link #handleMessage}, in the thread attached
 * to this handler.
 * 
 * @param uptimeMillis The absolute time at which the message should be
 *         delivered, using the
 *         {@link android.os.SystemClock#uptimeMillis} time-base.
 *         
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

這裡我們只列出了一種呼叫關係,其他呼叫關係大同小異,我們來分析一下

  1. 呼叫getPostMessage(r),把runnable物件新增到一個Message物件中。
  2. sendMessageDelayed(getPostMessage(r), 0),基本沒做什麼操作,又繼續呼叫sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)方法,在這個方法裡拿到建立這個Handler物件的執行緒持有的MessageQueue。
  3. 呼叫enqueueMessage(queue, msg, uptimeMillis)方法,給msg物件的target變數賦值為當前的Handler物件,然後放入到MessageQueue。

那傳送訊息說完了,那我們的訊息是怎樣被處理的呢?

我們看到message.target為該handler物件,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵程式碼。

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

我們看到這裡最終又呼叫到了我們重寫的handleMessage(Message msg)方法來做處理子執行緒發來的訊息或者呼叫handleCallback(Message message)去執行我們子執行緒中定義並傳過來的操作。

思考

為什麼要有Handler機制

這個問題可以這麼考慮

  1. 我們如何在子執行緒更新UI?——使用Handler機制傳遞訊息到主執行緒(UI執行緒)
  2. 為什麼我們不在子執行緒更新UI呢?——因為Android是單執行緒模型
  3. 為什麼要做成單執行緒模型呢?——多執行緒併發訪問UI可能會導致UI控制元件處於不可預期的狀態。如果加鎖,雖然能解決,但是缺點也很明顯:1.鎖機制讓UI訪問邏輯變得複雜;2.加鎖導致效率低下。

Handler機制與命令模式

我在之前分享過Android原始碼中的命令模式,我們仔細分下一下不難看出Handler機制其實是一個非典型的命令模式

  • 接收者:Handler,執行訊息處理操作。

  • 呼叫者:Looper,呼叫訊息的的處理方法。

  • 命令角色:Message,訊息類。

  • 客戶端:Thread,建立訊息並繫結Handler(接受者)。

Android主執行緒是如何管理子執行緒訊息的

我們知道Android上一個應用的入口,應該是ActivityThread。和普通的Java類一樣,入口是一個main方法。

public static void main(String[] args) {

    //~省略部分無關程式碼~

    //建立Looper和MessageQueue物件,用於處理主執行緒的訊息
    Looper.prepareMainLooper();

    //建立ActivityThread物件
    ActivityThread thread = new ActivityThread();

    //建立Binder通道 (建立新執行緒)
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

    //訊息迴圈執行
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

我們可以看到其實我們在這裡初始化了我們主執行緒(UI)的Looper並且啟動它。然後就可以處理子執行緒和其他元件發來的訊息了。

為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死或者不能處理其他事務

這裡涉及到的東西比較多,概括的理解是這樣的

  1. 為什麼不會卡死

    handler機制是使用pipe來實現的,主執行緒沒有訊息處理時會阻塞在管道的讀端。

    binder執行緒會往主執行緒訊息佇列裡新增訊息,然後往管道寫端寫一個位元組,這樣就能喚醒主執行緒從管道讀端返回,也就是說queue.next()會呼叫返回。

    主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

  2. 既然是死迴圈又如何去處理其他事務呢?

    答案是通過建立新執行緒的方式

    我們看到main方法裡呼叫了thread.attach(false),這裡便會建立一個Binder執行緒(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS傳送來的事件),該Binder執行緒通過Handler將Message傳送給主執行緒。

    ActivityThread對應的Handler是一個內部類H,裡邊包含了啟動Activity、處理Activity生命週期等方法。

參考資料

相關推薦

Android Handler 訊息機制解惑

Android中的訊息處理機制概述 大家對於Android中的訊息處理機制的用法一定都比較熟悉,至於工作原理估計不少人有研究。就像我們自己寫的類我們用起來比較熟悉一樣,如果我們熟悉了訊息處理機制的具體實現,那麼我們用起來肯定也會事半功倍。 博主之前只是稍有涉

Android訊息機制Handler、Looper、MessageQueue

注:本文原始碼基於Android7.0先大概概括一下訊息機制:這裡有三個角色,Handler、looper、MessageQueue。Handler負責發訊息和處理訊息,Looper負責從MessageQueue中取出訊息給Handler處理,MessageQueue則負責儲

Handler.post(Runnable r)再一次梳理Android訊息機制以及handler的記憶體洩露

Handler 每個初學Android開發的都繞不開Handler這個“坎”,為什麼說是個坎呢,首先這是Android架構的精髓之一,其次大部分人都是知其然卻不知其所以然。今天看到Handler.post這個方法之後決定再去翻翻原始碼梳理一下Handler

Android Handler 訊息機制分析

        Handler,Message,MessageQueue,Looper,是android一種訊息處理機制,在android開發中經常會用到,當Handler建立後,會被繫結到它所在的執行緒上,處理訊息的成員及其功能如下:        Handler:傳送一個

Android開發藝術探索》讀書筆記——Handler訊息機制ThreadLocal

ThreadLocal ThreadLocal是一個執行緒內部的資料儲存類。它可以為各執行緒儲存資料,同時只能由當前執行緒獲取到儲存的資料,對於其他執行緒來說則獲取不到。它可以在不同執行緒中維護一套資料的副本,並且彼此互不干擾。 一言不合上程式碼: privat

Android查缺補漏View--事件分發機制

touch事件 滑動沖突 今天 version schema ttr 步驟 isp win 事件分發機制是Android中非常重要的一個知識點,同時也是難點,相信到目前為止很多Android開發者對事件分發機制並沒有一個非常系統的認識,當然也包括博主個人在內。可能在平時的開

Android查缺補漏View--布局文件中的“@+id”和“@id”有什麽區別?

新增 布局 parent 直接 使用 margin 移除 控件 Coding Android布局文件中的“@+id”和“@id”有什麽區別? +id表示為控件指定一個id(新增一個id),如: <cn.codingblock.view.customer_view.

Android查缺補漏IPC-- 進程間通訊基礎知識熱身

內部 eat ack 學習過程 and ... 綁定 his nec 本文作者:CodingBlock 文章鏈接:http://www.cnblogs.com/codingblock/p/8479282.html 在Android中進程間通信是比較難的一部分,同時又非常重要

深入理解Java異常處理機制 籠統

throw 種類型 綜合 IV 算術 其它 wid all 作用 開篇 1.異常處理(Exception Handling):   就是一種解決這一問題的機制,能夠較好地處理程序不能正常運行的情況。 2.異常(Exception):   是程序在運行時可能出現的

Android Handler訊息機制學習

1.概述   Handler允許你傳送和處理Message,以及和執行緒相關聯的Runnable物件。每一個Handler例項都與一個執行緒及該執行緒的MessageQueue相關聯。既當你建立一個Handler時,該Handler必須繫結一個執行緒以及該執行緒的訊息佇列,一旦它被建立,它能把message

Android Handler訊息機制原始碼解析

好記性不如爛筆頭,今天來分析一下Handler的原始碼實現 Handler機制是Android系統的基礎,是多執行緒之間切換的基礎。下面我們分析一下Handler的原始碼實現。 Handler訊息機制有4個類合作完成,分別是Handler,MessageQueue,Looper,Message Handl

手把手帶你打造一個 Android 熱修復框架

本文來自網易雲社群作者:王晨彥前言熱修復和外掛化是目前 Android 領域很火熱的兩門技術,也是 Android 開發工程師必備的技能。目前比較流行的熱修復方案有微信的 Tinker,手淘的 Sophix,美團的 Robust,以及 QQ 空間熱修復方案。QQ 空間熱修復方

Android Handler訊息機制中的諸多疑問

前言 網上總是有很多闡述Android訊息機制的文章,基本上大同小異,都是講Handle,Message,Looper,MessageQueue這四個類會如何協同工作的。但是動腦筋的童鞋們可能總是會有如下的一些疑問,我翻閱了數多微博,很多年了,也沒有看到相關比較

Android記憶體洩露利器hprof

set processName=com.sec.android.app.dialertab;android.process.acore;com.sec.android.provider.logsprovider

訊息佇列入門

什麼是訊息佇列? 小時候,我的爸爸希望我多讀書,並常常尋找好書給我看,最開始他每次看見我寫完作業之後就給我拿來書,並親自監督我讀完之後他才忙自己的事情。久而久之,我養成了讀書的習慣。所以方式就改成了,爸爸想要我讀的書,都放在書架上,由於我已經養成了好習慣,一有

Android 開發神器系列工具Android WiFi ADB

               做為一個多年奮戰在Android 應用開發一線的程式設計師來說,程式除錯的苦是不言而喻的,在過去的很長一段時間裡,我們如果要除錯Android 應用只能通過USB資料線,一頭連著手機,一頭聯著電腦,不敢讓手機離開電腦半步。 、         

Android學習之旅第一 SurfaceView的原理以及使用場景

為什麼要使用SurfaceView來實現動畫? 因為View的繪圖存在以下缺陷: View缺乏雙緩衝機制 當程式需要更新View上的影象時,程式必須重繪View上顯示的整張圖片 新執行緒無法直接更新View元件 SurfaceView的繪圖機制

ASP.NET MVC學習---許可權過濾機制完結

相信對許可權過濾大傢伙都不陌生 使用者要訪問一個頁面時 先對其許可權進行判斷並進行相應的處理動作 在webform中 最直接也是最原始的辦法就是 在page_load事件中所有程式碼之前 先執行一個許可權判斷的方法 至於其專業的許可權機制這裡不做討論 想要了解的同學可以自行

Android Handler訊息機制原始碼解讀

這個東西在網上已經被很多人寫過了,自己也看過很多文章,大概因為自己比較愚笨一直對此不太理解,最近重新從原始碼的角度閱讀,並且配合著網上的一些相關部落格才算明白了一些 本文從原始碼的角度順著程式碼的執行去原始碼,限於作者的表達能力及技術水平,可能會有些問題,請耐性

值得關注的 25 個新 Android 庫和專案

這是我最新收集的 25 個 Android 庫和專案列表,你也許會發現它們有用,有趣,並值得檢視一下。所有的都在最近 3 個月釋出的,排序不分先後。 PS :有興趣的加入Android工程師交流QQ群:752016839 主要針對Android開發人員提升自己,突破瓶頸,相