Android 的线程和线程池

来源:互联网 发布:华语乐坛现状知乎 编辑:程序博客网 时间:2024/06/05 19:38

概述

线程在一个程序中是最小的调度单位,因为多线程的存在,我们的APP才得以并发处理事务。在Android中一个APP启动后已知会启动的一条主线程(UI线程),一条守护线程以及一条GC线程,其中Android中明确要求,我们不应该也不允许在主线程中进行耗时操作,否则这将导致ANR(Application Not Responding)。

这是因为在Android中主线程负责处理UI的更新事务,如果在该线程中处理耗时操作的话,将导致UI事件无法被即时处理,用户所看到的组件/动画也就变得卡顿,这对一个优秀的APP来说是十分致命的。

因此,我们必须独立开启一条新的线程(User Thread)来处理耗时任务。除了Java提供的Thread类/Runnale类以外,Android
还给我们提供了AsyncTask、IntentService、HandlerThread来处理。

其中AsyncTask主要用于开发者在子线程中更新UI,它的底层封装了Handler与线程池。

HandlerThread是一个普通的线程,但它的内部可以进行消息循环。

IntentService内部使用了HandlerThread来执行任务,当任务执行完毕后它就会退出,由于它是Service因而不容易被系统杀死。

在操作系统中,线程是最小的调度单位,它的本身不拥有资源依赖于父进程。尽管线程比进程要轻量的多,但是它的创建与销毁仍旧需要消耗系统的资源。因而提出了线程池这一概念,一个线程池拥有一定数量的线程,但它不会频繁的创建与销毁线程,而是通过对线程复用而完成任务。

Part.1 AsyncTask

1.1.AsyncTask使用方法

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把任务进行的进度传递给主线程。AsyncTask封装了Thread和Handler。

private class MyAsyncTask extends AsyncTask<Params,Progress,Result>{        public MyAsyncTask() {        }        @Override        protected void onPreExecute() {            super.onPreExecute();        }        @Override        protected Result doInBackground(Params...params) {            return null;        }        @Override        protected void onProgressUpdate(Progress...values) {            super.onProgressUpdate(values);        }        @Override        protected void onPostExecute(Result result) {            super.onPostExecute(result);        }}

以上就是一个AsyncTask类的构造方法以及一些最常用和最重要的方法,要使用AsyncTask类我们只需要覆写上述方法即可。

  1. onPreExecutet(),该方法会在异步任务执行之前被调用,在该方法内我们可以做一些准备工作。
  2. doInBackground(),该方法会在线程池中执行,这个方法也是我们重点要实现的方法,在这个方法中我们可以指定要做的异步任务,其中Params…params代表着异步任务输入的的参数,在该方法中可以调用publishProgress()方法以调用onProgressUpete()来更新显示给用户的进度。
  3. onProgressUpdate(),该方法在主线程中执行,负责更新用户设定的进度,当后台执行进度发生变化的时候会调用此方法。
  4. onPostExcute(),在主线程中执行,在异步操作完成之后执行,其中result是后台任务的返回值即doInBackground的返回值。

除了上述方法外,AsyncTask还提供了onCancelled()方法,当异步任务被取消的时候它会被调用。

要启用上述AsyncTask,只需要在主线程中:

new MyAsyncTask().execute(task1,task2,task3...);

由此可见,AsyncTask在处理一些轻量级的例如下载文件的操作的时候十分方便,这需要在调用时将URL传进去,在doInBackground中进行处理,最后在onPostExecute中接受即可。

尽管如此,AsyncTask还是存在一些需要注意的东西:

  1. AsyncTask必须在主线程中加载。
  2. AsyncTask对象必须在主线程中创建。
  3. execute方法必须在主线程中被调用。
  4. 不要直接通过AsyncTask的对象调用onPreExecute()、doInbackground()、onPostExecute()和onProgressUpdate()等方法。
  5. 一个AsyncTask对象只能执行一次excute方法。

1.2.AsyncTask的工作原理

    @MainThread    public final AsyncTask<Params, Progress, Result> execute(Params... params) {        return executeOnExecutor(sDefaultExecutor, params);    }    @MainThread    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,            Params... params) {        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();        mWorker.mParams = params;        exec.execute(mFuture);        return this;    }

既然我们调用AsyncTask的启动是execute方法,而execute方法又调用了excuteOnExecutor方法。在该方法中我们看到了需要我们实现的onPreEcecute()方法,因此在任务执行之前,该方法最先被执行。

紧接着,系统将我们传进来的任务输入参数赋值给了mWorker.mParams,mWork这个对象在AsyncTask对象的构造方法内完成了实例化。

WorkerRunnalbe是AsyncTask接入了Callable接口的内部类,它的内部只有一个Params参数,可见它的作用就是存储输入参数并协调任务的进行。

mWorker = new WorkerRunnable<Params, Result>() {            public Result call() throws Exception {                mTaskInvoked.set(true);                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);                //noinspection unchecked                Result result = doInBackground(mParams);                Binder.flushPendingCommands();                return postResult(result);            }        };

而execute中的sDefaultExecutor其实是一个线程池,该线程池是一个串行的线程池,所有的任务在这个线程池中需要排队进行。接下来我们看看这个sDefaultExecutor的线程池是如何实现的。

    private static class SerialExecutor implements Executor {        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();        Runnable mActive;        public synchronized void execute(final Runnable r) {            mTasks.offer(new Runnable() {                public void run() {                    try {                        r.run();                    } finally {                        scheduleNext();                    }                }            });            if (mActive == null) {                scheduleNext();            }        }        protected synchronized void scheduleNext() {            if ((mActive = mTasks.poll()) != null) {                THREAD_POOL_EXECUTOR.execute(mActive);            }        }    }

实际上它是AsyncTask的一个叫SerialExecutor内部类,该类是一个线程池,它的excute方法就是将每个任务作为一个线程存入线程池中,然后调用THREAD_POOL_EXECUTOR(另一个线程池)的execute方法。

executeOnExecutor中有一个mFuture的变量,该变量同样在AsyncTask中被实例化:

        mFuture = new FutureTask<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);                }            }        };

mFutrue将mWorker对象封装了起来,而mWorker对象中含有着我们的任务参数,而mFuture对象又被SerialExecutor存了起来并交给了THREAD_POOL_EXECUTOR真正执行任务。

在线程池中,future对象的run方法会被执行,并且回调mWorker的call方法,此时doInBackground方法就在线程池中开始执行了。当任务执行完毕后,它的返回值会被传递给postResult()。

该方法利用到了sHandler发送一个MESSAGE_POST_RESULT的消息,该sHandler的定义如下:

    private static class InternalHandler extends Handler {        public InternalHandler() {            super(Looper.getMainLooper());        }        @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;            }        }    }

由于该handler被生命成了static,static成员会在类初始化的时候进行加载,而AsyncTask要求必须在主线程中加载,因而当postResult方法被执行的时候,结果就从线程池中返回到了主线程中。其中finish方法会判断任务是否被cancel,如果是会调用onCancelled方法,否则会调用我们定义的onPostExecute方法对UI进行更新了。

上面提到过的SERIAL_EXECUTOR线程池实际上是在3.0之后新增加的一个线程池,它的作用就是为了对线程进行排队,以使得多个任务能够顺序的执行,该线程池是被static修饰的,因而整个APP中所有调用了AsyncTask去处理的任务都会进入到该线程池中进行排队执行,故当我们使用AsyncTask的时候并不能保证当前任务立刻会完成。

而在3.0之前的版本那是不存在该线程池的,当调用execute方法时会直接调用THREAD_POOL_EXECUTOR线程池去处理任务,该线程池最大线程数为5。

在1.6(Donut)之前:
在第一版的AsyncTask,任务是串行调度。一个任务执行完成另一个才能执行。由于串行执行任务,使用多个AsyncTask可能会带来有些问题。所以这并不是一个很好的处理异步(尤其是需要将结果作用于UI试图)操作的方法。

从1.6到2.3(Gingerbread)
后来Android团队决定让AsyncTask并行来解决1.6之前引起的问题,这个问题是解决了,新的问题又出现了。很多开发者实际上依赖于顺序执行的行为。于是很多并发的问题蜂拥而至。

3.0(Honeycomb)到现在
好吧,开发者可能并不喜欢让AsyncTask并行,于是Android团队又把AsyncTask改成了串行。当然这一次的修改并没有完全禁止 AsyncTask并行。你可以通过设置executeOnExecutor(Executor)来实现多个AsyncTask并行。关于API文档的描述如下

上述引用转载自Android中糟糕的AsyncTask

AsyncTask对于我们来说还是一个比较常用的API,个人从网络拉数据的惯用手法是开启一个Thread然后再自己新建一个Handler来处理。前面说过多次重复的新建销毁线程会带来巨大的内存损耗,因而对于这种小型的多线程操作还是建议使用AsyncTask来处理,毕竟它实际上就是handler+Thread集合的集成,而且解决了内存损耗的问题。

对于那些不需要顺序执行的任务,我们还可以使用executeOnExecutor方法来执行。

Part.2 HandlerThread

HandlerThread继承了Thread,实际上它就是封装了一个消息循环的Thread。其实现方法十分简单,即在run方法内开启Looper.prepare()创建消息队列,并通过Looper.loop()来开启消息循环即可。

然而使用HandlerThread与普通的Thread截然不同,Thread是直接在run方法内执行耗时方法,而HandlerThread则是通过Handler来通知它需要进行的操作。

另外需要注意的是,HandlerThread由于消息循环的存在,它并不会自我终结,所以在必要的时候我们需要通过quit或者quitSafely方法来退出它。

Part.3 IntentService

IntentService继承自Service类并且自己也是一个抽象类,要使用它必须创建它的子类并实现handlerMessage方法。

由于它本体是一个Service,因而它比普通的线程具有更高的优先级,因而它不那么容易被销毁。

实现:

    @Override    public void onCreate() {        // TODO: It would be nice to have an option to hold a partial wakelock        // during processing, and to have a static startService(Context, Intent)        // method that would launch the service & hand off a wakelock.        super.onCreate();        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");        thread.start();        mServiceLooper = thread.getLooper();        mServiceHandler = new ServiceHandler(mServiceLooper);    }    @Override    public void onStart(@Nullable Intent intent, int startId) {        Message msg = mServiceHandler.obtainMessage();        msg.arg1 = startId;        msg.obj = intent;        mServiceHandler.sendMessage(msg);    }

IntentService内部使用了HandlerThread和Handler,当IntentService第一次被创建的时候,onCreate方法会被调用,此时IntentService内部的handler会借助HanlderThread的Looper循环开启消息队列,当onStart()方法被调用的时候,该handler会发送一条信息,这个信息会在HandlerThread中完成处理。

handler收到信息之后会反过来调用onHandleMessage方法,此处中的intent是用户提供的intent,因此我们只需要在onHandlerMessage方法执行需要的任务即可。最后stopSelf(int startID)会被调用以停止该IntentService。

因此我们可以得到这样一个真相:
1. IntentService在执行完任务后会立刻停止,若有多个任务的时候IntentService会等所有任务结束以后再停止。
2. 由于(1),因此每次我们执行一个后台任务都要启动一次IntentService。
3. IntentService对任务的处理是利用消息循环模型,该模型对任务的处理是以队列处理的。
4. 由于(1)(2)(3),因此IntentService也是顺序的执行任务的,执行顺序会依照外部的启动顺序。

Part.4 Android中的线程池

Android中的线程池是利用Java中的Executor来实现的,其中真正的实现类是ThreadPoolExecut,该类提供了一系列参数来辅助开发者创建不同类型的线程池。

使用线程池能给我们的开发带来以下的好处:
1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
2. 能有效地控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
3. 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

4.1.ThreadPoolExecutor

该类的构造方法提供了一系列的参数以辅助我们来定制所需要的线程池。

    public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||            maximumPoolSize <= 0 ||            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)            throw new IllegalArgumentException();        if (workQueue == null || threadFactory == null || handler == null)            throw new NullPointerException();        this.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }

corePoolSize

线程的最大核心线程数,通常情况下,核心线程会一直存货,即便他们处于闲置状态。当allowCoreThreadTimeOut为true时,那么核心线程在等待新任务的到来时会有超时策略,时间由keepAliveTime决定,超时后和核心线程会终止。

maximumPoolSize

最大线程数,当线程数大于该值时,后置任务会被阻塞

keepAliveTime

闲置时长,该值控制非核心线程允许闲置的时间,超时后会被销毁。当allowCoreThreadTimeOut为true时,核心线程也会受该值影响,

unit

keepAlivevTime的单位,一般为毫秒、秒和分钟,

workQueue

线程池的任务队列,通过excute方法提交的Runnable对象会存储在该值中。

threadFactory

线程工厂,为线程池提供创建新线程的功能。

rejectedExecution

该参数提供一个handler用于当线程池无法执行新任务时通知调用者,该参数有好几个候选项,但该参数一般无需设置。

ThreadPoolExecutor遵循以下守则:

  1. 如果线程池中的线程数未达核心线程数,那么会直接启动一个核心线程。
  2. 如果线程数超过或达到核心线程数,那么新任务会被插入任务队列进行排队。
  3. 如果无法插入到任务队列时,即任务队列已满,此时若线程数未达线程池最大线程数的闲置,则会启动一个非核心线程来进行处理。
  4. 如果任务队列与最大线程数均达到满值,那么就会调用handler来通知调用者拒绝执行该任务。

4.2.线程池的分类

Android中的线程池总体来说可以分为四类,分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。这四类线程池都存在工厂方法生成。

1.FixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }

该类线程池通过Excutors.newFixedThreadPool(nThreads)创建,该类线程池值存在核心线程而且不存在超时机制,任务队列也没有大小限制。

2.CachedThreadCache

    public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }

该类线程池通过 Excutors.newCachedThreadPool()方法创建,它不存在核心线程但最大线程数为无限,存在超时机制,时长为60s,其任务队列相当于一个空队列。该线程池正好与第一类线程池相反,每次通过该线程池发放任务的时候都会启动一个新的线程来进行处理。当线程池处于闲置状态的时候,线程池中的线程都会因为超时而被销毁,此时该线程池几乎不占内存,因而该线程池适合处理大量耗时短的任务。

3.ScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {        return new ScheduledThreadPoolExecutor(corePoolSize);    }    public ScheduledThreadPoolExecutor(int corePoolSize) {        super(corePoolSize, Integer.MAX_VALUE,              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,              new DelayedWorkQueue());    }

该类线程池通过Excutors.newScheduledThreadPool(corePoolSize)方法创建,该线程池的核心线程数由用户决定,其最大线程数为无限,拥有超时机制且时间时间为10毫秒(相当于没有)。该类线程池用于执行周期性任务或延时性任务,通过调用schedule(command,delay,unit)方法和 scheduleAtFixedRate(command,initialDelay,period,unit)即可执行。

4.SingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }

该类线程通过Excutors.newSingleThreadExecutor()方法创建,从名字上便可得知该线程池中应该只有一条线程。该线程池的最大线程数与核心线程数都是1,且不存在超时机制。故该类线程池提供了一个顺序执行任务的功能,使得他们不需要处理线程同步的问题。

感谢《Android 开发艺术探索》

0 0
原创粉丝点击