Android中的AsyncTask和Handler应用实例二

Handler和Looper分析

Android应用程序是通过消息来驱动的,系统为每一个应用程序维护一个消息队例,应用程序的主线程不断地从这个消息队例中获取消息(Looper),然后对这些消息进行处理(Handler),这样就实现了通过消息来驱动应用程序的执行,本文将详细分析Android应用程序的消息处理机制。

        前面我们学习Android应用程序中的Activity启动(Android应用程序启动过程源代码分析Android应用程序内部启动Activity过程(startActivity)的源代码分析)、Service启动(Android系统在新进程中启动自定义服务过程(startService)的原理分析Android应用程序绑定服务(bindService)的过程源代码分析)以及广播发送(Android应用程序发送广播(sendBroadcast)的过程分析)时,它们都有一个共同的特点,当ActivityManagerService需要与应用程序进行并互时,如加载Activity和Service、处理广播待,会通过Binder进程间通信机制来知会应用程序,应用程序接收到这个请求时,它不是马上就处理这个请求,而是将这个请求封装成一个消息,然后把这个消息放在应用程序的消息队列中去,然后再通过消息循环来处理这个消息。这样做的好处就是消息的发送方只要把消息发送到应用程序的消息队列中去就行了,它可以马上返回去处理别的事情,而不需要等待消息的接收方去处理完这个消息才返回,这样就可以提高系统的并发性。实质上,这就是一种异步处理机制。

        这样说可能还是比较笼统,我们以Android应用程序启动过程源代码分析一文中所介绍的应用程序启动过程的一个片断来具体看看是如何这种消息处理机制的。在这篇文章中,要启动的应用程序称为Activity,它的默认Activity是MainActivity,它是由Launcher来负责启动的,而Launcher又是通过ActivityManagerService来启动的,当ActivityManagerService为这个即将要启的应用程序准备好新的进程后,便通过一个Binder进程间通信过程来通知这个新的进程来加载MainActivity,如下图所示:


        它对应Android应用程序启动过程中的Step 30到Step 35,有兴趣的读者可以回过头去参考Android应用程序启动过程源代码分析一文。这里的Step 30中的scheduleLaunchActivity是ActivityManagerService通过Binder进程间通信机制发送过来的请求,它请求应用程序中的ActivityThread执行Step 34中的performLaunchActivity操作,即启动MainActivity的操作。这里我们就可以看到,Step 30的这个请求并没有等待Step 34这个操作完成就返回了,它只是把这个请求封装成一个消息,然后通过Step 31中的queueOrSendMessage操作把这个消息放到应用程序的消息队列中,然后就返回了。应用程序发现消息队列中有消息时,就会通过Step 32中的handleMessage操作来处理这个消息,即调用Step 33中的handleLaunchActivity来执行实际的加载MainAcitivy类的操作。

        了解Android应用程序的消息处理过程之后,我们就开始分样它的实现原理了。与Windows应用程序的消息处理过程一样,Android应用程序的消息处理机制也是由消息循环、消息发送和消息处理这三个部分组成的,接下来,我们就详细描述这三个过程。

        1. 消息循环

        在消息处理机制中,消息都是存放在一个消息队列中去,而应用程序的主线程就是围绕这个消息队列进入一个无限循环的,直到应用程序退出。如果队列中有消息,应用程序的主线程就会把它取出来,并分发给相应的Handler进行处理;如果队列中没有消息,应用程序的主线程就会进入空闲等待状态,等待下一个消息的到来。在Android应用程序中,这个消息循环过程是由Looper类来实现的,它定义在frameworks/base/core/java/android/os/Looper.java文件中,在分析这个类之前,我们先看一下Android应用程序主线程是如何进入到这个消息循环中去的。

        在Android应用程序进程启动过程的源代码分析一文中,我们分析了Android应用程序进程的启动过程,Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的,我们来看看这个函数的实现,它定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

[java]  view plain copy
  1. public final class ActivityThread {  
  2.     ......  
  3.   
  4.     public static final void main(String[] args) {  
  5.         ......  
  6.   
  7.         Looper.prepareMainLooper();  
  8.   
  9.         ......  
  10.   
  11.         ActivityThread thread = new ActivityThread();  
  12.         thread.attach(false);  
  13.           
  14.         ......  
  15.   
  16.         Looper.loop();  
  17.   
  18.         ......  
  19.   
  20.         thread.detach();  
  21.   
  22.         ......  
  23.     }  
  24. }  
        这个函数做了两件事情,一是在主线程中创建了一个ActivityThread实例,二是通过Looper类使主线程进入消息循环中,这里我们只关注后者。

        首先看Looper.prepareMainLooper函数的实现,这是一个静态成员函数,定义在frameworks/base/core/java/android/os/Looper.java文件中:

[java]  view plain copy
  1. public class Looper {  
  2.     ......  
  3.   
  4.     private static final ThreadLocal sThreadLocal = new ThreadLocal();  
  5.   
  6.     final MessageQueue mQueue;  
  7.   
  8.     ......  
  9.   
  10.     /** Initialize the current thread as a looper. 
  11.     * This gives you a chance to create handlers that then reference 
  12.     * this looper, before actually starting the loop. Be sure to call 
  13.     * {@link #loop()} after calling this method, and end it by calling 
  14.     * {@link #quit()}. 
  15.     */  
  16.     public static final void prepare() {  
  17.         if (sThreadLocal.get() != null) {  
  18.             throw new RuntimeException("Only one Looper may be created per thread");  
  19.         }  
  20.         sThreadLocal.set(new Looper());  
  21.     }  
  22.   
  23.     /** Initialize the current thread as a looper, marking it as an application's main  
  24.     *  looper. The main looper for your application is created by the Android environment, 
  25.     *  so you should never need to call this function yourself. 
  26.     * {@link #prepare()} 
  27.     */  
  28.   
  29.     public static final void prepareMainLooper() {  
  30.         prepare();  
  31.         setMainLooper(myLooper());  
  32.         if (Process.supportsProcesses()) {  
  33.             myLooper().mQueue.mQuitAllowed = false;  
  34.         }  
  35.     }  
  36.   
  37.     private synchronized static void setMainLooper(Looper looper) {  
  38.         mMainLooper = looper;  
  39.     }  
  40.   
  41.     /** 
  42.     * Return the Looper object associated with the current thread.  Returns 
  43.     * null if the calling thread is not associated with a Looper. 
  44.     */  
  45.     public static final Looper myLooper() {  
  46.         return (Looper)sThreadLocal.get();  
  47.     }  
  48.   
  49.     private Looper() {  
  50.         mQueue = new MessageQueue();  
  51.         mRun = true;  
  52.         mThread = Thread.currentThread();  
  53.     }  
  54.   
  55.     ......  
  56. }  
        函数prepareMainLooper做的事情其实就是在线程中创建一个Looper对象,这个Looper对象是存放在sThreadLocal成员变量里面的,成员变量sThreadLocal的类型为ThreadLocal,表示这是一个线程局部变量,即保证每一个调用了prepareMainLooper函数的线程里面都有一个独立的Looper对象。在线程是创建Looper对象的工作是由prepare函数来完成的,而在创建Looper对象的时候,会同时创建一个消息队列MessageQueue,保存在Looper的成员变量mQueue中,后续消息就是存放在这个队列中去。消息队列在Android应用程序消息处理机制中最重要的组件,因此,我们看看它的创建过程,即它的构造函数的实现,实现frameworks/base/core/java/android/os/MessageQueue.java文件中:

[java]  view plain copy
  1. public class MessageQueue {  
  2.     ......  
  3.   
  4.     private int mPtr; // used by native code  
  5.   
  6.     private native void nativeInit();  
  7.   
  8.     MessageQueue() {  
  9.         nativeInit();  
  10.     }  
  11.   
  12.     ......  
  13. }  
        它的初始化工作都交给JNI方法nativeInit来实现了,这个JNI方法定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:

[cpp]  view plain copy
  1. static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {  
  2.     NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();  
  3.     if (! nativeMessageQueue) {  
  4.         jniThrowRuntimeException(env, "Unable to allocate native queue");  
  5.         return;  
  6.     }  
  7.   
  8.     android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);  
  9. }  
        在JNI中,也相应地创建了一个消息队列NativeMessageQueue,NativeMessageQueue类也是定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中,它的创建过程如下所示:

[cpp]  view plain copy
  1. NativeMessageQueue::NativeMessageQueue() {  
  2.     mLooper = Looper::getForThread();  
  3.     if (mLooper == NULL) {  
  4.         mLooper = new Looper(false);  
  5.         Looper::setForThread(mLooper);  
  6.     }  
  7. }  
        它主要就是在内部创建了一个Looper对象,注意,这个Looper对象是实现在JNI层的,它与上面Java层中的Looper是不一样的,不过它们是对应的,下面我们进一步分析消息循环的过程的时候,读者就会清楚地了解到它们之间的关系。

        这个Looper的创建过程也很重要,不过我们暂时放一放,先分析完android_os_MessageQueue_nativeInit函数的执行,它创建了本地消息队列NativeMessageQueue对象之后,接着调用android_os_MessageQueue_setNativeMessageQueue函数来把这个消息队列对象保存在前面我们在Java层中创建的MessageQueue对象的mPtr成员变量里面:

[cpp]  view plain copy
  1. static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,  
  2.         NativeMessageQueue* nativeMessageQueue) {  
  3.     env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,  
  4.              reinterpret_cast<jint>(nativeMessageQueue));  
  5. }  
        这里传进来的参数messageQueueObj即为我们前面在Java层创建的消息队列对象,而gMessageQueueClassInfo.mPtr即表示在Java类MessageQueue中,其成员变量mPtr的偏移量,通过这个偏移量,就可以把这个本地消息队列对象natvieMessageQueue保存在Java层创建的消息队列对象的mPtr成员变量中,这是为了后续我们调用Java层的消息队列对象的其它成员函数进入到JNI层时,能够方便地找回它在JNI层所对应的消息队列对象。

        我们再回到NativeMessageQueue的构造函数中,看看JNI层的Looper对象的创建过程,即看看它的构造函数是如何实现的,这个Looper类实现在frameworks/base/libs/utils/Looper.cpp文件中:

[cpp]  view plain copy
  1. Looper::Looper(bool allowNonCallbacks) :  
  2.     mAllowNonCallbacks(allowNonCallbacks),  
  3.     mResponseIndex(0) {  
  4.     int wakeFds[2];  
  5.     int result = pipe(wakeFds);  
  6.     ......  
  7.   
  8.     mWakeReadPipeFd = wakeFds[0];  
  9.     mWakeWritePipeFd = wakeFds[1];  
  10.   
  11.     ......  
  12.   
  13. #ifdef LOOPER_USES_EPOLL  
  14.     // Allocate the epoll instance and register the wake pipe.  
  15.     mEpollFd = epoll_create(EPOLL_SIZE_HINT);  
  16.     ......  
  17.   
  18.     struct epoll_event eventItem;  
  19.     memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
  20.     eventItem.events = EPOLLIN;  
  21.     eventItem.data.fd = mWakeReadPipeFd;  
  22.     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);  
  23.     ......  
  24. #else  
  25.     ......  
  26. #endif  
  27.   
  28.     ......  
  29. }  
        这个构造函数做的事情非常重要,它跟我们后面要介绍的应用程序主线程在消息队列中没有消息时要进入等待状态以及当消息队列有消息时要把应用程序主线程唤醒的这两个知识点息息相关。它主要就是通过pipe系统调用来创建了一个管道了:

[cpp]  view plain copy
  1. int wakeFds[2];  
  2. int result = pipe(wakeFds);  
  3. ......  
  4.   
  5. mWakeReadPipeFd = wakeFds[0];  
  6. mWakeWritePipeFd = wakeFds[1];  
        管道是Linux系统中的一种进程间通信机制,具体可以参考前面一篇文章 Android学习启动篇 推荐的一本书《Linux内核源代码情景分析》中的第6章--传统的Uinx进程间通信。简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。这个等待和唤醒的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。 Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。但是这里我们其实只需要监控的IO接口只有mWakeReadPipeFd一个,即前面我们所创建的管道的读端,为什么还需要用到epoll呢?有点用牛刀来杀鸡的味道。其实不然,这个Looper类是非常强大的,它除了监控内部所创建的管道接口之外,还提供了addFd接口供外界面调用,外界可以通过这个接口把自己想要监控的IO事件一并加入到这个Looper对象中去,当所有这些被监控的IO接口上面有事件发生时,就会唤醒相应的线程来处理,不过这里我们只关心刚才所创建的管道的IO事件的发生。

        要使用Linux系统的epoll机制,首先要通过epoll_create来创建一个epoll专用的文件描述符:

[cpp]  view plain copy
  1. mEpollFd = epoll_create(EPOLL_SIZE_HINT);  
       传入的参数EPOLL_SIZE_HINT是在这个mEpollFd上能监控的最大文件描述符数。

       接着还要通过epoll_ctl函数来告诉epoll要监控相应的文件描述符的什么事件:

[cpp]  view plain copy
  1. struct epoll_event eventItem;  
  2. memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union  
  3. eventItem.events = EPOLLIN;  
  4. eventItem.data.fd = mWakeReadPipeFd;  
  5. result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);  
       这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
       C++层的这个Looper对象创建好了之后,就返回到JNI层的NativeMessageQueue的构造函数,最后就返回到Java层的消息队列MessageQueue的创建过程,这样,Java层的Looper对象就准备好了。有点复杂,我们先小结一下这一步都做了些什么事情:

       A. 在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;

       B. 在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;

       C. 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。

       回到ActivityThread类的main函数中,在上面这些工作都准备好之后,就调用Looper类的loop函数进入到消息循环中去了:

[cpp]  view plain copy
  1. public class Looper {  
  2.     ......  
  3.   
  4.     public static final void loop() {  
  5.         Looper me = myLooper();  
  6.         MessageQueue queue = me.mQueue;  
  7.   
  8.         ......  
  9.   
  10.         while (true) {  
  11.             Message msg = queue.next(); // might block  
  12.             ......  
  13.   
  14.             if (msg != null) {  
  15.                 if (msg.target == null) {  
  16.                     // No target is a magic identifier for the quit message.  
  17.                     return;  
  18.                 }  
  19.   
  20.                 ......  
  21.   
  22.                 msg.target.dispatchMessage(msg);  
  23.                   
  24.                 ......  
  25.   
  26.                 msg.recycle();  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     ......  
  32. }  
        这里就是进入到消息循环中去了,它不断地从消息队列mQueue中去获取下一个要处理的消息msg,如果消息的target成员变量为null,就表示要退出消息循环了,否则的话就要调用这个target对象的dispatchMessage成员函数来处理这个消息,这个target对象的类型为Handler,下面我们分析消息的发送时会看到这个消息对象msg是如设置的。

        这个函数最关键的地方便是从消息队列中获取下一个要处理的消息了,即MessageQueue.next函数,它实现frameworks/base/core/java/android/os/MessageQueue.java文件中:

[java]  view plain copy
  1. public class MessageQueue {  
  2.     ......  
  3.   
  4.     final Message next() {  
  5.         int pendingIdleHandlerCount = -1// -1 only during first iteration  
  6.         int nextPollTimeoutMillis = 0;  
  7.   
  8.         for (;;) {  
  9.             if (nextPollTimeoutMillis != 0) {  
  10.                 Binder.flushPendingCommands();  
  11.             }  
  12.             nativePollOnce(mPtr, nextPollTimeoutMillis);  
  13.   
  14.             synchronized (this) {  
  15.                 // Try to retrieve the next message.  Return if found.  
  16.                 final long now = SystemClock.uptimeMillis();  
  17.                 final Message msg = mMessages;  
  18.                 if (msg != null) {  
  19.                     final long when = msg.when;  
  20.                     if (now >= when) {  
  21.                         mBlocked = false;  
  22.                         mMessages = msg.next;  
  23.                         msg.next = null;  
  24.                         if (Config.LOGV) Log.v("MessageQueue""Returning message: " + msg);  
  25.                         return msg;  
  26.                     } else {  
  27.                         nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);  
  28.                     }  
  29.                 } else {  
  30.                     nextPollTimeoutMillis = -1;  
  31.                 }  
  32.   
  33.                 // If first time, then get the number of idlers to run.  
  34.                 if (pendingIdleHandlerCount < 0) {  
  35.                     pendingIdleHandlerCount = mIdleHandlers.size();  
  36.                 }  
  37.                 if (pendingIdleHandlerCount == 0) {  
  38.                     // No idle handlers to run.  Loop and wait some more.  
  39.                     mBlocked = true;  
  40.                     continue;  
  41.                 }  
  42.   
  43.                 if (mPendingIdleHandlers == null) {  
  44.                     mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];  
  45.                 }  
  46.                 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);  
  47.             }  
  48.   
  49.             // Run the idle handlers.  
  50.             // We only ever reach this code block during the first iteration.  
  51.             for (int i = 0; i < pendingIdleHandlerCount; i++) {  
  52.                 final IdleHandler idler = mPendingIdleHandlers[i];  
  53.                 mPendingIdleHandlers[i] = null// release the reference to the handler  
  54.   
  55.                 boolean keep = false;  
  56.                 try {  
  57.                     keep = idler.queueIdle();  
  58.                 } catch (Throwable t) {  
  59.                     Log.wtf("MessageQueue""IdleHandler threw exception", t);  
  60.                 }  
  61.   
  62.                 if (!keep) {  
  63.                     synchronized (this) {  
  64.                         mIdleHandlers.remove(idler);  
  65.                     }  
  66.                 }  
  67.             }  
  68.   
  69.             // Reset the idle handler count to 0 so we do not run them again.  
  70.             pendingIdleHandlerCount = 0;  
  71.   
  72.             // While calling an idle handler, a new message could have been delivered  
  73.             // so go back and look again for a pending message without waiting.  
  74.             nextPollTimeoutMillis = 0;  
  75.         }  
  76.     }  
  77.   
  78.     ......  
  79. }  
        调用这个函数的时候,有可能会让线程进入等待状态。什么情况下,线程会进入等待状态呢?两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。消息队列中的消息是按时间先后来排序的,后面我们在分析消息的发送时会看到。

        执行下面语句是看看当前消息队列中有没有消息:

[java]  view plain copy
  1. nativePollOnce(mPtr, nextPollTimeoutMillis);  
        这是一个JNI方法,我们等一下再分析,这里传入的参数mPtr就是指向前面我们在JNI层创建的NativeMessageQueue对象了,而参数nextPollTimeoutMillis则表示如果当前消息队列中没有消息,它要等待的时候,for循环开始时,传入的值为0,表示不等待。

        当前nativePollOnce返回后,就去看看消息队列中有没有消息:

[java]  view plain copy
  1. final Message msg = mMessages;  
  2. if (msg != null) {  
  3.     final long when = msg.when;  
  4.     if (now >= when) {  
  5.         mBlocked = false;  
  6.         mMessages = msg.next;  
  7.         msg.next = null;  
  8.         if (Config.LOGV) Log.v("MessageQueue""Returning message: " + msg);  
  9.         return msg;  
  10.     } else {  
  11.         nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);  
  12.     }  
  13. else {  
  14.     nextPollTimeoutMillis = -1;  
  15. }  
        如果消息队列中有消息,并且当前时候大于等于消息中的执行时间,那么就直接返回这个消息给Looper.loop消息处理,否则的话就要等待到消息的执行时间:

[java]  view plain copy
  1. nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);  
        如果消息队列中没有消息,那就要进入无穷等待状态直到有新消息了:

[java]  view plain copy
  1. nextPollTimeoutMillis = -1;  
        -1表示下次调用nativePollOnce时,如果消息中没有消息,就进入无限等待状态中去。

        这里计算出来的等待时间都是在下次调用nativePollOnce时使用的。

        这里说的等待,是空闲等待,而不是忙等待,因此,在进入空闲等待状态前,如果应用程序注册了IdleHandler接口来处理一些事情,那么就会先执行这里IdleHandler,然后再进入等待状态。IdlerHandler是定义在MessageQueue的一个内部类:

[java]  view plain copy
  1. public class MessageQueue {  
  2.     ......  
  3.   
  4.     /** 
  5.     * Callback interface for discovering when a thread is going to block 
  6.     * waiting for more messages. 
  7.     */  
  8.     public static interface IdleHandler {  
  9.         /** 
  10.         * Called when the message queue has run out of messages and will now 
  11.         * wait for more.  Return true to keep your idle handler active, false 
  12.         * to have it removed.  This may be called if there are still messages 
  13.         * pending in the queue, but they are all scheduled to be dispatched 
  14.         * after the current time. 
  15.         */  
  16.         boolean queueIdle();  
  17.     }  
  18.   
  19.     ......  
  20. }  
        它只有一个成员函数queueIdle,执行这个函数时,如果返回值为false,那么就会从应用程序中移除这个IdleHandler,否则的话就会在应用程序中继续维护着这个IdleHandler,下次空闲时仍会再执会这个IdleHandler。MessageQueue提供了addIdleHandler和removeIdleHandler两注册和删除IdleHandler。

        回到MessageQueue函数中,它接下来就是在进入等待状态前,看看有没有IdleHandler是需要执行的:

[java]  view plain copy
  1. // If first time, then get the number of idlers to run.  
  2. if (pendingIdleHandlerCount < 0) {  
  3.     pendingIdleHandlerCount = mIdleHandlers.size();  
  4. }  
  5. if (pendingIdleHandlerCount == 0) {  
  6.     // No idle handlers to run.  Loop and wait some more.  
  7.     mBlocked = true;  
  8.     continue;  
  9. }  
  10.   
  11. if (mPendingIdleHandlers == null) {  
  12.     mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];  
  13. }  
  14. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);  
        如果没有,即pendingIdleHandlerCount等于0,那下面的逻辑就不执行了,通过continue语句直接进入下一次循环,否则就要把注册在mIdleHandlers中的IdleHandler取出来,放在mPendingIdleHandlers数组中去。

        接下来就是执行这些注册了的IdleHanlder了:

[java]  view plain copy
  1. // Run the idle handlers.  
  2. // We only ever reach this code block during the first iteration.  
  3. for (int i = 0; i < pendingIdleHandlerCount; i++) {  
  4.       final IdleHandler idler = mPendingIdleHandlers[i];  
  5.       mPendingIdleHandlers[i] = null// release the reference to the handler  
  6.   
  7.       boolean keep = false;  
  8.       try {  
  9.             keep = idler.queueIdle();  
  10.       } catch (Throwable t) {  
  11.             Log.wtf("MessageQueue""IdleHandler threw exception", t);  
  12.       }  
  13.   
  14.       if (!keep) {  
  15.             synchronized (this) {  
  16.                     mIdleHandlers.remove(idler);  
  17.             }  
  18.       }  
  19. }  
         执行完这些IdleHandler之后,线程下次调用nativePollOnce函数时,就不设置超时时间了,因为,很有可能在执行IdleHandler的时候,已经有新的消息加入到消息队列中去了,因此,要重置nextPollTimeoutMillis的值:

[java]  view plain copy
  1. // While calling an idle handler, a new message could have been delivered  
  2. // so go back and look again for a pending message without waiting.  
  3. nextPollTimeoutMillis = 0;  
        分析完MessageQueue的这个next函数之后,我们就要深入分析一下JNI方法nativePollOnce了,看看它是如何进入等待状态的,这个函数定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:

[cpp]  view plain copy
  1. static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,  
  2.         jint ptr, jint timeoutMillis) {  
  3.     NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);  
  4.     nativeMessageQueue->pollOnce(timeoutMillis);  
  5. }  
        这个函数首先是通过传进入的参数ptr取回前面在Java层创建MessageQueue对象时在JNI层创建的NatvieMessageQueue对象,然后调用它的pollOnce函数:

[cpp]  view plain copy
  1. void NativeMessageQueue::pollOnce(int timeoutMillis) {  
  2.     mLooper->pollOnce(timeoutMillis);  
  3. }  
        这里将操作转发给mLooper对象的pollOnce函数处理,这里的mLooper对象是在C++层的对象,它也是在前面在JNI层创建的NatvieMessageQueue对象时创建的,它的pollOnce函数定义在frameworks/base/libs/utils/Looper.cpp文件中:

[cpp]  view plain copy
  1. int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {  
  2.     int result = 0;  
  3.     for (;;) {  
  4.         ......  
  5.   
  6.         if (result != 0) {  
  7.             ......  
  8.   
  9.             return result;  
  10.         }  
  11.   
  12.         result = pollInner(timeoutMillis);  
  13.     }  
  14. }  
        为了方便讨论,我们把这个函数的无关部分都去掉,它主要就是调用pollInner函数来进一步操作,如果pollInner返回值不等于0,这个函数就可以返回了。

        函数pollInner的定义如下:

[cpp]  view plain copy
  1. int Looper::pollInner(int timeoutMillis) {  
  2.     ......  
  3.   
  4.     int result = ALOOPER_POLL_WAKE;  
  5.   
  6.     ......  
  7.   
  8. #ifdef LOOPER_USES_EPOLL  
  9.     struct epoll_event eventItems[EPOLL_MAX_EVENTS];  
  10.     int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
  11.     bool acquiredLock = false;  
  12. #else  
  13.     ......  
  14. #endif  
  15.   
  16.     if (eventCount < 0) {  
  17.         if (errno == EINTR) {  
  18.             goto Done;  
  19.         }  
  20.   
  21.         LOGW("Poll failed with an unexpected error, errno=%d", errno);  
  22.         result = ALOOPER_POLL_ERROR;  
  23.         goto Done;  
  24.     }  
  25.   
  26.     if (eventCount == 0) {  
  27.         ......  
  28.         result = ALOOPER_POLL_TIMEOUT;  
  29.         goto Done;  
  30.     }  
  31.   
  32.     ......  
  33.   
  34. #ifdef LOOPER_USES_EPOLL  
  35.     for (int i = 0; i < eventCount; i++) {  
  36.         int fd = eventItems[i].data.fd;  
  37.         uint32_t epollEvents = eventItems[i].events;  
  38.         if (fd == mWakeReadPipeFd) {  
  39.             if (epollEvents & EPOLLIN) {  
  40.                 awoken();  
  41.             } else {  
  42.                 LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);  
  43.             }  
  44.         } else {  
  45.             ......  
  46.         }  
  47.     }  
  48.     if (acquiredLock) {  
  49.         mLock.unlock();  
  50.     }  
  51. Done: ;  
  52. #else  
  53.     ......  
  54. #endif  
  55.   
  56.     ......  
  57.   
  58.     return result;  
  59. }  
        这里,首先是调用epoll_wait函数来看看epoll专用文件描述符mEpollFd所监控的文件描述符是否有IO事件发生,它设置监控的超时时间为timeoutMillis:

[cpp]  view plain copy
  1. int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
        回忆一下前面的Looper的构造函数,我们在里面设置了要监控mWakeReadPipeFd文件描述符的EPOLLIN事件。

        当mEpollFd所监控的文件描述符发生了要监控的IO事件后或者监控时间超时后,线程就从epoll_wait返回了,否则线程就会在epoll_wait函数中进入睡眠状态了。返回后如果eventCount等于0,就说明是超时了:

[cpp]  view plain copy
  1. if (eventCount == 0) {  
  2.     ......  
  3.     result = ALOOPER_POLL_TIMEOUT;  
  4.     goto Done;  
  5. }  
       如果eventCount不等于0,就说明发生要监控的事件:

[cpp]  view plain copy
  1. for (int i = 0; i < eventCount; i++) {  
  2.     int fd = eventItems[i].data.fd;  
  3.     uint32_t epollEvents = eventItems[i].events;  
  4.     if (fd == mWakeReadPipeFd) {  
  5.         if (epollEvents & EPOLLIN) {  
  6.             awoken();  
  7.         } else {  
  8.             LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);  
  9.         }  
  10.     } else {  
  11.             ......  
  12.     }  
  13. }  
        这里我们只关注mWakeReadPipeFd文件描述符上的事件,如果在mWakeReadPipeFd文件描述符上发生了EPOLLIN就说明应用程序中的消息队列里面有新的消息需要处理了,接下来它就会先调用awoken函数清空管道中把内容,以便下次再调用pollInner函数时,知道自从上次处理完消息队列中的消息后,有没有新的消息加进来。

        函数awoken的实现很简单,它只是把管道中的内容都读取出来:

[cpp]  view plain copy
  1. void Looper::awoken() {  
  2.     ......  
  3.   
  4.     char buffer[16];  
  5.     ssize_t nRead;  
  6.     do {  
  7.         nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));  
  8.     } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));  
  9. }  
        因为当其它的线程向应用程序的消息队列加入新的消息时,会向这个管道写入新的内容来通知应用程序主线程有新的消息需要处理了,下面我们分析消息的发送的时候将会看到。

        这样,消息的循环过程就分析完了,这部分逻辑还是比较复杂的,它利用Linux系统中的管道(pipe)进程间通信机制来实现消息的等待和处理,不过,了解了这部分内容之后,下面我们分析消息的发送和处理就简单多了。

        2. 消息的发送
        应用程序的主线程准备就好消息队列并且进入到消息循环后,其它地方就可以往这个消息队列中发送消息了。我们继续以文章开始介绍的Android应用程序启动过程源代码分析一文中的应用程序启动过为例,说明应用程序是如何把消息加入到应用程序的消息队列中去的。

        在Android应用程序启动过程源代码分析这篇文章的Step 30中,ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函数通知应用程序,它可以加载应用程序的默认Activity了,这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

[java]  view plain copy
  1. public final class ActivityThread {    
  2.     
  3.     ......    
  4.     
  5.     private final class ApplicationThread extends ApplicationThreadNative {    
  6.     
  7.         ......    
  8.     
  9.         // we use token to identify this activity without having to send the    
  10.         // activity itself back to the activity manager. (matters more with ipc)    
  11.         public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,    
  12.                 ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,    
  13.                 List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) {    
  14.             ActivityClientRecord r = new ActivityClientRecord();    
  15.     
  16.             r.token = token;    
  17.             r.ident = ident;    
  18.             r.intent = intent;    
  19.             r.activityInfo = info;    
  20.             r.state = state;    
  21.     
  22.             r.pendingResults = pendingResults;    
  23.             r.pendingIntents = pendingNewIntents;    
  24.     
  25.             r.startsNotResumed = notResumed;    
  26.             r.isForward = isForward;    
  27.     
  28.             queueOrSendMessage(H.LAUNCH_ACTIVITY, r);    
  29.         }    
  30.     
  31.         ......    
  32.     
  33.     }    
  34.     
  35.     ......    
  36. }    
        这里把相关的参数都封装成一个ActivityClientRecord对象r,然后调用queueOrSendMessage函数来往应用程序的消息队列中加入一个新的消息(H.LAUNCH_ACTIVITY),这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

[java]  view plain copy
  1. public final class ActivityThread {    
  2.     
  3.     ......    
  4.     
  5.     private final class ApplicationThread extends ApplicationThreadNative {    
  6.     
  7.         ......    
  8.     
  9.         // if the thread hasn't started yet, we don't have the handler, so just    
  10.         // save the messages until we're ready.    
  11.         private final void queueOrSendMessage(int what, Object obj) {    
  12.             queueOrSendMessage(what, obj, 00);    
  13.         }    
  14.     
  15.         ......    
  16.     
  17.         private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {    
  18.             synchronized (this) {    
  19.                 ......    
  20.                 Message msg = Message.obtain();    
  21.                 msg.what = what;    
  22.                 msg.obj = obj;    
  23.                 msg.arg1 = arg1;    
  24.                 msg.arg2 = arg2;    
  25.                 mH.sendMessage(msg);    
  26.             }    
  27.         }    
  28.     
  29.         ......    
  30.     
  31.     }    
  32.     
  33.     ......    
  34. }    
        在queueOrSendMessage函数中,又进一步把上面传进来的参数封装成一个Message对象msg,然后通过mH.sendMessage函数把这个消息对象msg加入到应用程序的消息队列中去。这里的mH是ActivityThread类的成员变量,它的类型为H,继承于Handler类,它定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

[java]  view plain copy
  1. public final class ActivityThread {    
  2.     
  3.     ......    
  4.     
  5.     private final class H extends Handler {    
  6.     
  7.         ......    
  8.     
  9.         public void handleMessage(Message msg) {    
  10.             ......    
  11.             switch (msg.what) {      
  12.             ......    
  13.             }    
  14.     
  15.         ......    
  16.     
  17.     }    
  18.     
  19.     ......    
  20. }   

        这个H类就是通过其成员函数handleMessage函数来处理消息的了,后面我们分析消息的处理过程时会看到。
        ActivityThread类的这个mH成员变量是什么时候创建的呢?我们前面在分析应用程序的消息循环时,说到当应用程序进程启动之后,就会加载ActivityThread类的main函数里面,在这个main函数里面,在通过Looper类进入消息循环之前,会在当前进程中创建一个ActivityThread实例:

[java]  view plain copy
  1. public final class ActivityThread {  
  2.     ......  
  3.   
  4.     public static final void main(String[] args) {  
  5.         ......  
  6.   
  7.         ActivityThread thread = new ActivityThread();  
  8.         thread.attach(false);  
  9.   
  10.         ......  
  11.     }  
  12. }  
        在创建这个实例的时候,就会同时创建其成员变量mH了:

[java]  view plain copy
  1. public final class ActivityThread {  
  2.     ......  
  3.   
  4.     final H mH = new H();  
  5.   
  6.     ......  
  7. }   
        前面说过,H类继承于Handler类,因此,当创建这个H对象时,会调用Handler类的构造函数,这个函数定义在frameworks/base/core/java/android/os/Handler.java文件中:

[java]  view plain copy
  1. public class Handler {  
  2.     ......  
  3.   
  4.     public Handler() {  
  5.         ......  
  6.   
  7.         mLooper = Looper.myLooper();  
  8.         ......  
  9.   
  10.         mQueue = mLooper.mQueue;  
  11.         ......  
  12.     }  
  13.   
  14.   
  15.     final MessageQueue mQueue;  
  16.     final Looper mLooper;  
  17.     ......  
  18. }  
        在Hanlder类的构造函数中,主要就是初始成员变量mLooper和mQueue了。这里的myLooper是Looper类的静态成员函数,通过它来获得一个Looper对象,这个Looper对象就是前面我们在分析消息循环时,在ActivityThread类的main函数中通过Looper.prepareMainLooper函数创建的。Looper.myLooper函数实现在frameworks/base/core/java/android/os/Looper.java文件中:

[java]  view plain copy
  1. public class Looper {  
  2.     ......  
  3.   
  4.     public static final Looper myLooper() {  
  5.         return (Looper)sThreadLocal.get();  
  6.     }  
  7.   
  8.     ......  
  9. }  
        有了这个Looper对象后,就可以通过Looper.mQueue来访问应用程序的消息队列了。

        有了这个Handler对象mH后,就可以通过它来往应用程序的消息队列中加入新的消息了。回到前面的queueOrSendMessage函数中,当它准备好了一个Message对象msg后,就开始调用mH.sendMessage函数来发送消息了,这个函数定义在frameworks/base/core/java/android/os/Handler.java文件中:

[java]  view plain copy
  1. public class Handler {  
  2.     ......  
  3.   
  4.     public final boolean sendMessage(Message msg)  
  5.     {  
  6.         return sendMessageDelayed(msg, 0);  
  7.     }  
  8.   
  9.     public final boolean sendMessageDelayed(Message msg, long delayMillis)  
  10.     {  
  11.         if (delayMillis < 0) {  
  12.             delayMillis = 0;  
  13.         }  
  14.         return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
  15.     }  
  16.   
  17.     public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  18.     {  
  19.         boolean sent = false;  
  20.         MessageQueue queue = mQueue;  
  21.         if (queue != null) {  
  22.             msg.target = this;  
  23.             sent = queue.enqueueMessage(msg, uptimeMillis);  
  24.         }  
  25.         else {  
  26.             ......  
  27.         }  
  28.         return sent;  
  29.     }  
  30.   
  31.     ......  
  32. }  
        在发送消息时,是可以指定消息的处理时间的,但是通过sendMessage函数发送的消息的处理时间默认就为当前时间,即表示要马上处理,因此,从sendMessage函数中调用sendMessageDelayed函数,传入的时间参数为0,表示这个消息不要延时处理,而在sendMessageDelayed函数中,则会先获得当前时间,然后加上消息要延时处理的时间,即得到这个处理这个消息的绝对时间,然后调用sendMessageAtTime函数来把消息加入到应用程序的消息队列中去。

        在sendMessageAtTime函数,首先得到应用程序的消息队列mQueue,这是在Handler对象构造时初始化好的,前面已经分析过了,接着设置这个消息的目标对象target,即这个消息最终是由谁来处理的:

[java]  view plain copy
  1. msg.target = this;  
        这里将它赋值为this,即表示这个消息最终由这个Handler对象来处理,即由ActivityThread对象的mH成员变量来处理。

        函数最后调用queue.enqueueMessage来把这个消息加入到应用程序的消息队列中去,这个函数实现在frameworks/base/core/java/android/os/MessageQueue.java文件中:

[java]  view plain copy
  1. public class MessageQueue {  
  2.     ......  
  3.   
  4.     final boolean enqueueMessage(Message msg, long when) {  
  5.         ......  
  6.   
  7.         final boolean needWake;  
  8.         synchronized (this) {  
  9.             ......  
  10.   
  11.             msg.when = when;  
  12.             //Log.d("MessageQueue", "Enqueing: " + msg);  
  13.             Message p = mMessages;  
  14.             if (p == null || when == 0 || when < p.when) {  
  15.                 msg.next = p;  
  16.                 mMessages = msg;  
  17.                 needWake = mBlocked; // new head, might need to wake up  
  18.             } else {  
  19.                 Message prev = null;  
  20.                 while (p != null && p.when <= when) {  
  21.                     prev = p;  
  22.                     p = p.next;  
  23.                 }  
  24.                 msg.next = prev.next;  
  25.                 prev.next = msg;  
  26.                 needWake = false// still waiting on head, no need to wake up  
  27.             }  
  28.   
  29.         }  
  30.         if (needWake) {  
  31.             nativeWake(mPtr);  
  32.         }  
  33.         return true;  
  34.     }  
  35.   
  36.     ......  
  37. }  
        把消息加入到消息队列时,分两种情况,一种当前消息队列为空时,这时候应用程序的主线程一般就是处于空闲等待状态了,这时候就要唤醒它,另一种情况是应用程序的消息队列不为空,这时候就不需要唤醒应用程序的主线程了,因为这时候它一定是在忙着处于消息队列中的消息,因此不会处于空闲等待的状态。

        第一种情况比较简单,只要把消息放在消息队列头就可以了:

[java]  view plain copy
  1. msg.next = p;  
  2. mMessages = msg;  
  3. needWake = mBlocked; // new head, might need to wake up  
        第二种情况相对就比较复杂一些了,前面我们说过,当往消息队列中发送消息时,是可以指定消息的处理时间的,而消息队列中的消息,就是按照这个时间从小到大来排序的,因此,当把新的消息加入到消息队列时,就要根据它的处理时间来找到合适的位置,然后再放进消息队列中去:

[java]  view plain copy
  1. Message prev = null;  
  2. while (p != null && p.when <= when) {  
  3.     prev = p;  
  4.     p = p.next;  
  5. }  
  6. msg.next = prev.next;  
  7. prev.next = msg;  
  8. needWake = false// still waiting on head, no need to wake up  
        把消息加入到消息队列去后,如果应用程序的主线程正处于空闲等待状态,就需要调用natvieWake函数来唤醒它了,这是一个JNI方法,定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:

[java]  view plain copy
  1. static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {  
  2.     NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);  
  3.     return nativeMessageQueue->wake();  
  4. }  
        这个JNI层的NativeMessageQueue对象我们在前面分析消息循环的时候创建好的,保存在Java层的MessageQueue对象的mPtr成员变量中,这里把它取回来之后,就调用它的wake函数来唤醒应用程序的主线程,这个函数也是定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:

[java]  view plain copy
  1. void NativeMessageQueue::wake() {  
  2.     mLooper->wake();  
  3. }  
        这里它又通过成员变量mLooper的wake函数来执行操作,这里的mLooper成员变量是一个C++层实现的Looper对象,它定义在frameworks/base/libs/utils/Looper.cpp文件中:

[java]  view plain copy
  1. void Looper::wake() {  
  2.     ......  
  3.   
  4.     ssize_t nWrite;  
  5.     do {  
  6.         nWrite = write(mWakeWritePipeFd, "W"1);  
  7.     } while (nWrite == -1 && errno == EINTR);  
  8.   
  9.     .......  
  10. }  
        这个wake函数很简单,只是通过打开文件描述符mWakeWritePipeFd往管道的写入一个"W"字符串。其实,往管道写入什么内容并不重要,往管道写入内容的目的是为了唤醒应用程序的主线程。前面我们在分析应用程序的消息循环时说到,当应用程序的消息队列中没有消息处理时,应用程序的主线程就会进入空闲等待状态,而这个空闲等待状态就是通过调用这个Looper类的pollInner函数来进入的,具体就是在pollInner函数中调用epoll_wait函数来等待管道中有内容可读的。

        这时候既然管道中有内容可读了,应用程序的主线程就会从这里的Looper类的pollInner函数返回到JNI层的nativePollOnce函数,最后返回到Java层中的MessageQueue.next函数中去,这里它就会发现消息队列中有新的消息需要处理了,于就会处理这个消息。

        3. 消息的处理

        前面在分析消息循环时,说到应用程序的主线程是在Looper类的loop成员函数中进行消息循环过程的,这个函数定义在frameworks/base/core/java/android/os/Looper.java文件中:

[java]  view plain copy
  1. public class Looper {  
  2.     ......  
  3.   
  4.     public static final void loop() {  
  5.         Looper me = myLooper();  
  6.         MessageQueue queue = me.mQueue;  
  7.   
  8.         ......  
  9.   
  10.         while (true) {  
  11.             Message msg = queue.next(); // might block  
  12.             ......  
  13.   
  14.             if (msg != null) {  
  15.                 if (msg.target == null) {  
  16.                     // No target is a magic identifier for the quit message.  
  17.                     return;  
  18.                 }  
  19.   
  20.                 ......  
  21.   
  22.                 msg.target.dispatchMessage(msg);  
  23.                   
  24.                 ......  
  25.   
  26.                 msg.recycle();  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     ......  
  32. }  
        它从消息队列中获得消息对象msg后,就会调用它的target成员变量的dispatchMessage函数来处理这个消息。在前面分析消息的发送时说过,这个消息对象msg的成员变量target是在发送消息的时候设置好的,一般就通过哪个Handler来发送消息,就通过哪个Handler来处理消息。

        我们继续以前面分析消息的发送时所举的例子来分析消息的处理过程。前面说到,在Android应用程序启动过程源代码分析这篇文章的Step 30中,ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函数通知应用程序,它可以加载应用程序的默认Activity了,而ApplicationThread类的scheduleLaunchActivity函数最终把这个请求封装成一个消息,然后通过ActivityThread类的成员变量mH来把这个消息加入到应用程序的消息队列中去。现在要对这个消息进行处理了,于是就会调用H类的dispatchMessage函数进行处理。

        H类没有实现自己的dispatchMessage函数,但是它继承了父类Handler的dispatchMessage函数,这个函数定义在frameworks/base/core/java/android/os/ Handler.java文件中:

[java]  view plain copy
  1. public class Handler {  
  2.     ......  
  3.   
  4.     public void dispatchMessage(Message msg) {  
  5.         if (msg.callback != null) {  
  6.             handleCallback(msg);  
  7.         } else {  
  8.             if (mCallback != null) {  
  9.                 if (mCallback.handleMessage(msg)) {  
  10.                     return;  
  11.                 }  
  12.             }  
  13.             handleMessage(msg);  
  14.         }  
  15.     }  
  16.   
  17.     ......  
  18. }  
        这里的消息对象msg的callback成员变量和Handler类的mCallBack成员变量一般都为null,于是,就会调用Handler类的handleMessage函数来处理这个消息,由于H类在继承Handler类时,重写了handleMessage函数,因此,这里调用的实际上是H类的handleMessage函数,这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

[java]  view plain copy
  1. public final class ActivityThread {    
  2.     
  3.     ......    
  4.     
  5.     private final class H extends Handler {    
  6.     
  7.         ......    
  8.     
  9.         public void handleMessage(Message msg) {    
  10.             ......    
  11.             switch (msg.what) {    
  12.             case LAUNCH_ACTIVITY: {    
  13.                 ActivityClientRecord r = (ActivityClientRecord)msg.obj;    
  14.     
  15.                 r.packageInfo = getPackageInfoNoCheck(    
  16.                     r.activityInfo.applicationInfo);    
  17.                 handleLaunchActivity(r, null);    
  18.             } break;    
  19.             ......    
  20.             }    
  21.     
  22.         ......    
  23.     
  24.     }    
  25.     
  26.     ......    
  27. }    
         因为前面在分析消息的发送时所举的例子中,发送的消息的类型为H.LAUNCH_ACTIVITY,因此,这里就会调用ActivityThread类的handleLaunchActivity函数来真正地处理这个消息了,后面的具体过程就可以参考 Android应用程序启动过程源代码分析 这篇文章了。

         至此,我们就从消息循环、消息发送和消息处理三个部分分析完Android应用程序的消息处理机制了,为了更深理解,这里我们对其中的一些要点作一个总结:

         A. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。

         B. Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。

         C. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。

         D. 当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。

         E. 当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。





android的消息处理机制(图+源码分析)——Looper,Handler,Message


作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想。android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了。这不,前几天为了了解android的消息处理机制,我看了Looper,Handler,Message这几个类的源码,结果又一次被googler的设计震撼了,特与大家分享。

android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类。下面一一介绍:

线程的魔法师 Looper

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

复制代码
public class LooperThread extends Thread {
@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();

// ...其他处理,如实例化handler

// 开始循环处理消息队列
Looper.loop();
}
}
复制代码

通过上面两行核心代码,你的线程就升级为Looper线程了!!!是不是很神奇?让我们放慢镜头,看看这两行代码各自做了什么。

1)Looper.prepare()

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,为什么呢?咱们来看源码。

复制代码
public class Looper {
// 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper内的消息队列
final MessageQueue mQueue;
// 当前线程
Thread mThread;
// 。。。其他属性

// 每个Looper对象中有它的消息队列,和它所属的线程
private Looper() {
mQueue
= new MessageQueue();
mRun
= true;
mThread
= Thread.currentThread();
}

// 我们调用该方法会在调用线程的TLS中创建Looper对象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 试图在有Looper的线程中再次创建Looper将抛出异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(
new Looper());
}
// 其他方法
}
复制代码

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。如果你还不清楚什么是ThreadLocal,请参考《理解ThreadLocal》

2)Looper.loop()

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:

复制代码
  public static final void loop() {
Looper me
= myLooper(); //得到当前线程Looper
MessageQueue queue = me.mQueue; //得到当前looper的MQ

// 这两行没看懂= = 不过不影响理解
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 开始循环
while (true) {
Message msg
= queue.next(); // 取出message
if (msg != null) {
if (msg.target == null) {
// message没有target为结束信号,退出循环
return;
}
// 日志。。。
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
// 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
msg.target.dispatchMessage(msg);
// 还是日志。。。
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);

// 下面没看懂,同样不影响理解
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(
"Looper", "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);
}
// 回收message资源
msg.recycle();
}
}
}
复制代码

除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如

Looper.myLooper()得到当前线程looper对象:

    public static final Looper myLooper() {
// 在任意线程调用Looper.myLooper()返回的都是那个线程的looper
return (Looper)sThreadLocal.get();
}

getThread()得到looper对象所属线程:

    public Thread getThread() {
return mThread;
}

quit()方法结束looper循环:

复制代码
    public void quit() {
// 创建一个空的message,它的target为NULL,表示结束循环消息
Message msg = Message.obtain();
// 发出消息
mQueue.enqueueMessage(msg, 0);
}
复制代码

到此为止,你应该对Looper有了基本的了解,总结几点:

1.每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

那么,我们如何往MQ上添加消息呢?下面有请Handler!(掌声~~~)

异步处理大师 Handler

什么是handler?handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:

复制代码
public class handler {

final MessageQueue mQueue; // 关联的MQ
final Looper mLooper; // 关联的looper
final Callback mCallback;
// 其他属性

public Handler() {
// 没看懂,直接略过,,,
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());
}
}
// 默认将关联当前线程的looper
mLooper = Looper.myLooper();
// looper不能为空,即该默认的构造方法只能在looper线程中使用
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
mQueue = mLooper.mQueue;
mCallback
= null;
}

// 其他方法
}
复制代码

下面我们就可以为之前的LooperThread类加入Handler:

复制代码
public class LooperThread extends Thread {
private Handler handler1;
private Handler handler2;

@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();

// 实例化两个handler
handler1 = new Handler();
handler2
= new Handler();

// 开始循环处理消息队列
Looper.loop();
}
}
复制代码

加入handler后的效果如下图:

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

Handler发送消息

有了handler之后,我们就可以使用 post(Runnable),postAtTime(Runnable, long)postDelayed(Runnable, long),sendEmptyMessage(int)sendMessage(Message),sendMessageAtTime(Message, long)sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:

复制代码
    // 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行
public final boolean post(Runnable r)
{
// 注意getPostMessage(r)将runnable封装成message
return sendMessageDelayed(getPostMessage(r), 0);
}

private final Message getPostMessage(Runnable r) {
Message m
= Message.obtain(); //得到空的message
m.callback = r; //将runnable设为message的callback,
return m;
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue
= mQueue;
if (queue != null) {
msg.target
= this; // message的target必须设为该handler!
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e
= new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w(
"Looper", e.getMessage(), e);
}
return sent;
}
复制代码

其他方法就不罗列了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);

2.post发出的message,其callback为Runnable对象

Handler处理消息

说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的,见源码

复制代码
    // 处理消息,该方法由looper调用
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 如果message设置了callback,即runnable消息,处理callback!
handleCallback(msg);
}
else {
// 如果handler本身设置了callback,则执行callback
if (mCallback != null) {
/* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果message没有callback,则调用handler的钩子方法handleMessage
handleMessage(msg);
}
}

// 处理runnable消息
private final void handleCallback(Message message) {
message.callback.run();
//直接调用run方法!
}
// 由子类实现的钩子方法
public void handleMessage(Message msg) {
}
复制代码

可以看到,除了handleMessage(Message msg)和Runnable对象的run方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!

Handler的用处

我在小标题中将handler描述为“异步处理大师”,这归功于Handler拥有下面两个重要的特点:

1.handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。

              

2.handler是在它关联的looper线程中处理消息的。

这就解决了android最经典的不能在其他非主线程中更新UI的问题。android的主线程也是一个looper线程(looper在android中运用很广),我们在其中创建的handler默认将关联主线程MQ。因此,利用handler的一个solution就是在activity中创建handler并将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知activity更新UI。(过程如图)

下面给出sample代码,仅供参考:

复制代码
public class TestDriverActivity extends Activity {
private TextView textview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textview
= (TextView) findViewById(R.id.textview);
// 创建并启动工作线程
Thread workerThread = new Thread(new SampleTask(new MyHandler()));
workerThread.start();
}

public void appendText(String msg) {
textview.setText(textview.getText()
+ "\n" + msg);
}

class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String result
= msg.getData().getString("message");
// 更新UI
appendText(result);
}
}
}
复制代码
复制代码
public class SampleTask implements Runnable {
private static final String TAG = SampleTask.class.getSimpleName();
Handler handler;

public SampleTask(Handler handler) {
super();
this.handler = handler;
}

@Override
public void run() {
try { // 模拟执行某项任务,下载等
Thread.sleep(5000);
// 任务完成后通知activity更新UI
Message msg = prepareMessage("task completed!");
// message将被添加到主线程的MQ中
handler.sendMessage(msg);
}
catch (InterruptedException e) {
Log.d(TAG,
"interrupted!");
}

}

private Message prepareMessage(String str) {
Message result
= handler.obtainMessage();
Bundle data
= new Bundle();
data.putString(
"message", str);
result.setData(data);
return result;
}

}
复制代码

当然,handler能做的远远不仅如此,由于它能post Runnable对象,它还能与Looper配合实现经典的Pipeline Thread(流水线线程)模式。请参考此文《Android Guts: Intro to Loopers and Handlers》

封装任务 Message

在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点需要注意(待补充):

1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存

3.擅用message.what来标识信息,以便用不同方式处理message。




AsyncTask详解与应用一


AsyncTask 能够让你恰当容易地使用UI线程。AsyncTask其实是Android给开发者提供的一个简单轻量级的多线程类,通过它我们可以很容易新建一个线程做一些耗时的操作,并在这个过程中更新UI。之所以说它轻量级,是因为缺少了直接使用Thread的灵活性。这个类允许执行后台操作,在UI线程上发布的结果而无需操纵线程或Handler。AsyncTask设计出来的目的就是作为Thread和Handler的一个辅助类,并不构成一个通用线程框架。asynctasks应用于短作业(最多几秒钟)。如果你需要保持线程运行很长一段时间,那么强烈建议你使用javaAPIjava.util.concurrent包里面的类,例如Executor, ThreadPoolExecutor and FutureTask。一个AsyncTask任务由计算运行在后台线程上,其结果发表在UI线程上。它有三种参数类型, Params, Progress and Result和四个步骤:onPreExecute, doInBackground, onProgressUpdate and onPostExecute。

         AsyncTask必须使用子类,也就是必须继承  AsyncTask才能使用它。子类会覆盖至少一个方法(doInBackground(Params…)),通常将覆盖第二个(onPostExecute(Result))。
        下面看一个例子:

[java]  view plain copy
  1. private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {  
  2.     protected Long doInBackground(URL... urls) {  
  3.         int count = urls.length;  
  4.         long totalSize = 0;  
  5.         for (int i = 0; i < count; i++) {  
  6.             totalSize += Downloader.downloadFile(urls[i]);  
  7.             publishProgress((int) ((i / (float) count) * 100));  
  8.             // Escape early if cancel() is called  
  9.             if (isCancelled()) break;  
  10.         }  
  11.         return totalSize;  
  12.     }  
  13.   
  14.     protected void onProgressUpdate(Integer... progress) {  
  15.         setProgressPercent(progress[0]);  
  16.     }  
  17.   
  18.     protected void onPostExecute(Long result) {  
  19.         showDialog("Downloaded " + result + " bytes");  
  20.     }  
  21. }  
           一旦创建,执行一个任务非常简单:
[java]  view plain copy
  1. new DownloadFilesTask().execute(url1, url2, url3);  
           android的类AsyncTask对线程间通讯进行了包装,提供了简易的编程方式来使后台线程和UI线程进行通讯:后台线程执行异步任务,并把操作结果通知UI线程。
     AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。
     Params 启动任务执行的输入参数,比如HTTP请求的URL。
     Progress 后台任务执行的百分比。
     Result 后台执行任务最终返回的结果,比如String,Integer。

                                                                                       

              AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,开发者需要实现这些方法。
                1) 继承AsyncTask
                2) 实现AsyncTask中定义的下面一个或几个方法
                     onPreExecute(), 该方法将在执行实际的后台操作前被UI 线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化,这个方法可以不用实现。
                    doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台处理工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
                    onProgressUpdate(Progress...),在publishProgress方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
                    onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户. 
                   onCancelled(),在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。
           为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
            1) Task的实例必须在UI 线程中创建
            2) execute方法必须在UI 线程中调用
            3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用。
           4) 该task只能被执行一次,否则多次调用时将会出现异常

                                                                    

                 doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。阅读AsyncTask的源码可知,AsyncTask是使用java.util.concurrent 框架来管理线程以及任务的执行的。


实例

                 1、一个模拟下载进度条的例子

[java]  view plain copy
  1. package com.example.asynctaskdemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.AsyncTask;  
  5. import android.os.Bundle;  
  6. import android.view.View;  
  7. import android.widget.Button;  
  8. import android.widget.ProgressBar;  
  9. import android.widget.TextView;  
  10.   
  11. public class Asy1Activity extends Activity {  
  12.   
  13.     Button download;  
  14.     ProgressBar pb;  
  15.     TextView tv;  
  16.   
  17.     /** Called when the activity is first created. */  
  18.     @Override  
  19.     public void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.asy1);  
  22.         pb = (ProgressBar) findViewById(R.id.pb);  
  23.         tv = (TextView) findViewById(R.id.tv);  
  24.   
  25.         download = (Button) findViewById(R.id.download);  
  26.         download.setOnClickListener(new View.OnClickListener() {  
  27.             @Override  
  28.             public void onClick(View v) {  
  29.                 DownloadTask task=new DownloadTask();  
  30.                 task.execute(100);  
  31.             }  
  32.         });  
  33.     }  
  34.     class DownloadTask extends AsyncTask<Integer, Integer, String>{  
  35.   
  36.         @Override  
  37.         protected void onCancelled() {  
  38.             // TODO Auto-generated method stub  
  39.             super.onCancelled();  
  40.         }  
  41.   
  42.         @Override  
  43.         protected void onPostExecute(String result) {  
  44.             setTitle(result);   
  45.             super.onPostExecute(result);  
  46.         }  
  47.   
  48.         @Override  
  49.         protected void onPreExecute() {  
  50.             // TODO Auto-generated method stub  
  51.             super.onPreExecute();  
  52.         }  
  53.   
  54.         @Override  
  55.         protected void onProgressUpdate(Integer... values) {  
  56.             // TODO Auto-generated method stub  
  57.             super.onProgressUpdate(values);  
  58.             tv.setText(values[0]+"%");  
  59.               
  60.         }  
  61.   
  62.         @Override  
  63.         protected String doInBackground(Integer... params) {  
  64.             for(int i=0;i<=100;i++){  
  65.                 pb.setProgress(i);  
  66.                 publishProgress(i);    
  67.                  try {    
  68.                         Thread.sleep(params[0]);    
  69.                     } catch (InterruptedException e) {    
  70.                         e.printStackTrace();    
  71.                     }    
  72.             }  
  73.             return "执行完毕";  
  74.         }  
  75.           
  76.     }  
  77.       
  78. }  

                                                                                                   

         1、一个从网络下载图片的例子

[java]  view plain copy
  1. package com.example.asynctaskdemo;  
  2.   
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.net.HttpURLConnection;  
  7. import java.net.MalformedURLException;  
  8. import java.net.URL;  
  9. import android.app.Activity;  
  10. import android.graphics.Bitmap;  
  11. import android.graphics.BitmapFactory;  
  12. import android.os.AsyncTask;  
  13. import android.os.Bundle;  
  14. import android.view.View;  
  15. import android.view.View.OnClickListener;  
  16. import android.widget.Button;  
  17. import android.widget.ImageView;  
  18. import android.widget.ProgressBar;  
  19.   
  20. public class Asy2Activity extends Activity {  
  21.   
  22.     private Button button;  
  23.     private ProgressBar progressBar;  
  24.     private ImageView imageView;  
  25.     private final String imageUrl = "http://avatar.csdn.net/D/1/4/1_wangjinyu501.jpg";  
  26.   
  27.     @Override  
  28.     protected void onCreate(Bundle savedInstanceState) {  
  29.         // TODO Auto-generated method stub  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.asy2);  
  32.         initView();  
  33.     }  
  34.   
  35.     private void initView() {  
  36.         button = (Button) findViewById(R.id.button);  
  37.         button.setOnClickListener(new OnClickListener() {  
  38.   
  39.             @Override  
  40.             public void onClick(View arg0) {  
  41.                 AsyncTaskLoadImage asyncTaskLoadImage = new AsyncTaskLoadImage();  
  42.   
  43.                 asyncTaskLoadImage.execute(imageUrl);  
  44.             }  
  45.         });  
  46.         progressBar = (ProgressBar) findViewById(R.id.pb);  
  47.         imageView = (ImageView) findViewById(R.id.imageview);  
  48.   
  49.     }  
  50.   
  51.     class AsyncTaskLoadImage extends AsyncTask<String, Integer, Bitmap> {  
  52.   
  53.         @Override  
  54.         protected void onPreExecute() {  
  55.             super.onPreExecute();  
  56.   
  57.         }  
  58.   
  59.         @Override  
  60.         protected Bitmap doInBackground(String... params) {  
  61.             Bitmap bitmap = null;  
  62.             try {  
  63.                 URL url = new URL(params[0]);  
  64.                 HttpURLConnection urlConnection = (HttpURLConnection) url  
  65.                         .openConnection();  
  66.                 urlConnection.connect();  
  67.                 int MAX = urlConnection.getContentLength();  
  68.                 progressBar.setMax(MAX);  
  69.   
  70.                 InputStream inputStream = urlConnection.getInputStream();  
  71.   
  72.                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
  73.                 byte[] b = new byte[1024];  
  74.                 int len = 0;  
  75.                 int processBarNum = 0;  
  76.                 while ((len = inputStream.read(b)) != -1) {  
  77.                     byteArrayOutputStream.write(b, 0, len);  
  78.                     processBarNum += len;  
  79.                     publishProgress(processBarNum);  
  80.                 }  
  81.                // bitmap = BitmapFactory.decodeStream(inputStream);  
  82.                 bitmap = BitmapFactory.decodeByteArray(  
  83.                         byteArrayOutputStream.toByteArray(), 0, MAX);  
  84.                 inputStream.close();  
  85.             } catch (MalformedURLException e) {  
  86.                 e.printStackTrace();  
  87.             } catch (IOException e) {  
  88.                 e.printStackTrace();  
  89.             }  
  90.             return bitmap;  
  91.         }  
  92.   
  93.         @Override  
  94.         protected void onProgressUpdate(Integer... values) {  
  95.             progressBar.setProgress(values[0]);  
  96.             super.onProgressUpdate(values);  
  97.   
  98.         }  
  99.   
  100.         @Override  
  101.         protected void onPostExecute(Bitmap result) {  
  102.   
  103.             imageView.setImageBitmap(result);  
  104.             super.onPostExecute(result);  
  105.         }  
  106.   
  107.     }  
  108.   
  109. }  

                                                                                                

Android之Looper、Handler、Message、MessageQueue应用篇


简介

      上一篇文章介绍了Handler、Message、MessageQueue等Android线程交互方面的内容,Android之理解Looper、Handler、Message、MessageQueue。下面开始实践,学习如何去使用以及应用到程序里面。

实例

      在这里使用ListView作为异步下载图片的环境。

      1、Handle+Runnable

           实现思路是:

          

[java]  view plain copy
  1. package com.example.handlerloadiage;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.URL;  
  5. import android.app.Activity;  
  6. import android.graphics.drawable.Drawable;  
  7. import android.os.Bundle;  
  8. import android.os.Handler;  
  9. import android.util.Log;  
  10. import android.view.LayoutInflater;  
  11. import android.view.Menu;  
  12. import android.view.View;  
  13. import android.view.ViewGroup;  
  14. import android.widget.BaseAdapter;  
  15. import android.widget.ImageView;  
  16. import android.widget.ListView;  
  17.   
  18. public class Handler_Runnable_Mode extends Activity {  
  19.   
  20.     private ListView listview;  
  21.   
  22.     @Override  
  23.     protected void onCreate(Bundle savedInstanceState) {  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_handlerimageloader);  
  26.   
  27.         listview = (ListView) findViewById(R.id.listview);  
  28.         listview.setAdapter(new MyAdapter());  
  29.     }  
  30.   
  31.     private class MyAdapter extends BaseAdapter {  
  32.   
  33.         public MyAdapter() {  
  34.   
  35.         }  
  36.   
  37.         @Override  
  38.         public int getCount() {  
  39.             // TODO Auto-generated method stub  
  40.             return 100;  
  41.         }  
  42.   
  43.         @Override  
  44.         public Object getItem(int position) {  
  45.             // TODO Auto-generated method stub  
  46.             return null;  
  47.         }  
  48.   
  49.         @Override  
  50.         public long getItemId(int position) {  
  51.             // TODO Auto-generated method stub  
  52.             return 0;  
  53.         }  
  54.   
  55.         @Override  
  56.         public View getView(int position, View convertView, ViewGroup parent) {  
  57.             if (convertView == null) {  
  58.                 convertView = LayoutInflater.from(getApplicationContext())  
  59.                         .inflate(R.layout.list_item, null);  
  60.             }  
  61.             final ImageView image = (ImageView) convertView  
  62.                     .findViewById(R.id.imageview);  
  63.             final String imageURL = "http://avatar.csdn.net/D/1/4/3_wangjinyu501.jpg";  
  64.   
  65.             Handler handler = new Handler();  
  66.             handler.post(new Runnable() {  
  67.                 public void run() {  
  68.                     Drawable drawable = null;  
  69.                     try {  
  70.                         drawable = Drawable.createFromStream(  
  71.                                 new URL(imageURL).openStream(), "image.jpg");  
  72.                     } catch (IOException e) {  
  73.                         Log.d("test", e.getMessage());  
  74.                     }  
  75.                     if (drawable == null) {  
  76.                         Log.d("test""null drawable");  
  77.                     } else {  
  78.                         Log.d("test""not null drawable");  
  79.                     }  
  80.                     if (drawable == null) {  
  81.                         image.setImageResource(R.drawable.ic_launcher);  
  82.                     } else {  
  83.                         image.setImageDrawable(drawable);  
  84.                     }  
  85.                 }  
  86.             });  
  87.             return convertView;  
  88.         }  
  89.   
  90.     }  
  91.   
  92.     @Override  
  93.     public boolean onCreateOptionsMenu(Menu menu) {  
  94.         getMenuInflater().inflate(R.menu.main, menu);  
  95.         return true;  
  96.     }  
  97.   
  98. }  
      效果如下:

                                                          

      快速滑动的过程中,还是出现了ANR的现象。

                                                           


     这是因为handler.post(new Runnable()这个方法,并没有开启一个新的线程,他还是在UI主线程中,所以导致出现ANR现象。


     2、Handler+Runnable+Message

    实现思路:

           1:在UI线程中启动一个线程,让这个线程去下载图片。

           2:图片完成下载后发送一个消息去通知UI线程

           3:UI线程获取到消息后,更新UI。

     实现代码:

[java]  view plain copy
  1. package com.example.handlerloadiage;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.URL;  
  5. import android.app.Activity;  
  6. import android.graphics.drawable.Drawable;  
  7. import android.os.Bundle;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.util.Log;  
  11. import android.view.LayoutInflater;  
  12. import android.view.Menu;  
  13. import android.view.View;  
  14. import android.view.ViewGroup;  
  15. import android.widget.BaseAdapter;  
  16. import android.widget.ImageView;  
  17. import android.widget.ListView;  
  18.   
  19. public class Handler_Runnable_Mode extends Activity {  
  20.   
  21.     private ListView listview;  
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_handlerimageloader);  
  27.   
  28.         listview = (ListView) findViewById(R.id.listview);  
  29.         listview.setAdapter(new MyAdapter());  
  30.     }  
  31.   
  32.     private class MyAdapter extends BaseAdapter {  
  33.   
  34.         public MyAdapter() {  
  35.   
  36.         }  
  37.   
  38.         @Override  
  39.         public int getCount() {  
  40.             // TODO Auto-generated method stub  
  41.             return 100;  
  42.         }  
  43.   
  44.         @Override  
  45.         public Object getItem(int position) {  
  46.             // TODO Auto-generated method stub  
  47.             return null;  
  48.         }  
  49.   
  50.         @Override  
  51.         public long getItemId(int position) {  
  52.             // TODO Auto-generated method stub  
  53.             return 0;  
  54.         }  
  55.   
  56.         @Override  
  57.         public View getView(int position, View convertView, ViewGroup parent) {  
  58.             if (convertView == null) {  
  59.                 convertView = LayoutInflater.from(getApplicationContext())  
  60.                         .inflate(R.layout.list_item, null);  
  61.             }  
  62.             final ImageView image = (ImageView) convertView  
  63.                     .findViewById(R.id.imageview);  
  64.             final String imageURL = "http://avatar.csdn.net/D/1/4/3_wangjinyu501.jpg";  
  65.   
  66.             final Handler handler = new Handler(){  
  67.   
  68.                 @Override  
  69.                 public void handleMessage(Message msg) {  
  70.                       
  71.                     super.handleMessage(msg);  
  72.                     Drawable d=(Drawable) msg.obj;  
  73.                     if (d == null) {  
  74.                         image.setImageResource(R.drawable.ic_launcher);  
  75.                     } else {  
  76.                         image.setImageDrawable(d);  
  77.                     }  
  78.                 }  
  79.                   
  80.             };  
  81.             handler.post(new Runnable() {  
  82.                 public void run() {  
  83.                     Drawable drawable = null;  
  84.                     try {  
  85.                         drawable = Drawable.createFromStream(  
  86.                                 new URL(imageURL).openStream(), "image.jpg");  
  87.                         Message message=handler.obtainMessage();  
  88.                         message.obj=drawable;  
  89.                         handler.sendMessage(message);  
  90.                           
  91.                     } catch (IOException e) {  
  92.                         Log.d("test", e.getMessage());  
  93.                     }  
  94.                     if (drawable == null) {  
  95.                         Log.d("test""null drawable");  
  96.                     } else {  
  97.                         Log.d("test""not null drawable");  
  98.                     }  
  99.                       
  100.                 }  
  101.             });  
  102.             return convertView;  
  103.         }  
  104.   
  105.     }  
  106.   
  107.     @Override  
  108.     public boolean onCreateOptionsMenu(Menu menu) {  
  109.         getMenuInflater().inflate(R.menu.main, menu);  
  110.         return true;  
  111.     }  
  112.   
  113. }  
    也是会出现ANR的现象,原因和之前一样。


     3、Handler+Thread+Message

    这种模式使用了线程,所以是异步加载。

[java]  view plain copy
  1. package com.example.handlerloadiage;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.URL;  
  5. import android.app.Activity;  
  6. import android.graphics.drawable.Drawable;  
  7. import android.os.Bundle;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.util.Log;  
  11. import android.view.LayoutInflater;  
  12. import android.view.Menu;  
  13. import android.view.View;  
  14. import android.view.ViewGroup;  
  15. import android.widget.BaseAdapter;  
  16. import android.widget.ImageView;  
  17. import android.widget.ListView;  
  18.   
  19. public class HandlerThreadMessageActivity extends Activity {  
  20.   
  21.     private ListView listview;  
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_handlerimageloader);  
  27.   
  28.         listview = (ListView) findViewById(R.id.listview);  
  29.         listview.setAdapter(new MyAdapter());  
  30.     }  
  31.   
  32.     private class MyAdapter extends BaseAdapter {  
  33.   
  34.         public MyAdapter() {  
  35.   
  36.         }  
  37.   
  38.         @Override  
  39.         public int getCount() {  
  40.             return 100;  
  41.         }  
  42.   
  43.         @Override  
  44.         public Object getItem(int position) {  
  45.             return null;  
  46.         }  
  47.   
  48.         @Override  
  49.         public long getItemId(int position) {  
  50.             return 0;  
  51.         }  
  52.   
  53.         @Override  
  54.         public View getView(int position, View convertView, ViewGroup parent) {  
  55.             if (convertView == null) {  
  56.                 convertView = LayoutInflater.from(getApplicationContext())  
  57.                         .inflate(R.layout.list_item, null);  
  58.             }  
  59.             final ImageView image = (ImageView) convertView  
  60.                     .findViewById(R.id.imageview);  
  61.             final String imageURL = "http://avatar.csdn.net/D/1/4/3_wangjinyu501.jpg";  
  62.   
  63.             final Handler handler = new Handler() {  
  64.   
  65.                 @Override  
  66.                 public void handleMessage(Message msg) {  
  67.                     super.handleMessage(msg);  
  68.                     Drawable d = (Drawable) msg.obj;  
  69.                     if (d == null) {  
  70.                         image.setImageResource(R.drawable.ic_launcher);  
  71.                     } else {  
  72.                         image.setImageDrawable(d);  
  73.                     }  
  74.                 }  
  75.   
  76.             };  
  77.               
  78.             Thread thread = new Thread() {  
  79.                 @Override  
  80.                 public void run() {  
  81.                     Drawable drawable = null;  
  82.                     try {  
  83.                         drawable = Drawable.createFromStream(  
  84.                                 new URL(imageURL).openStream(), "image.jpg");  
  85.                     } catch (IOException e) {  
  86.                         Log.d("test", e.getMessage());  
  87.                     }  
  88.                     // 模拟网络延时  
  89.                     // SystemClock.sleep(2000);  
  90.                     Message message = handler.obtainMessage();  
  91.                     message.obj = drawable;  
  92.                     handler.sendMessage(message);  
  93.                 }  
  94.             };  
  95.             thread.start();  
  96.             thread = null;  
  97.             return convertView;  
  98.         }  
  99.   
  100.     }  
  101.   
  102.     @Override  
  103.     public boolean onCreateOptionsMenu(Menu menu) {  
  104.         getMenuInflater().inflate(R.menu.main, menu);  
  105.         return true;  
  106.     }  
  107.   
  108. }  
        运行程序,发现图片加载速度非常快,而且滑动过程中很顺畅。但是,这不是一个很好的解决方法,因为每次下载图片图片都需要新建一个线程,如果线程非常多,那么处理器显然不能承受。所以就需要对线程数量进行规划和维护,使其在一个合理的范围内运行。

          4、Handler+ExecutorService+Message

       因为能开线程的个数毕竟是有限的,不能开很多线程,对于手机更是如此。所以一个办法就是使用线程池。Android拥有与Java相同的ExecutorService实现。线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。下面的例子是创建一个可重用固定线程数的线程池。

看代码:

[java]  view plain copy
  1. package com.example.handlerloadiage;  
  2.   
  3. import java.net.URL;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6. import android.app.Activity;  
  7. import android.graphics.drawable.Drawable;  
  8. import android.os.Bundle;  
  9. import android.os.Handler;  
  10. import android.os.Message;  
  11. import android.view.LayoutInflater;  
  12. import android.view.Menu;  
  13. import android.view.View;  
  14. import android.view.ViewGroup;  
  15. import android.widget.BaseAdapter;  
  16. import android.widget.ImageView;  
  17. import android.widget.ListView;  
  18.   
  19. public class HandlerExecutorServiceActivity extends Activity {  
  20.   
  21.     private ListView listview;  
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_handlerimageloader);  
  27.   
  28.         listview = (ListView) findViewById(R.id.listview);  
  29.         listview.setAdapter(new MyAdapter());  
  30.     }  
  31.   
  32.     private class MyAdapter extends BaseAdapter {  
  33.   
  34.         public MyAdapter() {  
  35.   
  36.         }  
  37.   
  38.         @Override  
  39.         public int getCount() {  
  40.             return 100;  
  41.         }  
  42.   
  43.         @Override  
  44.         public Object getItem(int position) {  
  45.             return null;  
  46.         }  
  47.   
  48.         @Override  
  49.         public long getItemId(int position) {  
  50.             return 0;  
  51.         }  
  52.   
  53.         @Override  
  54.         public View getView(int position, View convertView, ViewGroup parent) {  
  55.             if (convertView == null) {  
  56.                 convertView = LayoutInflater.from(getApplicationContext())  
  57.                         .inflate(R.layout.list_item, null);  
  58.             }  
  59.             final ImageView image = (ImageView) convertView  
  60.                     .findViewById(R.id.imageview);  
  61.             final String imageURL = "http://avatar.csdn.net/D/1/4/3_wangjinyu501.jpg";  
  62.   
  63.             final Handler handler = new Handler() {  
  64.   
  65.                 @Override  
  66.                 public void handleMessage(Message msg) {  
  67.                     super.handleMessage(msg);  
  68.                     Drawable d = (Drawable) msg.obj;  
  69.                     if (d == null) {  
  70.                         image.setImageResource(R.drawable.ic_launcher);  
  71.                     } else {  
  72.                         image.setImageDrawable(d);  
  73.                     }  
  74.                 }  
  75.   
  76.             };  
  77.             ExecutorService executorService = Executors.newFixedThreadPool(5);  
  78.             executorService.submit(new Runnable() {  
  79.                 public void run() {  
  80.                     try {  
  81.                         final Drawable drawable = Drawable.createFromStream(  
  82.                                 new URL(imageURL).openStream(), "image.png");  
  83.                         // 模拟网络延时  
  84.                         // SystemClock.sleep(2000);  
  85.                         Message message = handler.obtainMessage();  
  86.                         message.obj = drawable;  
  87.                         handler.sendMessage(message);  
  88.                     } catch (Exception e) {  
  89.                         throw new RuntimeException(e);  
  90.                     }  
  91.                 }  
  92.             });  
  93.             return convertView;  
  94.         }  
  95.   
  96.     }  
  97.   
  98.     @Override  
  99.     public boolean onCreateOptionsMenu(Menu menu) {  
  100.         getMenuInflater().inflate(R.menu.main, menu);  
  101.         return true;  
  102.     }  
  103.   
  104. }<strong>  
  105. </strong>  
    这个方法的效果从表面上来看,其实和方法3差不太多,当然我说的是效果。那优势也是显而易见的,对线程的使用比较合理。但是,查看logcat就会发现,不管哪个方法都会出现GC_FOR_MALLOC freed 4843 objects / 473240 bytes in 64ms( 感兴趣朋友可以看一下这个 )。这个意思就是堆内部不足,如果有很多图片要加载的话,就可能出现OOM了,所以就需要进一步优化。

方案
     

     可以说对于下载网络图片,上面的方法只是一个开始。要想成为一个App的解决方案,其实还需要考虑更多的东西。先说一下可以进一步优化的地方:

     1、使用弱引用

     2、使用本地存储(SD卡)

     3、对图片进行缩放

     4、定时清除缓存

     5、存储到数据库

     下面看一个比较成熟的解决方案:

     ImageUtil.java  这是一个图片下载工具类

[java]  view plain copy
  1. import java.io.File;  
  2. import java.io.FileNotFoundException;  
  3. import java.io.FileOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.lang.ref.SoftReference;  
  7. import java.net.MalformedURLException;  
  8. import java.net.URL;  
  9. import java.net.URLConnection;  
  10. import java.text.DecimalFormat;  
  11. import java.util.Date;  
  12. import java.util.LinkedHashMap;  
  13. import android.annotation.SuppressLint;  
  14. import android.content.Context;  
  15. import android.database.Cursor;  
  16. import android.graphics.Bitmap;  
  17. import android.graphics.BitmapFactory;  
  18. import android.graphics.BitmapFactory.Options;  
  19. import android.graphics.Rect;  
  20. import android.os.Build;  
  21. import android.os.Environment;  
  22. import android.os.Handler;  
  23. import android.os.Message;  
  24. import android.util.Log;  
  25. import android.widget.ImageView;  
  26. import cn.eoe.app.R;  
  27. import cn.eoe.app.db.DBHelper;  
  28. import cn.eoe.app.db.ImageCacheColumn;  
  29.   
  30. public class ImageUtil {  
  31.     private static final String TAG = "ImageUtil";  
  32.     private static int DayCount = 15;// 天数  
  33.     private static final long CLEARTIME = DayCount * 24 * 60 * 60 * 1000;//秒  
  34.     /** 
  35.      * 默认图片 
  36.      */  
  37.     private final static int Default_Img = R.drawable.bg_load_default;  
  38.   
  39.     private static Object lock = new Object();  
  40.   
  41.     /** 
  42.      * 内存图片软引用缓冲 
  43.      */  
  44.     private static LinkedHashMap<String, SoftReference> imageCache = new LinkedHashMap<String, SoftReference>(  
  45.             20);  
  46.   
  47.     /** 
  48.      * 入口 
  49.      *  
  50.      * @param imageUrl 
  51.      * @param iv_item_image 
  52.      * @param context 
  53.      * @param callback 
  54.      * @param b 
  55.      */  
  56.     public static void setThumbnailView(String imageUrl,  
  57.             ImageView iv_item_image, Context context, ImageCallback callback,  
  58.             boolean b) {  
  59.         DBHelper dbHelper = DBHelper.getInstance(context);//获取数据库实例  
  60.         String md5 = ImageUtil.md5(imageUrl);  
  61.         String cachePath = context.getCacheDir().getAbsolutePath() + "/" + md5; // data里的缓存  
  62.         String imagePath = getExternalCacheDir(context) + File.separator + md5; // sd卡  
  63.         // 缓存目录  
  64.   
  65.         if (!CommonUtil.sdCardIsAvailable())/* true 为可用 */{  
  66.             setThumbnailImage(iv_item_image, imageUrl, cachePath, dbHelper,  
  67.                     callback, b);  
  68.             iv_item_image.setTag(cachePath);//SD卡不可用就是用data里面缓存的图片  
  69.         } else {  
  70.             setThumbnailImage(iv_item_image, imageUrl, imagePath, dbHelper,  
  71.                     callback, b);  
  72.             iv_item_image.setTag(imagePath);//  
  73.         }  
  74.     }  
  75.   
  76.     /** 
  77.      * 获得程序在sd开上的cahce目录 
  78.      *  
  79.      * @param context 
  80.      *            The context to use 
  81.      * @return The external cache dir 
  82.      */  
  83.     @SuppressLint("NewApi")  
  84.     public static String getExternalCacheDir(Context context) {  
  85.         // android 2.2 以后才支持的特性  
  86.         if (hasExternalCacheDir()) {  
  87.             return context.getExternalCacheDir().getPath() + File.separator  
  88.                     + "img";  
  89.         }  
  90.         // Before Froyo we need to construct the external cache dir ourselves  
  91.         // 2.2以前我们需要自己构造  
  92.         final String cacheDir = "/Android/data/" + context.getPackageName()  
  93.                 + "/cache/img/";  
  94.         return Environment.getExternalStorageDirectory().getPath() + cacheDir;  
  95.     }  
  96.   
  97.     public static boolean hasExternalCacheDir() {  
  98.         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;  
  99.     }  
  100.   
  101.     /** 
  102.      * 设置图片函数 
  103.      *  
  104.      * @param view 
  105.      * @param imageUrl 
  106.      * @param cachePath 
  107.      * @param callback 
  108.      * @param b 
  109.      */  
  110.     private static void setThumbnailImage(ImageView view, String imageUrl,  
  111.             String cachePath, DBHelper dbHelper, ImageCallback callback,  
  112.             boolean b) {  
  113.         Bitmap bitmap = null;  
  114.         bitmap = ImageUtil.loadThumbnailImage(cachePath, imageUrl, dbHelper,  
  115.                 callback, b);  
  116.         if (bitmap == null) {// 先查找数据库,再查找本地sd卡,若没有.再从网站加载,若网站上没有图片或错误时返回null  
  117.             // 设置默认图片  
  118.             view.setImageResource(Default_Img);  
  119.         } else {  
  120.             // 设置本地SD卡缓存图片  
  121.             view.setImageBitmap(bitmap);  
  122.         }  
  123.     }  
  124.   
  125.     private static Bitmap getImageFromDB(String imagePath, String imageUrl,  
  126.             DBHelper dbHelper) {  
  127.         Cursor cursor = queryFromDbByImgUrl(dbHelper, imageUrl);  
  128.         if (cursor.moveToFirst()) {  
  129.             long currTimestamp = (new Date()).getTime();  
  130.             long timestamp = cursor.getLong(cursor  
  131.                     .getColumnIndex(ImageCacheColumn.TIMESTAMP));  
  132.             long spanTime = currTimestamp - timestamp;  
  133.             int Past_time = cursor.getInt(cursor  
  134.                     .getColumnIndex(ImageCacheColumn.PAST_TIME));  
  135.             if (spanTime > Past_time * 24 * 60 * 60 * 1000) {  
  136.                 // 过期  
  137.                 // 删除本地文件  
  138.                 deleteImageFromLocal(imagePath);  
  139.                 return null;  
  140.             } else {  
  141.                 // 没过期  
  142.                 return getImageFromLocal(imagePath);  
  143.             }  
  144.         } else {  
  145.             return null;  
  146.         }  
  147.     }  
  148.   
  149.     private static Cursor queryFromDbByImgUrl(DBHelper dbHelper, String imageUrl) {  
  150.         // return dbHelper.query(ImageCacheColumn.TABLE_NAME, null,  
  151.         // ImageCacheColumn.Url + "=?", new String[] { imageUrl });  
  152.         return dbHelper.rawQuery("select * from " + ImageCacheColumn.TABLE_NAME  
  153.                 + "  where " + ImageCacheColumn.Url + "='" + imageUrl + "'",  
  154.                 null);  
  155.     }  
  156.   
  157.     /** 
  158.      * 保存图片到SD卡 
  159.      *  
  160.      * @param imagePath 
  161.      * @param buffer 
  162.      * @throws IOException 
  163.      */  
  164.     public static void saveImage(String imagePath, byte[] buffer)  
  165.             throws IOException {  
  166.         File f = new File(imagePath);  
  167.         if (f.exists()) {  
  168.             return;  
  169.         } else {  
  170.             File parentFile = f.getParentFile();  
  171.             if (!parentFile.exists()) {  
  172.                 parentFile.mkdirs();  
  173.             }  
  174.             f.createNewFile();  
  175.             FileOutputStream fos = new FileOutputStream(imagePath);  
  176.             fos.write(buffer);  
  177.             fos.flush();  
  178.             fos.close();  
  179.         }  
  180.     }  
  181.   
  182.     /** 
  183.      * 保存图片到缓存 
  184.      *  
  185.      * @param imagePath 
  186.      * @param bm 
  187.      */  
  188.     public static void saveImage(String imagePath, Bitmap bm) {  
  189.   
  190.         if (bm == null || imagePath == null || "".equals(imagePath)) {  
  191.             return;  
  192.         }  
  193.   
  194.         File f = new File(imagePath);  
  195.         if (f.exists()) {  
  196.             return;  
  197.         } else {  
  198.             try {  
  199.                 File parentFile = f.getParentFile();  
  200.                 if (!parentFile.exists()) {  
  201.                     parentFile.mkdirs();  
  202.                 }  
  203.                 f.createNewFile();  
  204.                 FileOutputStream fos;  
  205.                 fos = new FileOutputStream(f);  
  206.                 bm.compress(Bitmap.CompressFormat.PNG, 100, fos);  
  207.                 fos.close();  
  208.             } catch (FileNotFoundException e) {  
  209.                 f.delete();  
  210.                 e.printStackTrace();  
  211.             } catch (IOException e) {  
  212.                 e.printStackTrace();  
  213.                 f.delete();  
  214.             }  
  215.         }  
  216.     }  
  217.   
  218.     private static void saveImageByDb(String imageUrl, DBHelper dbHelper) {  
  219.         String sql = null;  
  220.         if (queryFromDbByImgUrl(dbHelper, imageUrl).moveToFirst()) {  
  221.             sql = "update " + ImageCacheColumn.TABLE_NAME + " set "  
  222.                     + ImageCacheColumn.TIMESTAMP + "='"  
  223.                     + (new Date().getTime()) + "' where "  
  224.                     + ImageCacheColumn.Url + "='" + imageUrl + "'";  
  225.         } else {  
  226.             sql = "insert into " + ImageCacheColumn.TABLE_NAME + "("  
  227.                     + ImageCacheColumn.Url + "," + ImageCacheColumn.TIMESTAMP  
  228.                     + "," + ImageCacheColumn.PAST_TIME + ") values('"  
  229.                     + imageUrl + "'," + (new Date().getTime()) + "," + DayCount  
  230.                     + ")";  
  231.         }  
  232.         dbHelper.ExecSQL(sql);  
  233.     }  
  234.   
  235.     /** 
  236.      * 从SD卡加载图片 
  237.      *  
  238.      * @param imagePath 
  239.      * @return 
  240.      */  
  241.     public static Bitmap getImageFromLocal(String imagePath) {  
  242.         File file = new File(imagePath);  
  243.         if (file.exists()) {  
  244.             Bitmap bitmap = BitmapFactory.decodeFile(imagePath);  
  245.             file.setLastModified(System.currentTimeMillis());  
  246.             return bitmap;  
  247.         }  
  248.         return null;  
  249.     }  
  250.   
  251.     /** 
  252.      * 从本地文件中删除文件 
  253.      *  
  254.      * @param imagePath 
  255.      */  
  256.     private static void deleteImageFromLocal(String imagePath) {  
  257.         File file = new File(imagePath);  
  258.         if (file.exists()) {  
  259.             file.delete();  
  260.         }  
  261.     }  
  262.   
  263.     /** 
  264.      * 从本地或者服务端异步加载缩略图图片 
  265.      *  
  266.      * @return 
  267.      * @param imagePath 
  268.      *            本地缓存路径 
  269.      * @param imgUrl 
  270.      *            拼接后的请求路径 
  271.      * @param callback 
  272.      *            得到数据后的处理方法回调 
  273.      * @throws IOException 
  274.      */  
  275.     public static Bitmap loadThumbnailImage(final String imagePath,  
  276.             final String imgUrl, final DBHelper dbHelper,  
  277.             final ImageCallback callback, final boolean b) {  
  278.         // 在软链接缓存中,则返回Bitmap对象  
  279.         if (imageCache.containsKey(imgUrl)) {  
  280.             SoftReference reference = imageCache.get(imgUrl);  
  281.             Bitmap bitmap = (Bitmap) reference.get();  
  282.             if (bitmap != null) {  
  283.                 return bitmap;  
  284.             }  
  285.         }  
  286.         // 若软链接缓存没有  
  287.         Bitmap bitmap = null;  
  288.         // 查询数据库 返回bitmap  
  289.         bitmap = getImageFromDB(imagePath, imgUrl, dbHelper);// 从本地加载  
  290.         if (bitmap != null) {  
  291.             return bitmap;  
  292.         } else {  
  293.             // 从网上加载  
  294.             final Handler handler = new Handler() {  
  295.                 @Override  
  296.                 public void handleMessage(Message msg) {  
  297.                     if (msg.obj != null) {  
  298.                         Bitmap bitmap = (Bitmap) msg.obj;  
  299.                         callback.loadImage(bitmap, imagePath);  
  300.                     }  
  301.                 }  
  302.             };  
  303.             Runnable runnable = new Runnable() {  
  304.                 @Override  
  305.                 public void run() {  
  306.                     try {  
  307.                         URL url = new URL(imgUrl);  
  308.                         URLConnection conn = url.openConnection();  
  309.                         conn.setConnectTimeout(5000);  
  310.                         conn.setReadTimeout(5000);  
  311.                         conn.connect();  
  312.                         InputStream in = conn.getInputStream();  
  313.                         BitmapFactory.Options options = new Options();  
  314.                         options.inSampleSize = 1;  
  315.                         Bitmap bitmap = BitmapFactory.decodeStream(in,  
  316.                                 new Rect(0000), options);  
  317.                         imageCache.put(imgUrl, new SoftReference(bitmap));  
  318.   
  319.                         Message msg = handler.obtainMessage();  
  320.                         msg.obj = bitmap;  
  321.                         handler.sendMessage(msg);  
  322.                         if (bitmap != null) {  
  323.                             // 保存文件到sd卡  
  324.                             saveImage(imagePath, bitmap);  
  325.                             // 保存到数据库  
  326.                             saveImageByDb(imgUrl, dbHelper);  
  327.                         }  
  328.                     } catch (MalformedURLException e) {  
  329.                         e.printStackTrace();  
  330.                         Log.e(ImageUtil.class.getName(), "图片url不存在");  
  331.                     } catch (IOException e) {  
  332.                         e.printStackTrace();  
  333.                     }  
  334.                 }  
  335.             };  
  336.             ThreadPoolManager.getInstance().addTask(runnable);  
  337.         }  
  338.         return null;  
  339.     }  
  340.   
  341.     /** 
  342.      * MD5 
  343.      *  
  344.      * @param paramString 
  345.      * @return 
  346.      */  
  347.     private static String md5(String paramString) {  
  348.         return MD5.encode(paramString);  
  349.     }  
  350.   
  351.     // ///  
  352.     // 公共方法  
  353.   
  354.     public interface ImageCallback {  
  355.         public void loadImage(Bitmap bitmap, String imagePath);  
  356.     }  
  357.   
  358.     /** 
  359.      * 每次打开含有大量图片的activity时,开一个新线程,检查并清理缓存 
  360.      *  
  361.      * @param context 
  362.      */  
  363.     public static void checkCache(final Context context) {  
  364.         new Thread() {  
  365.             public void run() {  
  366.                 int state = 0;// 记录清除结果 0为都没清除, 1为只清除了sd卡, 2为只清除了rom Cache ,3  
  367.                                 // 为都清除了  
  368.                 String cacheS = "0M";  
  369.                 String cacheD = "0M";  
  370.                 File sdCache = new File(getExternalCacheDir(context)); // sd卡"mnt/sdcard/android/data/cn.eoe.app/cache/";  
  371.                 File cacheDir = context.getCacheDir(); // 手机data/data/com.mengniu.app/cache  
  372.                 try {  
  373.                     if (sdCache != null && sdCache.exists()) {  
  374.                         long sdFileSize = getFileSize(sdCache);  
  375.                         if (sdFileSize > 1024 * 1024 * 50) {  
  376.                             // SD需要清理  
  377.                             long clearFileSize = clear(sdCache);  
  378.                             state += 1;  
  379.                             cacheS = clearFileSize + "";  
  380.                         }  
  381.                     }  
  382.                     if (cacheDir != null && cacheDir.exists()) {  
  383.                         long cacheFileSize = getFileSize(cacheDir);  
  384.                         if (cacheFileSize > 1024 * 1024 * 50) {  
  385.                             // ROM需要清理  
  386.                             long clearFileSize = clear(cacheDir);  
  387.                             state += 2;  
  388.                             cacheD = clearFileSize + "";  
  389.                         }  
  390.                     }  
  391.                 } catch (Exception e) {  
  392.                     e.printStackTrace();  
  393.                 }  
  394.             };  
  395.         }.start();  
  396.     }  
  397.   
  398.     /** 
  399.      * 清除路径 
  400.      *  
  401.      * @param cacheDir 
  402.      * @return 
  403.      */  
  404.     public static long clear(File cacheDir) {  
  405.         long clearFileSize = 0;  
  406.         File[] files = cacheDir.listFiles();  
  407.         if (files == null)  
  408.             return 0;  
  409.         for (File f : files) {  
  410.             if (f.isFile()) {  
  411.                 if (System.currentTimeMillis() - f.lastModified() > CLEARTIME) {  
  412.                     long fileSize = f.length();  
  413.                     if (f.delete()) {  
  414.                         clearFileSize += fileSize;  
  415.                     }  
  416.                 }  
  417.             } else {  
  418.                 clear(f);  
  419.             }  
  420.         }  
  421.         return clearFileSize;  
  422.     }  
  423.   
  424.     /** 
  425.      * 取得文件大小 
  426.      *  
  427.      * @param f 
  428.      * @return 
  429.      * @throws Exception 
  430.      */  
  431.     public static long getFileSize(File f) throws Exception {  
  432.         long size = 0;  
  433.         File flist[] = f.listFiles();  
  434.         for (int i = 0; i < flist.length; i++) {  
  435.             if (flist[i].isDirectory()) {  
  436.                 size = size + getFileSize(flist[i]);  
  437.             } else {  
  438.                 size = size + flist[i].length();  
  439.             }  
  440.         }  
  441.         return size;  
  442.     }  
  443.   
  444.     /** 
  445.      * 转换文件大小 
  446.      *  
  447.      * @param fileS 
  448.      * @return 
  449.      */  
  450.     public static String FormetFileSize(long fileS) {  
  451.         DecimalFormat df = new DecimalFormat("#.00");  
  452.         String fileSizeString = "";  
  453.         if (fileS < 1024) {  
  454.             fileSizeString = df.format((double) fileS) + "B";  
  455.         } else if (fileS < 1048576) {  
  456.             fileSizeString = df.format((double) fileS / 1024) + "K";  
  457.         } else if (fileS < 1073741824) {  
  458.             fileSizeString = df.format((double) fileS / 1048576) + "M";  
  459.         } else {  
  460.             fileSizeString = df.format((double) fileS / 1073741824) + "G";  
  461.         }  
  462.         return fileSizeString;  
  463.     }  
  464.   
  465. }  
   DBHelper.java 和数据库有关的操作

[java]  view plain copy
  1. import android.content.ContentValues;  
  2. import android.content.Context;  
  3. import android.database.Cursor;  
  4. import android.database.sqlite.SQLiteDatabase;  
  5. import android.database.sqlite.SQLiteDatabase.CursorFactory;  
  6. import android.database.sqlite.SQLiteOpenHelper;  
  7. import android.provider.BaseColumns;  
  8.   
  9. public class DBHelper extends SQLiteOpenHelper {  
  10.   
  11.     private static final String DB_NAME = "cn";  
  12.     private static final int DB_VERSION = 2;  
  13.   
  14.     private SQLiteDatabase db;  
  15.       
  16.     private static DBHelper mdbHelper;  
  17.       
  18.     public static DBHelper getInstance(Context context)  
  19.     {  
  20.         if(mdbHelper==null)  
  21.         {  
  22.             mdbHelper=new DBHelper(context);  
  23.         }  
  24.         return mdbHelper;  
  25.     }  
  26.   
  27.     private DBHelper(Context context) {  
  28.         super(context, DB_NAME, null, DB_VERSION);  
  29.     }  
  30.   
  31.     private DBHelper(Context context, String name, CursorFactory factory,  
  32.             int version) {  
  33.         super(context, name, factory, version);  
  34.         // TODO Auto-generated constructor stub  
  35.     }  
  36.   
  37.     @Override  
  38.     public void onCreate(SQLiteDatabase db) {  
  39.         this.db = db;  
  40.         operateTable(db, "");  
  41.     }  
  42.   
  43.     @Override  
  44.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  45.         // TODO Auto-generated method stub  
  46.         if (oldVersion == newVersion) {  
  47.             return;  
  48.         }  
  49.         operateTable(db, "DROP TABLE IF EXISTS ");  
  50.         onCreate(db);  
  51.     }  
  52.   
  53.     public void operateTable(SQLiteDatabase db, String actionString) {  
  54.         Class<DatabaseColumn>[] columnsClasses = DatabaseColumn.getSubClasses();  
  55.         DatabaseColumn columns = null;  
  56.   
  57.         for (int i = 0; i < columnsClasses.length; i++) {  
  58.             try {  
  59.                 columns = columnsClasses[i].newInstance();  
  60.                 if ("".equals(actionString) || actionString == null) {  
  61.                     db.execSQL(columns.getTableCreateor());  
  62.                 } else {  
  63.                     db.execSQL(actionString + columns.getTableName());  
  64.                 }  
  65.             } catch (Exception e) {  
  66.                 e.printStackTrace();  
  67.             }  
  68.         }  
  69.   
  70.     }  
  71.   
  72.     public long insert(String Table_Name, ContentValues values) {  
  73.         if (db == null)  
  74.             db = getWritableDatabase();  
  75.         return db.insert(Table_Name, null, values);  
  76.     }  
  77.   
  78.     /** 
  79.      *  
  80.      * @param Table_Name 
  81.      * @param id 
  82.      * @return 影响行数 
  83.      */  
  84.     public int delete(String Table_Name, int id) {  
  85.         if (db == null)  
  86.             db = getWritableDatabase();  
  87.         return db.delete(Table_Name, BaseColumns._ID + "=?",  
  88.                 new String[] { String.valueOf(id) });  
  89.     }  
  90.   
  91.     /** 
  92.      * @param Table_Name 
  93.      * @param values 
  94.      * @param WhereClause 
  95.      * @param whereArgs 
  96.      * @return 影响行数 
  97.      */  
  98.     public int update(String Table_Name, ContentValues values,  
  99.             String WhereClause, String[] whereArgs) {  
  100.         if (db == null) {  
  101.             db = getWritableDatabase();  
  102.         }  
  103.         return db.update(Table_Name, values, WhereClause, whereArgs);  
  104.     }  
  105.   
  106.     public Cursor query(String Table_Name, String[] columns, String whereStr,  
  107.             String[] whereArgs) {  
  108.         if (db == null) {  
  109.             db = getReadableDatabase();  
  110.         }  
  111.         return db.query(Table_Name, columns, whereStr, whereArgs, nullnull,  
  112.                 null);  
  113.     }  
  114.   
  115.     public Cursor rawQuery(String sql, String[] args) {  
  116.         if (db == null) {  
  117.             db = getReadableDatabase();  
  118.         }  
  119.         return db.rawQuery(sql, args);  
  120.     }  
  121.   
  122.     public void ExecSQL(String sql) {  
  123.         if (db == null) {  
  124.             db = getWritableDatabase();  
  125.         }  
  126.         db.execSQL(sql);  
  127.     }  
  128.   
  129.     public void closeDb() {  
  130.         if (db != null) {  
  131.             db.close();  
  132.             db = null;  
  133.         }  
  134.     }  
  135.   
  136. }  
    CommonUtil.java

[java]  view plain copy
  1. import java.io.File;  
  2. import android.content.Context;  
  3. import android.os.Environment;  
  4. import android.os.StatFs;  
  5.   
  6. public class CommonUtil {  
  7.   
  8.     /** 
  9.      * 检测sdcard是否可用 
  10.      *  
  11.      * @return true为可用,否则为不可用 
  12.      */  
  13.     public static boolean sdCardIsAvailable() {  
  14.         String status = Environment.getExternalStorageState();  
  15.         if (!status.equals(Environment.MEDIA_MOUNTED))  
  16.             return false;  
  17.         return true;  
  18.     }  
  19.   
  20.     /** 
  21.      * Checks if there is enough Space on SDCard 
  22.      *  
  23.      * @param updateSize 
  24.      *            Size to Check 
  25.      * @return True if the Update will fit on SDCard, false if not enough space on SDCard Will also return false, if the SDCard is 
  26.      *         not mounted as read/write 
  27.      */  
  28.     public static boolean enoughSpaceOnSdCard(long updateSize) {  
  29.         String status = Environment.getExternalStorageState();  
  30.         if (!status.equals(Environment.MEDIA_MOUNTED))  
  31.             return false;  
  32.         return (updateSize < getRealSizeOnSdcard());  
  33.     }  
  34.   
  35.     /** 
  36.      * get the space is left over on sdcard 
  37.      */  
  38.     public static long getRealSizeOnSdcard() {  
  39.         File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath());  
  40.         StatFs stat = new StatFs(path.getPath());  
  41.         long blockSize = stat.getBlockSize();  
  42.         long availableBlocks = stat.getAvailableBlocks();  
  43.         return availableBlocks * blockSize;  
  44.     }  
  45.   
  46.     /** 
  47.      * Checks if there is enough Space on phone self 
  48.      *  
  49.      */  
  50.     public static boolean enoughSpaceOnPhone(long updateSize) {  
  51.         return getRealSizeOnPhone() > updateSize;  
  52.     }  
  53.   
  54.     /** 
  55.      * get the space is left over on phone self 
  56.      */  
  57.     public static long getRealSizeOnPhone() {  
  58.         File path = Environment.getDataDirectory();  
  59.         StatFs stat = new StatFs(path.getPath());  
  60.         long blockSize = stat.getBlockSize();  
  61.         long availableBlocks = stat.getAvailableBlocks();  
  62.         long realSize = blockSize * availableBlocks;  
  63.         return realSize;  
  64.     }  
  65.       
  66.     /** 
  67.      * 根据手机分辨率从dp转成px 
  68.      * @param context 
  69.      * @param dpValue 
  70.      * @return 
  71.      */  
  72.     public static  int dip2px(Context context, float dpValue) {    
  73.         final float scale = context.getResources().getDisplayMetrics().density;    
  74.         return (int) (dpValue * scale + 0.5f);    
  75.     }    
  76.         
  77.     /**  
  78.      * 根据手机的分辨率从 px(像素) 的单位 转成为 dp  
  79.      */    
  80.     public static  int px2dip(Context context, float pxValue) {    
  81.         final float scale = context.getResources().getDisplayMetrics().density;    
  82.         return (int) (pxValue / scale + 0.5f)-15;    
  83.     }    
  84.   
  85.       
  86. }  
    ThreadPoolManager.java  线程池管理类

[java]  view plain copy
  1. import java.util.concurrent.ExecutorService;  
  2. import java.util.concurrent.Executors;  
  3.   
  4. public class ThreadPoolManager {  
  5.     private ExecutorService service;  
  6.       
  7.     private ThreadPoolManager(){  
  8.         int num = Runtime.getRuntime().availableProcessors();  
  9.         service = Executors.newFixedThreadPool(num*2);  
  10.     }  
  11.       
  12.     private static ThreadPoolManager manager;  
  13.       
  14.       
  15.     public static ThreadPoolManager getInstance(){  
  16.         if(manager==null)  
  17.         {  
  18.             manager= new ThreadPoolManager();  
  19.         }  
  20.         return manager;  
  21.     }  
  22.       
  23.     public void addTask(Runnable runnable){  
  24.         service.submit(runnable);  
  25.     }  
  26.       
  27. }  
      MD5.java

[java]  view plain copy
  1. import java.security.MessageDigest;  
  2. import java.security.NoSuchAlgorithmException;  
  3.   
  4. import org.apache.http.impl.auth.UnsupportedDigestAlgorithmException;  
  5.   
  6. import android.util.Log;  
  7.   
  8. /** 
  9.  * @version 1.0 
  10.  */  
  11. public final class MD5 {  
  12.     private static final String LOG_TAG = "MD5";  
  13.     private static final String ALGORITHM = "MD5";  
  14.   
  15.     private static char sHexDigits[] = {  
  16.         '0''1''2''3''4''5''6''7''8''9''a''b''c''d''e''f'  
  17. };  
  18.     private static MessageDigest sDigest;  
  19.   
  20.     static {  
  21.         try {  
  22.             sDigest = MessageDigest.getInstance(ALGORITHM);  
  23.         } catch (NoSuchAlgorithmException e) {  
  24.             Log.e(LOG_TAG, "Get MD5 Digest failed.");  
  25.             throw new UnsupportedDigestAlgorithmException(ALGORITHM, e);  
  26.         }  
  27.     }  
  28.   
  29.     private MD5() {  
  30.     }  
  31.   
  32.       
  33.     final public static String encode(String source) {  
  34.         byte[] btyes = source.getBytes();  
  35.         byte[] encodedBytes = sDigest.digest(btyes);  
  36.   
  37.         return Utility.hexString(encodedBytes);  
  38.     }  
  39.   
  40. }  
      ImageCacheColumn.java

[java]  view plain copy
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. import android.net.Uri;  
  4.   
  5. public class ImageCacheColumn extends DatabaseColumn {  
  6.   
  7.     public final static String TABLE_NAME = "imageCache";  
  8.     public final static String TIMESTAMP = "timestamp";  
  9.     public final static String Url = "url";  
  10.     /** 
  11.      * 单位:天 
  12.      */  
  13.     public final static String PAST_TIME = "past_time";  
  14.       
  15.     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY  
  16.             + "/" + TABLE_NAME);  
  17.     private static final Map<String, String> mColumnMap = new HashMap<String, String>();  
  18.     static {  
  19.   
  20.         mColumnMap.put(_ID, "integer primary key autoincrement");  
  21.         mColumnMap.put(TIMESTAMP, "TimeStamp");  
  22.         mColumnMap.put(Url, "text");  
  23.         mColumnMap.put(PAST_TIME, "TimeStamp");  
  24.     }  
  25.       
  26.   
  27.     @Override  
  28.     public String getTableName() {  
  29.         // TODO Auto-generated method stub  
  30.         return TABLE_NAME;  
  31.     }  
  32.   
  33.     @Override  
  34.     public Uri getTableContent() {  
  35.         // TODO Auto-generated method stub  
  36.         return CONTENT_URI;  
  37.     }  
  38.   
  39.     @Override  
  40.     protected Map<String, String> getTableMap() {  
  41.         // TODO Auto-generated method stub  
  42.         return mColumnMap;  
  43.     }  
  44.   
  45. }  


handler分页加载ListView


在前面两节中,我们了解了如何从服务器中加载JSON数据。

现在,我们将把服务器中的JSON数据加载更新到ListView。

并且,结合之前博文的  “动态追加分页ListView数据”的相关知识,实现将服务器中的分页JSON数据,填充到ListView中。


回顾

以BaseAdapter做适配器的ListView:http://blog.csdn.net/jueblog/article/details/12114513

ListView内数据的动态追加:http://blog.csdn.net/jueblog/article/details/12148259


Activity的实现

[java]  view plain copy
  1. package com.app.myhandler;  
  2.   
  3. import org.json.JSONArray;  
  4. import org.json.JSONException;  
  5. import org.json.JSONObject;  
  6.   
  7. import android.app.Activity;  
  8. import android.os.Bundle;  
  9. import android.os.Handler;  
  10. import android.view.ContextMenu;  
  11. import android.view.ContextMenu.ContextMenuInfo;  
  12. import android.view.LayoutInflater;  
  13. import android.view.MenuItem;  
  14. import android.view.View;  
  15. import android.widget.AdapterView;  
  16. import android.widget.AdapterView.AdapterContextMenuInfo;  
  17. import android.widget.ListView;  
  18. import android.widget.TextView;  
  19. import android.widget.Toast;  
  20.   
  21. import com.app.adapter.MyWeixinJSON;  
  22. import com.app.util.MyApplication;  
  23. import com.app.util.MyThread;  
  24.   
  25. /** 
  26.  * 点击 追加数据的ListView 
  27.  * @author 402-9 
  28.  */  
  29. public class ListViewPage extends Activity {  
  30.     private ListView lv;  
  31.     private MyWeixinJSON mJson;  
  32.     private JSONArray mData = new JSONArray();// JSON数据源  
  33.     private View view_page_footer;// 底部视图  
  34.     private int page = 1;// 加载页码  
  35.     private int totalPage;// 总数据  
  36.     private String url = new String("http://192.168.8.4/wt_guang.php?frm=3g&cid=100");  
  37.     private TextView text_page;  
  38.       
  39.     private Handler handler = new Handler() {  
  40.         public void handleMessage(android.os.Message msg) {  
  41.             try {  
  42.                 String result = msg.obj.toString();  
  43.                 JSONObject object = new JSONObject(result);  
  44.                 totalPage = (Integer) object.get("totalPage");  
  45.                 if(mJson==null) {  
  46.                     mData = (JSONArray) object.get("items");  
  47.                     mJson = new MyWeixinJSON(mData, ListViewPage.this);  
  48.                     lv.setAdapter(mJson);//为ListView绑定适配器     
  49.                 } else {  
  50.                     mData = MyApplication.joinJSONArray(mData, (JSONArray) object.get("items"));  
  51.                     mJson.setList(mData);  
  52.                     mJson.notifyDataSetChanged();  
  53.                 }     
  54.                 text_page.setText("下一页");  
  55.             } catch (JSONException e) {  
  56.                 // TODO Auto-generated catch block  
  57.                 e.printStackTrace();  
  58.             }  
  59.         };  
  60.     };  
  61.   
  62.     @Override  
  63.     protected void onCreate(Bundle savedInstanceState) {  
  64.         // TODO Auto-generated method stub  
  65.         super.onCreate(savedInstanceState);  
  66.         setContentView(R.layout.weixin);  
  67.         lv = (ListView) findViewById(R.id.lv);  
  68.   
  69.         new MyThread(handler, url.toString(), 1).start();  
  70.           
  71.         view_page_footer = LayoutInflater.from(this).inflate(R.layout.view_page_footer, null);  
  72.         lv.addFooterView(view_page_footer);// 添加底部视图  
  73.         lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
  74.             @Override  
  75.             public void onItemClick(AdapterView<?> parent, View view,  
  76.                     int position, long id) {  
  77.                 // TODO Auto-generated method stub  
  78.                 try {  
  79.                     JSONObject jsonObject = (JSONObject) mData.get(position);  
  80.                     Toast.makeText(ListViewPage.this,  
  81.                             jsonObject.getString("title"), Toast.LENGTH_SHORT)  
  82.                             .show();  
  83.                 } catch (JSONException e) {  
  84.                     // TODO Auto-generated catch block  
  85.                     e.printStackTrace();  
  86.                 }  
  87.             }  
  88.         });  
  89.   
  90.         text_page = (TextView) view_page_footer.findViewById(R.id.text_page);  
  91.         text_page.setOnClickListener(new View.OnClickListener() {  
  92.             // 点击按钮 追加数据 并通知适配器  
  93.             @Override  
  94.             public void onClick(View v) {  
  95.                 // TODO Auto-generated method stub  
  96.                 page++;  
  97.                 if(page<=totalPage) {              
  98.                     text_page.setText("正在加载中...");  
  99.                     new MyThread(handler, url+"&p="+page, 1).start();                 
  100.                 } else {  
  101.                     text_page.setText("已是最末页...");  
  102.                     text_page.setEnabled(false);  
  103.                 }  
  104.             }  
  105.         });  
  106.     }  
  107. }  
[java]  view plain copy
  1. mData = MyApplication.joinJSONArray(mData, (JSONArray) object.get("items"));  
所调用的方法为 http://blog.csdn.net/jueblog/article/details/12622423中提供的方法。

适配器的修改

[java]  view plain copy
  1. package com.app.adapter;  
  2.   
  3. import org.json.JSONArray;  
  4. import org.json.JSONObject;  
  5.   
  6. import android.content.Context;  
  7. import android.view.LayoutInflater;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10. import android.widget.BaseAdapter;  
  11. import android.widget.ImageView;  
  12. import android.widget.TextView;  
  13.   
  14. import com.app.myhandler.R;  
  15.   
  16. public class MyWeixinJSON extends BaseAdapter {  
  17.     private LayoutInflater mInflater;// 动态布局映射  
  18.     private JSONArray list;  
  19.     private Context context;  
  20. //  private int i = 0;  
  21.   
  22.     public void setList(JSONArray list) {  
  23.         this.list = list;  
  24.     }  
  25.   
  26.     public MyWeixinJSON(JSONArray list, Context context) {  
  27.         this.list = list;  
  28.         this.context = context;  
  29.         this.mInflater = LayoutInflater.from(context);  
  30.     }  
  31.   
  32.     @Override  
  33.     public int getCount() {  
  34.         // TODO Auto-generated method stub  
  35.         return list.length();  
  36.     }  
  37.   
  38.     @Override  
  39.     public Object getItem(int position) {  
  40.         // TODO Auto-generated method stub  
  41.         return null;  
  42.     }  
  43.   
  44.     @Override  
  45.     public long getItemId(int position) {  
  46.         // TODO Auto-generated method stub  
  47.         return 0;  
  48.     }  
  49.   
  50.     @Override  
  51.     public View getView(int position, View convertView, ViewGroup parent) {  
  52.         // TODO Auto-generated method stub  
  53.         // System.out.println("正在渲染第"+position+"行  +++  "+ i++);  
  54.         OneView oneView;  
  55.         if (convertView == null) {  
  56.             convertView = mInflater.inflate(R.layout.item_weixin, null);// 根据布局文件实例化view  
  57.             oneView = new OneView();  
  58.             oneView.title = (TextView) convertView.findViewById(R.id.title);// 找某个控件  
  59.             oneView.time = (TextView) convertView.findViewById(R.id.time);  
  60.             oneView.info = (TextView) convertView.findViewById(R.id.info);  
  61.             oneView.img = (ImageView) convertView.findViewById(R.id.img);  
  62.             convertView.setTag(oneView);// 把View和某个对象关联起来  
  63.         } else {  
  64.             oneView = (OneView) convertView.getTag();  
  65.         }  
  66.         JSONObject jObject = null;  
  67.         try {  
  68.             jObject = list.getJSONObject(position);// 根据position获取集合类中某行数据  
  69.             oneView.title.setText(jObject.get("itemID").toString());// 给该控件设置数据(数据从集合类中来)  
  70.             oneView.time.setText(jObject.get("price").toString());  
  71.             oneView.info.setText(jObject.get("title").toString());  
  72.             oneView.img.setBackgroundResource(R.drawable.special_spring_head2);  
  73.             // oneView.img.setImageDrawable(Drawable.createFromStream(new  
  74.             // URI(jObject.getString("pic_url"))., "src"));  
  75.         } catch (Exception e) {  
  76.             // TODO: handle exception  
  77.         }  
  78.         return convertView;  
  79.     }  
  80.   
  81.     /** 把每行布局文件的各个控件包装成一个对象 */  
  82.     private class OneView {  
  83.         TextView title;  
  84.         TextView time;  
  85.         TextView info;  
  86.         ImageView img;  
  87.     }  
  88. }  

JSON数据

最后 瞄一眼服务器上的JSON数据:
[plain]  view plain copy
  1. {  
  2.     "totalCount": 9,  
  3.     "totalPage": 2,  
  4.     "items": [  
  5.         {  
  6.             "itemID": "13889239752",  
  7.             "title": "PEACE DOVE 正品* 真兔毛 范儿厚毛衣开衫 清库价",  
  8.             "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i5/T1ELuGXj0yXXbAjkA._083138.jpg",  
  9.             "click_url": "http://item.taobao.com/item.htm?id=13889239752",  
  10.             "price": "98.00",  
  11.             "promotion_price": "0.00",  
  12.             "liked": "0"  
  13.         },  
  14.         {  
  15.             "itemID": "13357878044",  
  16.             "title": "现货 呛口小辣椒11月29日香港行snidel 翻领双排扣毛呢大衣外套",  
  17.             "pic_url": "http://img03.taobaocdn.com/bao/uploaded/i3/T1KTWDXipfXXXmSFDX_113804.jpg",  
  18.             "click_url": "http://item.taobao.com/item.htm?id=13357878044",  
  19.             "price": "278.00",  
  20.             "promotion_price": "0.00",  
  21.             "liked": "0"  
  22.         },  
  23.         {  
  24.             "itemID": "12740033159",  
  25.             "title": "2011新款呛口小辣椒子萱王翔貉子毛毛领军绿色工装棉服棉衣大衣",  
  26.             "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i1/T1WiCoXi4BXXad.Qs9_073952.jpg",  
  27.             "click_url": "http://item.taobao.com/item.htm?id=12740033159",  
  28.             "price": "628.00",  
  29.             "promotion_price": "0.00",  
  30.             "liked": "0"  
  31.         },  
  32.         {  
  33.             "itemID": "8603809978",  
  34.             "title": "圣诞款大衣 三色入",  
  35.             "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i1/T1GWXTXhhxXXaMGqo3_045745.jpg",  
  36.             "click_url": "http://s.click.taobao.com/t_8?e=7HZ6jHSTZTlU5Xur%2B%2F50d2poSR5Ba0A%2Fi8tpiW6m04z%2BWKzb6yELcZsJq6zxF7EMTqmpst%2FNOGepsZQvjjXKR0huW7F8S1whVzYKzICAu1lT&p=mm_10009952_0_0",  
  37.             "price": "165.00",  
  38.             "promotion_price": "0.00",  
  39.             "liked": "0"  
  40.         },  
  41.         {  
  42.             "itemID": "10629952619",  
  43.             "title": "裁诺 春装新款2012女装 韩版带帽中长款加厚卫衣 女 秋冬外套大码",  
  44.             "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i1/T1PASfXfpoXXc.O0vb_093452.jpg",  
  45.             "click_url": "http://item.taobao.com/item.htm?id=10629952619",  
  46.             "price": "158.00",  
  47.             "promotion_price": "0.00",  
  48.             "liked": "0"  
  49.         },  
  50.         {  
  51.             "itemID": "9158721999",  
  52.             "title": "清仓包邮 出口欧美大码超酷水洗打磨斜开襟修身加厚抓绒卫衣外套",  
  53.             "pic_url": "http://img03.taobaocdn.com/bao/uploaded/i3/T12t5KXXJqXXbjSw.8_100610.jpg",  
  54.             "click_url": "http://s.click.taobao.com/t_8?e=7HZ6jHSTZPhaK%2FmBeumaH2YuQWkORNRTfXSCf08v9iowvo%2F0Akhswsb9gfNlScFQL%2Bc%2Bp%2BFUQiV5QW7qQyiq%2BJQ4CS399exvrZIKVVH3ZZNULw%3D%3D&p=mm_10009952_0_0",  
  55.             "price": "398.00",  
  56.             "promotion_price": "0.00",  
  57.             "liked": "0"  
  58.         }  
  59.     ]  
  60. }  

效果图

由于服务器JSON数据是比较随意找的,所以内容不是很适合“微信”屏幕。
不要在意这些细节、、、

程序执行的第一页:


点击下一页,将会动态追加第二页的数据


由于服务器中只有两页数据,所以当再次点击“下一页”时,会显示“已是最末页”


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值