Android学习笔记:线程:Message和Runnable .
fmms
13年前
<p>程序需要相应用户的操作,最要能在200ms(0.2s)之内,如果超过5秒没有反应,ActivityManager会没有提示就kill了activity。然而,activity可能真的需要时间来进行处理,这往往会用到后台线程-background thread。后台线程可以<strong>安全</strong>地和UI线程进行交互,其中后台线程是不能修改UI的。我不太确切知晓“不能修改UI”到何种程度,例如在下面的例子进度条状态的修改是允许的,在复杂的例子中可能会出现问题,例如两个后台线程都要处理同一个widget,可能会有不可预知的情况出现,然而就编程而言,确实不应当如此处理,后台进程应避免涉及UI的处理,UI的归UI,处理的规处理。这样才是良好的编程风格或者是一种编程原则。</p> <p>通过创建一个Handler子类的对象,每个acvivity只需一个Handler对象。后台进程可通过两种方式Handler进行通信:message和Runnable对象,其结果实质都是将在Handler的队列中放入内容,message是放置信息,可以传递一些参数,Handler获取这些信息并将判度如何处理,而Runnable则是直接给出处理的方法。队列就是依次执行,Handler会处理完一个消息或者执行完某个处理在进行下一步,这样不会出现多个线程同时要求进行UI处理而引发的混乱现象。</p> <p>这些队列中的内容(无论Message还是Runnable)可以要求马上执行,延迟一定时间执行或者指定某个时刻执行,如果将他们放置在队列头,则表示具有最高有限级别,立即执行。这些函数包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。</p> <p>一般而言,推荐是Messge方式,这样程序设计得可以更为灵活,而Runnable在某些简单明确的方式中使用。我们将通过三种方法编写一个小例子来学习。这个例子是一个进度条,每隔1秒,进度条步进5,如果acvity停止时,进度条归零。</p> <p><img border="0" alt="" align="right" src="https://simg.open-open.com/show/eb79cce88e62465ed692a47ca0c413d1.png" width="200" height="67" /></p> <p><strong>Android XML</strong>:</p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p><span style="color:#888888;"><?xml version="1.0" encoding="utf-8"?></span><br /> <span style="color:#888888;"><LinearLayout ...... /></span><br /> <ProgressBar android:id="@+id/c15_progress" <br /> <strong> style="?android:attr/progressBarStyleHorizontal"</strong> <span style="color:#3366ff;"> <!-- 这表明采用传统水平进度条的方式--></span><br /> <span style="color:#888888;"> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content"</span> /><br /> <span style="color:#888888;"></LinearLayout></span></p> </blockquote> <p><strong>例子一:线程开启,采用Message传递后台线程和UI主线程之间的信息</strong></p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p>public class Chapter15Test1 extends Activity{<br /> <span style="color:#808080;"> private ProgressBar bar = null;<br /> private boolean isRunning = false;</span><br /> <br /> <span style="color:#3366ff;"> <span style="color:#008080;">/* 我们为这个Acivity创建一个用于和后台程序通信的handler,简单地,只要一收到message,就将progressbar进度增加5。*/</span></span><br /> <span style="color:#3366ff;"> /* 步骤1:创建Handler,并通过handleMessage()给出当收到消息是UI需要进行如何处理,例子简单不对msg的内容进行分析*/</span><br /> Handler handler= new Handler(){<br /> public void <strong>handleMessage</strong>(Message msg) {<br /> bar.incrementProgressBy(5);<br /> }<br /> };<br /> <br /> <span style="color:#808080;"> protected void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.chapter_15_test1);<br /> bar=(ProgressBar)findViewById(R.id.c15_progress);<br /> }<br /> </span><br /> <span style="color:#008080;">/*on Start是UI初始化并显示时调用*/</span><br /> protected void onStart() {<br /> <span style="color:#999999;"> super.onStart();<br /> bar.setProgress(0);</span><br /> <span style="color:#3366ff;"> /*步骤2:建立后台线程处理,采用Thread,其中run()的内容,就是线程并行处理的内容,Thread是Runnable的implements*/</span><br /> Thread background = new Thread(new Runnable(){<br /> public void <strong>run</strong>() {<br /> try{<br /> for(int i = 0; i < 20 && isRunning; i ++){<br /> Thread.sleep(1000);<br /> <span style="color:#3366ff;">/* 步骤2.1:发送Message到队列中,参数中的obtainMessage()是用于给出一个新Message,本例无参数,对应的在handler在队列中收到这条消息时,则通过handleMessage()进行处理*/</span><br /> handler.<strong>sendMessage</strong>(handler.<strong>obtainMessage</strong>());<br /> }<br /> }catch(Throwable t){<br /> <span style="color:#808080;">//jest end the thread</span><br /> }<br /> }<br /> <br /> });<br /> <span style="color:#999999;"> isRunning = true;</span><br /> <span style="color:#3366ff;"> /*步骤3:启动线程*/</span><br /> background.start();<br /> }<br /> <br /> <span style="color:#008080;">/*onStop是UI停止显示时调用,例如我们按了返回键*/</span><br /> protected void onStop() {<br /> <span style="color:#999999;"> super.onStop();<br /> isRunning = false;</span><br /> } <br /> }</p> </blockquote> <p><strong>例子2:采用Runnable</strong></p> <p>我们在上面的例子的基础上进行修改,如下</p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p><span style="color:#3366ff;"> /*步骤1:由于不需要处理Message,也即不需要处理handleMessage()*/</span><br /> Handler handler= new Handler();<br /> <span style="color:#3366ff;">/*步骤1.1:定义处理动作,采用Runnable的实例,通过implements run()来定制处理,这里是简单将进度条步进5。由于我们将在Thread中使用这个实例,所以考虑采用final的方式*/</span><br /> final Runnable r = <strong>new Runnable()</strong>{<br /> public void run(){<br /> <span style="color:#808080;">bar.incrementProgressBy(5);</span><br /> } <br /> };<br /> <span style="color:#3366ff;">/* ... ...在onStart()中的步骤2:线程的处理,和提供message不同,对于runnable方式,采用post */</span><br /> <span style="color:#808080;"> Thread background = new Thread(new Runnable(){<br /> public void run() {<br /> try{<br /> for(int i = 0; i < 20 && isRunning; i ++){<br /> Thread.sleep(1000);</span><br /> <strong>handler.post(r);</strong><br /> <span style="color:#808080;"> }<br /> }catch(Throwable t){<br /> //jest end the thread<br /> }<br /> } <br /> });<br /> background.start(); </span></p> </blockquote> <p><strong>例子3:可以用延迟处理实现定时触发,让程序更为简单</strong></p> <p>在这里例子,事实我们是进行定时的处理,利用Handler队列可以设置延期处理的方式,我们并不需要创建一个后台运行的线程,也可以实现</p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p> Handler handler= new Handler();<br /> <br /> <span style="color:#666699;"> ... ... 在onStart() ... ...</span><br /> <span style="color:#3366ff;"> //利用handler.postDelayed(r,1000),在队列中要求延迟1秒后进行r的处理,而在r的处理中, 最后在handler的队列中加 入一个要求延迟1秒的处理,如是,就可以实现每隔1秒的定期处理。</span><br /> handler.postDelayed(<strong>new Runnable()</strong>{<br /> <span style="color:#333333;"> public void run() {<br /> <span style="color:#808080;"> if(isRunning && Chapter15Test2.step < 20){<br /> step ++;<br /> bar.incrementProgressBy(5);</span><br /> <strong>handler.postDelayed(this, 1000);</strong><br /> <span style="color:#808080;"> }</span><br /> } </span> <br /> },<strong>1000</strong>);</p> </blockquote> <p>在这个例子中,我们基础某种判度,自动停止向队列加入处理。如果有某种情况,我们需要清除队列中的消息或者理,可以使用removMessages()或者removeCallbacks()的处理,这种对于延迟处理方式是非常有用的,可以中断定期的处理。当然,一般来讲我们希望能够得到某种判度,以使得定期处理能够优雅地结束,而不是简单地从队列中将消息或者处理删除。</p> <p><strong>例子4:不知道在UI主线程还是在后台线程</strong></p> <p>有时候,我们并不清楚代码将在UI线程还是后台线程运行,例如这些代码封装为一个JAR,提供给其他人调用,我们并不清楚其他人如何使用这些代码。为了解决这个问题Android在activity中提供了runOnUiThread(),如果在UI线程,则马上执行,如果在后台线程,则将Runnable的执行内容加入到后台线程的队列中,这样无论代码在UI线程还是后台线程都能安全地执行。</p> <p>我们在例子1的基础上进行试验:</p> <p>1、建立一个Runnable,以便我们将在UI和后台Thread中进行试验</p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p> Runnable runAction = new Runnable(){<br /> public void run(){<br /> <span style="color:#3366ff;"> //注意,我们不能使用Toast.makeText(this,....),因为我们无法确定Runnable具体运行的context</span><br /> <span style="color:#000000;"> Toast.makeText(<strong>getApplicationContext</strong>(),"Hello!",Toast.LENGTH_SHORT).show();</span><br /> <span style="color:#888888;"> <span style="color:#666699;">//Log.d("WEI","runAction .... is called");</span></span><br /> }<br /> };</p> </blockquote> <p>由于Toast的显示和隐藏需要一定的时间,而间隔1秒显然不够,我们将例子1的间隔时间1000ms,改为5000ms这样会比较清晰,当然可以采用Log.d的方式来替代。</p> <p>2、在UI线程中执行该操作,在后台线程中增加该操作,这个操作无论是在UI还是在后台线程都是可以正确执行的。</p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p>protected void onStart() {<br /> ... ... <br /> Thread background = new Thread(new Runnable(){<br /> <span style="color:#808080;"> public void run() {<br /> try{<br /> for(int i = 0; i < 20 && isRunning; i ++){<br /> Thread.sleep(5000);<br /> handler.sendMessage(handler.obtainMessage());</span><br /> <strong>runOnUiThread</strong>(runAction);<br /> <span style="color:#808080;"> }<br /> }catch(Throwable t){<br /> //jest end the thread<br /> }</span><br /> } <br /> });<br /> <br /> <span style="color:#808080;"> isRunning = true;<br /> background.start();</span><br /> <strong>runOnUiThread</strong>(runAction);<br /> }</p> </blockquote> <p><strong>例子5:HandlerThread</strong></p> <p>在上面的例子中,无论是否使用了后台线程(例子1-2),Handler的处理实际就是UI主线程的处理,一般的使用方式为我们通过后台线程执行某些操作,如果需要进行UI的互动,将消息或者处理方式到Handler的的队列中,然手在UI主线程中进行处理。这是我们通用的情况。</p> <p>之前我们讨论过为何UI的归UI,处理的处理,然而,可能有这样的需求,举个例子,在某些情况下,Handler收到消息触发的处理中可能会有Sleep(),这会导致main线程进入sleep状态,不是我们期待的。因此我们希望通过一个线程专门处理Hanlder的消息,这个线程也是依次从Handler的队列中获取信息,逐个进行处理,保证安全,不会出现混乱引发的异常。</p> <p>针对此Android提供的HandlerThread。方式使用方法如下:</p> <blockquote style="border-bottom:#ffbcbc thin dashed;border-left:#ffbcbc thin dashed;padding-bottom:5pt;line-height:140%;background-color:#ffffea;margin-top:11px;text-indent:0em;padding-left:5pt;padding-right:5pt;font-family:微软雅黑;margin-bottom:11px;margin-left:10pt;font-size:9pt;border-top:#ffbcbc thin dashed;border-right:#ffbcbc thin dashed;padding-top:5pt;"> <p><span style="color:#3366ff;">//步骤1:创新HandlerThread的一个对象,并开启这个线程,HandlerThread将通过Looper来处理Handler对来中的消息,也就是如果发现Handler中有消息,将在HandlerThread这个线程中进行处理。</span><br /> HandlerThread ht = new HandlerThread("hander_thread");<br /> <span style="color:#3366ff;">//步骤2:启动handerhandler这个线程;</span><br /> ht.start();<br /> <br /> <span style="color:#3366ff;">//步骤3:创建handler中,带上Looper的参数,即handlerThread.getLooper()。注意,此处理必须在HandlerThread启动后才能调用,否则会报<span style="color:#3366ff;">错</span></span><span style="color:#3366ff;"> ,getLooper()会返回null,则程序异常出错</span><br /> Handler handler = new Handler(ht.getLooper()){<br /> ....<br /> public void handleMessage(Message msg){<br /> ... ... <span style="color:#008080;"> /*这里的处理,将不在主线程中执行,而在HandlerThread线程中执行,可以通过Thread.currentThread().getId()或者Thread.currentThread().getName()来确定*/</span><br /> }<br /> };</p> </blockquote> <span style="background-color:#ffffea;">转自:<a href="/misc/goto?guid=4959517942437674189">http://blog.csdn.net/flowingflying/article/details/6370184</a></span>