Android中的多线程和Java中的多线程语法基本相同。定义一个新的线程首先需要新建一个类继承自Thread,然后重写父类方法run(),在run()方法中编写耗时逻辑即可。一般情况这个类为内部类。
class TestThread extends Thread{
@Override
public void run(){
//处理具体的逻辑
}
}
如何启动这个线程呢?只需要new一个TestThread的实例,然后调用它的start()方法。
new TestThread().start();
使用继承的方式实现多线程有一个弊端,耦合性有点高。所以一般情况下选择实现Runnable()接口的方式来定义一个线程。
class TestThread extends Runnable{
@Override
public void run(){
//处理具体的逻辑
}
}
使用实现Runnable()接口的方式,启动线程的方法也需要改变,如下:
TestThread testThread = new TestThread();
new Thread(testThread).start();
上面两行代码表示Thread的构造函数接收的是一个Runnable参数,而我们new出的TestThread正是一个实现了Runnable接口的对象,所以可以直接将它传入。最后调用start()方法即可运行。当使用线程熟练过后,可以使用匿名类的方式实现,这种方法更常见。
new Thread(new Runnable(){
@Override
public void run(){
//处理具体的逻辑
}
}).start();
Android中的多线程和Java中的多线程虽然语法基本相同,但还是有不同的地方。首先Android中的UI线程是不安全的,也也就是说想要更新程序中的UI,就必须在主线程中进行,否则就会出现以下异常。
android.view.ViewRootImp$CalledFromWrongThreadException: Only thr original
thread that created a view hierarchy can touch its views.
但是在某些情况下,我们又不得不在子线程中更新UI,针对这种情况Android提供一套异步消息处理机制,完美的解决了在子线程中更新UI的问题。先看下面的代码示例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
public static final int UPDATE_TEXT = 1;
private TextView tv;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case UPDATE_TEXT:
//在这里更新UI
tv.setText("hello world");
break;
default:
break;
}
}
};
...
@Override
public void onClick(View v){
switch(v.getId()){
case R.id.tv:
new Thread(new Runnable(){
@Override
public void run(){
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default;
break;
}
}
}
首选定义了一个常量表示UPDATE_TEXT表示更新UI这个动作,然后创建一个Handler对象,并重写了父类的handlerMessage()方法,在此方法中对具体的Message进行了处理。如果发现message的what字段为UPDATE_TEXT,就进行UI更新。在点击事件中创建了一个Message(android.os.Message)对象,并将它的what字段指定为UPDATE_TEXT,然后调用Handler的sendMessage()方法发送。handler收到这个message后,就在handlerMessage中对这个message进行处理,判断what字段是否为UPDATE_TEXT,如果匹配就更新UI。
在Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue、Looper。其中Message和Handler在刚刚已经了解过了,而MessageQueue和Looper是接下来介绍的重点。
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。除了what字段,还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段来携带一个Object对象。
Handler主要用于发送和处理消息,发送消息一般是使用Handler的sendMessage()方法,需要传递的消息最后到达handlerMessage()方法中。
MessageQueue是消息队列的意思,主要作用为存放所有通过Handler发送的消息。这部分消息会一直存在消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无线循环中,然后每当发现MessageQueue中存在一条消息,就会将它取出来,并传递到Handler的handlerMessage()方法中。每个线程也只有一个Looper对象。
了解了上面的四个概念后,我们再把异步消息处理的流程梳理一遍。首先在主线程中创建一个Handler对象,并重写handlerMessage()方法。然后在子线程中需要进行UI操作的时候,就创建一个Message对象,并通过Handler将这条消息发送出去,之后这条消息就会被添加到MessageQueue的队列中等待被处理,而Looper会一直尝试从MessageQueue中取出待处理的消息,最后消息被分发到Handler的handlerMessage()方法中。
AsyncTask实现的原理是基于异步消息处理机制的,只是Android已经做好了封装。AsyncTask是一个抽象类,要想使用它就需要创建一个子类去继承它。在继承的时候需要为AsyncTask类指定3个泛型参数
因此,一个最简单的AsyncTask就可以写成如下方式:
class TestTask extends AsyncTask{
...
}
这里我们把第一次参数指定为Void,表示在执行TestTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。第三个泛型参数指定为Boolean,则表示使用布尔数据来反馈执行结果。上面自定的Task还是一个空任务,需要重写AsyncTask其中的几个方法才能完成对任务的定制,经常重写的方法有以下四个:
下面是一个比较完成的AsyncTask:
class DownloadTask extends AsyncTask{
@Override
protected void onPreExecute(){
progressDialog.show();//显示进度对话框
}
@Override
protected Boolean doInBackground(Void... params){
try{
while(true){
int downloadPercent = doDownload();//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()方法中架构当前的下载进度传递进来,这样inProgressUpdate()方法就会被调用,在这里就可以进行UI操作了。
当下载完成后,doInBackground()方法会返回一个布尔类型的变量,这样onPostExecute()方法就会被调用,这个方法也是在主线程中运行,所以我们可以使用Toast来进行提示。
简单来说,AsyncTask的使用方式就是,在doInBackground()中执行具体的耗时任务,在onProgressUpdate()中执行UI操作,在onPostExecute()中执行收尾工作。
箱套启动这个任务,只需要执行下面的代码即可:
new DownloadTask().execute();