版权声明:本文为博主原创文章,未经博主允许不得转载。
转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/47079737;
本文出自:【张鸿洋的博客】
1、概述
话说最近股市变动不变,也成了热火朝天的话题。不知道大家有没有考虑做个实时更新股市数据的app呢?假设我们要做一个股市数据实时更新的app,我们可以在网上找个第三方的股市数据接口,然后在我们的app中每隔1分钟(合适的时间)去更新数据,然后更新我们的UI即可。
当然了,本文不是要教大家做这样一个app,只是举个场景。言归正传,回到我们的HandlerThread,大家一定听说过Looper、Handler、Message三者的关系(如果不了解,可以查看Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系),在我们的UI线程默默的为我们服务。其实我们可以借鉴UI线程Looper的思想,搞个子线程,也通过Handler、Message通信,可以适用于很多场景。
对了,我之前写过一篇博文Android Handler 异步消息处理机制的妙用 创建强大的图片加载类,这篇博文中就在子线程中创建了Looper,Handler,原理和HandlerThread一模一样,可惜当时我并不知道这个类,不过大家也可以当做HandlerThread应用场景进行学习。
2、HandlerThread实例
下面看我们模拟大盘走势的代码,其实非常简单,就一个Activity
<code class="language-java hljs has-numbering"> <span class="hljs-keyword">package</span> com.zhy.blogcodes.intentservice; <span class="hljs-keyword">import</span> android.os.Bundle; <span class="hljs-keyword">import</span> android.os.Handler; <span class="hljs-keyword">import</span> android.os.HandlerThread; <span class="hljs-keyword">import</span> android.os.Message; <span class="hljs-keyword">import</span> android.support.v7.app.AppCompatActivity; <span class="hljs-keyword">import</span> android.text.Html; <span class="hljs-keyword">import</span> android.widget.TextView; <span class="hljs-keyword">import</span> com.zhy.blogcodes.R; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HandlerThreadActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span> <span class="hljs-keyword">private</span> TextView mTvServiceInfo; <span class="hljs-keyword">private</span> HandlerThread mCheckMsgThread; <span class="hljs-keyword">private</span> Handler mCheckMsgHandler; <span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> isUpdateInfo; <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MSG_UPDATE_INFO = <span class="hljs-number">0x110</span>; <span class="hljs-comment">//与UI线程管理的handler</span> <span class="hljs-keyword">private</span> Handler mHandler = <span class="hljs-keyword">new</span> Handler(); <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) { <span class="hljs-keyword">super</span>.onCreate(savedInstanceState); setContentView(R.layout.activity_thread_handler); <span class="hljs-comment">//创建后台线程</span> initBackThread(); mTvServiceInfo = (TextView) findViewById(R.id.id_textview); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onResume</span>() { <span class="hljs-keyword">super</span>.onResume(); <span class="hljs-comment">//开始查询</span> isUpdateInfo = <span class="hljs-keyword">true</span>; mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPause</span>() { <span class="hljs-keyword">super</span>.onPause(); <span class="hljs-comment">//停止查询</span> isUpdateInfo = <span class="hljs-keyword">false</span>; mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO); } <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initBackThread</span>() { mCheckMsgThread = <span class="hljs-keyword">new</span> HandlerThread(<span class="hljs-string">"check-message-coming"</span>); mCheckMsgThread.start(); mCheckMsgHandler = <span class="hljs-keyword">new</span> Handler(mCheckMsgThread.getLooper()) { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) { checkForUpdate(); <span class="hljs-keyword">if</span> (isUpdateInfo) { mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, <span class="hljs-number">1000</span>); } } }; } <span class="hljs-javadoc">/** * 模拟从服务器解析数据 */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">checkForUpdate</span>() { <span class="hljs-keyword">try</span> { <span class="hljs-comment">//模拟耗时</span> Thread.sleep(<span class="hljs-number">1000</span>); mHandler.post(<span class="hljs-keyword">new</span> Runnable() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() { String result = <span class="hljs-string">"实时更新中,当前大盘指数:<font color='red'>%d</font>"</span>; result = String.format(result, (<span class="hljs-keyword">int</span>) (Math.random() * <span class="hljs-number">3000</span> + <span class="hljs-number">1000</span>)); mTvServiceInfo.setText(Html.fromHtml(result)); } }); } <span class="hljs-keyword">catch</span> (InterruptedException e) { e.printStackTrace(); } } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onDestroy</span>() { <span class="hljs-keyword">super</span>.onDestroy(); <span class="hljs-comment">//释放资源</span> mCheckMsgThread.quit(); } } </code>
可以看到我们在onCreate中,去创建和启动了HandlerThread,并且关联了一个mCheckMsgHandler。然后我们分别在onResume和onPause中去开启和暂停我们的查询,最后在onDestory中去释放资源。
这样就实现了我们每隔5s去服务端查询最新的数据,然后更新我们的UI,当然我们这里通过Thread.sleep()模拟耗时,返回了一个随机数,大家可以很轻易的换成真正的数据接口。
布局文库
<code class="language-xml hljs has-numbering"><span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span> <span class="hljs-attribute">xmlns:tools</span>=<span class="hljs-value">"http://schemas.android.com/tools"</span> <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:paddingLeft</span>=<span class="hljs-value">"@dimen/activity_horizontal_margin"</span> <span class="hljs-attribute">android:paddingRight</span>=<span class="hljs-value">"@dimen/activity_horizontal_margin"</span> <span class="hljs-attribute">android:paddingTop</span>=<span class="hljs-value">"@dimen/activity_vertical_margin"</span> <span class="hljs-attribute">android:paddingBottom</span>=<span class="hljs-value">"@dimen/activity_vertical_margin"</span>></span> <span class="hljs-tag"><<span class="hljs-title">TextView </span> <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/id_textview"</span> <span class="hljs-attribute">android:text</span>=<span class="hljs-value">"正在加载大盘指数..."</span> <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span> <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>/></span> <span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span></code>
运行效果图
别问我为什么要用红色!!!
ok,当然了,我们的效果很单调,但是你完全可以去扩展,比如ListView显示用户关注的股票数据。或者是其他的需要一直检测更新的数据。
看到这,你是否好奇呢,HandlerThread的内部是如何做的呢?其实非常的简单,如果你看过Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系估计扫几眼就会了。
HandlerThread 源码分析
对于所有Looper,Handler相关细节统一参考上面提到的文章。
我们轻轻的掀开HandlerThread的源码,还记得我们是通过
<code class="language-java hljs has-numbering"> mCheckMsgThread = <span class="hljs-keyword">new</span> HandlerThread(<span class="hljs-string">"check-message-coming"</span>); mCheckMsgThread.start(); </code>
创建和启动的对象,那么随便扫一眼:
<code class="language-java hljs has-numbering"> <span class="hljs-keyword">package</span> android.os; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HandlerThread</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Thread</span> {</span> <span class="hljs-keyword">int</span> mPriority; <span class="hljs-keyword">int</span> mTid = -<span class="hljs-number">1</span>; Looper mLooper; <span class="hljs-keyword">public</span> <span class="hljs-title">HandlerThread</span>(String name) { <span class="hljs-keyword">super</span>(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onLooperPrepared</span>() { } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() { mTid = Process.myTid(); Looper.prepare(); <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -<span class="hljs-number">1</span>; } </code>看到了什么,其实我们就是初始化和启动了一个线程;然后我们看run()方法,可以看到该方法中调用了Looper.prepare(),Loop.loop();
prepare()呢,中创建了一个Looper对象,并且把该对象放到了该线程范围内的变量中(sThreadLocal),在Looper对象的构造过程中,初始化了一个MessageQueue,作为该Looper对象成员变量。
loop()就开启了,不断的循环从MessageQueue中取消息处理了,当没有消息的时候会阻塞,有消息的到来的时候会唤醒。如果你不明白我说的,参考上面推荐的文章。
接下来,我们创建了一个mCheckMsgHandler,是这么创建的:
<code class="language-java hljs has-numbering">mCheckMsgHandler = <span class="hljs-keyword">new</span> Handler(mCheckMsgThread.getLooper())</code>
对应源码
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> Looper <span class="hljs-title">getLooper</span>() { <span class="hljs-keyword">if</span> (!isAlive()) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>; } <span class="hljs-comment">// If the thread has been started, wait until the looper has been created.</span> <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) { <span class="hljs-keyword">while</span> (isAlive() && mLooper == <span class="hljs-keyword">null</span>) { <span class="hljs-keyword">try</span> { wait(); } <span class="hljs-keyword">catch</span> (InterruptedException e) { } } } <span class="hljs-keyword">return</span> mLooper; }</code>
mCheckMsgThread.getLooper()返回的就是我们在run方法中创建的mLooper。
那么Handler的构造呢,其实就是在Handler中持有一个指向该Looper.mQueue对象,当handler调用sendMessage方法时,其实就是往该mQueue中去插入一个message,然后Looper.loop()就会取出执行。
好了,到这我们就分析完了,其实就几行代码;不过有一点我想提一下:
如果你够细心你会发现,run方法里面当mLooper创建完成后有个notifyAll(),getLooper()中有个wait(),这是为什么呢?因为的mLooper在一个线程中执行,而我们的handler是在UI线程初始化的,也就是说,我们必须等到mLooper创建完成,才能正确的返回getLooper();wait(),notify()就是为了解决这两个线程的同步问题。
不过对于这样的线程间的同步问题,我非常喜欢使用Semaphore。
还记得在Android Handler 异步消息处理机制的妙用 创建强大的图片加载类一文中有类似的说明:
如果你比较细心,可能会发现里面还有一些信号量的操作的代码,如果你不了解什么是信号量,可以参考:Java 并发专题 : Semaphore 实现 互斥 与 连接池 。 简单说一下mSemaphore(信号数为1)的作用,由于mPoolThreadHander实在子线程初始化的,所以我在初始化前调用了mSemaphore.acquire去请求一个信号量,然后在初始化完成后释放了此信号量,我为什么这么做呢?因为在主线程可能会立即使用到mPoolThreadHander,但是mPoolThreadHander是在子线程初始化的,虽然速度很快,但是我也不能百分百的保证,主线程使用时已经初始化结束。
哈,当时也有很多人问,为什么使用这个Semaphore,到这里我想大家应该清楚了。话说假设我当时真的HanderThread这个类,可能之前的代码能简化不少呢~
对了,你可能会问与Timer相比有什么优势呢?
直接参考这篇文章吧:Handler vs Timer
ok~~