1. 线程的定义
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一个进程中可以并发多个线程,每条线程并行执行不同的任务。每个线程都会分配一个私有内存区域,该区域主要用于在执行过程中存储方法、局部变量和参数。一旦线程终止私有内存区将会被销毁。更多关于进程与线程的区别和联系请参照上一篇文章关于Android进程的那些事儿,在此不再重复。
2. Android的单线程模式
在Android的系统启动流程和应用启动流程这篇文章中有提到,Android应用启动时,zygote进程fork自身,开启一个Linux进程和一个主线程(Main Thread),主线程负责UI显示、更新、控件交互,因此主线程又叫做UI线程。
Android中的单线程模式必须遵循两条规则:
不要阻塞 UI 线程
原因:
由于UI绘制和事件分发采用单线程模式,当UI线程中执行耗时操作(网络访问或数据库查询)时,UI线程会被阻塞,从而出现ANR(application not responding)或者类似NetworkOnMainThreadException之类的错误。不要在 UI 线程之外访问 Android UI 工具包
原因:
UI的显示、更新和控件交互主要通过Android UI 工具包(Android UI toolkit,包括android.widget和android.view)来实现的,而Android UI toolkit并非线程安全的工具包,如果我们在工作线程中刷新UI的时候,主线程也恰好在刷新UI,就会出现冲突。因此Android不允许在UI线程之外进行UI的相关操作,否则会出现CalledFromWrongThreadException的错误。
3. 工作线程和UI线程(主线程)
为了遵循上述单线程模式的两条规则,我们必须将耗时操作放在工作线程中,将UI操作放在UI线程中,同时有需要两个线程之间的切换。
3.1 主线程中开启工作线程
Java中提供了两种方法:Thread和Runnable
Thread
继承Thread类覆写run()然后thread.start()Runnable
实现Runnable接口复写run()然后New Thread(Runnable).start()。
但是,在Android中这两种方法是不值得推荐的。最好使用Android自带的Handler,AsyncTask,IntentService, Loader,Service等方式来实现。下面会详细说明。
3.2 工作线程中访问UI线程
下面三个方法可以访问UI线程:
- Activity.runOnUIThread(Runable)
- View.post(Runable)
- View.postDelayed(Runable,long)
但是如果主进程和工作线程之间需要更复杂的交互和操作,上面的方法就无能为力了,现在轮到Handler, AsyncTask, HandlerThread上场了,下面会展开详述。
4. 线程间通信
4.1 Handler
由于Android单线程模式下的两条原则的存在,线程间的通信显得尤为重要,Handler正是用来解决这个问题的。消息(Message)的传递需要Handler,MessageQueue, Looper共同来完成。其中Looper在Handler和MessageQueue之间起到桥梁和纽带的作用。在此之前,我们有必要了解一下相关概念
4.1.1 相关概念
Message
Message类实现了Parcelable接口,因此它可以包含任意的数据对象,并把它传递给Handler。尽管Message的构造方法是public,但是我们还是推荐使用Message.obtain()或者Message.obtainMessage()来得到一个Message实例。-
MessageQueue
MessageQueue(消息队列)顾名思义,Message组成的一个List,但是Message并不是直接加入到MessageQueue的,而是通过与Looper绑定的Handler来实现的。获取当前线程的MessageQueue的方法是Looper.MyQueue()。
Looper
Looper类用来操作一个线程的消息循环。用法如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();//创建looper
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();//
}
}
-
Handler
handler跟一个线程和该线程的messagequeue绑定的,当我们新建一个Handler的时候,它就会与当前线程,和线程的消息队列绑定在一起。Handler负责传递Message或者Runnable对象给MessageQueue,并当他们从消息队列出来的时候执行相关操作。
Handler有两个用途:
(1)安排message和runnable在将来的某个时刻得到执行
常用实现方法有
post(Runnable)
[postAtTime(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postAtTime(java.lang.Runnable, long))
[postDelayed(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long))
sendEmptyMessage(int)
sendMessage(Message)
[sendMessageAtTime(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageAtTime(android.os.Message, long))
[sendMessageDelayed(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageDelayed(android.os.Message, long))
其中,Message的处理是通过Handler的 handleMessage(Message)来实现的(需要继承Handler的基类)。
(2)在其他线程中执行某些操作。
Handler可以在线程间传递消息,工作原理如下
4.1.2 工作机制
Handler用来发送和处理Message或者Runnable对象,每个Handler实例都会绑定到一个线程和这个线程的MessageQueue。当创建Handler的时候,他会默认绑定到创建它的线程上,他会给MessageQueue发送message和runnable,在Looper轮训到该条消息的时候,回调创建该消息的Handler的handlerMessage方法,处理从message queue出来的message和runnable。
具体流程如下图:
当应用启动时,新的进程被创建的时候,它的主线程会运行一个message queue来管理上层应用对象(Activity,Broadcast Receiver)和创建的窗口。也可以创建工作线程,然后创建Handler,在工作线程中调用postRunnable或者sendMessage类方法来传递消息给主线程。这时message或者Runnable对象将会在handler的message queue中排队并在它出站时得到执行。
4.2 AsyncTask
4.2.1 使用方法
首先,继承AsyncTask类,至少重写doInBackground这个方法。比如
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
protected Long doInBackground(URL... urls) {//这个方法必须被重写
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
@Override
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(Long result) {//doInBackground执行完成之后调用UI线程
showDialog("Downloaded " + result + " bytes");
}
}
然后在主线程中执行:
new DownloadFilesTask().execute(url1, url2, url3);
4.2.2 执行过程
一个异步任务的4步走:
onPreExecute
doInBackground
onProgressUpdate
onPostExecute
从方法名可以理解这四个方法的功能和执行顺序,不再赘述。
4.2.3 注意事项
- 上述四个方法不可以手动调用
- 每个task的加载,创建,执行都必须在UI线程中执行。
- 该任务只能执行一次,第二次执行会抛异常
- AsyncTask开启线程的方法asyncTask.execute()默认是也是开启一个线程和一个队列的,不过也可以通过asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)开启一个含有5个新线程的线程池,也就是说有个5个队列了,假如说你执行第6个耗时任务时,除非前面5个都还没执行完,否则任务是不会阻塞的,这样就可以大大减少耗时任务延迟的可能性。
除了常用的Handler和AsyncTask之外,IntentService和HandlerThread也为线程间的通信提供了极大地便利
4.3 IntentService
有了intentService就可以不用自己启动和结束线程了,IntentService内部会自动执行线程的开启和销毁。
4.3.1 使用步骤
(1)继承IntentService类,并重写onHandleIntent()方法,这个方法中,执行耗时操作
public class myIntentService extends IntentService {
public myIntentService() {
super("myIntentService");
// 注意构造函数参数为空,这个字符串就是worker thread的名字
}
@Override
protected void onHandleIntent(Intent intent) {
//根据Intent的不同进行不同的事务处理
String taskName = intent.getExtras().getString("taskName");
switch (taskName) {
case "task1":
Log.i("myIntentService", "do task1");
break;
case "task2":
Log.i("myIntentService", "do task2");
break;
default:
break;
}
}
//--------------------用于打印生命周期--------------------
@Override
public void onCreate() {
Log.i("myIntentService", "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("myIntentService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i("myIntentService", "onDestroy");
super.onDestroy();
}
}
然后通过startService(Intent i)启动服务,Intent 会携带相关数据,onHandleIntent会对这些数据进行识别,。
//同一服务只会开启一个worker thread,在onHandleIntent函数里依次处理intent请求。
Intent i = new Intent("cn.scu.finch");
Bundle bundle = new Bundle();
bundle.putString("taskName", "task1");
i.putExtras(bundle);
startService(i);
4.3.2 执行过程
onCreate--->onStartCommand-->onHandleIntent-->onDestroy
intentservice的oncreate方法中会通过HandlerThread 开启新线程,创建对应的Looper,和MessageQueue。
通过onStartCommand()传递给服务intent被依次插入到工作队列中。工作队列又把intent逐个发送给onHandleIntent()。
通过在onHandleIntent((Intent)msg.obj)中调用你的处理程序.
处理完后会自动停止自己的服务Ondestroy
4.3.3 注意事项
- IntentService 会创建一个线程,来处理所有传给onStartCommand()的Intent请求。
- 对于startService()请求执行onHandleIntent()中的耗时任务,会生成一个消息队列,每次只有一个Intent传入onHandleIntent()方法并执行。也就是同一时间只会有一个耗时任务被执行,其他的请求还要在后面排队, onHandleIntent()方法不会多线程并发执行。
- 当所有startService()请求被执行完成后,IntentService 会自动销毁,所以不需要自己写stopSelf()或stopService()来销毁服务。
- 提供默认的onBind()实现 ,即返回null,不适合绑定的 Service。采用StartService来启动,即使启动它的Activity被销毁也不会影响service的生命周期
- 提供默认的 onStartCommand() 实现,将intent传入等待队列中,然后到onHandleIntent()的实现。
- Service等四大组件默认是运行在主线程上的!没有界面不代表不再UI线程。
4.4. HandlerThread
HandlerThread是Thread的子类,可以创建一个带有looper的线程,并创建与这个looper绑定的Handler,代码如下
HandlerThread mThread = new HandlerThread("message");
mThread.start();
Handler mHandler = new Handler(mThread.getLooper())
{
@Override
public void handleMessage(Message msg)
{
// to do sth
}
};
可以看出,跟Handler非常类似,只是新建HandlerThread时,与该线程绑定的looper也被创建了,所以不再需要我们自己去调用Looper.prepare(),Loop.loop()。直接通过thread.getLooper()就可以获取到当前线程的looper,类似于根据looper获取MesssageQueue的方法:Looper.MyQueue()。
5. 总结
由于Android系统的单线程模式,线程之间的通信必不可少,必须掌握Handler,AsyncTask, IntentService, HandlerThread的工作原理并加以熟练使用。其中Handler跟HandlerThread工作原理是一样的,很多情况下使用HandlerThread可以简化很多工作。