更新时间 | 修改意见 |
---|---|
2016-08-02 | 陈敏 |
第1节 概述
安卓应用只有一个主线程-各个组件都是在这个线程中运行。作为组件的之一的Activity就是在这个线程中更新应用界面的,例如,用户点击界面上的一个按钮,按钮得到响应,整个过程就是在这个主线程里。所以这个主线程绝对不可以做耗时的操作。假如在按钮中做了耗时的操作,那么当它进行耗时操作的时候,你去点击界面上的其它按钮是不会有反应的,就好像程序冻在了那里。
我们的代码一旦连续占用这个线程超过一定的时间,系统就会弹出“程序无响应的”提示,这个提示叫做ANR
-Applicatin No Response。
这就好比你在正在做一件事情A,突然另一件事情B来打扰你,你不得不停下手头的工作来完成,做完了才能继续之前的工作;这时如果有另外一个人(另一个线程)来帮助你,把事情B全部包揽了,那你就不用分心了。当另一个人把事情B做完后,告诉你一声就可以了。
启动一个新的线程,分担耗时工作的方法是一种异步操作:我让你帮我做一件事情,布置任务后,我就去做其他的事情了,等你做完了再告诉我结果;
与它对应的是同步操作:我让你帮我做一件事情,布置任务后,我啥也不做,就等着你做完了告诉我结果;
例如,一个视频播放应用,获取视频信息所需要的时间是个不能确定的事情。如果视频很少,也许几十毫秒就能完成,如果视频很多(比如几十个),也许就要花二十多秒。
因此,我们可以考虑把获取视频信息的操作放到一个单独的线程thread中进行。启动一个新线程-工作线程thread-查询视频信息,查询完成后,工作线程再将结果通知到主线程,让主线程将查询到的结果显示到界面上。因为界面的更新一定要在主线程中进行,不能在别的线程修改,否则系统会提示运行错误。因此我们一定要将查询的结果发送给主线程,让主线程处理界面的更新。
这里就涉及到了线程的创建,工作分配,以及它们之间的配合-信息的传递。
/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。
/*******************************************************************/
第2节 线程Thread
一个Activity运行在一个主线程上,它不能进行耗时巨大的操作,也不能执行耗时不确定的操作。因此需要在新的线程中进行这些耗时的操作,这种进行耗时操作的线程称作工作线程。
2.1 Thread的简单使用
创建一个新的线程,
创建一个
Runnable
,重写它的run()
函数,这个函数里用来进行耗时的操作;@Override public void run() { //耗时的操作 while(xxx) { } } };
以
Runnable
为参数,创建一个Thread
,调用Thread
的start()
方法后,新线程就运行起来了,并执行Runnable
中的run()
函数;Thread thread = new Thread(runnable); thread.start();
当
run()
函数执行完毕后,新线程就退出;
在线程执行耗时操作的过程中,有时要取消这个线程的操作,
最好的办法是在
run()
函数中增加一个标志位,让工作线程可以检查这个标志位是否被设置上,如果设置上了,就让run()
函数,立即返回,//设置标志位,为退出线程使用 boolean needStop = false; ...... Runnable runnable = new Runnable() { @Override public void run() { //耗时的操作 while(!needStop) { } } };
如果主线程要取消这个线程的工作,修改这个标志位就好了,
needStop = true;
2.2 Thread的创建
Thread可以拥有不同的优先级,从低到高有10级。操作系统根据线程的优先级来进行资源调度,优先为优先级高的线程分配CPU资源。在默认情况下,新创建的线程使用默认的优先级NORM_PRIORITY
。设置优先级的方式很简单,
Thread thread = new Thread(runnable);
thread.setPriority(Thread.NORM_PRIORITY);
可以为线程取名字,当我们用Android Monitor
工具调试应用的时候,就能看到创建线程时它的名字,方便我们观察、调试程序,
Thread thread = new Thread(runnable, "新线程的名字");
有时,一个应用会同时启动多个Thread,在创建它们的时候可以为它们设置ThreadGroup
参数,将它们分成一组,便于对这些线程进行统一的管理。比如,中断这个组里所有线程的运行;
ThreadGroup group = new ThreadGroup("线程组");
Thread thread1 = new Thread(runnable, group);
thread1.start();
Thread thread2 = new Thread(runnable, group);
thread2.start();
Thread thread3 = new Thread(runnable, group);
thread3.start();
//中断3个线程的执行
group.interrupt();
2.3 Thread的停止
Thread的停止就是指这个线程的退出。线程的退出原因无外乎两种,
- 工作线程的工作完成了;
- 工作线程虽然正在进行耗时工作,但是被取消了,要提前结束;
2.3.1 正常退出
当Runnable
中的run()
函数执行完并返回后,当前的Thread就退出了。
2.3.2 使用标志位
在
run()
函数中增加一个标志位,工作线程会随时检查这个标志位是否被设置上,如果设置上了,就让run()
函数,立即返回,//设置标志位,为退出线程使用 boolean needStop = false; ...... Runnable runnable = new Runnable() { @Override public void run() { //耗时的操作 while(!needStop) { } } };
如果主线程要取消这个线程的工作,修改这个标志位就好了,
needStop = true;
2.3.3 使用interrupt()方法
interrupt()
是线程提供的一个标准的线程退出方法,如果当前的工作线程正被Object.wait
、Thread.join
或Thread.sleep
阻塞,那么使用thread.interrupt()
之后,正在运行的线程会抛出一个InterruptedException
异常,
Runnable runnable = new Runnable() {
@Override
public void run() {
...
while(!needStop)
{
try {
......
Sleep(1000);
} catch ( InterruptedException e ) {
needStop = true
}
}
}
};
不过interrupt()
方法并不是万能的,不是所有阻塞情况下都能够让线程立即退出。
例如当该线程正在用ServerSocket
的accept()
方法等待连接的时候,即使调用了这个工作线程的interrupt()
方法,该线程还是不会抛出异常的。
它只对Object.wait
、Thread.join
或Thread.sleep
这几种阻塞有效果。对于网络读取数据时阻塞状态解除是没有效果的。
*对于网络读取数据时造成的阻塞,我们会在以后相应的章节详细介绍解决方法。
2.4 线程之间的同步
线程间的同步就是指线程A执行到一个地方的时候,停了下来,等待线程B的执行结果;等线程B执行出结果后,线程A才能继续执行。
2.4.1 join()方法
最常见的就是主线程的执行,依赖于工作线程的退出。
主线程启动了工作线程以后,有时候需要等到工作线程结束以后再进行接下来的操作。
例如一个Activity,在onCreate()
的时候创建了一个工作线程Thread B;后来在用户的要求下,Activity退出,要被销毁了;销毁Activity时,主线程要等到Thread B执行完了才能继续接着进行剩下的清理工作,那么Activity可以在它的onDestroy()函数中可以使用join()
方法,等待子线程的结束,
private Thread mTreadB;
@Override
protected void onCreate() {
super.onCreate();
mThreadB = new Thread(runnable);
mThreadB.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
mThreadB.join();
//进行剩下的清理操作
}
join()
方法,会一直处于阻塞状态,直到线程B退出。
在onDestroy()
中使用join()
有天生的缺点:不能在主线程中进行耗时不可控的操作(例如这里等待工作线程执行完毕),万一工作线程的退出花费了很长的时间,那就有问题了。这里的场景只是用来举一个例子而已。
2.4.2 wait()方法
利用Object
的wait()
方法实现线程间的同步,需要线程之间共享一个“锁”-Object
对象。
final Object lock = new Object();
当主线程A执行到一个阶段的时候,如果要等待线程B,就把锁置于等待状态,
synchronize(lock) {
lock.wait()
}
此时,线程A就出于阻塞的状态,不能往下执行了。
线程B继续它的运行,当它执行到一个阶段的时候,将锁设置成放行状态,
synchronize(lock) {
lock.notify();
}
此时,线程A的阻塞状态解除,可以继续往下执行了。
/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。
*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。
*最后再次感谢各位读者对安豆
的支持,谢谢:)
/*******************************************************************/