Android 多线程编程

线程的基本用法

定义一个线程只需要新建一个类继承自 Thread,然后重写父类的 run()方法,并在里面编写耗时逻辑即可,如下所示:
class MyThread extends Thread {
@Override
public void run() {
// 处理具体的逻辑
}
}
需要 new 出 MyThread 的实例,然后调用它的 start()方法,这样 run()方法中的代码就会在子线程当中运行了,如下所示:
new MyThread().start();
使用继承的方式耦合性有点高,更多的时候我们都会选择使用实现 Runnable 接口的方式来定义一个线程,如下所示:
class MyThread implements Runnable {
@Override
public void run() {
// 处理具体的逻辑
}
}
使用这种写法,启动线程的方法,如下所示:
MyThread myThread = new MyThread();
new Thread(myThread).start();
Thread 的构造函数接收一个 Runnable 参数,而我们 new出的 MyThread 正是一个实现了 Runnable接口的对象,所以可以直接将它传入到 Thread 的构造函数里。接着调用 Thread 的 start()方法,run()方法中的代码就会在子线程当中运行了。当然,如果你不想专门再定义一个类去实现 Runnable接口,也可以使用匿名类的方式,这种写法更为常见,如下所示:
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();

在子线程中更新 UI

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");
break;
}
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); //  将Message 对象发送出去
}
}).start();
break;
}
}
}

先定义一个整型常量 UPDATE_TEXT,用于表示更新 TextView这个动作。然后新增一个 Handler 对象,并重写父类的 handleMessage 方法,在这里对具体的 Message
进行处理。如果发现 Message 的 what 字段的值等于 UPDATE_TEXT,就将 TextView显示的内容改成 Nice。

解析异步消息处理机制

Android 中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue和Looper。
1. Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。
2. Handler
Handler 顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler的 sendMessage()方法,而发出的消息经过一系列地辗转处理后,
最终会传递到 Handler的 handleMessage()方法中。
3. MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过 Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
4. Looper
Looper是每个线程中的 MessageQueue的管家,调用 Looper的 loop()方法后,就会进入到一个无限循环当中,然后每当发现 MessageQueue 中存在一条消息,就会将它取
出,并传递到 Handler 的 handleMessage()方法中。每个线程中也只会有一个 Looper对象。

用法:

首先需要在主线程当中创建一个 Handler 对象,并重写handleMessage()方法。然后当子线程中需要进行 UI 操作时,就创建一个 Message 对象,并通过 Handler 将这条消息发送出去。之后这条消息会被添加到 MessageQueue 的队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息,最后分发回 Handler的 handleMessage()方法中。由于 Handler是在主线程中创建的,所以此时 handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行 UI 操作了。

使用 AsyncTask

AsyncTask 的基本用法,由于 AsyncTask 是一个抽象类,就必须要创建一个子类去继承它。在继承时我们可以为 AsyncTask 类指定三个泛型参数,这三个参数的用途如下。
1. Params
在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
2. Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
3. Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。因此,一个最简单的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
这里我们把 AsyncTask 的第一个泛型参数指定为 Void,表示在执行 AsyncTask 的时候不需要传入参数给后台任务。第二个泛型参数指定为 Integer,表示使用整型数据来作为进度显示单位。第三个泛型参数指定为 Boolean,则表示使用布尔型数据来反馈执行结果。目前我们自定义的 DownloadTask 还是一个空任务,并不能进行任何实际的操作,
我们需要去重写 AsyncTask 中的几个方法才能完成对任务的定制。常需要去重写的方法有以下四个。
1. onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask 的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。在这个方法中是不可以进行 UI 操作的,如果需要更新 UI元素,比如说反馈当前任务的执行进度,可以调用 publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...)
当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。
4. onPostExecute(Result)
当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
因此,一个比较完整的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected  void  onPreExecute() {
progressDialog.show(); // 显示进度对话框
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload(); // 这是一个虚构的方法
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 在这里更新下载进度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected 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
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值