在学习java的时候,你们一定对多线程这个词不陌生,而android多线程跟java相比并没有什么特殊的地方,基本都是相同的语法。
而线程的使用就是在执行一些耗时的操作,比如发起一条网络请求时,考虑到网速等原因,服务器不一定会立刻响应我们的请求,如果不把这些操作放在子线程中,会导致主线程被阻塞,从而影响用户对软件的正常使用。
线程的基本用法
下面先看一下android中,三种启用线程的方法
1.只需要新建一个类继承自Thread,然后重写父类的run()
方法来实现一个线程:
public class MyThread extends Thread {
//继承Thread类,并改写其run方法
private final static String TAG = "My Thread ===> ";
public void run(){
//具体的实现
Log.d(TAG, "run");
for(int i = 0; i<100; i++)
{
Log.e(TAG, Thread.currentThread().getName() + "i = " + i);
}
}
}
启动:
new MyThread().start();
2.创建一个实现Runnable接口的对象
public class MyRunnable implements Runnable{
private final static String TAG = "My Runnable ===> ";
@Override
public void run() {
//具体的逻辑
// TODO Auto-generated method stub
Log.d(TAG, "run");
for(int i = 0; i<1000; i++)
{
Log.e(TAG, Thread.currentThread().getName() + "i = " + i);
}
}
}
启动:
new Thread(new MyRunnable()).start();
可以看到,Thread
的构造函数接受一个Runnable
参数,而我们new
出的MyRunnable
正是一个实现了Runnable
接口的对象,所以可以直接将他传入到Thread
的构造函数里。接着调用Thread
的start()
方法,run()
方法中的代码就会在子线程中运行了。
3.另外一种启用方式:匿名类(常见)
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//具体逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
});
#异步消息处理机制
1.尝试在子线程中更新UI
看代码
和其他许多GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
上面这个例子就恰好证明了android确实不能在子线程中进行UI操作的,但是有些时候,我们必须在子线程中执行一些耗时任务,然后再根据执行结果来更新UI,这时候就要用到异步消息处理机制。
2.Message,Handler,MessageQueue和Looper
看代码
3.进一步探究Handler Looper机制
要分析Handler Looper机制,自然想到去看Handler类和Looper类的源码(分别位于Handler.java和Looper.java两个文件中)。简单阅读两个类的描述后,在Looper类的描述中能找到以下一段示例代码。
<p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
这段代码给出了Handler Looper机制实现进程间通信的三大基本步骤,包括Looper的两个函数prepare()和loop(),以及Handler的handleMessage函数。
由图可见,基于Handler Looper机制传递消息主要包括以下几个步骤。
(1)目标线程调用Looper.prepare()创建Looper对象和消息队列。
(2)目标线程通过new Handler()创建handler对象,将Handler,Looper,消息队列三者关联起来。并覆盖其handleMessage函数。
(3)目标线程调用Looper.loop()监听消息队列。
(4)消息源线程调用Handler.sendMessage发送消息。
(5)消息源线程调用MessageQueue.enqueueMessage将待发消息插入消息队列。
(6)目标线程的loop()检测到消息队列有消息插入,将其取出。
(7)目标线程将取出的消息通过Handler.dispatchMessage派发给Handler.handleMessage进行消息处理。
4.使用AsyncTask
android提供了一个很好的工具,就比如AsyncTask。借助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("当前下载进度:" + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
这里我们模拟了一个下载任务,在doInBackground()方法中去执行具体的下载逻辑,在onProgressUpdate()方法中显示当前的下载进度,在onPostExecute()方法中来提示任务的执行结果。如果想要启动这个任务,只需要简单地调用以下代码即可:
new DownloadTask().execute();
如果想深入学习还是推荐研究一下源码。
#浅谈服务
Service是Android中的四大组件之一,和windows中的服务是类似,服务一般没有用户操作界面,它运行于系统中不容易被用户发觉,可以使用它开发如监控之类的程序Service,手机中有的程序的更新,服务的推送。Android系统中,Service与Activity类似,都需要AndroidManifest.xml文件中配置,而且生命周期有点类似。Service不像Activity在前台运行,而是默默的在后台默默的工作