自从学了反编译之后,刘关张三人如虎添翼,到处学习别人的源码。这一日,张飞提议:“大哥,曹贼这么盗窃我们的工作成果,不能便宜了他,咱们也去找找他的工程然后反编译过来!”
关羽:“三弟此言有理啊!”
刘备:“他对不我够意思,那我就得好好的意思意思。他不让我有意思,我就让他没意思!”
张飞挠了挠头:“什么意思?”
刘备:“没什么意思。二弟、三弟,快去网上下载曹贼的应用安装包,我看他怎么死的!”
于是三人在网上把曹操的应用下了个遍,反编译成源码之后各种看。
张飞:“大哥,这曹贼是不是把代码进行混淆了啊,我怎么看不懂啊?这都什么乱七八糟的?”
刘备:“这些东西我也不太懂啊,莫非有诈?!”
关羽:“炸你妹啊!曹贼的项目规模都比较大,很多地方都用到了多线程。我有过一点Java的多线程功底,大概能看出一些端倪,不过有些东西我也不太明白。”
刘备:“不得不承认,咱们实力上还是有差距的呀!咱们得好好学学,不然抄都不会抄!”
关羽:“大哥,我们这不叫抄,这叫学习!”
刘备:“话说,这多线程是何方神圣啊?”
1.1. 多线程的简要介绍
多线程可以将一个程序划分成多个任务,它们彼此独立的工作,用多线程可以有效地使用处理器资源,节约用户的时间。多线程常常用于编写占用大量CPU资源的程序和阻塞式的网络通信编程中。线程可以在程序里独立执行。每个线程负责程序里某项任务的执行,通常由操作系统负责多个线程的调度和执行。
多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行能力,由操作系统负责调度。
1.1.1.线程与进程的区别
每个运行的程序都是一个进程,每一个进程都有独立的一块内存空间、一组系统资源,并且子进程和父进程有不同的代码和数据空间。
每个进程包含一到多个线程,每个线程执行不同的任务。线程与进程相似,负责运行程序中某个特定的模块。但与进程不同的是,同类的多个线程共享一块内存空间和一组系统资源,所以系统在各个线程之间切换时,占用的资源要比进程小得多,正因如此,线程也被称为轻量级进程。进程和线程的区别与联系如图22-1所示:
图22-1进程和线程的区别与联系
1.1.2.多线程的利弊
何时使用多线程技术,何时避免用它,是我们需要掌握的重要课题。多线程技术是一把双刃剑,在使用时需要充分考虑它的优缺点。由于多线程将程序划分成多个独立的任务,因此可以在以下几个方面显著提高性能:
l 多任务可以同步进行。使用多线程可以把占据时间较长的任务放到后台去处理,或者定期将处理器的时间让给其他任务。
l 用户界面更友好。例如当用户点击了一个按钮去触发了某些耗时操作时,可以利用多线程弹出一个进度条。
l 提高应用程序的响应速度。使用多线程可以使一些任务释放一些宝贵的系统资源,从而加快程序的运行速度,例如一些等待的任务:用户输入、文件读写和网络收发数据等。
l 可以分别设置各个任务的优先级优化应用程序的性能。
然而,多线程并不是万能的,它在提高程序运行效率的同时也会带来如下的弊端:
l 如果多线程使用得不恰当或者多线程的数量过多时,有可能降低系统的性能,因为当操作系统在多个线程之间不停切换时,需要额外的资源开销。
l 更多的线程需要更多的资源。
l 多线程编程和单线程编程相比,更容易出现程序上的bug,例如数据在多个线程间共享时,需要防止由资源竞争导致的线程死锁;当多个线程需要对公有变量进行写操作时,要注意数据的不一致性;由于中断时间的不确定性,会导致数据在一个线程内的操作产生错误,而这种错误是程序员无法预知的。
1.2. Android中的多线程
在使用多线程技术时,需要充分的考虑它的方方面面,包括创建线程、调度和管理多线程、多线程编程的复杂性以及线程切换开销等。如果用户的应用程序需要多个任务同时进行相应的处理,则使用多线程是较为理想的选择。本节将从线程的构造、停止、优先级和同步四个方面介绍Android中的多线程。
1.2.1. 线程体的构造
Android中构造线程的方法和Java类似,需要利用Java的线程类java.lang.Thread。当生成一个Thread类的对象后,一个新的线程就产生了。每个线程都是通过Thread对象的线程体run()方法来完成其操作的。常见的实现线程体的方式有三种,下面将逐一介绍。
实现线程体的方式一
第一种方式采用继承Thread的方式实现线程体。由于Java只支持单重继承,如果采用这种方式,用这种方法定义的类不能再继承其他父类。继承Thread方式的示例代码如下:
//采用继承Thread并重写run()的方式实现线程体
public class ThreadTest extends Thread {
@Override
publicvoid run() {
//线程体
}
}
在Android的Activity类的onCreate()方法中,可以使用如下代码启动线程:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ThreadTest thread = new ThreadTest();
thread.start();
}
上述代码通过new ThreadTest ()创建了一个线程,调用start()方法启动线程,线程启动后就开始执行run()方法。
实现线程体的方式二
第二种方式实现了接口Runnable类,将该类作为目标线程输入参数。构造线程的方法:Thread(Runnable target, String name),参数target是一个实现了Runnable接口的实例,它的作用是构造线程体的run()方法,target参数可以为null,表示由Thread实例来执行线程;name参数用来指定线程的名字,如果没有特别指定该参数,则线程的名字是由JVM自动分配的,例如thread-1、thread-2等线程名。实现Runnable接口的代码如下所示:
public class ThreadTest implements Runnable {
private Thread mThread;
public ThreadTest() {
mThread = newThread(this);
mThread.start();
}
@Override
public void run() {
//线程体
}
}
在Android的Activity类的onCreate()方法中,使用如下代码启动线程:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ThreadTest thread = new ThreadTest();
}
和方式一不同,方式二实现了Runnable接口并采用带参的构造方法new Thread(this)创建线程。Runnable接口实现了线程体run()方法。值得注意的是,在方式二中,ThreadTest包含了一个Thread成员变量mThread。成员变量mThread利用构造方法new Thread(this)将实现Runnable接口的本实例this传递给构造方法Thread(Runnable target),最终创建了一个线程。
实现线程体的方式三
第三种方式是方式二的变种,本质上还是实现线程体方式二,在Android应用开发中经常采用第三种方式。其代码如下所示:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//线程体
}
});
thread.start();
实现Runnable接口相对于继承Thread类的方式来说,有如下好处:可以避免由于Java的单继承特性带来的局限;代码能够被多个线程共享,代码与数据是独立的。
1.2.2.线程的停止
事实上Thread类提供了一个stop()方法用于停止线程,但是由于这种方法会产生线程死锁等问题,在新版的JDK中已经废止了这种方法。它的替代解决方法就是在线程体中增加标识,通过修改标识停止线程,如下代码所示:
boolean isRunning=true;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//线程体
while(isRunning){
}
}
});
thread.start();
在上述代码中,isRunning便是增加的标识符,当isRunning变量为true时,线程会不停地循环执行线程体,当需要停止线程时,只需要设置isRunning为false即可。
1.2.3.线程的优先级
Android系统线程的优先级设置可以处理很多并发线程的阻塞问题。一般情况下,如果优先级较高的线程在工作,就不会给优先级较低的线程分配CPU时间片。例如给接收用户输入的线程指定较高的优先级,而其他线程占用CPU时间执行相应的任务。当用户输入了信息时,接收用户输入线程会因为优先级较高而能够立即占用CPU时间片,在短时间内快速处理用户的输入数据。
可以利用android.os.Process.setThreadPriority(intpriority)方法设置线程优先级,参数priority的取值范围为(-20,19),数字越低,优先级越高。例如当priority为-20时,该线程拥有最高的优先级。
Android系统对线程的优先级定义如下22-1表所示:
表22-1 线程优先级
优先级 | 定义 | 整型值 |
THREAD_PRIORITY_URGENT_AUDIO | 标准较重要音频播放优先级 | -19 |
THREAD_PRIORITY_AUDIO | 标准音乐播放使用的线程优先级 | -16 |
THREAD_PRIORITY_URGENT_DISPLAY | 标准较重要显示优先级(输入事件也适用) | -8 |
THREAD_PRIORITY_DISPLAY | 标准显示系统优先级,主要是改善UI的刷新 | -4 |
THREAD_PRIORITY_FOREGROUND | 标准前台线程优先级 | -2 |
THREAD_PRIORITY_MORE_FAVORABLE | 高于favorable | -1 |
THREAD_PRIORITY_DEFAULT | 默认应用的优先级 | 0 |
THREAD_PRIORITY_LESS_FAVORABLE | 低于favorable | 1 |
THREAD_PRIORITY_BACKGROUND | 标准后台程序 | 10 |
THREAD_PRIORITY_LOWEST | 有效的线程最低的优先级 | 19 |
例如:假设MediaProvider 的优先级为 11,可以使用如下代码:
//MediaScannerService.java
public void run(){
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND+
Process.THREAD_PRIORITY_LESS_FAVORABLE); // 10 + 1 = 11
Looper.prepare();
}
这样当有优先级比11小的线程和MediaProvider同时工作时,MediaProvider线程抢占CPU的能力就会大于这些线程。
1.2.4.线程的同步
所谓线程的同步,是指在某一时刻只有一个线程可以访问变量。如果不能确保对变量的访问是同步的,就可能会产生错误或不可预料的结果。一般情况下,当一个线程写入一个变量,同时有其他线程读取或写入这个变量时,就应同步变量。
例如,两个线程thread1和thread2具有相同的优先级,而且同时在系统上运行。在第一个时间片中,第一个线程在共享资源variable变量中写入了某个值。而在下一个时间片中,另一个线程尝试读取或者在variable中写入另一个值。如果在第一个时间片中没有完成variable变量的写操作,当另一个线程尝试读取或者修改这个变量时,就会产生错误。通过同步使得同一时刻只有一个线程能使用变量variable,就可以避免出现这种情况。
线程同步的基本思路是给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。Android里可以使用synchronized关键字给代码段加锁,如下代码所示:
//lock是公用同步锁
public static final Object lock = newObject();
synchronized(lock){
// 代码段 A
// 访问共享资源 resource
}
synchronized(lock){
// 代码段 B
// 访问共享资源 resource
}
这样,代码A和代码B就可以安全地访问共享资源resource。
张飞:军师为什么例子中的同步锁要使用静态变量static呢? 孔明:因为那个同步锁是在方法体内部产生的。每个线程调用这段代码的时候,都会产生一个新的同步锁。那么多个线程之间,使用的是不同的同步锁,根本达不到同步的目的。实际上不一定要把同步锁声明为static或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。 |
synchronized关键字不仅可以用来修饰共享变量,也可以用来修饰共享方法。把synchronized当作方法修饰符时,示例代码如下:
public synchronized void methodAAA(){
}
同步方法锁定的是调用这个同步方法的对象。也就是说,当一个对象P在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。上述的示例代码等同于如下代码:
public void methodAAA(){
synchronized (this){
}
}
1.3. 线程池
线程池,顾名思义就是用来存放“线程”的对象池。本节首先介绍线程池,然后讲解Android最常用的ExecutorService线程池。
1.3.1. 线程池简介
在面向对象的编程中,经常需要创建对象和销毁对象,在一些特殊的情况下,短时间内需要创建大量对象,在执行简单处理之后又要销毁这些刚刚建立的对象,这是一个非常低效的行为。很多面向对象的语言都在其内部使用对象池来处理这种情况,以提高性能。
在Android多线程编程时也会遇到上面的情况,如果创建了过多的线程会增加Android系统中资源的占用率,使代码的执行流程和资源竞争情况变得复杂,稍不留心就会产生bug。在使用多线程时还需要特别注意一些需要同步的资源,例如系统资源(系统端口)、共享资源、应用程序的资源(静态变量)。使用线程池可以很好地解决这些问题,跟单个线程相比,线程池有如下优点:
l 缩短应用程序的响应时间。线程池的线程处于等待分配任务状态,无需创建新的线程,从而提高了相应时间。
l 不必管理和维护生存周期短暂的线程。
l 有些线程池会根据当前系统特点对池内的线程进行优化处理。
1.3.2. 线程池的适用场合
事实上,线程池并不是万能的,它有其特定的使用场合。线程池致力于减少线程本身的开销对应用程序产生的影响。这是有前提的,前提就是线程本身开销与线程执行任务相比不可忽略。如果线程本身的开销相对于线程任务执行的开销而言是可以忽略不计的,那么此时线程池所带来的好处是不明显的,比如对于FTP服务器和Telnet服务器,通常传送文件的时间较长,开销较大,那么此时,采用线程池未必是理想的方法,可以选择“即时创建,即时销毁”的策略。一般的,线程池适合用于如下场合:
l 单位时间内需要频繁地处理任务并且每个任务的处理时间较短。
l 对实时性要求较高的应用程序。如果接收到任务后在创建线程,可能满足不了实时要求,因此必须预先采用线程池创建线程。
l 必须经常面对高突发性事件,例如当有足球转播时,Web服务器将产生巨大的冲击。此时如果采取传统方法,则必须不停的产生线程,销毁线程。此时采用动态线程池可以避免这种情况的发生。
1.3.3.线程池的种类
下面将要介绍几种不同的线程池:
l CachedThreadPool:一个可以动态创建新线程的线程池,该线程池先查看有没有已经建立的线程,如果有,则直接使用这些线程。对于执行很多短期任务的程序而言,这些线程池通常可以提高程序的性能。调用 execute ()方法将重用以前构造的线程。如果没有可用的线程,则创建一个新线程并添加到池中。这种线程池会自动从缓存中移除那些已有 60 秒未被使用的线程。因此,当使用这种线程池时,长时间保持空闲的线程不会再占用任何资源。
l FixedThreadPool:FixedThreadPool与CacheThreadPool差不多,该线程池先查看有没有已经建立的线程,如果有,则直接使用这些线程,但FixedThreadPool不能随时创建新的线程。FixedThreadPool线程池的独特之处在于,对任意的时间点而言,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止并被移出FixedThreadPool线程池,新的线程才能得以执行。FixedThreadPool主要用于一些固定的并发线程,多用于服务器。
l SingleThreadExecutor:单例线程,任意时间池中只能有一个线程。
1.3.4.ExecutorService构造线程池
在Android中可以利用ExecutorService建立和使用线程池,其步骤如下:
1. 新建一个任务,即创建一个实现Runnable接口的类。
2. 利用Executors构造相应的线程池,常用的构造方法如下表22-2所示:
表22-2 Executors构造方法
方法 | 说明 |
Executors.newCachedThreadPool() | 创建一个CachedThreadPool线程池。 |
Executors.newFixedThreadPool(int nThreads) | 创建一个FixedThreadPool线程池,nThreads表示该线程池可以容纳的线程数。 |
Executors.newSingleThreadExecutor() | 创建一个SingleThreadExecutor线程池。 |
例如,构造一个FixedThreadPool线程池如下代码所示:
//构造一个最多容纳5个线程的线程池
ExecutorService executorService =Executors.newFixedThreadPool(5)
3. 利用Executor的execute()方法将一个任务添加进线程池。
下面是一个利用ExecutorService构造线程池的例子:
public class TestCachedThreadPool {
public static void main(String[] args){
//构造一个FixedThreadPool的线程,并固定最大的线程数为5
ExecutorServiceexecutorService = Executors.newFixedThreadPool(5);
for (int i = 0; i <5; i++) {
//将一个任务添加进executorService线程池
executorService.execute(new TestRunnable());
}
}
}
//新建一个任务,实现了Runnable接口
class TestRunnable implements Runnable {
public void run() {
while (true) {
try {
Thread.sleep(5000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.4. 线程间通信与消息机制
在Android中,线程内部或者线程之间进行信息交互时会使用消息机制。熟悉消息机制的原理,将会能更好地驾驭系统,避免使用多线程时一些低级的错误。
1.4.1.Message类
消息对象Message,顾名思义就是记录消息的类。Message类有几个比较重要的字段:
l arg1和arg2:这两个字段用来存放需要传递的整型值。例如在Service中,该字段可以用来存放Service的ID。
l obj:该字段用来存放需要传递的类。
l what:这个字段是消息的标志,可以根据这个字段的不同的值对接收到的消息进行不同的处理,例如在处理Button事件时,通过该字段确定点击了哪个按钮。
在使用Message时,可以通过new Message()创建一个Message实例,Android推荐通过Message.obtain()或者Handler.obtainMessage()这两个方法获取Message对象。这两个方法不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实例,存在则直接取出并;如果消息池中没有可用的Message实例,则根据给定的参数新建一个新Message对象。Android系统的消息池在默认情况下包含10个实例化的Message对象。
1.4.2.MessageQueue类
消息队列,用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。MessageQueue将Message对象以链表的方式串联起来。MessageQueue对象不需要自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。可以通过Looper.myQueue()方法获取当前线程中的MessageQueue。
1.4.3.Looper类
Looper是MessageQueue的管理者。在一个线程中,如果存在Looper对象,则必定存在MessageQueue对象,并且只存在一个Looper对象和一个MessageQueue对象。在Android系统中,除了主线程有默认的Looper对象,其它线程默认是没有Looper对象的。如果想让新创建的线程拥有Looper对象,首先应调用Looper.prepare()方法,然后再调用Looper.loop()方法。典型的用法如下所示:
class LooperThread extends Thread{
public Handler mHandler;
public void run(){
Looper.prepare();
//其它需要处理的操作
Looper.loop();
}
}
线程中存在的Looper对象可以通过Looper.myLooper()方法获取,此外还可以通过Looper.getMainLooper()方法获取当前应用程序中主线程的Looper对象。值得注意的是,假如Looper对象位于应用程序的主线程中,则方法Looper.myLooper()和Looper.getMainLooper()获取的是同一个对象。
1.4.4.Handler类
android.os.Handler是Android中多个线程间消息传递和计划任务的“工具”类,该类是消息的处理者。通过Handler对象可以封装Message对象,然后通过sendMessage(msg)方法把Message对象添加到MessageQueue中;当MessageQueue循环到该Message时,就会调用该Message对象对应的Handler对象的handleMessage()方法对其进行处理。一般编写一个类继承自Handler,然后在该类的handleMessage()方法中编写需要进行的操作。Handler类会在多个线程之间发送Message、执行Runnable。Handler工具类在Android多线程中有两方面的应用:
发送消息
android.os.Handler类通过下面的方法发送消息:
l sendEmptyMessage():发送一个空的消息。
l sendMessage(Message msg):发送消息,msg携带参数。
l sendMessageAtTime(Message msg,longtime):指定某一时间点time发送消息。
l sendMessageDelayed(Message msg,longtime):延时time毫秒发送消息。
一个线程发出消息后,另外的线程要接收消息,接收消息的线程通过重写Handler类的handleMessage(Message msg)方法实现消息的接收,如下代码所示:
handler = new Handler() {
@Override
public void handleMessage(Message msg){
switch(msg.what) {
//处理消息
}
}
};
计划任务
android.os.Handler类通过下面的方法执行计划任务:
l post(Runnable rbl):提交计划任务rbl并立即执行。
l postAtTime(Runnable rbl,long time):提交计划任务rbl并在指定的时间点time上执行。
l postDelayed(Runnable rbl,long time):提交计划任务rbl并延时time毫秒执行。
张飞:军师常常给我指派任务,可是这里提交的任务,由谁执行呢? 孔明:需要执行提交任务的“人”体现在Runnable接口的run()方法里,在实现该接口时就已经指定了这个任务由“谁”执行。 |
计划任务也可以向一个线程通信发请求,请求它执行某些处理,但不可以携带数据,计划任务的线程处理方法就是重写run()方法,代码如下所示:
Runnable r = new Runnable() {
public void run() {
//任务
}
};
发送消息和计划任务提交之后,它们都会进入目标线程的消息队列中。
1.5. AsyncTask
在开发Android时,通常会将耗时的操作放在单独的线程中执行,避免其占用主线程。由于子线程无法更新UI 线程,Android通过消息机制Handler在子线程中更新UI线程。但是费时的任务操作总会启动一些匿名的子线程,太多的子线程给系统带来巨大的负担,随之带来一些性能问题,体现在:
l 线程的开销较大,如果每个任务都要创建一个线程,那么应用程序的效率太低。
l 线程无法管理,匿名线程创建并启动后不受控制,如果有很多个请求发送,那么就会启动非常多的线程,系统将不堪重负。
l 在新线程中更新UI还必须要引入Handler,这让代码看上去非常臃肿。
Android提供了一个工具类AsyncTask,用于异步执行任务,该类适合处理一些后台比较耗时的任务,不再需要子线程和Handler就可以完成异步操作并且刷新用户界面。
AsyncTask定义了三种泛型Params、Progress和Result。
l Params:启动任务执行的输入参数,比如HTTP请求的URL。
l Progress:后台任务执行的百分比。
l Result:后台执行任务最终返回的结果,比如String。
AsyncTask的执行需要重写AsyncTask中的这几个方法:
l onPreExecute():该方法将在执行后台操作前被UI 线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化。
l doInBackground(Params):将在onPreExecute()方法执行后执行,该方法运行在后台线程中。该方法主要负责执行那些很耗时的后台处理工作,可以调用 publishProgress()方法来更新实时的任务进度。
l onProgressUpdate(Progress):在publishProgress()方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
l onPostExecute(Result):在doInBackground()执行完成后,onPostExecute()方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户.
l onCancelled():在主线程中调用onCancelled()的时候调用。
AsyncTask在执行时会顺序调用除onCancelled()外的以上方法,合理地重写这些方法可以实现我们想要的效果。
1.6. Android多线程的实例
本节将通过多线程实现显示下载进度条的例子,例子通过多线程和AsyncTask两种不同的方法实现下载进度条。
1.6.1.多线程实例
下面的实例使用Android多线程编写进度条,然后用TextView来显示进度值。实例首先编写布局文件main.xml,布局文件包含了一个Button、一个TextView和一个Progressbar。当点击Button后,TextView和Progressbar将实时更新当前的进度。main.xml代码如下所示:
main.xml代码清单22-6-1:
<!--张飞:我叫小飞飞,文能提笔安天下,武能上马定乾坤;上炕认识娘儿们,下炕认识鞋。-->
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/Btn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Download"/>
<TextView
android:id="@+id/Txt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="当前进度条显示" />
<ProgressBar
android:id="@+id/PrgBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
然后,新建一个Activity类,命名为ActivityMain,其代码如下所示:
ActivityMain.java代码清单22-6-2:
/**
* @author关羽:我是一个玉洁冰清的人,贞烈贤良就是我的代名词,我走到哪,贞洁牌坊就跟
到哪,我决不接外活。
*/
public class MainActivity extends Activityimplements OnClickListener{
privateProgressBar progressBar;
privateTextView mTxtView;
@Override
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Buttonbtn=(Button)findViewById(R.id.Btn);
btn.setOnClickListener(this);
progressBar=(ProgressBar)findViewById(R.id.PrgBar);
mTxtView=(TextView)findViewById(R.id.Txt);
}
publicvoid onClick(View arg0) {
progressBar.setVisibility(View.VISIBLE);
handler.post(updateThread);
}
//Handler处理收到的子线程消息,更新UI
Handlerhandler =new Handler(){
@Override
publicvoid handleMessage(android.os.Message msg) {
//设置progressBar并提交任务
mTxtView.setText(""+msg.arg1+"%");
progressBar.setProgress(msg.arg1);
handler.post(updateThread);
};
};
//子线程计算进度,这里Thread.sleep(1000)表示休眠1秒,达到每1秒进度跳增加10
RunnableupdateThread= new Runnable() {
inti = 0;
publicvoid run() {
i+=10;
if(i>=100)
i=100;
//通过obtainMessage()获得消息
Messagemsg=handler.obtainMessage();
//在arg1字段上设置消息
msg.arg1=i;
//持续一秒
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
handler.sendMessage(msg);
//当加载完毕后,从handler中移除updateThread
if(i>=100)
handler.removeCallbacks(updateThread);
}
};
}
上述Activity使用了Handler类接收从子线程更新的消息,重写了handleMessage()方法用于更新UI界面。运行程序,点击按钮后,Handler将启动线程updateThread。在updateThread线程体run()方法中,将每隔一秒增加进度10%,发送一次Message。主线程接收到Message后更新TextView和Progressbar的当前进度百分比。结果如图22-1和22-2所示:
图22-1未点击Download按钮前的界面
图22-2下载完成后的界面
1.6.2.AsyncTask实例
下面将使用AsyncTask编写下载进度条,布局文件与22.6.1节main.xml文件相同。AsyncTask实例的ActivityMain.java代码如下:
ActivityMain.java代码清单22-6-2:
/**
* @author孔明:小飞飞,我得表扬你,聪明,悟性也好,给你军师露脸,你看你打的这字,才
错了7个。咱们看第二行。
*/
public class MainActivity extendsActivity {
//声明控件
ButtonmDownload;
ProgressBarmProBar;
TextViewmTxtView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//加载控件
mProBar=(ProgressBar)findViewById(R.id.ProBar);
mTxtView=(TextView)findViewById(R.id.Txt);
mDownload = (Button)findViewById(R.id.download);
mDownload.setOnClickListener(new View.OnClickListener() {
publicvoid onClick(View v) {
//新建一个AsyncTask并调用execute执行
DownloadTaskdTask = new DownloadTask();
dTask.execute(100);
}
});
}
//尖括号内分别是AsyncTask使用的参数
class DownloadTask extends AsyncTask<Integer, Integer,String>{
//第一个方法
@Override
protectedvoid onPreExecute() {
super.onPreExecute();
}
//第二个方法,在onPreExecute()执行完后执行
@Override
protectedString doInBackground(Integer... params) {
for(inti=0;i<=100;i++){
mProBar.setProgress(i);
publishProgress(i);
try{
Thread.sleep(params[0]);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
returnnull;
}
//这个函数在doInBackground调用publishProgress时触发,虽然调用时只有一个参数
//但是这里取到的是一个数组,所以要用progesss[0]来取值
//第n个参数就用progress[n]来取值
@Override
protectedvoid onProgressUpdate(Integer... progress) {
mTxtView.setText(progress[0]+"%");
super.onProgressUpdate(progress);
}
//doInBackground返回时触发
//这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"
@Override
protectedvoid onPostExecute(String result) {
super.onPostExecute(result);
}
}
}
该Activity重写了AsyncTask中的onPreExecute()、doInBackground(Integer...params)、onProgressUpdate(Integer... progress)和onPostExecute(Stringresult)方法。在doInBackground()方法中,通过publishProgress(i)方法将当前进度i传递给onProgressUpdate()方法,在onProgressUpdate()方法中更新TextView的进度。运行程序,得到和22.6.1节相同的运行结果,如图22-3和图22-4所示:
图22-3未点击Download按钮前的界面
图22-4下载完成后的界面
1.7. 玄德有话说
刘备:军师,使用AsyncTask都需要注意些什么啊?
孔明:为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1、任务的实例必须在UI 线程中创建。
2、execute方法必须在UI 线程中调用。
3、不要手动的调用onPreExecute()、onPostExecute(Result)、doInBackground(Params)、onProgressUpdate(Progress)这几个方法。
4、该任务只能被执行一次,否则多次调用时将会出现异常。