【Android】安卓学习笔记之多线程、异步消息处理机制、使用AsyncTask在子线程中对UI 进行操作

来源:互联网 发布:数据库可以没有主键吗 编辑:程序博客网 时间:2024/04/29 20:15

1、线程的基本用法

      Android 多线程编程其实并不比Java 多线程编程特珠,基本都是使用相同的语法。比如说,定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可,如下所示:
class MyThread extends Thread {@Overridepublic void run() {// 处理具体的逻辑}}

      那么该如何启动这个线程呢?其实也很简单,只需要new 出MyThread 的实例,然后调用它的start()方法,这样run()方法中的代码就会在子线程当中运行了,如下所示:
      new MyThread().start();

      当然,使用继承的方式耦合性有点高,更多的时候我们都会选择使用实现Runnable 接口的方式来定义一个线程,如下所示:
class MyThread implements Runnable {@Overridepublic void run() {// 处理具体的逻辑}}

      如果使用了这种写法,启动线程的方法也需要进行相应的改变,如下所示:
      MyThread myThread = new MyThread();
      new Thread(myThread).start();

      可以看到,Thread 的构造函数接收一个Runnable 参数,而我们new 出的MyThread 正是一个实现了Runnable 接口的对象,所以可以直接将它传入到Thread 的构造函数里。接着调用Thread 的start()方法,run()方法中的代码就会在子线程当中运行了。
      当然,如果你不想专门再定义一个类去实现Runnable 接口,也可以使用匿名类的方式,这种写法更为常见,如下所示:
new Thread(new Runnable() {@Overridepublic void run() {// 处理具体的逻辑}}).start();

      以上几种线程的使用方式相信你都不会感到陌生,因为在Java 中创建和启动线程也是使用同样的方式。了解了线程的基本用法后,下面我们来看一下Android 多线程编程与Java多线程编程不同的地方。

2、异步消息处理机制

      Android 确实是不允许在子线程中进行UI 操作的。但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI 控件,这该
如何是好呢?
      对于这种情况,Android 提供了一套异步消息处理机制,完美地解决了在子线程中进行UI 操作的问题。
      修改MainActivity 中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {public static final int UPDATE_TEXT = 1;private TextView text;private Button changeText;private Handler handler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {case UPDATE_TEXT:// 在这里可以进行UI操作text.setText("Nice to meet you");break;default:break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);text = (TextView) findViewById(R.id.text);changeText = (Button) findViewById(R.id.change_text);changeText.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text:new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();message.what = UPDATE_TEXT;handler.sendMessage(message); // 将Message对象发送出去}}).start();break;default:break;}}}

      这里我们先是定义了一个整型常量UPDATE_TEXT,用于表示更新TextView 这个动作。然后新增一个Handler 对象,并重写父类的handleMessage 方法,在这里对具体的Message进行处理。如果发现Message 的what 字段的值等于UPDATE_TEXT,就将TextView 显示的内容改成Nice to meet you。
      下面再来看一下Change Text 按钮的点击事件中的代码。可以看到,这次我们并没有在子线程里直接进行UI 操作,而是创建了一个Message(android.os.Message)对象,并将它的what 字段的值指定为UPDATE_TEXT,然后调用Handler 的sendMessage()方法将这条Message 发送出去。很快,Handler 就会收到这条Message,并在handleMessage()方法中对它进行处理。注意此时handleMessage()方法中的代码就是在主线程当中运行的了,所以我们可以放心地在这里进行UI 操作。接下来对Message 携带的what 字段的值进行判断,如果等于UPDATE_TEXT,就将TextView 显示的内容改成Nice to meet you。
      现在重新运行程序,可以看到屏幕的正中央显示着Hello world。然后点击一下ChangeText 按钮,显示的内容着就被替换成Nice to meet you,如图所示。


      Android 中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue 和Looper。其中Message 和Handler 在上一小节中我们已经接触过了,而MessageQueue 和Looper对于你来说还是全新的概念,下面我就对这四个部分进行一下简要的介绍。

1. Message
      Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message 的what 字段,除此之外还可以使
用arg1 和arg2 字段来携带一些整型数据,使用obj 字段携带一个Object 对象。
2. Handler
      Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler 的sendMessage()方法,而发出的消息经过一系列地辗转处理后,
最终会传递到Handler 的handleMessage()方法中。
3. MessageQueue
      MessageQueue 是消息队列的意思,它主要用于存放所有通过Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
4. Looper
      Looper 是每个线程中的MessageQueue 的管家,调用Looper 的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue 中存在一条消息,就会将它取出,并传递到Handler 的handleMessage()方法中。每个线程中也只会有一个Looper 对象。
      了解了Message、Handler、MessageQueue 以及Looper 的基本概念后,我们再来对异步消息处理的整个流程梳理一遍。首先需要在主线程当中创建一个Handler 对象,并重写handleMessage()方法。然后当子线程中需要进行UI 操作时,就创建一个Message 对象,并通过Handler 将这条消息发送出去。之后这条消息会被添加到MessageQueue 的队列中等待被处理,而Looper 则会一直尝试从MessageQueue 中取出待处理消息,最后分发回Handler的handleMessage()方法中。由于Handler 是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI 操作了。


3、使用AsyncTask在子线程中对UI 进行操作

      借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask 背后的实现原理也是基于异步消息处理机制的,只是Android 帮我们做了很好的封装而已。
      一个比较完整的自定义AsyncTask 可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {@Overrideprotected void onPreExecute() {progressDialog.show(); // 显示进度对话框}@Overrideprotected Boolean doInBackground(Void... params) {try {while (true) {int downloadPercent = doDownload(); // 这是一个虚构的方法publishProgress(downloadPercent);if (downloadPercent >= 100) {break;}}} catch (Exception e) {return false;}return true;}@Overrideprotected void onProgressUpdate(Integer... values) {// 在这里更新下载进度progressDialog.setMessage("Downloaded " + values[0] + "%");}@Overrideprotected void onPostExecute(Boolean result) {progressDialog.dismiss(); // 关闭进度对话框// 在这里提示下载结果if (result) {Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();} else {Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show();}}}

      在这个DownloadTask 中,我们在doInBackground()方法里去执行具体的下载任务。这个方法里的代码都是在子线程中运行的,因而不会影响到主线程的运行。注意这里虚构了一个doDownload()方法,这个方法用于计算当前的下载进度并返回,我们假设这个方法已经存在了。在得到了当前的下载进度后, 下面就该考虑如何把它显示到界面上了, 由于doInBackground()方法是在子线程中运行的,在这里肯定不能进行UI 操作,所以我们可以调用publishProgress()方法并将当前的下载进度传进来,这样onProgressUpdate()方法就会很快被调用,在这里就可以进行UI 操作了。
      当下载完成后,doInBackground()方法会返回一个布尔型变量,这样onPostExecute()方法就会很快被调用,这个方法也是在主线程中运行的。然后在这里我们会根据下载的结果来弹出相应的Toast 提示,从而完成整个DownloadTask 任务。
      简单来说,使用AsyncTask 的诀窍就是,在doInBackground()方法中去执行具体的耗时任务,在onProgressUpdate()方法中进行UI 操作,在onPostExecute()方法中执行一些任务的收尾工作。
      如果想要启动这个任务,只需编写以下代码即可:
      new DownloadTask().execute();
0 0
原创粉丝点击