在很多编程语言中,线程都是一个重要的组成部分,多线程的支持可以给程序员更加灵活的程序功能实现代码编写方式,线程一般用于处理一些比较耗时的任务(下载文件、复制或者移动文件。。。)。那么Android作为一个最热门的移动操作系统,当然支持多线程编程(严格来说应该是java支持多线程编程,Android使用的是java编程语言)。下面来看一下怎么去使用Android多线程:
Android的线程和java的线程使用的都是相同的语法,如果你熟悉java,那么一定不会感到难,新建一个子线程:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 这里加入我们要做的事情的代码
}
});
thread.start();
开启一个子线程的标准写法就是这样,在子线程的run方法里面我们可以加入我们想要做的事情的代码逻辑,但是值得注意的是:子线程里面是不可以更新UI的,如果要更新UI必须在UI线程(主线程)中完成。例:
import android.sax.StartElementListener;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView textView = null;
private Button button = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread.start();
}
});
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
textView.setText("子线程更新UI测试");
}
});
}
布局文件就不贴了,比较简单,一个TextView控件和一个Button按钮控件 如果采用以上写法,程序运行单击按钮的时候会崩溃退出,如图:
看看LogCat打印的日志:
大致意思就是只有创建了这个View对象的才能够对这个View的UI进行操作(即只有UI线程才能更新UI)。那么我们怎么才能通过子线程来更新UI呢?直接更新肯定是不行的,Android为我们提供了一个类:Handler,这个类可以对子线程发出的消息进行处理,那么我们就能通过将Handler类对象定义在主线程中然后对子线程发来的消息进行处理(更新UI)来达到子线程更新UI的目的,我们仍然以上面那个例子来看,我们把MainActivity.java改一下:
import android.os.Handler;
import android.os.Message;
import android.sax.StartElementListener;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final int UPDATE_UI = 1;
private TextView textView = null;
private Button button = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread.start();
}
});
}
/*
* 新建一个Handler 对象,重写handleMessage方法用于处理 子线程发送过来的消息
*/
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
/*
* 对发送过来的消息进行处理
*/
switch (msg.what) {
case UPDATE_UI:
textView.setText("子线程更新UI测试");
break;
}
}
};
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
/*
* 新建一个 Message 对象做为消息发送给Handler对象去处理
*/
Message message = new Message();
message.what = UPDATE_UI;
handler.sendMessage(message);
}
});
}
可以看到,我们在MainActivity.java里面加了一个新的全局对象:handler,这个对象就是用于处理子线程发送过来的消息的(在handleMessage方法里面)。而在子线程中,我们加了几行代码: 其实就是发送消息的代码,将消息发送到消息队列然后让Handler对象去处理。ok,再运行一下:
成功更新了UI,而且并没有报错。这就是典型的异步通信的例子:子线程执行的结果返回给主线程然后由主线程进行对应的处理。我们来看一下这里面涉及到的一些东西:
Message:其实就是一个普通的类,保存了一些数据,可以储存少量信息数据,在不同的线程中作为“通信者”的角色,可以用于不同的线程交换数据。
Handler:处理者的意思,它本身也用于在子线程中发送消息进入消息队列中,它也对对消息队列中通过它自己发送的Message对象取出并且进行一些处理(通过handleMessage方法),对于Handler对象来说,它不分线程,只要是它发送的消息,它都会接收到
消息队列(MessageQueue):储存通过Handler对象发送的消息,这些消息在被Handler对象取出处理之前会一直存在消息队列中,每个线程只会有一个MessageQueue对象,Android已经帮我们封装起来了,我们并不需要管它
Looper:管理每个线程中的MessageQueue,负责把消息队列中的数据取出,然后交给Handler对象去处理这条消息,每个线程也只会有一个Looper对象
整个过程:
画的不好,理解原理就行了。
其实为了方便我们在子线程更新UI操作,Android提供了一个更加好用的类:AsyncTask,下面来看一下这个类的用法: 首先,它是一个抽象类,我们必须继承它,并且要为它提供3个泛型参数,一般的写法:
public class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {
....
}
之后必须重写里面的一个抽象方法:doInBackground方法,这个方法即为进行我们要执行的耗时操作的方法,我们在这个方法里面进行我们需要的耗时操作,其实,我们一般会重写里面的4个方法:
onPreExecute() : 这个方法会在任务(doInBackground方法)开始之前调用,用于一些初始化的操作
doInBackground(Void…) : 这个方法就是在后台进行的耗时操作的方法,里面的所有代码都会在Android新建的一个子线程中运行,并且这个方法不可以进行UI操作(这个方法是在子线程中执行的),我们可以调用publishProgress(Intger…)方法来调用专门的UI更新方法来进行UI的更新。这个方法的返回值会传递给onPostExecute方法用于收尾
onProgressUpdate(Interge…) : 这个方法里进行UI的更新,当在doInBackground方法中调用了publishProgress方法之后,就会调用这个方法来及时的进行UI更新
onPostExecute(Boolean result) : 这个方法用于收尾,当doInBackground方法执行完成之后就会调用这个方法,主要是对于操作进行判断是否成功…
可以看到,我们在继承AsyncTask传入的三个泛型参数:一个参数类型是doInBackGround方法的参数类型,第二个参数的参数类型是onProgressUpdate方法的参数类型,(这里传入Integer用于进度的UI更新),第三个参数的参数类型为onPostExecute方法的参数类型,并且也是doInBackground方法返回值类型(这里传入Boolean用于判断执行的结果),当然,我们可以根据自己的需要来传入对应的参数类型。那么一个基本的自定义AsyncTask就成了这样:
import android.os.AsyncTask;
/**
* Created by Administrator on 2017/2/26.
*/
public class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {
@Override
public void onPreExecute() {
}
@Override
protected Boolean doInBackground(Void... params) {
int percent = 0;
try {
while (true) {
// 执行一些操作,
publishProgress(percent); // 调用onProgressUpdate方法更新UI
if (percent >= 100) {
return true;
}
}
} catch(Exception e) {
return false;
}
}
@Override
protected void onProgressUpdate(Integer... progress) {
// Integer... progress 这种形式的写法相当于一个int类型的数组,其他同理
// 在这里更新UI
}
@Override
protected void onPostExecute(Boolean result) {
// 这里做一些操作的结果提示
}
}
OK,如果我们要调用这个类的对象,我们只需编写:
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
因为我们定义的MyAsyncTask中第一个参数为Void,所以这里调用execute方法不需要传入参数,但是如果换成别的类型那就需要了,这里根据具体需求来运用。 接下来仍然以上面那个例子,我们用AsyncTask来实现它: 在原来的工程基础上新建一个类MyAsyncTask.java:
import android.content.Context;
import android.os.AsyncTask;
import android.widget.TextView;
import android.widget.Toast;
import org.w3c.dom.Text;
/**
* Created by Administrator on 2017/2/26.
*/
public class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private Context myContext = null;
private TextView myTextView = null;
private String string;
public MyAsyncTask(Context context, TextView textView, String str) {
myContext = context;
myTextView = textView;
string = str;
}
@Override
protected Boolean doInBackground(Void... params) {
Log.i("ThreadTest", "MyAsyncTask thread: " + Thread.currentThread().getId()); // 打印出当前线程的 id 信息
int percent = 100;
try {
publishProgress(percent);
return true;
} catch(Exception e) {
return false;
}
}
@Override
protected void onProgressUpdate(Integer... progress) {
// Integer... progress 这种形式的写法相当于一个int类型的数组,其他同理
// 在这里更新UI
if(100 == progress[0]) {
myTextView.setText(string);
}
}
@Override
protected void onPostExecute(Boolean result) {
// 这里做一些操作的结果提示
String str = "操作";
if(result) {
str += "完成";
} else {
str += "失败";
}
Toast.makeText(myContext, str, Toast.LENGTH_SHORT).show();
}
}
我们还要对MainActivity.java进行小小的改动:
就是将按钮的点击事件成了执行MyAsyncTask对象中的方法,并且用一个LogCat信息打印出 MainActivity 所在的线程 Id,对于MyAsyncTask来说,基本的和上面的用法差不多,只不过用AsyncTask来进行这么简单的处理实在是有点大材小用,下面看结果:
开始运行的界面和上面没多大区别,点击按钮之后出现操作成功的提示并且TextView的文字也更新了。并且LogCat 中打印的信息中我们可以发现 MyAsyncTask 和 MainActivity 确实不是在同一个线程之中(MyAsyncTask 的 doInBackground 方法在子线程中执行), 那么一个AsyncTask的简单用法就完成了。
如果博客中有什么不正确的地方还请多多指点。 谢谢观看。。。