博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Handler学习笔记
阅读量:5106 次
发布时间:2019-06-13

本文共 6210 字,大约阅读时间需要 20 分钟。

已经习惯了挖坑不填,继续任性一下,周一到周五继续挖坑,每周六周日负责填坑。

1.从Android UI线程谈起

出于性能考虑,Android 中的UI操作并不是线程安全的,所以Android中规定只能在主线程中修改Activity中的UI组件,故主线程又称UI线程。Android程序启动时会同时启动UI线程,该线程负责处理UI相关事件,并且进行事件分发。

  • 不是线程安全可以理解为两个人同时独立的在一张纸上画画,最后画出来的就是错乱的。实现线程安全可以通过加锁进行数据保护,以后有机会写C++相关博客再介绍多线程问题。

在某些情况下我们需要让新启动的线程周期性更新UI(比如游戏动画中),所以要找一个方式在子线程中发送消息,在主线程接受消息,配合起来更新UI,这时我们就需要Handler了。

2.Handler是个什么鬼

参考资料:

疯狂Android讲义第三章

Handler的主要作用有两个:1)在新线程发送消息,2)在主线程获取和处理消息

先用了再说:

/**主线程中使用Handler*/private static final String TAG = "HandlerActivity";private Handler mHandler;protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_handler_test);  //在主线程中创建Hander对象    mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {          //接受消息对象并处理            super.handleMessage(msg);            Log.e(TAG,"------------> msg.what = " + msg.what);        }    };    new Thread(new Runnable() {      @Override      public void run() {        SystemClock.sleep(2000);       //在子线程中使用Handler对象发送消息        mHandler.sendEmptyMessage(0);      }    }).start();}

感觉很好用,说起来就是Android不允许子线程访问UI组件,所以我们要在新线程中发送消息,让系统更新UI组件。在new出来的Handler对象中重写了handleMessage(),当通过新线程发送消息是,该方法自动被回调。

接下来总结一下Handler中的消息发送和处理方法:

算了,懒得总结,直接看

常用的有这些:

发送消息:sendMessage(),sendEmptyMessage(),sendMessageDelayed(),sendEmptyMessageDelayed()

接受处理消息:handleMessage,obtainMessage()

3.可以在子线程创建Handler吗

/**子线程中使用Handler*/private static final String TAG = "TestHandlerActivity";//主线程中的handlerprivate Handler mHandler;//子线程中的handlerprivate Handler mHandlerThread = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_handler_test);//创建handler对象  mHandler = new Handler(){    @Override    public void handleMessage(Message msg) {      super.handleMessage(msg);      //获得刚才发送的Message对象,然后在这里进行UI操作      Log.e(TAG,"------------> msg.what = " + msg.what);    }  };    new Thread(new Runnable() {    @Override    public void run() {      SystemClock.sleep(2000);      //通过Handler发送一个消息切换回主线程(mHandler所在的线程)      mHandler.sendEmptyMessage(0);      /*      //调用Looper.prepare()方法      Looper.prepare();      */            //在子线程中创建Handler      mHandlerThread = new Handler(){        @Override        public void handleMessage(Message msg) {          super.handleMessage(msg);          //在子线程中调用handleMessage接受和处理message对象          Log.e("sub thread","---------> msg.what = " + msg.what);        }      };      mHandlerThread.sendEmptyMessage(1);      /*      /*      //调用Looper.prepare()方法      Looper.loop();      */      */    }  }).start();    }

很不幸,崩溃了!

使用Handler的两个经典异常:

CalledFromWrongThreadException:这种异常是因为尝试在子线程中去更新UI,进而产生异常。

Can’t create handle inside thread that ha not called Looper.prepared:是因为我们在子线程中去创建Handler,而产生的异常。

修改方式,调用Looper.prepare()方法,将上面的注释放开。

4.异步消息处理——Handler

Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?

异步消息处理线程启动之后处于无限循环体中,每循环一次,则从内部消息队列中取出一条消息,然后回掉相关消息处理函数。

执行完一个消息之后会继续循环,若消息队列为空,则线程阻塞等待。

Looper负责创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。

Looper的核心方法

Looper的构造方法:

private Looper(boolean quitAllowed) {          mQueue = new MessageQueue(quitAllowed);    //构造方法主要是创建消息队列        mRun = true;          mThread = Thread.currentThread();  }

prepare()方法

public static final void prepare() {   //sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。        if (sThreadLocal.get() != null) {              throw new RuntimeException("Only one Looper may be created per thread");          }          sThreadLocal.set(new Looper(true));  }

第5行行代码中将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例。

loop方法:

public static void loop() {        final Looper me = myLooper();  //返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }  //拿到该looper实例中的mQueue(消息队列)        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);        //把消息交给msg的target的dispatchMessage方法去处理,即实现消息分发。        //Msg的target是什么呢?其实就是handler对象            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.recycle();        }}

Looper主要作用:

1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。

5.理解一下

Android系统中专门启动一个线程用于异步消息处理,从都源码可以看到该线程为sLocalThread,该线程中启动一个Looper对象,Looper对象中的Loop()函数负责遍历消息队列和分发消息,每一个Looper中有且仅有一个MessageQueue,该对象用于保存所有的msg。

可以理解为 one loop per thread (陈硕在muduo的设计理念中提到)。

10.一个坑爹的问题——为什么在有些时候子线程中是可以直接更新UI的?

转载于:https://www.cnblogs.com/mypl-dmx/p/7128745.html

你可能感兴趣的文章
第一个Spring冲刺周期团队进展报告
查看>>
C++函数基础知识
查看>>
红黑树 c++ 实现
查看>>
Android 获取网络链接类型
查看>>
报表服务框架:WEB前端UI
查看>>
5.9UDP客户端服务器-基于OK6410
查看>>
java自学基础、项目实战网站推荐
查看>>
软件包的使用
查看>>
linux中启动与终止lnmp的脚本
查看>>
gdb中信号的处理[转]
查看>>
学习Javascript闭包(Closure)
查看>>
LeetCode【709. 转换成小写字母】
查看>>
如何在Access2007中使用日期类型查询数据
查看>>
Jzoj4757 树上摩托
查看>>
CF992E Nastya and King-Shamans(线段树二分+思维)
查看>>
基于docker的spark-hadoop分布式集群之一: 环境搭建
查看>>
oracle 几个时间函数探究
查看>>
第一个Java Web程序
查看>>
Atomic
查看>>
div 显示滚动条与div显示隐藏的CSS代码
查看>>