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文件中:
- public final class ActivityThread {
- ......
- public static final void main(String[] args) {
- ......
- Looper.prepareMainLooper();
- ......
- ActivityThread thread = new ActivityThread();
- thread.attach(false);
- ......
- Looper.loop();
- ......
- thread.detach();
- ......
- }
- }
首先看Looper.prepareMainLooper函数的实现,这是一个静态成员函数,定义在frameworks/base/core/java/android/os/Looper.java文件中:
- public class Looper {
- ......
- private static final ThreadLocal sThreadLocal = new ThreadLocal();
- final MessageQueue mQueue;
- ......
- /** Initialize the current thread as a looper.
- * This gives you a chance to create handlers that then reference
- * this looper, before actually starting the loop. Be sure to call
- * {@link #loop()} after calling this method, and end it by calling
- * {@link #quit()}.
- */
- public static final void prepare() {
- if (sThreadLocal.get() != null) {
- throw new RuntimeException("Only one Looper may be created per thread");
- }
- sThreadLocal.set(new Looper());
- }
- /** Initialize the current thread as a looper, marking it as an application's main
- * looper. The main looper for your application is created by the Android environment,
- * so you should never need to call this function yourself.
- * {@link #prepare()}
- */
- public static final void prepareMainLooper() {
- prepare();
- setMainLooper(myLooper());
- if (Process.supportsProcesses()) {
- myLooper().mQueue.mQuitAllowed = false;
- }
- }
- private synchronized static void setMainLooper(Looper looper) {
- mMainLooper = looper;
- }
- /**
- * Return the Looper object associated with the current thread. Returns
- * null if the calling thread is not associated with a Looper.
- */
- public static final Looper myLooper() {
- return (Looper)sThreadLocal.get();
- }
- private Looper() {
- mQueue = new MessageQueue();
- mRun = true;
- mThread = Thread.currentThread();
- }
- ......
- }
- public class MessageQueue {
- ......
- private int mPtr; // used by native code
- private native void nativeInit();
- MessageQueue() {
- nativeInit();
- }
- ......
- }
- static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
- NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
- if (! nativeMessageQueue) {
- jniThrowRuntimeException(env, "Unable to allocate native queue");
- return;
- }
- android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
- }
- NativeMessageQueue::NativeMessageQueue() {
- mLooper = Looper::getForThread();
- if (mLooper == NULL) {
- mLooper = new Looper(false);
- Looper::setForThread(mLooper);
- }
- }
这个Looper的创建过程也很重要,不过我们暂时放一放,先分析完android_os_MessageQueue_nativeInit函数的执行,它创建了本地消息队列NativeMessageQueue对象之后,接着调用android_os_MessageQueue_setNativeMessageQueue函数来把这个消息队列对象保存在前面我们在Java层中创建的MessageQueue对象的mPtr成员变量里面:
- static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
- NativeMessageQueue* nativeMessageQueue) {
- env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
- reinterpret_cast<jint>(nativeMessageQueue));
- }
我们再回到NativeMessageQueue的构造函数中,看看JNI层的Looper对象的创建过程,即看看它的构造函数是如何实现的,这个Looper类实现在frameworks/base/libs/utils/Looper.cpp文件中:
- Looper::Looper(bool allowNonCallbacks) :
- mAllowNonCallbacks(allowNonCallbacks),
- mResponseIndex(0) {
- int wakeFds[2];
- int result = pipe(wakeFds);
- ......
- mWakeReadPipeFd = wakeFds[0];
- mWakeWritePipeFd = wakeFds[1];
- ......
- #ifdef LOOPER_USES_EPOLL
- // Allocate the epoll instance and register the wake pipe.
- mEpollFd = epoll_create(EPOLL_SIZE_HINT);
- ......
- struct epoll_event eventItem;
- memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
- eventItem.events = EPOLLIN;
- eventItem.data.fd = mWakeReadPipeFd;
- result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
- ......
- #else
- ......
- #endif
- ......
- }
- int wakeFds[2];
- int result = pipe(wakeFds);
- ......
- mWakeReadPipeFd = wakeFds[0];
- mWakeWritePipeFd = wakeFds[1];
要使用Linux系统的epoll机制,首先要通过epoll_create来创建一个epoll专用的文件描述符:
- mEpollFd = epoll_create(EPOLL_SIZE_HINT);
接着还要通过epoll_ctl函数来告诉epoll要监控相应的文件描述符的什么事件:
- struct epoll_event eventItem;
- memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
- eventItem.events = EPOLLIN;
- eventItem.data.fd = mWakeReadPipeFd;
- result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
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函数进入到消息循环中去了:
- public class Looper {
- ......
- public static final void loop() {
- Looper me = myLooper();
- MessageQueue queue = me.mQueue;
- ......
- while (true) {
- Message msg = queue.next(); // might block
- ......
- if (msg != null) {
- if (msg.target == null) {
- // No target is a magic identifier for the quit message.
- return;
- }
- ......
- msg.target.dispatchMessage(msg);
- ......
- msg.recycle();
- }
- }
- }
- ......
- }
这个函数最关键的地方便是从消息队列中获取下一个要处理的消息了,即MessageQueue.next函数,它实现frameworks/base/core/java/android/os/MessageQueue.java文件中:
- public class MessageQueue {
- ......
- final Message next() {
- int pendingIdleHandlerCount = -1; // -1 only during first iteration
- int nextPollTimeoutMillis = 0;
- for (;;) {
- if (nextPollTimeoutMillis != 0) {
- Binder.flushPendingCommands();
- }
- nativePollOnce(mPtr, nextPollTimeoutMillis);
- synchronized (this) {
- // Try to retrieve the next message. Return if found.
- final long now = SystemClock.uptimeMillis();
- final Message msg = mMessages;
- if (msg != null) {
- final long when = msg.when;
- if (now >= when) {
- mBlocked = false;
- mMessages = msg.next;
- msg.next = null;
- if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
- return msg;
- } else {
- nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
- }
- } else {
- nextPollTimeoutMillis = -1;
- }
- // If first time, then get the number of idlers to run.
- if (pendingIdleHandlerCount < 0) {
- pendingIdleHandlerCount = mIdleHandlers.size();
- }
- if (pendingIdleHandlerCount == 0) {
- // No idle handlers to run. Loop and wait some more.
- mBlocked = true;
- continue;
- }
- if (mPendingIdleHandlers == null) {
- mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
- }
- mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
- }
- // Run the idle handlers.
- // We only ever reach this code block during the first iteration.
- for (int i = 0; i < pendingIdleHandlerCount; i++) {
- final IdleHandler idler = mPendingIdleHandlers[i];
- mPendingIdleHandlers[i] = null; // release the reference to the handler
- boolean keep = false;
- try {
- keep = idler.queueIdle();
- } catch (Throwable t) {
- Log.wtf("MessageQueue", "IdleHandler threw exception", t);
- }
- if (!keep) {
- synchronized (this) {
- mIdleHandlers.remove(idler);
- }
- }
- }
- // Reset the idle handler count to 0 so we do not run them again.
- pendingIdleHandlerCount = 0;
- // While calling an idle handler, a new message could have been delivered
- // so go back and look again for a pending message without waiting.
- nextPollTimeoutMillis = 0;
- }
- }
- ......
- }
执行下面语句是看看当前消息队列中有没有消息:
- nativePollOnce(mPtr, nextPollTimeoutMillis);
当前nativePollOnce返回后,就去看看消息队列中有没有消息:
- final Message msg = mMessages;
- if (msg != null) {
- final long when = msg.when;
- if (now >= when) {
- mBlocked = false;
- mMessages = msg.next;
- msg.next = null;
- if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
- return msg;
- } else {
- nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
- }
- } else {
- nextPollTimeoutMillis = -1;
- }
- nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
- nextPollTimeoutMillis = -1;
这里计算出来的等待时间都是在下次调用nativePollOnce时使用的。
这里说的等待,是空闲等待,而不是忙等待,因此,在进入空闲等待状态前,如果应用程序注册了IdleHandler接口来处理一些事情,那么就会先执行这里IdleHandler,然后再进入等待状态。IdlerHandler是定义在MessageQueue的一个内部类:
- public class MessageQueue {
- ......
- /**
- * Callback interface for discovering when a thread is going to block
- * waiting for more messages.
- */
- public static interface IdleHandler {
- /**
- * Called when the message queue has run out of messages and will now
- * wait for more. Return true to keep your idle handler active, false
- * to have it removed. This may be called if there are still messages
- * pending in the queue, but they are all scheduled to be dispatched
- * after the current time.
- */
- boolean queueIdle();
- }
- ......
- }
回到MessageQueue函数中,它接下来就是在进入等待状态前,看看有没有IdleHandler是需要执行的:
- // If first time, then get the number of idlers to run.
- if (pendingIdleHandlerCount < 0) {
- pendingIdleHandlerCount = mIdleHandlers.size();
- }
- if (pendingIdleHandlerCount == 0) {
- // No idle handlers to run. Loop and wait some more.
- mBlocked = true;
- continue;
- }
- if (mPendingIdleHandlers == null) {
- mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
- }
- mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
接下来就是执行这些注册了的IdleHanlder了:
- // Run the idle handlers.
- // We only ever reach this code block during the first iteration.
- for (int i = 0; i < pendingIdleHandlerCount; i++) {
- final IdleHandler idler = mPendingIdleHandlers[i];
- mPendingIdleHandlers[i] = null; // release the reference to the handler
- boolean keep = false;
- try {
- keep = idler.queueIdle();
- } catch (Throwable t) {
- Log.wtf("MessageQueue", "IdleHandler threw exception", t);
- }
- if (!keep) {
- synchronized (this) {
- mIdleHandlers.remove(idler);
- }
- }
- }
- // While calling an idle handler, a new message could have been delivered
- // so go back and look again for a pending message without waiting.
- nextPollTimeoutMillis = 0;
- static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
- jint ptr, jint timeoutMillis) {
- NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
- nativeMessageQueue->pollOnce(timeoutMillis);
- }
- void NativeMessageQueue::pollOnce(int timeoutMillis) {
- mLooper->pollOnce(timeoutMillis);
- }
- int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
- int result = 0;
- for (;;) {
- ......
- if (result != 0) {
- ......
- return result;
- }
- result = pollInner(timeoutMillis);
- }
- }
函数pollInner的定义如下:
- int Looper::pollInner(int timeoutMillis) {
- ......
- int result = ALOOPER_POLL_WAKE;
- ......
- #ifdef LOOPER_USES_EPOLL
- struct epoll_event eventItems[EPOLL_MAX_EVENTS];
- int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
- bool acquiredLock = false;
- #else
- ......
- #endif
- if (eventCount < 0) {
- if (errno == EINTR) {
- goto Done;
- }
- LOGW("Poll failed with an unexpected error, errno=%d", errno);
- result = ALOOPER_POLL_ERROR;
- goto Done;
- }
- if (eventCount == 0) {
- ......
- result = ALOOPER_POLL_TIMEOUT;
- goto Done;
- }
- ......
- #ifdef LOOPER_USES_EPOLL
- for (int i = 0; i < eventCount; i++) {
- int fd = eventItems[i].data.fd;
- uint32_t epollEvents = eventItems[i].events;
- if (fd == mWakeReadPipeFd) {
- if (epollEvents & EPOLLIN) {
- awoken();
- } else {
- LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
- }
- } else {
- ......
- }
- }
- if (acquiredLock) {
- mLock.unlock();
- }
- Done: ;
- #else
- ......
- #endif
- ......
- return result;
- }
- int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
当mEpollFd所监控的文件描述符发生了要监控的IO事件后或者监控时间超时后,线程就从epoll_wait返回了,否则线程就会在epoll_wait函数中进入睡眠状态了。返回后如果eventCount等于0,就说明是超时了:
- if (eventCount == 0) {
- ......
- result = ALOOPER_POLL_TIMEOUT;
- goto Done;
- }
- for (int i = 0; i < eventCount; i++) {
- int fd = eventItems[i].data.fd;
- uint32_t epollEvents = eventItems[i].events;
- if (fd == mWakeReadPipeFd) {
- if (epollEvents & EPOLLIN) {
- awoken();
- } else {
- LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
- }
- } else {
- ......
- }
- }
函数awoken的实现很简单,它只是把管道中的内容都读取出来:
- void Looper::awoken() {
- ......
- char buffer[16];
- ssize_t nRead;
- do {
- nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
- } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
- }
这样,消息的循环过程就分析完了,这部分逻辑还是比较复杂的,它利用Linux系统中的管道(pipe)进程间通信机制来实现消息的等待和处理,不过,了解了这部分内容之后,下面我们分析消息的发送和处理就简单多了。
2. 消息的发送
应用程序的主线程准备就好消息队列并且进入到消息循环后,其它地方就可以往这个消息队列中发送消息了。我们继续以文章开始介绍的Android应用程序启动过程源代码分析一文中的应用程序启动过为例,说明应用程序是如何把消息加入到应用程序的消息队列中去的。
在Android应用程序启动过程源代码分析这篇文章的Step 30中,ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函数通知应用程序,它可以加载应用程序的默认Activity了,这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:
- public final class ActivityThread {
- ......
- private final class ApplicationThread extends ApplicationThreadNative {
- ......
- // we use token to identify this activity without having to send the
- // activity itself back to the activity manager. (matters more with ipc)
- public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) {
- ActivityClientRecord r = new ActivityClientRecord();
- r.token = token;
- r.ident = ident;
- r.intent = intent;
- r.activityInfo = info;
- r.state = state;
- r.pendingResults = pendingResults;
- r.pendingIntents = pendingNewIntents;
- r.startsNotResumed = notResumed;
- r.isForward = isForward;
- queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
- }
- ......
- }
- ......
- }
- public final class ActivityThread {
- ......
- private final class ApplicationThread extends ApplicationThreadNative {
- ......
- // if the thread hasn't started yet, we don't have the handler, so just
- // save the messages until we're ready.
- private final void queueOrSendMessage(int what, Object obj) {
- queueOrSendMessage(what, obj, 0, 0);
- }
- ......
- private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
- synchronized (this) {
- ......
- Message msg = Message.obtain();
- msg.what = what;
- msg.obj = obj;
- msg.arg1 = arg1;
- msg.arg2 = arg2;
- mH.sendMessage(msg);
- }
- }
- ......
- }
- ......
- }
- public final class ActivityThread {
- ......
- private final class H extends Handler {
- ......
- public void handleMessage(Message msg) {
- ......
- switch (msg.what) {
- ......
- }
- ......
- }
- ......
- }
这个H类就是通过其成员函数handleMessage函数来处理消息的了,后面我们分析消息的处理过程时会看到。
ActivityThread类的这个mH成员变量是什么时候创建的呢?我们前面在分析应用程序的消息循环时,说到当应用程序进程启动之后,就会加载ActivityThread类的main函数里面,在这个main函数里面,在通过Looper类进入消息循环之前,会在当前进程中创建一个ActivityThread实例:
- public final class ActivityThread {
- ......
- public static final void main(String[] args) {
- ......
- ActivityThread thread = new ActivityThread();
- thread.attach(false);
- ......
- }
- }
- public final class ActivityThread {
- ......
- final H mH = new H();
- ......
- }
- public class Handler {
- ......
- public Handler() {
- ......
- mLooper = Looper.myLooper();
- ......
- mQueue = mLooper.mQueue;
- ......
- }
- final MessageQueue mQueue;
- final Looper mLooper;
- ......
- }
- public class Looper {
- ......
- public static final Looper myLooper() {
- return (Looper)sThreadLocal.get();
- }
- ......
- }
有了这个Handler对象mH后,就可以通过它来往应用程序的消息队列中加入新的消息了。回到前面的queueOrSendMessage函数中,当它准备好了一个Message对象msg后,就开始调用mH.sendMessage函数来发送消息了,这个函数定义在frameworks/base/core/java/android/os/Handler.java文件中:
- public class Handler {
- ......
- public final boolean sendMessage(Message msg)
- {
- return sendMessageDelayed(msg, 0);
- }
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- {
- if (delayMillis < 0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
- public boolean sendMessageAtTime(Message msg, long uptimeMillis)
- {
- boolean sent = false;
- MessageQueue queue = mQueue;
- if (queue != null) {
- msg.target = this;
- sent = queue.enqueueMessage(msg, uptimeMillis);
- }
- else {
- ......
- }
- return sent;
- }
- ......
- }
在sendMessageAtTime函数,首先得到应用程序的消息队列mQueue,这是在Handler对象构造时初始化好的,前面已经分析过了,接着设置这个消息的目标对象target,即这个消息最终是由谁来处理的:
- msg.target = this;
函数最后调用queue.enqueueMessage来把这个消息加入到应用程序的消息队列中去,这个函数实现在frameworks/base/core/java/android/os/MessageQueue.java文件中:
- public class MessageQueue {
- ......
- final boolean enqueueMessage(Message msg, long when) {
- ......
- final boolean needWake;
- synchronized (this) {
- ......
- msg.when = when;
- //Log.d("MessageQueue", "Enqueing: " + msg);
- Message p = mMessages;
- if (p == null || when == 0 || when < p.when) {
- msg.next = p;
- mMessages = msg;
- needWake = mBlocked; // new head, might need to wake up
- } else {
- Message prev = null;
- while (p != null && p.when <= when) {
- prev = p;
- p = p.next;
- }
- msg.next = prev.next;
- prev.next = msg;
- needWake = false; // still waiting on head, no need to wake up
- }
- }
- if (needWake) {
- nativeWake(mPtr);
- }
- return true;
- }
- ......
- }
第一种情况比较简单,只要把消息放在消息队列头就可以了:
- msg.next = p;
- mMessages = msg;
- needWake = mBlocked; // new head, might need to wake up
- Message prev = null;
- while (p != null && p.when <= when) {
- prev = p;
- p = p.next;
- }
- msg.next = prev.next;
- prev.next = msg;
- needWake = false; // still waiting on head, no need to wake up
- static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {
- NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
- return nativeMessageQueue->wake();
- }
- void NativeMessageQueue::wake() {
- mLooper->wake();
- }
- void Looper::wake() {
- ......
- ssize_t nWrite;
- do {
- nWrite = write(mWakeWritePipeFd, "W", 1);
- } while (nWrite == -1 && errno == EINTR);
- .......
- }
这时候既然管道中有内容可读了,应用程序的主线程就会从这里的Looper类的pollInner函数返回到JNI层的nativePollOnce函数,最后返回到Java层中的MessageQueue.next函数中去,这里它就会发现消息队列中有新的消息需要处理了,于就会处理这个消息。
3. 消息的处理
前面在分析消息循环时,说到应用程序的主线程是在Looper类的loop成员函数中进行消息循环过程的,这个函数定义在frameworks/base/core/java/android/os/Looper.java文件中:
- public class Looper {
- ......
- public static final void loop() {
- Looper me = myLooper();
- MessageQueue queue = me.mQueue;
- ......
- while (true) {
- Message msg = queue.next(); // might block
- ......
- if (msg != null) {
- if (msg.target == null) {
- // No target is a magic identifier for the quit message.
- return;
- }
- ......
- msg.target.dispatchMessage(msg);
- ......
- msg.recycle();
- }
- }
- }
- ......
- }
我们继续以前面分析消息的发送时所举的例子来分析消息的处理过程。前面说到,在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文件中:
- public class Handler {
- ......
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
- ......
- }
- public final class ActivityThread {
- ......
- private final class H extends Handler {
- ......
- public void handleMessage(Message msg) {
- ......
- switch (msg.what) {
- case LAUNCH_ACTIVITY: {
- ActivityClientRecord r = (ActivityClientRecord)msg.obj;
- r.packageInfo = getPackageInfoNoCheck(
- r.activityInfo.applicationInfo);
- handleLaunchActivity(r, null);
- } break;
- ......
- }
- ......
- }
- ......
- }
至此,我们就从消息循环、消息发送和消息处理三个部分分析完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))。
下面看一个例子:
- private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
- protected Long doInBackground(URL... urls) {
- int count = urls.length;
- long totalSize = 0;
- for (int i = 0; i < count; i++) {
- totalSize += Downloader.downloadFile(urls[i]);
- publishProgress((int) ((i / (float) count) * 100));
- // Escape early if cancel() is called
- if (isCancelled()) break;
- }
- return totalSize;
- }
- protected void onProgressUpdate(Integer... progress) {
- setProgressPercent(progress[0]);
- }
- protected void onPostExecute(Long result) {
- showDialog("Downloaded " + result + " bytes");
- }
- }
- new DownloadFilesTask().execute(url1, url2, url3);
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、一个模拟下载进度条的例子
- package com.example.asynctaskdemo;
- import android.app.Activity;
- import android.os.AsyncTask;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Button;
- import android.widget.ProgressBar;
- import android.widget.TextView;
- public class Asy1Activity extends Activity {
- Button download;
- ProgressBar pb;
- TextView tv;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.asy1);
- pb = (ProgressBar) findViewById(R.id.pb);
- tv = (TextView) findViewById(R.id.tv);
- download = (Button) findViewById(R.id.download);
- download.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- DownloadTask task=new DownloadTask();
- task.execute(100);
- }
- });
- }
- class DownloadTask extends AsyncTask<Integer, Integer, String>{
- @Override
- protected void onCancelled() {
- // TODO Auto-generated method stub
- super.onCancelled();
- }
- @Override
- protected void onPostExecute(String result) {
- setTitle(result);
- super.onPostExecute(result);
- }
- @Override
- protected void onPreExecute() {
- // TODO Auto-generated method stub
- super.onPreExecute();
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- // TODO Auto-generated method stub
- super.onProgressUpdate(values);
- tv.setText(values[0]+"%");
- }
- @Override
- protected String doInBackground(Integer... params) {
- for(int i=0;i<=100;i++){
- pb.setProgress(i);
- publishProgress(i);
- try {
- Thread.sleep(params[0]);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return "执行完毕";
- }
- }
- }
1、一个从网络下载图片的例子
- package com.example.asynctaskdemo;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.HttpURLConnection;
- import java.net.MalformedURLException;
- import java.net.URL;
- import android.app.Activity;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.AsyncTask;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.ImageView;
- import android.widget.ProgressBar;
- public class Asy2Activity extends Activity {
- private Button button;
- private ProgressBar progressBar;
- private ImageView imageView;
- private final String imageUrl = "http://avatar.youkuaiyun.com/D/1/4/1_wangjinyu501.jpg";
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.asy2);
- initView();
- }
- private void initView() {
- button = (Button) findViewById(R.id.button);
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- AsyncTaskLoadImage asyncTaskLoadImage = new AsyncTaskLoadImage();
- asyncTaskLoadImage.execute(imageUrl);
- }
- });
- progressBar = (ProgressBar) findViewById(R.id.pb);
- imageView = (ImageView) findViewById(R.id.imageview);
- }
- class AsyncTaskLoadImage extends AsyncTask<String, Integer, Bitmap> {
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- }
- @Override
- protected Bitmap doInBackground(String... params) {
- Bitmap bitmap = null;
- try {
- URL url = new URL(params[0]);
- HttpURLConnection urlConnection = (HttpURLConnection) url
- .openConnection();
- urlConnection.connect();
- int MAX = urlConnection.getContentLength();
- progressBar.setMax(MAX);
- InputStream inputStream = urlConnection.getInputStream();
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byte[] b = new byte[1024];
- int len = 0;
- int processBarNum = 0;
- while ((len = inputStream.read(b)) != -1) {
- byteArrayOutputStream.write(b, 0, len);
- processBarNum += len;
- publishProgress(processBarNum);
- }
- // bitmap = BitmapFactory.decodeStream(inputStream);
- bitmap = BitmapFactory.decodeByteArray(
- byteArrayOutputStream.toByteArray(), 0, MAX);
- inputStream.close();
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return bitmap;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- progressBar.setProgress(values[0]);
- super.onProgressUpdate(values);
- }
- @Override
- protected void onPostExecute(Bitmap result) {
- imageView.setImageBitmap(result);
- super.onPostExecute(result);
- }
- }
- }
Android之Looper、Handler、Message、MessageQueue应用篇
简介
上一篇文章介绍了Handler、Message、MessageQueue等Android线程交互方面的内容,Android之理解Looper、Handler、Message、MessageQueue。下面开始实践,学习如何去使用以及应用到程序里面。
实例
在这里使用ListView作为异步下载图片的环境。
1、Handle+Runnable
实现思路是:
- package com.example.handlerloadiage;
- import java.io.IOException;
- import java.net.URL;
- import android.app.Activity;
- import android.graphics.drawable.Drawable;
- import android.os.Bundle;
- import android.os.Handler;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.ListView;
- public class Handler_Runnable_Mode extends Activity {
- private ListView listview;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_handlerimageloader);
- listview = (ListView) findViewById(R.id.listview);
- listview.setAdapter(new MyAdapter());
- }
- private class MyAdapter extends BaseAdapter {
- public MyAdapter() {
- }
- @Override
- public int getCount() {
- // TODO Auto-generated method stub
- return 100;
- }
- @Override
- public Object getItem(int position) {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public long getItemId(int position) {
- // TODO Auto-generated method stub
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(getApplicationContext())
- .inflate(R.layout.list_item, null);
- }
- final ImageView image = (ImageView) convertView
- .findViewById(R.id.imageview);
- final String imageURL = "http://avatar.youkuaiyun.com/D/1/4/3_wangjinyu501.jpg";
- Handler handler = new Handler();
- handler.post(new Runnable() {
- public void run() {
- Drawable drawable = null;
- try {
- drawable = Drawable.createFromStream(
- new URL(imageURL).openStream(), "image.jpg");
- } catch (IOException e) {
- Log.d("test", e.getMessage());
- }
- if (drawable == null) {
- Log.d("test", "null drawable");
- } else {
- Log.d("test", "not null drawable");
- }
- if (drawable == null) {
- image.setImageResource(R.drawable.ic_launcher);
- } else {
- image.setImageDrawable(drawable);
- }
- }
- });
- return convertView;
- }
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- }
快速滑动的过程中,还是出现了ANR的现象。
这是因为handler.post(new Runnable()这个方法,并没有开启一个新的线程,他还是在UI主线程中,所以导致出现ANR现象。
2、Handler+Runnable+Message
实现思路:
1:在UI线程中启动一个线程,让这个线程去下载图片。
2:图片完成下载后发送一个消息去通知UI线程
3:UI线程获取到消息后,更新UI。
实现代码:
- package com.example.handlerloadiage;
- import java.io.IOException;
- import java.net.URL;
- import android.app.Activity;
- import android.graphics.drawable.Drawable;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.ListView;
- public class Handler_Runnable_Mode extends Activity {
- private ListView listview;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_handlerimageloader);
- listview = (ListView) findViewById(R.id.listview);
- listview.setAdapter(new MyAdapter());
- }
- private class MyAdapter extends BaseAdapter {
- public MyAdapter() {
- }
- @Override
- public int getCount() {
- // TODO Auto-generated method stub
- return 100;
- }
- @Override
- public Object getItem(int position) {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public long getItemId(int position) {
- // TODO Auto-generated method stub
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(getApplicationContext())
- .inflate(R.layout.list_item, null);
- }
- final ImageView image = (ImageView) convertView
- .findViewById(R.id.imageview);
- final String imageURL = "http://avatar.youkuaiyun.com/D/1/4/3_wangjinyu501.jpg";
- final Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- Drawable d=(Drawable) msg.obj;
- if (d == null) {
- image.setImageResource(R.drawable.ic_launcher);
- } else {
- image.setImageDrawable(d);
- }
- }
- };
- handler.post(new Runnable() {
- public void run() {
- Drawable drawable = null;
- try {
- drawable = Drawable.createFromStream(
- new URL(imageURL).openStream(), "image.jpg");
- Message message=handler.obtainMessage();
- message.obj=drawable;
- handler.sendMessage(message);
- } catch (IOException e) {
- Log.d("test", e.getMessage());
- }
- if (drawable == null) {
- Log.d("test", "null drawable");
- } else {
- Log.d("test", "not null drawable");
- }
- }
- });
- return convertView;
- }
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- }
3、Handler+Thread+Message
这种模式使用了线程,所以是异步加载。
- package com.example.handlerloadiage;
- import java.io.IOException;
- import java.net.URL;
- import android.app.Activity;
- import android.graphics.drawable.Drawable;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.ListView;
- public class HandlerThreadMessageActivity extends Activity {
- private ListView listview;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_handlerimageloader);
- listview = (ListView) findViewById(R.id.listview);
- listview.setAdapter(new MyAdapter());
- }
- private class MyAdapter extends BaseAdapter {
- public MyAdapter() {
- }
- @Override
- public int getCount() {
- return 100;
- }
- @Override
- public Object getItem(int position) {
- return null;
- }
- @Override
- public long getItemId(int position) {
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(getApplicationContext())
- .inflate(R.layout.list_item, null);
- }
- final ImageView image = (ImageView) convertView
- .findViewById(R.id.imageview);
- final String imageURL = "http://avatar.youkuaiyun.com/D/1/4/3_wangjinyu501.jpg";
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- Drawable d = (Drawable) msg.obj;
- if (d == null) {
- image.setImageResource(R.drawable.ic_launcher);
- } else {
- image.setImageDrawable(d);
- }
- }
- };
- Thread thread = new Thread() {
- @Override
- public void run() {
- Drawable drawable = null;
- try {
- drawable = Drawable.createFromStream(
- new URL(imageURL).openStream(), "image.jpg");
- } catch (IOException e) {
- Log.d("test", e.getMessage());
- }
- // 模拟网络延时
- // SystemClock.sleep(2000);
- Message message = handler.obtainMessage();
- message.obj = drawable;
- handler.sendMessage(message);
- }
- };
- thread.start();
- thread = null;
- return convertView;
- }
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- }
4、Handler+ExecutorService+Message
因为能开线程的个数毕竟是有限的,不能开很多线程,对于手机更是如此。所以一个办法就是使用线程池。Android拥有与Java相同的ExecutorService实现。线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。下面的例子是创建一个可重用固定线程数的线程池。
看代码:
- package com.example.handlerloadiage;
- import java.net.URL;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import android.app.Activity;
- import android.graphics.drawable.Drawable;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.view.LayoutInflater;
- import android.view.Menu;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.ListView;
- public class HandlerExecutorServiceActivity extends Activity {
- private ListView listview;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_handlerimageloader);
- listview = (ListView) findViewById(R.id.listview);
- listview.setAdapter(new MyAdapter());
- }
- private class MyAdapter extends BaseAdapter {
- public MyAdapter() {
- }
- @Override
- public int getCount() {
- return 100;
- }
- @Override
- public Object getItem(int position) {
- return null;
- }
- @Override
- public long getItemId(int position) {
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(getApplicationContext())
- .inflate(R.layout.list_item, null);
- }
- final ImageView image = (ImageView) convertView
- .findViewById(R.id.imageview);
- final String imageURL = "http://avatar.youkuaiyun.com/D/1/4/3_wangjinyu501.jpg";
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- Drawable d = (Drawable) msg.obj;
- if (d == null) {
- image.setImageResource(R.drawable.ic_launcher);
- } else {
- image.setImageDrawable(d);
- }
- }
- };
- ExecutorService executorService = Executors.newFixedThreadPool(5);
- executorService.submit(new Runnable() {
- public void run() {
- try {
- final Drawable drawable = Drawable.createFromStream(
- new URL(imageURL).openStream(), "image.png");
- // 模拟网络延时
- // SystemClock.sleep(2000);
- Message message = handler.obtainMessage();
- message.obj = drawable;
- handler.sendMessage(message);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- });
- return convertView;
- }
- }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.main, menu);
- return true;
- }
- }<strong>
- </strong>
方案
可以说对于下载网络图片,上面的方法只是一个开始。要想成为一个App的解决方案,其实还需要考虑更多的东西。先说一下可以进一步优化的地方:
1、使用弱引用
2、使用本地存储(SD卡)
3、对图片进行缩放
4、定时清除缓存
5、存储到数据库
下面看一个比较成熟的解决方案:
ImageUtil.java 这是一个图片下载工具类
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.ref.SoftReference;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.net.URLConnection;
- import java.text.DecimalFormat;
- import java.util.Date;
- import java.util.LinkedHashMap;
- import android.annotation.SuppressLint;
- import android.content.Context;
- import android.database.Cursor;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.graphics.BitmapFactory.Options;
- import android.graphics.Rect;
- import android.os.Build;
- import android.os.Environment;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.widget.ImageView;
- import cn.eoe.app.R;
- import cn.eoe.app.db.DBHelper;
- import cn.eoe.app.db.ImageCacheColumn;
- public class ImageUtil {
- private static final String TAG = "ImageUtil";
- private static int DayCount = 15;// 天数
- private static final long CLEARTIME = DayCount * 24 * 60 * 60 * 1000;//秒
- /**
- * 默认图片
- */
- private final static int Default_Img = R.drawable.bg_load_default;
- private static Object lock = new Object();
- /**
- * 内存图片软引用缓冲
- */
- private static LinkedHashMap<String, SoftReference> imageCache = new LinkedHashMap<String, SoftReference>(
- 20);
- /**
- * 入口
- *
- * @param imageUrl
- * @param iv_item_image
- * @param context
- * @param callback
- * @param b
- */
- public static void setThumbnailView(String imageUrl,
- ImageView iv_item_image, Context context, ImageCallback callback,
- boolean b) {
- DBHelper dbHelper = DBHelper.getInstance(context);//获取数据库实例
- String md5 = ImageUtil.md5(imageUrl);
- String cachePath = context.getCacheDir().getAbsolutePath() + "/" + md5; // data里的缓存
- String imagePath = getExternalCacheDir(context) + File.separator + md5; // sd卡
- // 缓存目录
- if (!CommonUtil.sdCardIsAvailable())/* true 为可用 */{
- setThumbnailImage(iv_item_image, imageUrl, cachePath, dbHelper,
- callback, b);
- iv_item_image.setTag(cachePath);//SD卡不可用就是用data里面缓存的图片
- } else {
- setThumbnailImage(iv_item_image, imageUrl, imagePath, dbHelper,
- callback, b);
- iv_item_image.setTag(imagePath);//
- }
- }
- /**
- * 获得程序在sd开上的cahce目录
- *
- * @param context
- * The context to use
- * @return The external cache dir
- */
- @SuppressLint("NewApi")
- public static String getExternalCacheDir(Context context) {
- // android 2.2 以后才支持的特性
- if (hasExternalCacheDir()) {
- return context.getExternalCacheDir().getPath() + File.separator
- + "img";
- }
- // Before Froyo we need to construct the external cache dir ourselves
- // 2.2以前我们需要自己构造
- final String cacheDir = "/Android/data/" + context.getPackageName()
- + "/cache/img/";
- return Environment.getExternalStorageDirectory().getPath() + cacheDir;
- }
- public static boolean hasExternalCacheDir() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
- }
- /**
- * 设置图片函数
- *
- * @param view
- * @param imageUrl
- * @param cachePath
- * @param callback
- * @param b
- */
- private static void setThumbnailImage(ImageView view, String imageUrl,
- String cachePath, DBHelper dbHelper, ImageCallback callback,
- boolean b) {
- Bitmap bitmap = null;
- bitmap = ImageUtil.loadThumbnailImage(cachePath, imageUrl, dbHelper,
- callback, b);
- if (bitmap == null) {// 先查找数据库,再查找本地sd卡,若没有.再从网站加载,若网站上没有图片或错误时返回null
- // 设置默认图片
- view.setImageResource(Default_Img);
- } else {
- // 设置本地SD卡缓存图片
- view.setImageBitmap(bitmap);
- }
- }
- private static Bitmap getImageFromDB(String imagePath, String imageUrl,
- DBHelper dbHelper) {
- Cursor cursor = queryFromDbByImgUrl(dbHelper, imageUrl);
- if (cursor.moveToFirst()) {
- long currTimestamp = (new Date()).getTime();
- long timestamp = cursor.getLong(cursor
- .getColumnIndex(ImageCacheColumn.TIMESTAMP));
- long spanTime = currTimestamp - timestamp;
- int Past_time = cursor.getInt(cursor
- .getColumnIndex(ImageCacheColumn.PAST_TIME));
- if (spanTime > Past_time * 24 * 60 * 60 * 1000) {
- // 过期
- // 删除本地文件
- deleteImageFromLocal(imagePath);
- return null;
- } else {
- // 没过期
- return getImageFromLocal(imagePath);
- }
- } else {
- return null;
- }
- }
- private static Cursor queryFromDbByImgUrl(DBHelper dbHelper, String imageUrl) {
- // return dbHelper.query(ImageCacheColumn.TABLE_NAME, null,
- // ImageCacheColumn.Url + "=?", new String[] { imageUrl });
- return dbHelper.rawQuery("select * from " + ImageCacheColumn.TABLE_NAME
- + " where " + ImageCacheColumn.Url + "='" + imageUrl + "'",
- null);
- }
- /**
- * 保存图片到SD卡
- *
- * @param imagePath
- * @param buffer
- * @throws IOException
- */
- public static void saveImage(String imagePath, byte[] buffer)
- throws IOException {
- File f = new File(imagePath);
- if (f.exists()) {
- return;
- } else {
- File parentFile = f.getParentFile();
- if (!parentFile.exists()) {
- parentFile.mkdirs();
- }
- f.createNewFile();
- FileOutputStream fos = new FileOutputStream(imagePath);
- fos.write(buffer);
- fos.flush();
- fos.close();
- }
- }
- /**
- * 保存图片到缓存
- *
- * @param imagePath
- * @param bm
- */
- public static void saveImage(String imagePath, Bitmap bm) {
- if (bm == null || imagePath == null || "".equals(imagePath)) {
- return;
- }
- File f = new File(imagePath);
- if (f.exists()) {
- return;
- } else {
- try {
- File parentFile = f.getParentFile();
- if (!parentFile.exists()) {
- parentFile.mkdirs();
- }
- f.createNewFile();
- FileOutputStream fos;
- fos = new FileOutputStream(f);
- bm.compress(Bitmap.CompressFormat.PNG, 100, fos);
- fos.close();
- } catch (FileNotFoundException e) {
- f.delete();
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- f.delete();
- }
- }
- }
- private static void saveImageByDb(String imageUrl, DBHelper dbHelper) {
- String sql = null;
- if (queryFromDbByImgUrl(dbHelper, imageUrl).moveToFirst()) {
- sql = "update " + ImageCacheColumn.TABLE_NAME + " set "
- + ImageCacheColumn.TIMESTAMP + "='"
- + (new Date().getTime()) + "' where "
- + ImageCacheColumn.Url + "='" + imageUrl + "'";
- } else {
- sql = "insert into " + ImageCacheColumn.TABLE_NAME + "("
- + ImageCacheColumn.Url + "," + ImageCacheColumn.TIMESTAMP
- + "," + ImageCacheColumn.PAST_TIME + ") values('"
- + imageUrl + "'," + (new Date().getTime()) + "," + DayCount
- + ")";
- }
- dbHelper.ExecSQL(sql);
- }
- /**
- * 从SD卡加载图片
- *
- * @param imagePath
- * @return
- */
- public static Bitmap getImageFromLocal(String imagePath) {
- File file = new File(imagePath);
- if (file.exists()) {
- Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
- file.setLastModified(System.currentTimeMillis());
- return bitmap;
- }
- return null;
- }
- /**
- * 从本地文件中删除文件
- *
- * @param imagePath
- */
- private static void deleteImageFromLocal(String imagePath) {
- File file = new File(imagePath);
- if (file.exists()) {
- file.delete();
- }
- }
- /**
- * 从本地或者服务端异步加载缩略图图片
- *
- * @return
- * @param imagePath
- * 本地缓存路径
- * @param imgUrl
- * 拼接后的请求路径
- * @param callback
- * 得到数据后的处理方法回调
- * @throws IOException
- */
- public static Bitmap loadThumbnailImage(final String imagePath,
- final String imgUrl, final DBHelper dbHelper,
- final ImageCallback callback, final boolean b) {
- // 在软链接缓存中,则返回Bitmap对象
- if (imageCache.containsKey(imgUrl)) {
- SoftReference reference = imageCache.get(imgUrl);
- Bitmap bitmap = (Bitmap) reference.get();
- if (bitmap != null) {
- return bitmap;
- }
- }
- // 若软链接缓存没有
- Bitmap bitmap = null;
- // 查询数据库 返回bitmap
- bitmap = getImageFromDB(imagePath, imgUrl, dbHelper);// 从本地加载
- if (bitmap != null) {
- return bitmap;
- } else {
- // 从网上加载
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.obj != null) {
- Bitmap bitmap = (Bitmap) msg.obj;
- callback.loadImage(bitmap, imagePath);
- }
- }
- };
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- try {
- URL url = new URL(imgUrl);
- URLConnection conn = url.openConnection();
- conn.setConnectTimeout(5000);
- conn.setReadTimeout(5000);
- conn.connect();
- InputStream in = conn.getInputStream();
- BitmapFactory.Options options = new Options();
- options.inSampleSize = 1;
- Bitmap bitmap = BitmapFactory.decodeStream(in,
- new Rect(0, 0, 0, 0), options);
- imageCache.put(imgUrl, new SoftReference(bitmap));
- Message msg = handler.obtainMessage();
- msg.obj = bitmap;
- handler.sendMessage(msg);
- if (bitmap != null) {
- // 保存文件到sd卡
- saveImage(imagePath, bitmap);
- // 保存到数据库
- saveImageByDb(imgUrl, dbHelper);
- }
- } catch (MalformedURLException e) {
- e.printStackTrace();
- Log.e(ImageUtil.class.getName(), "图片url不存在");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- };
- ThreadPoolManager.getInstance().addTask(runnable);
- }
- return null;
- }
- /**
- * MD5
- *
- * @param paramString
- * @return
- */
- private static String md5(String paramString) {
- return MD5.encode(paramString);
- }
- // ///
- // 公共方法
- public interface ImageCallback {
- public void loadImage(Bitmap bitmap, String imagePath);
- }
- /**
- * 每次打开含有大量图片的activity时,开一个新线程,检查并清理缓存
- *
- * @param context
- */
- public static void checkCache(final Context context) {
- new Thread() {
- public void run() {
- int state = 0;// 记录清除结果 0为都没清除, 1为只清除了sd卡, 2为只清除了rom Cache ,3
- // 为都清除了
- String cacheS = "0M";
- String cacheD = "0M";
- File sdCache = new File(getExternalCacheDir(context)); // sd卡"mnt/sdcard/android/data/cn.eoe.app/cache/";
- File cacheDir = context.getCacheDir(); // 手机data/data/com.mengniu.app/cache
- try {
- if (sdCache != null && sdCache.exists()) {
- long sdFileSize = getFileSize(sdCache);
- if (sdFileSize > 1024 * 1024 * 50) {
- // SD需要清理
- long clearFileSize = clear(sdCache);
- state += 1;
- cacheS = clearFileSize + "";
- }
- }
- if (cacheDir != null && cacheDir.exists()) {
- long cacheFileSize = getFileSize(cacheDir);
- if (cacheFileSize > 1024 * 1024 * 50) {
- // ROM需要清理
- long clearFileSize = clear(cacheDir);
- state += 2;
- cacheD = clearFileSize + "";
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- };
- }.start();
- }
- /**
- * 清除路径
- *
- * @param cacheDir
- * @return
- */
- public static long clear(File cacheDir) {
- long clearFileSize = 0;
- File[] files = cacheDir.listFiles();
- if (files == null)
- return 0;
- for (File f : files) {
- if (f.isFile()) {
- if (System.currentTimeMillis() - f.lastModified() > CLEARTIME) {
- long fileSize = f.length();
- if (f.delete()) {
- clearFileSize += fileSize;
- }
- }
- } else {
- clear(f);
- }
- }
- return clearFileSize;
- }
- /**
- * 取得文件大小
- *
- * @param f
- * @return
- * @throws Exception
- */
- public static long getFileSize(File f) throws Exception {
- long size = 0;
- File flist[] = f.listFiles();
- for (int i = 0; i < flist.length; i++) {
- if (flist[i].isDirectory()) {
- size = size + getFileSize(flist[i]);
- } else {
- size = size + flist[i].length();
- }
- }
- return size;
- }
- /**
- * 转换文件大小
- *
- * @param fileS
- * @return
- */
- public static String FormetFileSize(long fileS) {
- DecimalFormat df = new DecimalFormat("#.00");
- String fileSizeString = "";
- if (fileS < 1024) {
- fileSizeString = df.format((double) fileS) + "B";
- } else if (fileS < 1048576) {
- fileSizeString = df.format((double) fileS / 1024) + "K";
- } else if (fileS < 1073741824) {
- fileSizeString = df.format((double) fileS / 1048576) + "M";
- } else {
- fileSizeString = df.format((double) fileS / 1073741824) + "G";
- }
- return fileSizeString;
- }
- }
- import android.content.ContentValues;
- import android.content.Context;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- import android.database.sqlite.SQLiteDatabase.CursorFactory;
- import android.database.sqlite.SQLiteOpenHelper;
- import android.provider.BaseColumns;
- public class DBHelper extends SQLiteOpenHelper {
- private static final String DB_NAME = "cn";
- private static final int DB_VERSION = 2;
- private SQLiteDatabase db;
- private static DBHelper mdbHelper;
- public static DBHelper getInstance(Context context)
- {
- if(mdbHelper==null)
- {
- mdbHelper=new DBHelper(context);
- }
- return mdbHelper;
- }
- private DBHelper(Context context) {
- super(context, DB_NAME, null, DB_VERSION);
- }
- private DBHelper(Context context, String name, CursorFactory factory,
- int version) {
- super(context, name, factory, version);
- // TODO Auto-generated constructor stub
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- this.db = db;
- operateTable(db, "");
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // TODO Auto-generated method stub
- if (oldVersion == newVersion) {
- return;
- }
- operateTable(db, "DROP TABLE IF EXISTS ");
- onCreate(db);
- }
- public void operateTable(SQLiteDatabase db, String actionString) {
- Class<DatabaseColumn>[] columnsClasses = DatabaseColumn.getSubClasses();
- DatabaseColumn columns = null;
- for (int i = 0; i < columnsClasses.length; i++) {
- try {
- columns = columnsClasses[i].newInstance();
- if ("".equals(actionString) || actionString == null) {
- db.execSQL(columns.getTableCreateor());
- } else {
- db.execSQL(actionString + columns.getTableName());
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- public long insert(String Table_Name, ContentValues values) {
- if (db == null)
- db = getWritableDatabase();
- return db.insert(Table_Name, null, values);
- }
- /**
- *
- * @param Table_Name
- * @param id
- * @return 影响行数
- */
- public int delete(String Table_Name, int id) {
- if (db == null)
- db = getWritableDatabase();
- return db.delete(Table_Name, BaseColumns._ID + "=?",
- new String[] { String.valueOf(id) });
- }
- /**
- * @param Table_Name
- * @param values
- * @param WhereClause
- * @param whereArgs
- * @return 影响行数
- */
- public int update(String Table_Name, ContentValues values,
- String WhereClause, String[] whereArgs) {
- if (db == null) {
- db = getWritableDatabase();
- }
- return db.update(Table_Name, values, WhereClause, whereArgs);
- }
- public Cursor query(String Table_Name, String[] columns, String whereStr,
- String[] whereArgs) {
- if (db == null) {
- db = getReadableDatabase();
- }
- return db.query(Table_Name, columns, whereStr, whereArgs, null, null,
- null);
- }
- public Cursor rawQuery(String sql, String[] args) {
- if (db == null) {
- db = getReadableDatabase();
- }
- return db.rawQuery(sql, args);
- }
- public void ExecSQL(String sql) {
- if (db == null) {
- db = getWritableDatabase();
- }
- db.execSQL(sql);
- }
- public void closeDb() {
- if (db != null) {
- db.close();
- db = null;
- }
- }
- }
- import java.io.File;
- import android.content.Context;
- import android.os.Environment;
- import android.os.StatFs;
- public class CommonUtil {
- /**
- * 检测sdcard是否可用
- *
- * @return true为可用,否则为不可用
- */
- public static boolean sdCardIsAvailable() {
- String status = Environment.getExternalStorageState();
- if (!status.equals(Environment.MEDIA_MOUNTED))
- return false;
- return true;
- }
- /**
- * Checks if there is enough Space on SDCard
- *
- * @param updateSize
- * Size to Check
- * @return True if the Update will fit on SDCard, false if not enough space on SDCard Will also return false, if the SDCard is
- * not mounted as read/write
- */
- public static boolean enoughSpaceOnSdCard(long updateSize) {
- String status = Environment.getExternalStorageState();
- if (!status.equals(Environment.MEDIA_MOUNTED))
- return false;
- return (updateSize < getRealSizeOnSdcard());
- }
- /**
- * get the space is left over on sdcard
- */
- public static long getRealSizeOnSdcard() {
- File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath());
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long availableBlocks = stat.getAvailableBlocks();
- return availableBlocks * blockSize;
- }
- /**
- * Checks if there is enough Space on phone self
- *
- */
- public static boolean enoughSpaceOnPhone(long updateSize) {
- return getRealSizeOnPhone() > updateSize;
- }
- /**
- * get the space is left over on phone self
- */
- public static long getRealSizeOnPhone() {
- File path = Environment.getDataDirectory();
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long availableBlocks = stat.getAvailableBlocks();
- long realSize = blockSize * availableBlocks;
- return realSize;
- }
- /**
- * 根据手机分辨率从dp转成px
- * @param context
- * @param dpValue
- * @return
- */
- public static int dip2px(Context context, float dpValue) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int) (dpValue * scale + 0.5f);
- }
- /**
- * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
- */
- public static int px2dip(Context context, float pxValue) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return (int) (pxValue / scale + 0.5f)-15;
- }
- }
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class ThreadPoolManager {
- private ExecutorService service;
- private ThreadPoolManager(){
- int num = Runtime.getRuntime().availableProcessors();
- service = Executors.newFixedThreadPool(num*2);
- }
- private static ThreadPoolManager manager;
- public static ThreadPoolManager getInstance(){
- if(manager==null)
- {
- manager= new ThreadPoolManager();
- }
- return manager;
- }
- public void addTask(Runnable runnable){
- service.submit(runnable);
- }
- }
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import org.apache.http.impl.auth.UnsupportedDigestAlgorithmException;
- import android.util.Log;
- /**
- * @version 1.0
- */
- public final class MD5 {
- private static final String LOG_TAG = "MD5";
- private static final String ALGORITHM = "MD5";
- private static char sHexDigits[] = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
- };
- private static MessageDigest sDigest;
- static {
- try {
- sDigest = MessageDigest.getInstance(ALGORITHM);
- } catch (NoSuchAlgorithmException e) {
- Log.e(LOG_TAG, "Get MD5 Digest failed.");
- throw new UnsupportedDigestAlgorithmException(ALGORITHM, e);
- }
- }
- private MD5() {
- }
- final public static String encode(String source) {
- byte[] btyes = source.getBytes();
- byte[] encodedBytes = sDigest.digest(btyes);
- return Utility.hexString(encodedBytes);
- }
- }
- import java.util.HashMap;
- import java.util.Map;
- import android.net.Uri;
- public class ImageCacheColumn extends DatabaseColumn {
- public final static String TABLE_NAME = "imageCache";
- public final static String TIMESTAMP = "timestamp";
- public final static String Url = "url";
- /**
- * 单位:天
- */
- public final static String PAST_TIME = "past_time";
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
- + "/" + TABLE_NAME);
- private static final Map<String, String> mColumnMap = new HashMap<String, String>();
- static {
- mColumnMap.put(_ID, "integer primary key autoincrement");
- mColumnMap.put(TIMESTAMP, "TimeStamp");
- mColumnMap.put(Url, "text");
- mColumnMap.put(PAST_TIME, "TimeStamp");
- }
- @Override
- public String getTableName() {
- // TODO Auto-generated method stub
- return TABLE_NAME;
- }
- @Override
- public Uri getTableContent() {
- // TODO Auto-generated method stub
- return CONTENT_URI;
- }
- @Override
- protected Map<String, String> getTableMap() {
- // TODO Auto-generated method stub
- return mColumnMap;
- }
- }
handler分页加载ListView
在前面两节中,我们了解了如何从服务器中加载JSON数据。
现在,我们将把服务器中的JSON数据加载更新到ListView。
并且,结合之前博文的 “动态追加分页ListView数据”的相关知识,实现将服务器中的分页JSON数据,填充到ListView中。
回顾
以BaseAdapter做适配器的ListView:http://blog.youkuaiyun.com/jueblog/article/details/12114513
ListView内数据的动态追加:http://blog.youkuaiyun.com/jueblog/article/details/12148259
Activity的实现
- package com.app.myhandler;
- import org.json.JSONArray;
- import org.json.JSONException;
- import org.json.JSONObject;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.view.ContextMenu;
- import android.view.ContextMenu.ContextMenuInfo;
- import android.view.LayoutInflater;
- import android.view.MenuItem;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.AdapterView.AdapterContextMenuInfo;
- import android.widget.ListView;
- import android.widget.TextView;
- import android.widget.Toast;
- import com.app.adapter.MyWeixinJSON;
- import com.app.util.MyApplication;
- import com.app.util.MyThread;
- /**
- * 点击 追加数据的ListView
- * @author 402-9
- */
- public class ListViewPage extends Activity {
- private ListView lv;
- private MyWeixinJSON mJson;
- private JSONArray mData = new JSONArray();// JSON数据源
- private View view_page_footer;// 底部视图
- private int page = 1;// 加载页码
- private int totalPage;// 总数据
- private String url = new String("http://192.168.8.4/wt_guang.php?frm=3g&cid=100");
- private TextView text_page;
- private Handler handler = new Handler() {
- public void handleMessage(android.os.Message msg) {
- try {
- String result = msg.obj.toString();
- JSONObject object = new JSONObject(result);
- totalPage = (Integer) object.get("totalPage");
- if(mJson==null) {
- mData = (JSONArray) object.get("items");
- mJson = new MyWeixinJSON(mData, ListViewPage.this);
- lv.setAdapter(mJson);//为ListView绑定适配器
- } else {
- mData = MyApplication.joinJSONArray(mData, (JSONArray) object.get("items"));
- mJson.setList(mData);
- mJson.notifyDataSetChanged();
- }
- text_page.setText("下一页");
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- };
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.weixin);
- lv = (ListView) findViewById(R.id.lv);
- new MyThread(handler, url.toString(), 1).start();
- view_page_footer = LayoutInflater.from(this).inflate(R.layout.view_page_footer, null);
- lv.addFooterView(view_page_footer);// 添加底部视图
- lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- // TODO Auto-generated method stub
- try {
- JSONObject jsonObject = (JSONObject) mData.get(position);
- Toast.makeText(ListViewPage.this,
- jsonObject.getString("title"), Toast.LENGTH_SHORT)
- .show();
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- });
- text_page = (TextView) view_page_footer.findViewById(R.id.text_page);
- text_page.setOnClickListener(new View.OnClickListener() {
- // 点击按钮 追加数据 并通知适配器
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- page++;
- if(page<=totalPage) {
- text_page.setText("正在加载中...");
- new MyThread(handler, url+"&p="+page, 1).start();
- } else {
- text_page.setText("已是最末页...");
- text_page.setEnabled(false);
- }
- }
- });
- }
- }
- mData = MyApplication.joinJSONArray(mData, (JSONArray) object.get("items"));
适配器的修改
- package com.app.adapter;
- import org.json.JSONArray;
- import org.json.JSONObject;
- import android.content.Context;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- import com.app.myhandler.R;
- public class MyWeixinJSON extends BaseAdapter {
- private LayoutInflater mInflater;// 动态布局映射
- private JSONArray list;
- private Context context;
- // private int i = 0;
- public void setList(JSONArray list) {
- this.list = list;
- }
- public MyWeixinJSON(JSONArray list, Context context) {
- this.list = list;
- this.context = context;
- this.mInflater = LayoutInflater.from(context);
- }
- @Override
- public int getCount() {
- // TODO Auto-generated method stub
- return list.length();
- }
- @Override
- public Object getItem(int position) {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public long getItemId(int position) {
- // TODO Auto-generated method stub
- return 0;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // TODO Auto-generated method stub
- // System.out.println("正在渲染第"+position+"行 +++ "+ i++);
- OneView oneView;
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.item_weixin, null);// 根据布局文件实例化view
- oneView = new OneView();
- oneView.title = (TextView) convertView.findViewById(R.id.title);// 找某个控件
- oneView.time = (TextView) convertView.findViewById(R.id.time);
- oneView.info = (TextView) convertView.findViewById(R.id.info);
- oneView.img = (ImageView) convertView.findViewById(R.id.img);
- convertView.setTag(oneView);// 把View和某个对象关联起来
- } else {
- oneView = (OneView) convertView.getTag();
- }
- JSONObject jObject = null;
- try {
- jObject = list.getJSONObject(position);// 根据position获取集合类中某行数据
- oneView.title.setText(jObject.get("itemID").toString());// 给该控件设置数据(数据从集合类中来)
- oneView.time.setText(jObject.get("price").toString());
- oneView.info.setText(jObject.get("title").toString());
- oneView.img.setBackgroundResource(R.drawable.special_spring_head2);
- // oneView.img.setImageDrawable(Drawable.createFromStream(new
- // URI(jObject.getString("pic_url"))., "src"));
- } catch (Exception e) {
- // TODO: handle exception
- }
- return convertView;
- }
- /** 把每行布局文件的各个控件包装成一个对象 */
- private class OneView {
- TextView title;
- TextView time;
- TextView info;
- ImageView img;
- }
- }
JSON数据
- {
- "totalCount": 9,
- "totalPage": 2,
- "items": [
- {
- "itemID": "13889239752",
- "title": "PEACE DOVE 正品* 真兔毛 范儿厚毛衣开衫 清库价",
- "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i5/T1ELuGXj0yXXbAjkA._083138.jpg",
- "click_url": "http://item.taobao.com/item.htm?id=13889239752",
- "price": "98.00",
- "promotion_price": "0.00",
- "liked": "0"
- },
- {
- "itemID": "13357878044",
- "title": "现货 呛口小辣椒11月29日香港行snidel 翻领双排扣毛呢大衣外套",
- "pic_url": "http://img03.taobaocdn.com/bao/uploaded/i3/T1KTWDXipfXXXmSFDX_113804.jpg",
- "click_url": "http://item.taobao.com/item.htm?id=13357878044",
- "price": "278.00",
- "promotion_price": "0.00",
- "liked": "0"
- },
- {
- "itemID": "12740033159",
- "title": "2011新款呛口小辣椒子萱王翔貉子毛毛领军绿色工装棉服棉衣大衣",
- "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i1/T1WiCoXi4BXXad.Qs9_073952.jpg",
- "click_url": "http://item.taobao.com/item.htm?id=12740033159",
- "price": "628.00",
- "promotion_price": "0.00",
- "liked": "0"
- },
- {
- "itemID": "8603809978",
- "title": "圣诞款大衣 三色入",
- "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i1/T1GWXTXhhxXXaMGqo3_045745.jpg",
- "click_url": "http://s.click.taobao.com/t_8?e=7HZ6jHSTZTlU5Xur%2B%2F50d2poSR5Ba0A%2Fi8tpiW6m04z%2BWKzb6yELcZsJq6zxF7EMTqmpst%2FNOGepsZQvjjXKR0huW7F8S1whVzYKzICAu1lT&p=mm_10009952_0_0",
- "price": "165.00",
- "promotion_price": "0.00",
- "liked": "0"
- },
- {
- "itemID": "10629952619",
- "title": "裁诺 春装新款2012女装 韩版带帽中长款加厚卫衣 女 秋冬外套大码",
- "pic_url": "http://img01.taobaocdn.com/bao/uploaded/i1/T1PASfXfpoXXc.O0vb_093452.jpg",
- "click_url": "http://item.taobao.com/item.htm?id=10629952619",
- "price": "158.00",
- "promotion_price": "0.00",
- "liked": "0"
- },
- {
- "itemID": "9158721999",
- "title": "清仓包邮 出口欧美大码超酷水洗打磨斜开襟修身加厚抓绒卫衣外套",
- "pic_url": "http://img03.taobaocdn.com/bao/uploaded/i3/T12t5KXXJqXXbjSw.8_100610.jpg",
- "click_url": "http://s.click.taobao.com/t_8?e=7HZ6jHSTZPhaK%2FmBeumaH2YuQWkORNRTfXSCf08v9iowvo%2F0Akhswsb9gfNlScFQL%2Bc%2Bp%2BFUQiV5QW7qQyiq%2BJQ4CS399exvrZIKVVH3ZZNULw%3D%3D&p=mm_10009952_0_0",
- "price": "398.00",
- "promotion_price": "0.00",
- "liked": "0"
- }
- ]
- }