Handler是Android消息机制中非常重要的一块,Android系统源码许多地方使用到了Handler,现在项目中许多框架底层也是通过Handler来封装的,而且面试也是必问的一块,之前面试吃过几次亏,因为了解得不够深入,所以作为Android开发程序员来说,这块是必须要掌握的,不仅是使用,而且包括源码,底层实现流程。

之前就想写Handler的源码解析文章了,但是一直没有动手,因为里面有些流程一直没有弄懂,导致往后面一直在拖,现在趁着空闲来学习下Handler。


目录

一、Handler消息模型

二、ThreadLocal的工作原理

三、Message分析

四、Handler源码流程分析

五、消息的延时发送

六、异步消息和屏障消息

七、Handler常见面试问题分析

八、参考


一、Handler消息模型

Handler消息机制主要有四个部分组成,分别是Handler,Message,MessageQueue,Looper。首先Handler负责消息的发送,而Message负责携带消息,之后MessageQueue负责存储Message,Looper负责获取消息以及消息的分发。

上面的模型解释:
1 . sendMessage发送消息进入MessageQueue中保存到一个消息队列里面
2 . Looper会检测消息队列中的消息,没有消息就会阻塞,有消息就会取出
3 . 取出的消息通过Message里面的target分发给Handler的handleMessage处理


二、ThreadLocal的工作原理

消息机制中一个线程只会有一个Looper和MessageQueue,保证线程中唯一就需要借助ThreadLocal来完成,它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取存储的数据,对于其它线程是获取不到数据的。

大致的分析下流程

public T get() {//获取当前线程thread        Thread t = Thread.currentThread();        //获取线程的ThreadLocalMap对象        ThreadLocalMap map = getMap(t);        //map不为null        if (map != null) {        //获取Entry对象            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {            //获取Entry对象里面的value值,然后返回                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }    public void set(T value) {    //获取当前线程thread        Thread t = Thread.currentThread();        //通过getMap获取当前线程的ThreadLocalMap        ThreadLocalMap map = getMap(t);        if (map != null)        //不为null就将value设置到map中,key为当前threadLocal            map.set(this, value);        else        //map为null,就创建map,然后将map保存到Thread的threadLocals变量中            createMap(t, value);    }    ThreadLocalMap getMap(Thread t) {    //Thread内部是维护了一个ThreadLocalMap,保证了线程的唯一性        return t.threadLocals;    }class Thread implements Runnable{...ThreadLocal.ThreadLocalMap threadLocals = null}static class ThreadLocalMap {//数据最终会保存到这个Entry类的value变量中 static class Entry extends WeakReference<ThreadLocal<?>> {            Object value;            Entry(ThreadLocal<?> k, Object v) {                super(k);                value = v;            }        }private static final int INITIAL_CAPACITY = 16;private Entry[] table;}

上面代码流程已经分析了ThreadLocal的原理,它内部维护了一个ThreadLocalMap 静态内部类,这个类当我们调用set或者get方法时候会被设置到当前线程Thread里面,而ThreadLocalMap的Entry里面保存了我们要存放的值,它是以数组的形式存放在ThreadLocalMap里面,这样就保证了在线程里面的唯一性。当在别的线程里面去获取值时候,它里面的threadLocals就获取不到我们需要的值。


三、Message分析

Message是我们要传递消息数据封装的对象,它里面维护了一个对象池,它是一个单链表,结构是这样的

public final class Message implements Parcelable {...Message next;...}

由next指向下一个Message的引用

private static final int MAX_POOL_SIZE = 50;

我们在使用Message时候推荐使用Message.obtain(),这种方式会直接使用对象池的消息,避免Message的重复创建

  public static Message obtain() {      synchronized (sPoolSync) {        //sPool不为null,就直接复用对象池的消息          if (sPool != null) {            //获取sPool指向第一条Message              Message m = sPool;              //sPool指向下一Message              sPool = m.next;              //第一条消息脱离next指向              m.next = null;              //清除当前消息使用的标志              m.flags = 0; // clear in-use flag              sPoolSize--;              return m;          }      }      return new Message();  }

消息在回收时候会被重新加到对象池中

 void recycleUnchecked() {      // Mark the message as in use while it remains in the recycled object pool.      // Clear out all other details.      flags = FLAG_IN_USE;      what = 0;      arg1 = 0;      arg2 = 0;      obj = null;      replyTo = null;      sendingUid = -1;      when = 0;      target = null;      callback = null;      data = null;      synchronized (sPoolSync) {      //消息是否超过最大容量50          if (sPoolSize < MAX_POOL_SIZE) {          //头插法              next = sPool;              sPool = this;              sPoolSize++;          }      }  }

后面的MessageQueue就会用到对象池,这里先做分析了。


四、Handler源码流程分析

经过前面的准备工作,现在开始正式的源码流程分析

Handler消息机制发送消息之前Looper先要做准备工作,也就是Looper.prepare()和Looper.loop(),之后才能发送消息,至于我们平时在主线程可以直接使用Handler,是因为在ActivityThread启动过程的入口方法main()已经帮我们做好了准备

public static void main(String[] args) {...Looper.prepareMainLooper();Looper.loop();...}

里面的两个方法后面再来解释

Handler发送消息方式有很多种,如sendMessage,post等多种方式,但是无论哪种发送方式最终会调到enqueueMessage方法

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {//设置msg的target,后面会使用    msg.target = this;    //异步消息    if (mAsynchronous) {        msg.setAsynchronous(true);    }    //调用MessageQueue的enqueueMessage方法    return queue.enqueueMessage(msg, uptimeMillis);}

这个方法会调用MessageQueue方法的enqueueMessage方法,那queue是怎么来的?,看Handler的构造方法

public Handler() {    this(null, false);}public Handler(Callback callback, boolean async) {...    mLooper = Looper.myLooper();    //没有调用Loop.prepare()就会抛异常    if (mLooper == null) {        throw new RuntimeException(            "Can't create handler inside thread that has not called Looper.prepare()");    }    //从Looper中获取MessageQueue    mQueue = mLooper.mQueue;    mCallback = callback;    mAsynchronous = async;}

MessageQueue在Handler的构造方法中从Looper中获取的,接着进入MessageQueue的enqueueMessage方法

在这个方法里面会将消息插入一个单链表mMessage中,至于对象池的原理前面已经介绍过了,关于消息的插入过程后面详细介绍。此时消息已经插入消息单链表中,后面就是Looper去检测MessageQueue中的消息。

前面介绍过ActivityThread的main方法中会调用这两个方法

public static void main(String[] args) {...Looper.prepareMainLooper();Looper.loop();...}

下面就来重点看下这两个方法的原理,先来看下Looper.prepareMainLooper()

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()方法就可以了,点进这个方法看下

 private static void prepare(boolean quitAllowed) { //检测是否设置了Looper     if (sThreadLocal.get() != null) {         throw new RuntimeException("Only one Looper may be created per thread");     } //new一个Looper,然后设置给ThreadLocal ,quitAllowed标志是否允许Loop退出       sThreadLocal.set(new Looper(quitAllowed)); }

在prepareMainLooper()方法中设置quitAllowed为false,主线程不允许退出,然后new了一个Looper设置给ThreadLocal,保证在一个线程内Looper的唯一性,接着会调用myLooer(),将Looper引用赋值给sMainLooper,myLooper()就是获取ThreadLocal里面存放的Looper。所以prepareMainLooper()就是new了一个Looper,然后设置给了ThreadLocal。

值得一提的是Looper的构造方法

 private Looper(boolean quitAllowed) { //创建了MessageQueue,保证线程内的唯一性     mQueue = new MessageQueue(quitAllowed);     mThread = Thread.currentThread(); }

Looper中持有了MessageQueue对象的引用

接着看Looper.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;...//进入一个死循环    for (;;) {    //调用MessageQueue的next方法,可能会阻塞        Message msg = queue.next(); // might block       ...        try {        //分发消息,调用msg.target.dispatchMessage方法,target为msg持有的Handler引用            msg.target.dispatchMessage(msg);         ...        } finally {           ...        }      ...      //回收消息放入对象池        msg.recycleUnchecked();    }}

这个方法中会调用MessageQueue的next方法去获取消息,然后获取Message里面的target,它是一个Handler,之前已经介绍过了,最后会调用它的dispatchMessage方法,去处理消息

public void dispatchMessage(Message msg) {//处理handler.post方式发送的消息    if (msg.callback != null) {        handleCallback(msg);    } else {    //处理在构造方法中设置Callback的消息,如果mCallback.handleMessage设置为true,那么handleMessage就不会执行        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}

注释已经解释了dispatchMessage方法的执行流程,消息处理了是会被回收的,回收就是将消息重新放入对象池。

接着看一个比较重要的方法就是获取消息queue.next()

Message next() {    // Return here if the message loop has already quit and been disposed.    // This can happen if the application tries to restart a looper after quit    // which is not supported.    final long ptr = mPtr;    if (ptr == 0) {        return null;    }    int pendingIdleHandlerCount = -1; // -1 only during first iteration    int nextPollTimeoutMillis = 0;    for (;;) {        if (nextPollTimeoutMillis != 0) {            Binder.flushPendingCommands();        }//线程阻塞,底层就是linux系统调用,epoll机制,这个阻塞有三种情况//1.如果nextPollTimeoutMillis为-1,会一直阻塞,知道被唤醒//2.如果nextPollTimeoutMills为0,不会阻塞//3.如果nextPollTimeoutMills大于0,会阻塞,知道时间到了,会唤醒        nativePollOnce(ptr, nextPollTimeoutMillis);        synchronized (this) {            // Try to retrieve the next message.  Return if found.            final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            //mMessage是一个单链表,用来存放消息            Message msg = mMessages;             //处理屏障消息            if (msg != null && msg.target == null) {                // Stalled by a barrier.  Find the next asynchronous message in the queue.                do {                    prevMsg = msg;                    msg = msg.next;                    //查找异步消息                } while (msg != null && !msg.isAsynchronous());            }            if (msg != null) {            //当前时间是否小于链表中获取到的消息延时时间                if (now < msg.when) {                    // Next message is not ready.  Set a timeout to wake up when it is ready.                    //是的话,就计算需要阻塞的时间                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                } else {                    // Got a message.                    mBlocked = false;  //当前线程不是阻塞状态                    if (prevMsg != null) {                        prevMsg.next = msg.next;                    } else {                        mMessages = msg.next;                    }                    msg.next = null;                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);                    //标记消息在使用状态                    msg.markInUse();                    //获取mMessage中的消息                    return msg;                }            } else {                // No more messages.                //没消息就阻塞                nextPollTimeoutMillis = -1;            }            // Process the quit message now that all pending messages have been handled.            if (mQuitting) {                dispose();                return null;            }            // If first time idle, then get the number of idlers to run.            // Idle handles only run if the queue is empty or if the first message            // in the queue (possibly a barrier) is due to be handled in the future.            if (pendingIdleHandlerCount < 0                    && (mMessages == null || now < mMessages.when)) {                pendingIdleHandlerCount = mIdleHandlers.size();            }            if (pendingIdleHandlerCount <= 0) {                // No idle handlers to run.  Loop and wait some more.                //线程标记为阻塞中                mBlocked = true;                continue;            }            if (mPendingIdleHandlers == null) {                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];            }            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);        }//处理IdleHandler        // Run the idle handlers.        // We only ever reach this code block during the first iteration.        for (int i = 0; i < pendingIdleHandlerCount; i++) {            final IdleHandler idler = mPendingIdleHandlers[i];            mPendingIdleHandlers[i] = null; // release the reference to the handler            boolean keep = false;            try {                keep = idler.queueIdle();            } catch (Throwable t) {                Log.wtf(TAG, "IdleHandler threw exception", t);            }            if (!keep) {                synchronized (this) {                    mIdleHandlers.remove(idler);                }            }        }        // Reset the idle handler count to 0 so we do not run them again.        pendingIdleHandlerCount = 0;        // While calling an idle handler, a new message could have been delivered        // so go back and look again for a pending message without waiting.        nextPollTimeoutMillis = 0;    }}

这个方法首先进入一个死循环,接着调用nativePollOnce(),根据nextPollTimeoutMillis,来决定是否对线程进行阻塞,有三种情况

1.如果nextPollTimeoutMillis为-1,会一直阻塞,直到被唤醒,java层是nativeWake()进行唤醒
2.如果nextPollTimeoutMills为0,不会阻塞
3.如果nextPollTimeoutMills大于0,会阻塞,知道时间到了,会唤醒

这个方法底层是通过linux系统调用来完成线程的阻塞和唤醒,采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。接着会处理屏障消息,至于什么是屏障消息和异步消息后面会介绍。接着会去对象池mMessage中获取需要处理的mMessage。如果没有消息,就会阻塞线程,里面还会有种IdleHandler,如果mMessage中没有消息,就会执行IdleHandler,它可以用来处理闲时任务,当我们处理闲时任务时候,这种现象就叫线程挂起

整个消息处理流程到这里就分析完了。下面是它的UML类图:

接着是它的一个执行处理流程图


根据Handler的整个执行流程,写了一个简易版的Handler,代码在这里

Handler的整体流程已经分析完了,下面就对它其中一些细节处理进行介绍


五、消息的延时发送

之前面试经常问到,发送延时消息,内部是怎么实现的,这里就来详细解释下,之前已经介绍过了,消息最后会调用MessageQueue的enqueueMessage方法,来看下实现

 boolean enqueueMessage(Message msg, long when) { //是否设置了target     if (msg.target == null) {         throw new IllegalArgumentException("Message must have a target.");     }     //是否msg在使用中     if (msg.isInUse()) {         throw new IllegalStateException(msg + " This message is already in use.");     }     synchronized (this) {         if (mQuitting) {             IllegalStateException e = new IllegalStateException(                     msg.target + " sending message to a Handler on a dead thread");             Log.w(TAG, e.getMessage(), e);             msg.recycle();             return false;         }         msg.markInUse();         //设置msg的when         msg.when = when;         //获取MessageQueue的mMessage         Message p = mMessages;         boolean needWake;         //如果p为null或者要插入的消息when为0或者要插入的消息延时时间小于对象池中第一条消息的延时时间就直接插入链表头部         if (p == null || when == 0 || when < p.when) {             // New head, wake up the event queue if blocked.             msg.next = p;             mMessages = msg;             //needWake为true就唤醒线程             needWake = mBlocked;         } else {             // Inserted within the middle of the queue.  Usually we don't have to wake             // up the event queue unless there is a barrier at the head of the queue             // and the message is the earliest asynchronous message in the queue.             //这里是处理屏障消息和异步消息             //如果对象池中的第一条消息是屏障消息,并且待插入的消息是异步消息,并且线程是block的,就将needWake设置为true             needWake = mBlocked && p.target == null && msg.isAsynchronous();             Message prev;             //在for循环里面,根据消息延时时间的时间长度将消息插入链表             for (;;) {                 prev = p;                 p = p.next;                 if (p == null || when < p.when) {                     break;                 }                 //如果needWake设置为true,并且待插入的是异步消息,它之前也有异步消息,就将needWake设置为false,不需要唤醒线程                 if (needWake && p.isAsynchronous()) {                     needWake = false;                 }             }             //将msg插入链表中             msg.next = p; // invariant: p == prev.next             prev.next = msg;         }         // We can assume mPtr != 0 because mQuitting is false.         //needWake为true就通过nativeWake唤醒线程         if (needWake) {             nativeWake(mPtr);         }     }     return true; }

这个方法里面会根据消息的延时顺序将消息插入到指定位置,如果当前消息延时时间小于链表mMessage的第一条消息延时时间,就将当前消息直接插入队头,如果线程是阻塞的,就会唤醒线程。如果当前消息延时时间大于链表mMessage消息的第一条消息延时时间,就将消息插入链表中合适的位置。这里需要说下的是,如果当前第一条消息是屏障消息并且待插入消息是异步消息,它除了会将当前消息插入到合适的位置,如果待插入消息之前还有异步消息,就将needWake设置为false,否则为true,就会唤醒线程。如果线程是阻塞的,它会等下一条消息,下下一条消息满足唤醒线程情况就去唤醒线程。


六、异步消息和屏障消息

在Handler消息机制中,还有一种消息是异步消息和屏障消息,这种消息是用来处理紧急任务的,有它自己的处理方式,下面就来看下它们在Handler中的使用,首先看下异步消息,再来看下Handler的构造方法

public Handler(Callback callback, boolean async) {...    mAsynchronous = async;}

有个参数是async,这个参数就是用来作为异步消息发送的,再来看下Handler中的enqueueMessage方法

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

可以看到如果mAsynchronous为true,就会调用msg.setAsynchronous(true)变为异步消息,再来看下异步消息的处理

boolean enqueueMessage(Message msg, long when) {//发送消息时候target必须不为nullif (msg.target == null) {        throw new IllegalArgumentException("Message must have a target.");    }  ...    synchronized (this) {     ...        msg.markInUse();        msg.when = when;        Message p = mMessages;        boolean needWake;        if (p == null || when == 0 || when < p.when) {      ...        } else {        //当前插入消息是异步消息,链表中第一条消息是屏障消息,线程是阻塞的,needWake设置为true            // Inserted within the middle of the queue.  Usually we don't have to wake            // up the event queue unless there is a barrier at the head of the queue            // and the message is the earliest asynchronous message in the queue.            needWake = mBlocked && p.target == null && msg.isAsynchronous();            Message prev;            for (;;) {                prev = p;                p = p.next;                if (p == null || when < p.when) {                    break;                }                if (needWake && p.isAsynchronous()) {                    needWake = false;                }            }            msg.next = p; // invariant: p == prev.next            prev.next = msg;        }//needWake为true,唤醒线程        // We can assume mPtr != 0 because mQuitting is false.        if (needWake) {            nativeWake(mPtr);        }    }    return true;}//取异步消息的过程Message next() {...//当前消息是屏障消息,就去遍历异步消息if (msg != null && msg.target == null) { // Stalled by a barrier.  Find the next asynchronous message in the queue. do {     prevMsg = msg;     msg = msg.next; } while (msg != null && !msg.isAsynchronous());}

对于屏障消息message.target为null,它用来给后面普通消息添堵的,当前消息如果为屏障消息,就去查找链表中的异步消息,然后后面就是分发消息

Handler发送屏障消息可以调用postSyncBarrier

//MessageQueueprivate int postSyncBarrier(long when) {     // Enqueue a new sync barrier token.     // We don't need to wake the queue because the purpose of a barrier is to stall it.     synchronized (this) {         final int token = mNextBarrierToken++;         final Message msg = Message.obtain();         msg.markInUse();         msg.when = when;         msg.arg1 = token;         Message prev = null;         Message p = mMessages;         if (when != 0) {             while (p != null && p.when <= when) {                 prev = p;                 p = p.next;             }         }         if (prev != null) { // invariant: p == prev.next             msg.next = p;             prev.next = msg;         } else {             msg.next = p;             mMessages = msg;         }         return token;     } }

屏障消息也是将消息根据延时时间顺序直接插入单链表中,是用来拦截后面的普通消息,平且它会返回一个token,我们拿到这个token就可以根据这个token去remove这个屏障消息,调用removeSyncBarrier移除屏障。注意的是发送屏障消息android是不对外开放的,我们也不能去发送不带target的消息,如果消息target为null,那么会报错,

if (msg.target == null) {   throw new IllegalArgumentException("Message must have a target.");}

异步消息要和屏障消息配合使用才会有用,看一个简单例子

private void addBarrier(){try{Method method = MessageQueue.class.getMethod('postSyncBarrier');method.setAccessible(true);int barrierToken = (int) method.invoke(mHandler.getLooper().getQueue());mBarrierTokens.add(barrierToken)}catch(NoSuchMethodException e){e.printStackTrace();}catch(IllegalAccessException e){e.printStackTrace();}catch(InvocationTargetException e){e.printStackTrace();}}

通过反射添加屏障消息


七、Handler常见面试问题分析

接下来就来记录几个面试经常问到的Handler相关问题:

1、为什么主线程用Looper死循环不会引发ANR异常?

简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,
此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,
通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制。

2、为什么Handler构造方法里面的Looper不是直接new?

如果在Handler构造方法里面new Looper,怕是无法保证保证Looper唯一,只有用Looper.prepare()才能保证唯一性,具体去看prepare方法。

3、MessageQueue为什么要放在Looper私有构造方法初始化?

因为一个线程只绑定一个Looper,所以在Looper构造方法里面初始化就可以保证mQueue也是唯一的Thread对应一个Looper 对应一个 mQueue。

4、Handler.post的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?

由Looper所在线程决定的。逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。

5、MessageQueue.next()会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢?

因为在MessageQueue的enqueueMessage方法中,它会根据线程是否阻塞,而调用nativeWake()唤醒线程

6、Handler的dispatchMessage()分发消息的处理流程?

先处理post方式发送的消息,接着处理在Handler构造方法中传入的消息,接着如果构造方法中处理消息返回true,就不会执行handleMessage,否则接下来就会执行handleMessage

7、异步消息和屏障消息在Android源码中使用的地方

在android中用的地方不太多,但是在界面绘制这块是用到了

void scheduleTraversals() {     if (!mTraversalScheduled) {         mTraversalScheduled = true;         //在这里添加了屏障消息         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();         mChoreographer.postCallback(                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);         if (!mUnbufferedInputDispatch) {             scheduleConsumeBatchedInput();         }         notifyRendererOfFramePending();         pokeDrawLockIfNeeded();     } } void doTraversal() {  if (mTraversalScheduled) {        mTraversalScheduled = false;        //在这里移除屏障消息        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);        if (mProfile) {            Debug.startMethodTracing("ViewAncestor");        }        performTraversals();        if (mProfile) {            Debug.stopMethodTracing();            mProfile = false;        }    }}

八、参考

1.Android Handler:手把手带你深入分析 Handler机制源码
2.Android 消息机制——你真的了解Handler?
3.Handler消息机制(native层)

更多相关文章

  1. React Native Android从源码看WebView 没有OverrideUrl解决办法,
  2. Android(安卓)探索Activity和Window,View之间的关系
  3. react native极光推送全程教程android和ios
  4. [置顶] android 跨进程通信
  5. android中的设计模式--观察者模式
  6. Android(安卓)如何自定义一个ContentProvider
  7. Android的活动介绍
  8. android的CursorLoader用法小结
  9. Android开发之进程与线程

随机推荐

  1. Android(安卓)Contacts的使用(一)
  2. android 事件模型原理2
  3. Android基本功:Handler消息传送机制
  4. Android(安卓)dex分包
  5. android的一些常识
  6. 品尝Android(三)移动终端报表展示
  7. 在Ubuntu7.10上编译android
  8. Android:使用命令行工具adb、mksdcard等
  9. Android(安卓)蓝牙开发:第一日
  10. android中创建具有自动提示功能的菜单