Android:多线程编程
一、基本用法
与Java中的多线程编程类似,定义一个线程,只需要创建一个类,并且这个类继承自Thread类,然后重写run()方法。如:
class MyThread extends Thread{
@override
Public void run(){
//Todo something.
}
}
调用
new MyThread().start();
使用继承的方式耦合性有点高,大多数情况下我们使用Runnable接口来定义一个线程。如:
class MyThread extends Runnable{
@override
pblic void run(){
//Todo something.
}
}
调用:
MyThread myThread = new MyThread();
new Thread(myThread).start();
也可以使用匿名方法类的方式。如:
new Thread(new Runnable() {
@Override
public void run(){
//TODO
}
}
).start();
二、如何在子线程中更新UI
1.问题:在子线程中更新UI时出现异常
虽然Android中的多线程的创建和启动与Java中方式相同,但其使用方式还是不完全相同的。因为Android的UI也是线程不安全的,如果想要更新应用程序中的UI元素,必须在主线程中进行,否则会出现异常。
实例如下:
1.activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/change_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Text" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello world"
android:textSize="20sp" />
</RelativeLayout>
2.MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
text.setText(“Hello!”);
}
}).start();
break;
default:
break;
}
}
}
运行程序出错!
logcat中显示错误如下:
android view.ViewRootImpl$ … … :Only the original thread that created a view hierarchy can touch its views.
处理这个问题需要用异步消息处理(如下面的介绍)。
2.使用异步消息处理
Android 中的异步消息处理主要由 Message、Handler、MessageQueue 和 Looper。其关系如下图:
(1)Message
线程之间传递消息,它可以在内部携带少量信息,用于在不同线程之间交换数据。如:
message.what,message.arg1,message.arg2,message.obj等。
(2)Handler
用于发送(sendMessage())和处理消息(handleMessage())。
(3)MessageQueue
存放所有通过Handler发送的消息,每个线程中只有一个MessageQueue对象。
(4)Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,每当发现MessageQueue中存在一条消息时,就会取出,并传递到Handler的handleMessage()方法中。每个线程也只会有一个Looper对象。
实例:
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int UPDATE_TEXT = 1;
private TextView text;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行UI操作
text.setText("Hello!");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@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;
default:
break;
}
}
}
当然你可以直接使用runOnUiThread()方法直接实现对UI的更新,其实这个方法也是异步消息处理机制的封装,其实现原理与这里展示的方法是一样的。
三、使用AsyncTask
AsyncTask 的实现也是基于异步消息处理机制。其基本用法如下:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
… … … …
}
其中三个参数分别如下:
1.Params:执行AsyncTask时需要传入的参数,用于在后台任务中使用
2.Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
3.Result:当任务执行完,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
如上面的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操作,比如提醒任务执行的结果,以及关闭掉进度条对话框。
如下面代码:
public class DownloadTask extends AsyncTask<String, Integer, Integer> {
@Override
protected Void doInBackground(String... params) {
progressDialog.show();//显示进度对话框
}
@Override
protected Boolean doInBackground(String... params) {
//方法在子线程中运行,不会影响主线程运行,所以不能进行UI操作
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) {
//这里更新进度条,进行UI操作
progressDialog.setMessage("Download " + value[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
//下载完成时,doInBackground()方法返回布尔变量,此方法很快被调用。
//此方法是在主线程中运行的。
//此方法多执行一些收尾的工作。
if (result) {
Toast.makeText(context,"Download succeeded", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context,"Download failed", Toast.LENGTH_SHORT).show();
}
}
}
如果要启动这个任务,只需要使用下面的代码即可:
new DownloadTask().execute();