编程知识 购物 网址 新闻 笑话 | 软件 日历 阅读 图书馆 China
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
vbs/VBScript DOS/BAT hta htc python perl 游戏相关 VBA 远程脚本 ColdFusion ruby专题 autoit seraphzone PowerShell linux shell Lua Golang Erlang 其它教程 CSS/HTML/Xhtml html5 CSS XML/XSLT Dreamweaver教程 经验交流
站长资讯 .NET新手 ASP.NET C# WinForm Silverlight WCF CLR WPF XNA VisualStudio ASP.NET-MVC .NET控件开发 EntityFramework WinRT-Metro Java C++ PHP Delphi Python Ruby C语言 Erlang Go Swift Scala R语言 Verilog 其它语言 架构设计 面向对象 设计模式 领域驱动 Html-Css JavaScript jQuery HTML5 SharePoint GIS技术 SAP OracleERP DynamicsCRM K2 BPM 信息安全 企业信息 Android开发 iOS开发 WindowsPhone WindowsMobile 其他手机 敏捷开发 项目管理 软件工程 SQLServer Oracle MySQL NoSQL 其它数据库 Windows7 WindowsServer Linux
   -> Android开发 -> Android学习笔记 -> 正文阅读

[Android开发]Android学习笔记

内容摘要:Android Handler消息传递机制的学习总结、问题记录
Handler消息传递机制的目的:
1.实现线程间通信(如:Android平台只允许主线程(UI线程)修改Activity里的UI组件,而实际开发时会遇到新开的线程要改变界面组件属性的情况,这时就要有一种办法通知主线程更新UI)。Handler消息传递机制可用于线程间传递消息。
2.实现消息的异步处理。
机制的实现:(工作原理涉及Handler、Looper、Message(消息)、MessageQueue(消息队列);代码分消息接收方,发送方2处)
原理说明(本人理解有限,比较好的Handler说明看这篇):
Handler可发送Message到MessageQueue或处理从Looper收到的Message。
Message消息对象,在整个机制中传递。
MessageQueue是一个以先进先出方式管理Message的队列。
Looper管理MessageQueue,它把从MessageQueue里取到的Message分发给相应的Handler。
原理图:


注意:
1.Handler是建立在Looper上,实现Thread的消息系统处理模型,实现消息异步处理的;
2.MessageQueue会在Looper(Looper()构造函数)初始化时创建关联;
3.一个线程最多只能有一个Looper对象(Looper.prepare()方法创建Looper对象,规定了这个);
4.主线程(UI线程)系统已经帮初始化了一个Looper对象(简单分析看这,主线程源码详细分析看这),因此程序直接创建Handler即可;程序员自己启动的线程必须先创建Looper对象并启动(调用Looper.loop()),然后才能向该线程的消息队列发消息。
Looper源码参考:
prepare()方法保证每个线程最多只有一个Looper对象,loop()方法使用一个死循环不断取出MessageQueue中的消息,并把取出的消息分给对应的Handler处理。

1 //Looper初始化时创建并关联MessageQueue
2 private Looper(boolean quitAllowed) {
3     mQueue = new MessageQueue(quitAllowed);
4     mThread = Thread.currentThread();
5 }


 1 //一个线程最多一个Looper,调用prepare()方法创建Looper对象
 2 public static void prepare() {
 3     prepare(true);
 4 }
 5 
 6 private static void prepare(boolean quitAllowed) {
 7     if (sThreadLocal.get() != null) {
 8         throw new RuntimeException("Only one Looper may be created per thread");
 9     }
10     sThreadLocal.set(new Looper(quitAllowed));
11 }


 1 /**
 2  * Initialize the current thread as a looper, marking it as an
 3  * application's main looper. The main looper for your application
 4  * is created by the Android environment, so you should never need
 5  * to call this function yourself.  See also: {@link #prepare()}
 6  */
 7 //主UI线程初始化Looper对象调用的方法
 8 public static void prepareMainLooper() {
 9     prepare(false);
10     synchronized (Looper.class) {
11         if (sMainLooper != null) {
12             throw new IllegalStateException("The main Looper has already been prepared.");
13         }
14         sMainLooper = myLooper();
15     }
16 }


 1 /**
 2  * Run the message queue in this thread. Be sure to call
 3  * {@link #quit()} to end the loop.
 4  */
 5 public static void loop() {
 6     final Looper me = myLooper();
 7     if (me == null) {
 8         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 9     }
10     final MessageQueue queue = me.mQueue;
11 
12     // Make sure the identity of this thread is that of the local process,
13     // and keep track of what that identity token actually is.
14     Binder.clearCallingIdentity();
15     final long ident = Binder.clearCallingIdentity();
16 
17    //使用一个死循环不断从MessageQueue取Message,并发给对应Handler
18     for (;;) {
19         Message msg = queue.next(); // might block
20         if (msg == null) {
21             // No message indicates that the message queue is quitting.
22             return;
23         }
24 
25         // This must be in a local variable, in case a UI event sets the logger
26         final Printer logging = me.mLogging;
27         if (logging != null) {
28             logging.println(">>>>> Dispatching to " + msg.target + " " +
29                     msg.callback + ": " + msg.what);
30         }
31 
32         final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
33 
34         final long traceTag = me.mTraceTag;
35         if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
36             Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
37         }
38         final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
39         final long end;
40         try {
41             msg.target.dispatchMessage(msg);
42             end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
43         } finally {
44             if (traceTag != 0) {
45                 Trace.traceEnd(traceTag);
46             }
47         }
48         if (slowDispatchThresholdMs > 0) {
49             final long time = end - start;
50             if (time > slowDispatchThresholdMs) {
51                 Slog.w(TAG, "Dispatch took " + time + "ms on "
52                         + Thread.currentThread().getName() + ", h=" +
53                         msg.target + " cb=" + msg.callback + " msg=" + msg.what);
54             }
55         }
56 
57         if (logging != null) {
58             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
59         }
60 
61         // Make sure that during the course of dispatching the
62         // identity of the thread wasn't corrupted.
63         final long newIdent = Binder.clearCallingIdentity();
64         if (ident != newIdent) {
65             Log.wtf(TAG, "Thread identity changed from 0x"
66                     + Long.toHexString(ident) + " to 0x"
67                     + Long.toHexString(newIdent) + " while dispatching to "
68                     + msg.target.getClass().getName() + " "
69                     + msg.callback + " what=" + msg.what);
70         }
71 
72         msg.recycleUnchecked();
73     }
74 }

原理总结到这,下面看看在代码中具体如何实现
代码示例(只是一种实现,更多Handler用法总结看这):
1.消息接收方线程先调用Looper.prepare()创建Looper对象,然后创建Handler对象并定义处理消息的方法,接下来调用Looper.loop()启动Looper。

 1 class CallbackThread extends Thread {
 2     public Handler mHandler;
 3         
 4     public void run() {
 5         Looper.prepare();
 6         mHandler = new Handler() {
 7             @Override
 8             public void handleMessage(Message msg) {
 9                 if (msg.what == 123) {
10                     Toast.makeText(MainActivity.this, "get message!", Toast.LENGTH_SHORT).show();
11                 }
12             }
13         };
14         Looper.loop();
15     }
16 }

2.消息发送方线程通过调用Handler类相关方法向接收方线程的Handler对象发送消息,可用的方法有:



 1 callbackThread = new CallbackThread();
 2 callbackThread.start();
 3 
 4 //发空消息
 5 callbackThread.mHandler.sendEmptyMessage(123);
 6 
 7 //创建消息发送
 8 Message msg = new Message();
 9 msg.what = 123;
10 callbackThread.mHandler.sendMessage(msg);

以上是Handler学习总结,接下来是学习过程中遇到的问题记录。
问题记录
1.代码示例中接收消息的线程是先调用Looper.prepare(),再创建Handler实现消息处理方法,最后再Looper.loop()。为什么是prepare->Handler->loop这个顺序?可不可以换?
答:顺序不能换。首先prepare是肯定要在loop之前,因为prepare()方法源码注释中有这样一句话(Be sure to call* {@link #loop()} after calling this method),那就按它的来(因为我没看源码......)。可还有两种顺序是吧,一个个试下。Handler->prepare->loop不行,现象是发出的消息没被接收。prepare->loop->Handler也不可以,现象是导致APP退出。我(zhao)的(chao)理(wang)解(shang)是Handler要想正常工作首先要保证当前线程中有Looper对象(why?可能是能发消息首先要有消息队列?),所以先要prepare创建Looper对象;然后Looper.loop()使用死循环取消息,且当没有消息时会阻塞,这样的话放在它之后的代码——创建Handler的代码不会执行,当调用该Handler对象的sendMessage()一类方法时便会产生NullPointerException,如下(AV画质):


2.不是说只有UI线程能对UI组件操作,为什么当上面截图中代码把创建Handler对象放在prepare和loop之间时,子线程使用Toast不报错还能显示?
答:首先子线程直接用Toast是不行的,不会弹出Toast只会报错......之后我上网找啊找啊找到这篇,只是后面得出结论的时候说“Toast可能是属于修改UI界面”???这几个意思???于是我又找啊找啊,知乎找到这个问题,天啊!用了Toast这么久难道它不是更新UI操作,可能正如知乎上大佬说的——“吐司操作的是window,不属于checkThread抛主线程不能更新UI异常的管理范畴”?信息量太大,我能力有限还没理解,先存疑记录//Todo。总之,现在知道Toast要在子线程中使用可以借助Looper。
3.Looper.loop()使用死循环取消息难道不会很耗资源吗?
答:并不会,具体看这篇。简而言之,死循环中调用queue.next()读取下一条消息(在loop调用的线程中),如果读取到了就msg.target.dispatchMessage(),否则queue.next()则会一直阻塞到超过超时时间。
4.主线程Looper也调用了loop(),会不会也阻塞?
答:也会有阻塞,但不会卡死,其实和问题3是一个道理,MessageQueue没消息了都会阻塞进入休眠,之后会被句柄写操作唤醒epoll.wait。参考:知乎问题,CSDN文章(虽然文章标题和结论矛盾)。
5.queue.next()的阻塞是怎么实现的?
答:参考3,4中的链接。关键字:Linux pipe/epoll机制,loop()的queue.next()中的nativePollOnce()方法。
感想:第一篇博客花了我一晚上一早上加半个下午,妈呀!那些大佬都怎么这么高产的。问题其实还有更多的,但一部分忘了记录下来,一部分太不成熟,再就是还存在没发现的问题......越来越懵逼了,完全没有豁然开朗的感觉???主要是知道的太少了,一次性见识到这么多新的事物消化不来,学习笔记也很乱,毕竟第一次写博客,慢慢学吧,Android之路长着呢,嘻嘻嘻!
  Android开发 最新文章
Android动态权限申请
使用Kotlin,抛弃findViewById
.net程序员做的第一个安卓APP
Android学习笔记
计算机毕业设计源码分享
计算机毕业设计源码分享
ELF Format 笔记(一)—— 概述
Fragment学习笔记
Android复制Assets目录下的文件到指定目录
Android网络请求框架AsyncHttpClient实例详
上一篇文章      下一篇文章      查看所有文章
加:2017-12-08 23:27:21  更:2017-12-08 23:27:27 
 
360图书馆 软件开发资料 购物精选 新闻资讯 Chinese Culture 三丰软件 开发 中国文化 阅读网 日历 万年历 2019年9日历
2019-9-20 0:49:06
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  编程知识