【Android开发】线程间通讯机制(基础篇)——Handler、Runnable、HandlerThread、AsyncTask的使用

来源:互联网 发布:电脑什么软件跑分准 编辑:程序博客网 时间:2024/06/07 14:56

前言:

Android线程通讯机制是android应用开发的基础课程,对于很多初学android的朋友可能还没有完全理解,所以,今天我就做一下知识小结吧。

一、线程安全

可能有Java基本的朋友都知道什么叫线程安全。

线程安全:如果你的代码在所在的进程中有多个(两个或两个以上)的线程同时执行,若每次运行的结果和使用单线程模式运行的结果一致,并且变量的值也和预期的一样,这样就叫线程安全。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

在android系统中,当一个android应用启动的时候,会开启一个Lliunx进程和主线程,这个主线程我们有称UI线程。UI线程是负责显示,处理UI以及相关事件的线程。

androidUI操作不是线程安全,就是说要UI线程才能处理UI控件,而且当UI线程阻塞超过5秒的时候,系统会报出Force Close的强制关闭的提示。所以,如果要进行比较耗时的IO操作的时候,android系统就会出现假死的状态,甚至出现FC提示,就如下载文件、打开大文件等等。

二、线程间通讯实现

面对以上问题,我们为了让应用在用户面前展现更加流畅和平滑的效果(就是更好的用户体验),我们需要多线程操作,所以,我们要考虑的是,如果让UI线程和子线程交互。

强大的google开发的android系统里面已经提供了很优秀的线程通讯机制。

1、多线程任务开发可以通过以下几个方式实现:

1.1Handler+Message+Thread

1.2HandlerThread

1.3AsyncTask

2、如果子线程的数据想通知到UI线程中,可以一下的实现方法:

2.1、上述的三种方法

2.2Activity.runOnUIThread(Runnable)

2.3View.post(Runnable)

2.4View.postDelayed(Runnable, long)

三、分析

1Handler+Message+Thread(Runnable)

Handler

1、发送消息到消息队列

2、发送Runnable任务到消息队列

3、从消息队列中接受消息并在当前线程处理消息

4、从消息队列中接受Runnable任务并执行

5、按时间计划(指定时间、延迟时间、每隔时间段)来执行任务或者处理消息

Handler中分发消息的一些方法
        post(Runnable)
        postAtTime(Runnable,long)
        postDelayed(Runnable long)
        sendEmptyMessage(int)
        sendMessage(Message)
        sendMessageAtTime(Message,long)
        sendMessageDelayed(Message,long)
        以上post类方法允许你排列一个Runnable对象到主线程队列中,
        sendMessage类方法允许你安排一个带数据的Message对象到队列中,等待更新.

Message

封装了一系列的参数的消息实体类

ThreadRunnable

子线程

消息通知简单例子:

[html] view plain copy
  1. @Override  
  2.     protected void onCreate(Bundle savedInstanceState) {  
  3.         // TODO Auto-generated method stub  
  4.         super.onCreate(savedInstanceState);  
  5.           
  6.         /* 子线程执行耗时任务 */  
  7.         new Thread() {  
  8.             public void run() {  
  9.                 // 执行耗时的任务  
  10.                 try {  
  11.                     Thread.currentThread().sleep(7000);  
  12.                 } catch (Exception err) {  
  13.                 }  
  14.                 // 通过Handler获得Message对象  
  15.                 Message msg = new MyHandler().obtainMessage();  
  16.                 msg.obj = "任务执行完毕";  
  17.                 // 发送到Handler,在UI线程里处理Message  
  18.                 msg.sendToTarget();  
  19.             }  
  20.         }.start();  
  21.     }  
  22.   
  23.     /* 重写handleMessage方法,接受并处理Message消息 */  
  24.     public class MyHandler extends Handler {  
  25.         public void handleMessage(Message msg) {  
  26.             // 处理接受到的Message  
  27.             System.out.println("接受到消息:" + msg.obj + ",并成功处理");  
  28.         }  
  29.     }  




注意:不能在子线程操作UI线程中的UI控件,因为之前也说过UI线程是线程不安全的,有的人可能操作了UI但是也没有报错。这是因为此时UI资源没有被争夺,没有产生死锁,但是也存在这个问题,所以要编写高质量的代码就要编写健壮性强的代码。所以只能在Handler里面来操作UI

如果使用post(Runnable)postAtTime(Runnable,long)postDelayed(Runnable long)方法,里面的Runnable可以对UI操作,因为最后UI线程接受到Runnable的任务时,是直接执行run()方法,所以本质上在UI线程执行任务。

2、HandlerThread

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called. 

1、本身继承Thread,就是一个子线程

2、和Handler+Message+Thread的区别是:在Handler类里面处理Message消息可以是耗时操作,但是不能对UI操作。就是说把handleMessage方法在子线程执行了,而不是在UI线程执行,所以这个工具类应用不广,只是多线程处理接受到的Message对象。

下面是一个简单的例子:

[java] view plain copy
  1. public void onCreate(Bundle savedInstanceState) {  
  2.   
  3.         super.onCreate(savedInstanceState);  
  4.   
  5.         HandlerThread hThread = new HandlerThread("myThread");  
  6.   
  7.         hThread.start();  
  8.   
  9.         myhandler myhandler = new myhandler(hThread.getLooper());  
  10.   
  11.         Message msg = myhandler.obtainMessage();  
  12.   
  13.         msg.sendToTarget();// 把 Message发送到目标对象,目标对象就是生成msg的目标对象。  
  14.     }  
  15.   
  16.     class myhandler extends Handler {  
  17.         //必须调用Handler(Looper looper)方法  
  18.         public myhandler(Looper looper) {  
  19.             super(looper);  
  20.         }  
  21.   
  22.         public void handleMessage(Message msg) {  
  23.             Log.e("这是新线程""》》》》》》》》》》》》》》》》》新线程的测试");  
  24.         }  
  25.   
  26.     }  


3、AsyncTask

官方的描述(这个我就不翻译了):

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

为了解决多线程执行任务的问题,Android 1.5提供了一个工具类:AsyncTask,它使创建需要与用户界面交互的长时间运行的任务变得更简单。相对来说AsyncTask更轻量级一些,适用于简单的异步处理,不需要借助线程和Handler即可实现。其实说白了,这个工具类只是对线程和Handler的一个封装,具体内部如何实现我将会在提高篇解析。

这个工具类使用很简单主要有以下四个步骤

1、继承或者实现AsyncTask类(因为是abstract 修饰的抽象类)

2、实现以下方法

(1)onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。 
2doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。 
   3onProgressUpdate(Progress...),publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。 
    4onPostExecute(Result), doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread. 

3、UI线程中实例化定义好的AsyncTask或者其子类

4、调用execute(Params...)开始执行任务

为了正确使用AsyncTask类,必须遵循一下准则:

1、AsyncTask实例必须在UI线程中创建

2、execute方法必须在UI线程中调用

3、不允许手动调用onPreExecute(), onPostExecute(Result)doInBackground(Params...), onProgressUpdate(Progress...)这几个方法 

4、AsyncTaskexecute(Params...)执行方法只能执行一次,就是一个实例只能执行一次,执行多次会出现异常。需要说明AsyncTask不能完全取代线程,在一些逻辑较为复杂或者需要在后台反复执行的逻辑就可能需要线程来实现了。

下面有个简单的例子:



[java] view plain copy
  1. public void onCreate(Bundle savedInstanceState) {  
  2.   
  3.         super.onCreate(savedInstanceState);  
  4.   
  5.         //实例化AsyncTask  
  6.         AsyncTask task = new DownloadFilesTask();  
  7.           
  8.         //开始执行任务  
  9.         try {  
  10.             task.execute(new URL("www.hclab.cn"));  
  11.         } catch (MalformedURLException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.     }  
  15.   
  16.     /*一个加载URL的异步任务*/  
  17.     private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {  
  18.     /* 异步执行 */  
  19.          protected Long doInBackground(URL... urls) {  
  20.              int count = urls.length;  
  21.              long totalSize = 0;  
  22.              for (int i = 0; i < count; i++) {  
  23.                  totalSize += Downloader.downloadFile(urls[i]);  
  24.                  publishProgress((int) ((i / (float) count) * 100));  
  25.                  // Escape early if cancel() is called  
  26.                  if (isCancelled()) break;  
  27.              }  
  28.              return totalSize;  
  29.          }  
  30.     /* 任务数据更新 */  
  31.          protected void onProgressUpdate(Integer... progress) {  
  32.              setProgressPercent(progress[0]);  
  33.          }  
  34.     /* 处理最后执行结果 */  
  35.          protected void onPostExecute(Long result) {  
  36.              showDialog("Downloaded " + result + " bytes");  
  37.          }  
  38.      }  



四、总结

这篇博文只是对android多线程处理任务和子线程和UI线程的通讯实现方法做了简单的介绍。所以说,只是适合新手过目,这个也是android应用开发的基本功吧。通常学习某方面的知识的时候不要只局限于表面,聪明的人可能会问,为什么会这样实现的?为什么一定要按照的步骤实现?为什么会有这么多中实现方法,内部会不会是一样的实现原理呢?等等。

对,其实我想告诉初学者的一点学习建议(个人看法,适合自己的学习方法才是最重要):学习android api的时候,首先要学会用,用熟了,再去研究其源码或者内部底层实现原理。如果一下子就告诉你这个类的实现原理,可能会混淆你的总体逻辑思想,会越来越乱,连这个类到底是可以做什么都糊涂了。我个人就是这样,在使用新知识的时候,多问问为什么,多想想方方面面,带着问题,再去源码或者实现原理找答案。当你弄懂了,会感觉很爽,哈哈。这样的层次结构更加清晰。

所以,这就是我分基础篇(使用方法)和提高篇(内部原理解析)的原因了(提高篇之后会出)。


[java] view plain copy
  1. <pre name="code" class="java"><pre></pre>  
  2. <pre></pre>  
  3. <pre></pre>  
  4. <pre></pre>  
  5. <pre></pre>  
  6. <pre></pre>  
  7. <pre></pre>  
  8. <pre></pre>  
  9.      
  10. </pre>  
0 0