今天要分享的是Android基础知识篇,往往我们都是拿来主义,知道怎么用却不知道原理,今天就来讲讲Android线程相关的知识点吧。一如既往的,写文初心,便于追溯,总结知识,如果能给看到这篇文章的你提供帮助,那价值就更大了。
Android中线程分为主线程和子线程,主线程主要用于UI相关的事务,是进程默认情况下拥有的线程,looper是main looper,在主线程中不能做耗时操作,因为主线程对于响应速度要求很高,如果五秒不响应系统就会出现ANR报错,就算不超过五秒耗时操作也会造成界面的卡顿,严重影响用户体验,所以一定要记住耗时操作不要在主线程中进行,比如数据库访问,网络访问等,当然在Android3.0以上如果在主线程中进行网络访问就会报NetworkOnMainThreadExcepetion
异常。而子线程可以说是工作线程的,可用于耗时操作,网络请求等。
子线程我们最熟悉的实现方式就是Thread,但是Thread的大量new可不是一件好事情,在阿里巴巴编程规范里面就有提到建议不要直接new Thread 应该用线程池来代替,可以管理和复用线程。在Android中除了Thread的呢,还有HandlerThread,AsyncTask以及IntentService,当然啦,还有线程池。AsyncTask相信很多人都有用到过,用于在主线程创建并且进行异步操作,在回调方法中可进行UI的刷新和操作进程的监听等。 而IntentService呢,内部其实也是Thread和Handler实现的,它的优势在于它是一个service可以在后台运行,相对来说优先级比其他线程来说更好,不容易被系统杀死。
HandlerThread是具有消息循环的线程,我们可以利用HnandlerThread在其中运行Handler从而将一个Handler建立在子线程中运行。而AsyncTask则是内部封装了线程池和Handler,接下来我们会分别分析AsyncTask的源码看看它是如何完成整个过程的,以及IntentService,HandlerThread相关原理和使用。
AsyncTask
AsyncTask 用于异步任务执行,利用线程池在后台执行任务,然后借助于Handler将任务进程以及任务结果返回到UI,从而实现在子线程中进行耗时操作,而在主线程更新UI的功能,对于我们程序中请求网络并且刷新UI来说是个很好的选择,但是呢,从下面这段从源码摘抄过来的类注释可以看出,AsyncTask不适用于特别耗时的后台任务,只适用于几秒的操作,对于特别耗时的后台任务建议使用线程池或者FutureTask。
syncTask is designed to be a helper class around {@link Thread} and {@link Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the <code>java.util.concurrent</code> package such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
疑问: 为什么AsyncTask不适用于特别长时间的耗时操作呢?
- AsyncTask的生命周期和Activity不一致,如果是操作时间太长的话,当Activity由于旋转屏幕或者其他原因销毁了的时候,当操作执行完会找不到要更新的UI从而报错。
java.lang.IllegalArgumentException: View not attached to window manager
. 比如你想关于一个dialog,你并没有在onstop中去dimiss掉这个dialog。 - 因为AsyncTask在执行长时间的耗时任务时也会持有一个Activity对象,即使这个Activity已经不可见了,Android也无法对这个Activity进行回收,导致内存泄露。
- 当然你可能会问,难道AsyncTask不能手动cancel,答案当然是可以啦, 但是AsyncTask的cancel方法有一个弊端,那就是当doInBackground()正在执行一个不可打断的工作的方法会失效,比如BitmapFactory.decodeStream()的IO操作,当然只要你想cancel成功,你也可以在cancel之前强制停止IO操作,捕捉异常,保证AsyncTask准确的被cancel,关于AsyncTask的Cancel的使用等会会简单介绍一下,是有一点小差别的。
下面我们一起来看看AsyncTask的源码然后看看实现原理.
public abstract class AsyncTask<Params, Progress, Result> {
首先AsyncTask是一个抽象类,它有三个泛型参数,从字面意思可知,第一个是参数,第二个是任务进度,第三个是返回结果类型。
要使用AsyncTask的时候必须继承实现抽象方法。AsyncTask有4个核心的方法。 如下:
注: 此段源码摘抄自android-26
从注解可知: onPreExecute是工作在主线程中的一个方法,主要用于在开始执行异步任务之前做一些前期准备工作。
@MainThread
protected void onPreExecute() {
}
doInBackground是在工作线程即子线程执行后台任务的方法,在这里你可以实现你要执行的后台任务,数据库请求网络请求等。 参数Params和创建AsyncTask子类的时候Params同类型的参数,用于给后台任务提供一些信息。在这个方法,可以通过调用publicProgress来更新任务进度,publicProgress会调用onProgressUpdate方法。
并且在这个方法将返回任务执行的返回结果,结果会被onPostExecute接收并处理
@WorkerThread
protected abstract Result doInBackground(Params... params);
这个方法执行在主线程中用于监听当前任务的进度,可以根据进度更新主线程UI告知用户任务执行的进度的。
@MainThread
protected void onProgressUpdate(Progress... values) {
}
同样此方法运行在主线程中,用于接收任务的结果,
@MainThread
protected void onPostExecute(Result result) {
}
这四个方法的执行顺序是: 1.onPreExecute 2. doInBackground 最后是onPostExecute。在doInBackground中如果有调用publicProgress方法被调用的话就会执行onProgressUpdate方法。
AsyncTask还提供了cancel方法如下:
当mayInterruptIfRunning is true 则中断当前正在执行的任务,false则允许当前正在执行的任务执行完才结束。调用此方法之后会回调onCanceled()方法,不会调用onPostExecute方法、
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
,但是要注意的是,AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。
下面是一个简单的应用实例:
/**
* URL 下载地址
* Integer 下载进度
* Integer 下载结果 总共下载的文件长度
*/
public static class DownLoadingFileAsyncTask extends AsyncTask<URL, Integer, Integer>{
private static final String TAG = DownLoadingFileAsyncTask.class.getSimpleName();
@Override
protected void onPreExecute() {
Log.i(TAG, "onPostExecute");
}
@Override
protected void onPostExecute(Integer integer) {
Log.i(TAG, "onPostExecute");
}
@Override
protected void onProgressUpdate(Integer... values) {
Log.i(TAG, "onProgressUpdate" + values[0]);
}
@Override
protected Integer doInBackground(URL... urls) {
int length = 0;
for (URL url : urls) {
length = downloadFile(url);
publishProgress(length);
if (isCancelled()){
break;
}
}
return length;
}
}
使用方法:
new DownLoadingFileAsyncTask().execute(url1, url2, url3);
这里我只是示意一下所以实现都比较简单,我执行了一个下载文件的操作,并且调用publicProgress方法更新下载进度,在onProgressUpdate里面打印了当前下载的进度.
从我们调用的方法的入口我们来看看AsyncTask是如何实现的:
首先看看execute方法:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
从注解可以知道首先execute必须在主线程执行,可以看到这个方法只是调用了executeOnExecutor
方法。 sDefaultExecutor是一个串行的线程池d.定义如下:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
//向队列尾部插入一个新的runable对象
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
//取出队列头部第一个任务并且不为null的执行
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
//用于执行任务的线程池 THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
从上面可以知道sDefaultExecutor是一个静态的SerialExecutor对象,从SerialExecutor的实现可以看出它是一个一个执行任务的,当前没有active的任务的时候,就会调用scheduleNext()执行下一个任务。并且是串行执行。
需要注意的是:这个方法是执行的时候AsyncTask是串行执行还是并行执行取决于Android版本,在一开始的Android1.6之前AsyncTask是串行执行的,但Android1.6之后AsyncTask变成了并行执行,不过为了避免的并行带来的麻烦,Android3.0又开始使用单线程串行执行,这个在源码中的方法的注解中都有明确的说明的,不过不是说Android3.0方法以后就不能执行并行操作了,你可以用这个方法实现AsyncTask的的并行操作。executeOnExecutor 如下:
new DownLoadingFileAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url1, url2, url3);
new DownLoadingFileAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url1, url2, url3);
new DownLoadingFileAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url1, url2, url3);
new DownLoadingFileAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url1, url2, url3);
一起来看看executeOnExecutor
的实现:
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
//调用了onPreExecute()方法s
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
executeOnExecutor
首先判断一下当前AsyncTask的状态是否正在运行或者已经执行完了,然后是就抛出异常,所以这也决定了AsyncTask的execute
必须也只能调用一次 . 可以看到在这个方法中首先调用了onPreExecute
方法。 然后执行线程池执行了mFuture这个RunableTask,而mFuture呢就是执行的mWorker这个Runable的call方法。
mWorker和mFuture的定义如下:
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
//调用了doInBackground方法
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
//发送result到Handler
postResult(result);
}
return result;
}
};
mFuture = new FutureTaskd<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
executeOnExecutor
方法中将params
赋值给了mWorker
的params对象,mWorker的call方法中执行了doInBackground并且将结果通过Hanlder发送出去。
Handler如下:
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
在InternalHandler
接收到MESSAGE_POST_RESULT
之后调用了AsyncTask的finish方法如下:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
可以看到finish方法首先判断了是否被取消,如果取消了就调用onCanceled方法把结果返回,否则回调onPostExecute,将结果返回,至此我们将 一个任务的执行到返回结果的路径都跟踪完了。这就是任务异步执行的全过程, InternalHandler是一个运行在主线程中的Handler,new的语句如下:
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
下面我们一起看看更新进度的方法publishProgress:
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
显而易见,该方法向handler发送了MESSAGE_POST_PROGRESS
消息,Handler回调了onProgressUpdate(result.mData)
方法。
现在终于清楚了整个调用的过程了吧。 其实AsyncTask的源码注释说明里面也给出了很清楚的解说,所以多看看源码也有利于我们更加了解类的实现和原理。
这里需要注意的是: 在Android5.0以下AsyncTask必须在主线程中加载,至于为什么很简单,因为InternalHandler是一个静态内部类,而它又必须有主线程的looper,静态内部类在类加载的时候就完成初始化,所以这就要求AsyncTask必须在主线程中执行。
HandlerThread
我们直接看HandlerThread的源码:
public class HandlerThread extends Thread {
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
此处只贴出关键的部分,感兴趣的可以自己去看完整的源码哦,我就不全贴出来凑字数了。^_^
HandlerThread继承自Thread,在run里面利用Looper实现了消息队列功能,我们都知道Handler的原理中就包含了Looper,Looper负责消息的循环,这里也是一样的,HandlerThread借助Looper无线循环的轮询,从而执行对应的Message。同时HandlerThread提供了quit和quitSafely方法,因为looper是无线循环的,所以不需要时,记得养成良好的习惯停止HandlerThread,应用场景如下:
一般我们在程序中借助handlerThread来开启一个非主线程的Handler进行消息处理做一些耗时的操作。
HandlerThread handlerThread = new HandlerThread("worker thread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
HandlerThread相对简单一点,所以我们就将这么多了。
IntentService
顾名思义,IntentService是一个继承与service的类,内部利用HandlerThread和handler实现了对消息的传递。IntentService是一个抽象类,子类必须继承onHandlerIntent方法用于对消息的处理。
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
* When all requests have been handled, the IntentService stops itself,
* so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
* This may be null if the service is being restarted after
* its process has gone away; see
* {@link android.app.Service#onStartCommand}
* for details.
*/
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
从注释可以看到这个方法也是串行的,所以当有很多请求的时候回堵塞当前IntentService的其他请求,当所有请求都被执行完了之后,IntentService会调用stopSelf停止他自己。
IntentSerivce是如何对外界Intent进行处理的, 每次调用Service的时候虽然只会调用一次onCreate,但是会重复调用onStartCommond方法,onStartCommond里面调用了onStart方法,看看onStart做了什么。
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
onStart将startId以及Intent包裹在msg里面发送到了ServiceHandler,在看看ServiceHandler做了些什么。
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
不管接收到了什么消息,ServiceHandler都是直接回调onHandlerIntent
方法。然后停止自己。而上面我们说了onHandlerIntent的方法是有子类实现的,所以子类自己实现然后处理相应的Intent消息。
最后一下有关于线程池的知识:
线程池的优点: 实现线程的复用,控制线程池最大的并发数,对线程进行管理。
这里我只提及一下ThreadPoolExecutor的构造方法。
通过配置相关的参数创建一个线程池。
参数解说如下:
1.corePoolSize 线程池中核心线程数
2、maximumPoolSize 线程池中允许的最大线程数
3.keepAliveTime 非核心线程闲置时的超时时间,超过就会被回收掉,如果allowCoreThreadTimeOut为true,核心线程也会被回收掉。
4.unit 超时的时间单位
5. 线程池中的任务队列
6.创建线程工厂
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
核心线程会一直在线程池中不会被回收,除非allowCoreThreadTimeOut 为true。那么当核心线程闲置时间超过keepAliveTime的时候就会回收掉。
非核心线程当闲置的时候就会被回收掉。当活动线程达到最大线程池的时候,后面的线程就会被阻塞。
ThreadPoolExecutor执行任务的规则:
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务
- 如果线程池中的线程数量已经超过了核心线程数量则插入任务队列里面等待
- 当任务队列满了的时候,并且线程没有达到规定的最大线程的数量的时候则启动一个非核心线程执行任务。
- 如果线程数量已经超过了最大线程池数量,则拒绝任务调用RejectedExecutionHandler的rejectedExecution来通知调用者。
我们常见的线程池子类有: FixedThreadPool, CachedThreadPool,ScheduledThreadPool,SingleThreadExecutor。这些只是配置了不同参数的线程池而已,所以感兴趣的可以自己百度看看哦。
好了 码了这么久,终于学习笔记总结完了,总结了一下之后,感觉把之前看的知识点又重新温习了一遍,影响更加深刻了,受益匪浅。 不知道 看到这里,你学会了多少。有总结的不对的地方,请不吝指出,欢迎讨论, 谢谢。
网友评论