当我们需要执行一些耗时操作,如果不将这类操作放在子线程里去运行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用。
1.线程的基本用法
定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可。只要new出该类的实例,然后调用它的start()方法,这样run()方法中的代码就会在子线程中运行了。
class MyThread extends Thread{
@Override
public void run() {
//具体处理逻辑
}
}
new MyThread().start();
这种方式耦合性有点高,更多的时候我们选择使用实现Runnable接口的方式来定义一个线程。
class MyThread implements Runnable{
@Override
public void run() {
//具体处理逻辑
}
}
MyThread myThread=new MyThread();
new Thread(MyThread).start();
如果你不想再定义一个类,也可以使用匿名类的方式,这种方式更常见
new Thread (new Runnable()){
@Override
public void run() {
//具体处理逻辑
}
}).start();
2.在子线程中更新UI
Android的UI是线程不安全的,如果想更新应用程序的UI元素,则必须在主线程中进行。我们可以创建一个例子来证明。首先在按钮的点击事件里开启一个子线程,然后在子线程里将TextView显示的内容进行更换,结果发现点击按钮后程序崩溃。
但是有时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,这该如何是好呢?其实,安卓提供了一套异步消息处理机制,完美解决了这个问题。我们继续上面那个例子,只不过这次我们在里面加入异步消息机制。
public class MainActivity extends AppCompatActivity {
public static final int UPDATE_TEXT=1;
private TextView textView;
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case UPDATE_TEXT:
textView.setText("Nice to see you!");
break;
default:
break;
}
}
} ;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView)findViewById(R.id.text_view);
Button button=(Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
}
});
}
}
3.解析异步消息机制
异步消息处理主要由4部分组成:Message、Handler、MessageQueue和Looper。
(1)Message
是在线程间传递的消息,可以在内部携带少量信息。
(2)Handler
用于发送和处理消息。
(3)MessageQueue
存放所有通过Handler发送的消息。每个线程只能有一个MessageQueue对象。
(4)Looper
取出MessageQueue中的消息,并将它传递到Handler的handleMessage()方法中。
4.使用AsyncTask
借助AsyncTask,即使不了解异步消息处理机制也可以简单地从子线程切换到主线程。
AsyncTask的基本用法,创建一个子类去继承它,继承时为AsyncTask类指定三个泛型参数。Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。Progress:后台任务执行时,如果需要在界面上显示当前进度,则使用这里指定的泛型作为进度单位。Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
然后,我们还要重写AsyncTask的方法,有4个:
(1)onPreExecute()
这个方法会在后台任务执行之前调用,用于界面上的初始化操作,比如显示一条进度条对话框。
(2)doInBackground(Params...)
所有的耗时任务应该放在这里,这个方法里的代码会在子线程中运行。任务一旦完成,使用return返回任务的执行结果。注意这里不能进行UI操作,如果需要,比如反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
(3)onProgressUpdate(Progress...)
当后台任务调用了publishProgress(Progress...)方法后,onProgressUpdate(Progress...)方法很快就会被调用,该方法的参数就是在后台任务中传递来的,在这里可以通过传入的参数进行UI操作。
(4)onPostExecute(Result)
后台任务执行完毕并通过return语句返回时,这个方法很快会被调用。返回的数据会作为参数传递到此方法,可以利用这些数据进行UI操作。比如提醒任务执行结果,关闭进度条对话框等。