android handler使用

来源:互联网 发布:爬虫爬数据库犯法吗 编辑:程序博客网 时间:2024/06/05 15:01

Handler:是一个消息分发对象,进行发送和处理消息,并且其 Runnable 对象与一个线程的 MessageQueue 关联。
作用:调度消息,将一个任务切换到某个指定的线程中去执行。

为什么需要 Handler?

子线程不允许访问 UI

假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。
传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。

引入 Handler

采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。

Handler 使用

方式一: post(Runnable)

  • 创建一个工作线程,实现 Runnable 接口,实现 run 方法,处理耗时操作
  • 创建一个 handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。

    new Thread(new Runnable() {    @Override    public void run() {        /**         * 耗时操作         */       handler.post(new Runnable() {           @Override           public void run() {               /**                * 更新UI                */           }       });    }}).start();

方式二: sendMessage(Message)

  • 创建一个工作线程,继承 Thread,重新 run 方法,处理耗时操作
  • 创建一个 Message 对象,设置 what 标志及数据
  • 通过 sendMessage 进行投递消息
  • 创建一个handler,重写 handleMessage 方法,根据 msg.what 信息判断,接收对应的信息,再在这里更新 UI。

    private Handler handler = new Handler(){  @Override  public void handleMessage(Message msg) {      super.handleMessage(msg);      switch (msg.what) {      //判断标志位         case 1:             /**              * 获取数据,更新UI               */              break;       }     }  };public class WorkThread extends Thread {  @Override  public void run() {    super.run();   /**     * 耗时操作    */   Message msg =Message.obtain();  //从全局池中返回一个message实例,避免多次创建message(如new Message)   msg.obj = data;   msg.what=1;   //标志消息的标志   handler.sendMessage(msg);}new WorkThread().start();

Handler 存在的问题

内存方面

Handler 被作为 Activity 引用,如果为非静态内部类,则会引用外部类对象。当 Activity finish 时,Handler可能并未执行完,从而引起 Activity 的**内存泄漏**。故而在所有调用 Handler 的地方,都用静态内部类。

异常方面

当 Activity finish 时,在 onDestroy 方法中释放了一些资源。此时 Handler 执行到 handlerMessage 方法,但相关资源已经被释放,从而引起**空指针的异常**。
避免

  • 如果是使用 handlerMessage,则在方法中加try catch。
  • 如果是用 post 方法,则在Runnable方法中加try catch。

Handler 的改进

  • 内存方面:使用静态内部类创建 handler 对象,且对 Activity 持有弱引用
  • 异常方面:不加 try catch,而是在 onDestory 中把消息队列 MessageQueue 中的消息给 remove 掉。

则使用如下方式创建 handler 对象:

    /**    * 为避免handler造成的内存泄漏    * 1、使用静态的handler,对外部类不保持对象的引用    * 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用    */    private static class MyHandler extends Handler {        private final WeakReference<Activity> mActivityReference;           MyHandler(Activity activity) {            this.mActivityReference = new WeakReference<Activity>(activity);        }        @Override            public void handleMessage(Message msg) {            super.handleMessage(msg);            MainActivity activity = (MainActivity) mActivityReference.get();  //获取弱引用队列中的activity            switch (msg.what) {    //获取消息,更新UI                case 1:                    byte[] data = (byte[]) msg.obj;                    activity.threadIv.setImageBitmap(activity.getBitmap(data));                    break;            }        }    }

并在 onDesotry 中销毁:

@Overrideprotected void onDestroy() {    super.onDestroy();    //避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列    handler.removeCallbacks(postRunnable);   //清除runnable对应的message    //handler.removeMessage(what)  清除what对应的message}

Handler 的使用实现

  • 耗时操作采用从网络加载一张图片
  • 继承 Thread 或实现 Runnable 接口的线程,与 UI 线程进行分离,其中 Runnable 与主线程通过回调接口进行通信,降低耦合,提高代码复用性。

在 Activity 中创建 handler 对象,调用工作线程执行

public class MainActivity extends AppCompatActivity {ImageView threadIv;ImageView runnableIv;SendThread sendThread;PostRunnable postRunnable;private final MyHandler handler = new MyHandler(this);@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    threadIv = (ImageView) findViewById(R.id.thread_iv);    runnableIv = (ImageView) findViewById(R.id.runnable_iv);    sendThread = new SendThread(handler);    sendThread.start();    postRunnable = new PostRunnable(handler);    postRunnable.setRefreshUI(new PostRunnable.RefreshUI() {        @Override        public void setImage(byte[] data) {            runnableIv.setImageBitmap(getBitmap(data));        }    });    new Thread(postRunnable).start();}/** * 为避免handler造成的内存泄漏 * 1、使用静态的handler,对外部类不保持对象的引用 * 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用 */private static class MyHandler extends Handler {    private final WeakReference<Activity> mActivityReference;    MyHandler(Activity activity) {        this.mActivityReference = new WeakReference<Activity>(activity);    }    @Override    public void handleMessage(Message msg) {        super.handleMessage(msg);        MainActivity activity = (MainActivity) mActivityReference.get();  //获取弱引用队列中的activity        switch (msg.what) {    //获取消息,更新UI            case 1:                byte[] data = (byte[]) msg.obj;                activity.threadIv.setImageBitmap(activity.getBitmap(data));                break;        }    }}private Bitmap getBitmap(byte[] data) {    return BitmapFactory.decodeByteArray(data, 0, data.length);}@Overrideprotected void onDestroy() {    super.onDestroy();    //避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列    handler.removeCallbacks(postRunnable);   //清除runnable对应的message    //handler.removeMessage(what)  清除what对应的message}}

方式一:实现 runnable 接口,通过 post(Runnable)通信,并通过给定的回调接口通知 Activity 更新

public class PostRunnable implements Runnable {private Handler handler;private RefreshUI refreshUI;byte[] data = null;public PostRunnable(Handler handler) {    this.handler = handler;}@Overridepublic void run() {    /**     * 耗时操作     */    final Bitmap bitmap = null;    HttpClient httpClient = new DefaultHttpClient();    HttpGet httpGet = new HttpGet("http://i3.17173cdn.com/2fhnvk/YWxqaGBf/cms3/FNsPLfbkmwgBgpl.jpg");    HttpResponse httpResponse = null;    try {        httpResponse = httpClient.execute(httpGet);        if (httpResponse.getStatusLine().getStatusCode() == 200) {            data = EntityUtils.toByteArray(httpResponse.getEntity());        }    } catch (IOException e) {        e.printStackTrace();    }    //返回结果给UI线程    handler.post(new Runnable() {        @Override        public void run() {            refreshUI.setImage(data);        }    });}public interface RefreshUI {    public void setImage(byte[] data);}public void setRefreshUI(RefreshUI refreshUI) {    this.refreshUI = refreshUI;}}

方式二:继承Thread,通过handler的sendMessage通信

public class SendThread extends Thread {private Handler handler;public SendThread(Handler handler) {    this.handler = handler;}@Overridepublic void run() {    super.run();    /**     * 耗时操作     */    byte[]data=null;    HttpClient httpClient = new DefaultHttpClient();    HttpGet httpGet = new HttpGet("https://d36lyudx79hk0a.cloudfront.net/p0/descr/pc27/3095587d8c4560d8.png");    HttpResponse httpResponse = null;    try {        httpResponse = httpClient.execute(httpGet);        if(httpResponse.getStatusLine().getStatusCode()==200){            data= EntityUtils.toByteArray(httpResponse.getEntity());        }    } catch (IOException e) {        e.printStackTrace();    }    //返回结果给UI线程    doTask(data);}/** * 通过handler返回消息 * @param data */private void doTask(byte[] data) {    Message msg =Message.obtain();  //从全局池中返回一个message实例,避免多次创建message(如new Message)    msg.obj = data;    msg.what=1;   //标志消息的标志    handler.sendMessage(msg);}}

Handler 通信机制

Handler 通信机制

  • 创建Handler,并采用当前线程的Looper创建消息循环系统;
  • Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;
  • Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。

Handler如何关联Looper、MessageQueue

Handler及其关联的类图
Handler及其关联的类图

以上类图可以快速帮助我们理清Handler与Looper、MessageQueue的关系,以下从源码的角度慢慢分析:

1、Handler 发送消息

上一段很熟悉的代码:

 Message msg =Message.obtain();  //从全局池中返回一个message实例,避免多次创建message(如new Message) msg.obj = data; msg.what=1;   //标志消息的标志 handler.sendMessage(msg);  

从sendMessageQueue开始追踪,函数调用关系:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,携带者传来的message与Handler的mQueue一起通过enqueueMessage进入队列了。

对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。

2、消息入队列

在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息。

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

在MessageQueue中,由Message的消息链表进行入队列

    boolean enqueueMessage(Message msg, long when) {        if (msg.target == null) {            throw new IllegalArgumentException("Message must have a target.");        }        if (msg.isInUse()) {            throw new IllegalStateException(msg + " This message is already in use.");        }        synchronized (this) {            if (mQuitting) {                IllegalStateException e = new IllegalStateException(                        msg.target + " sending message to a Handler on a dead thread");                Log.w(TAG, e.getMessage(), e);                msg.recycle();                return false;            }            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                // Inserted within the middle of the queue.  Usually we don't have to wake                // up the event queue unless there is a barrier at the head of the queue                // and the message is the earliest asynchronous message in the queue.                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;                for (;;) {                    prev = p;                    p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p; // invariant: p == prev.next                prev.next = msg;            }            // We can assume mPtr != 0 because mQuitting is false.            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

3、Looper 处理消息

再说处理消息之前,先看Looper是如何构建与获取的:
  • 构造Looper时,构建消息循环队列,并获取当前线程
        private Looper(boolean quitAllowed) {            mQueue = new MessageQueue(quitAllowed);            mThread = Thread.currentThread();        }
  • 但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:
      public static void prepare() {          prepare(true);      }      private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));      } 
  • 这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?

    static final ThreadLocal sThreadLocal = new ThreadLocal();

    它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。

  • TheadLocal 如何保存与获取Looper?

      public void set(T value) {        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values == null) {            values = initializeValues(currentThread);        }        values.put(this, value);      }      public T get() {        // Optimized for the fast path.        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values != null) {            Object[] table = values.table;            int index = hash & values.mask;            if (this.reference == table[index]) {                return (T) table[index + 1];            }        } else {            values = initializeValues(currentThread);        }        return (T) values.getAfterMiss(this);      }
set 中都是通过 `values.put` 保存当前线程的 Looper 实例,通过 `values.getAfterMiss(this)`获取,其中`put``getAfterMiss`都有`key``value`,都是由Value对象的table数组保存的,那么在table数组里怎么存的呢?  table[index] = key.reference;  table[index + 1] = value;很显然在数组中,前一个保存着ThreadLocal对象引用的索引,后一个存储传入的Looper实例。
接下来看Looper在loop中如何处理消息

loop中,一个循环,通过next取出MessageQueue中的消息

  • 若取出的消息为null,则结束循环,返回。
    • 设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。
  • 若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。
    public static void loop() {        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }        final MessageQueue queue = me.mQueue;        // Make sure the identity of this thread is that of the local process,        // and keep track of what that identity token actually is.        Binder.clearCallingIdentity();        final long ident = Binder.clearCallingIdentity();        for (;;) {            Message msg = queue.next(); // might block            if (msg == null) {                // No message indicates that the message queue is quitting.                return;            }            // This must be in a local variable, in case a UI event sets the logger            Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            msg.target.dispatchMessage(msg);            if (logging != null) {                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);            }            // Make sure that during the course of dispatching the            // identity of the thread wasn't corrupted.            final long newIdent = Binder.clearCallingIdentity();            if (ident != newIdent) {                Log.wtf(TAG, "Thread identity changed from 0x"                        + Long.toHexString(ident) + " to 0x"                        + Long.toHexString(newIdent) + " while dispatching to "                        + msg.target.getClass().getName() + " "                        + msg.callback + " what=" + msg.what);            }            msg.recycleUnchecked();        }    }

4、handler处理消息

Looper把消息回调到handler的dispatchMessage中进行消息处理:

  • 若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback
  • 若handler的mCallback不为空,则交由通过callback创建handler方式去处理。
  • 否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。
    public void dispatchMessage(Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }   

总结

以一个时序图来总结handler的消息机制,包含上述如何关联Looper和MessageQueue的过程。

Handler-Looper-MessageQueue时序图
Handler-Looper-MessageQueue时序图



0 0
原创粉丝点击