android进阶(十一)-----Android线程和线程池

275 阅读9分钟

线程分为主线程和子线程,主线程主要处理和界面相关的事情,而子线程则用于执行耗时操作。

android找那个线程有很多种实现方式:AsyncTask、IntentService、HandlerThread。

AsyncTask封装了线程池和Handler,主要为了方便开发者在子线程中更新UI

HandlerThread是具有消息循环的线程,内部可以使用Handler

IntentService是一个服务,内部采用HandlerThread执行任务,任务执行完毕后会自动退出

一、主线程和子线程

Android沿用了java的线程模型,分为主线程和子线程,其中主线程也叫UI线程。主线程的作用是运行四大组件以及处理他们和用户的交互,而子线程则执行耗时任务,比如网络请求。I/O操作等。从android3.0开始系统要求网络访问必须在子线程中进行,否则会抛出NetworkOnMainThreadException异常,这样做是为了避免主线程由于被耗时操作阻塞而出现的ANR。

二、Android中的线程形态

1、AsyncTask:

AsyncTask是一种轻量级的异步任务类,可以在线程池中执行后台任务,然后把执行的进度和结果传递给主线程并在主线程中更新UI。AsyncTask封装了Thread和Handler,通过AsyncTask可以方便的执行后台任务以及在主线程中访问UI,但是AsyncTask不适合进行特别耗时的后台任务,特别耗时的任务建议使用线程池。

AsyncTask是一个抽象的泛型类,提供了Params、Progress和Result三个泛型参数,Params表示参数类型,Progress表示后台任务的执行进度的类型,Result表示后台任务的返回结果的类型

2、AsyncTask提供4个核心方法:

(1)onPreExecute():在主线程执行,在异步任务执行之前会被调用,可以做一些准备工作。

(2)doInBackground(Params...params):在线程池中执行,用于执行异步任务,params表示异步任务的输入参数。可以通过publishProgress方法更新任务的进度,publishProgress方法会调用onProgressUpdate方法,此方法需要返回计算结果给onPostExecute方法

(3)onProgressUpdate(Progress...values):在主线程执行,当后台任务的执行进度发生改变时调用此方法

(4)onPostExecute(Result result):在主线程中执行,在异步任务执行之后会被调用,result参数是后台任务的返回值,即doInBackground的返回值。

代码实例:

private class DownloadFilesTask extends AsyncTask<URL,Integer,Long>{ 
    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)); 
            if(isCancelled()){ 
                break; 
            } 
            return totalSize; 
        } 
    } 
    protected void onProgressUpdate(Integer...progress){ 
        setProgressPercent(progress[0]); 
    } 
    protected void onPostExecute(Long result){ 
        showDialog("Downloaded"+result+"bytes"); 
    } 
}

3、AsyncTask在使用过程中有一些条件限制,主要有以下几点:

(1)AsyncTask的类必须在主线程中加载。

(2)AsyncTask的对象必须在主线程中创建

(3)execute方法必须在UI线程调用

(4)不要在程序中直接调用onPreExecute、onPostExecute、doInBackground和onProgressUpdate方法

(5)一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常

4、AsyncTask的工作原理

从execute开始分析,execute会调用executeOnExecutor方法,代码如下:

public final AsyncTask<Params,Progress,Result> execute(Params...params){ 
    return executeOnExecutor(sDefaultExecutor,params); 
} 
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"); 
        } 
    } 
    mStatus = Status.RUNNING; 
    onPreExecute(); 
    mWroker.mParams = params; 
    exec.execute(mFuture); 
    return this; 
}

sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的线程池中排队执行。在executeOnExcutor方法中,AsyncTask的onPreExecute方法最先执行,然后线程池开始执行。下面看线程池的执行过程。

public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); 
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;   
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和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正的执行任务,InternalHandler用于将执行环境从线程池切换到主线程。

5、HandlerThread

HandlerThread继承Thread,它是一种可以使用Handler的Thread,他的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样就运行在HandlerThread中创建Handler,HandlerThread的run方法代码如下:

public void run(){ 
    mTid = Process.myTid(); 
    Looper.prepare(); 
    synchronized(this){ 
        mLooper = Looper.myLooper(); 
        notifyAll(); 
    } 
    Process.setThreadPriority(mPriority); 
    onLooperPrepared(); 
    Looper.loop(); 
    mTid = -1; 
}

6、IntentService

IntentService是一种特殊的Service,继承了Service并且他是一个抽象类,因此必须创建他的子类才能使用。IntentService用于执行后台耗时任务,当任务执行完成会自动停止,同时它的优先级比线程高很多。IntentService封装了HandlerThread和Handler,可以从它的onCreate中看出,代码如下:

public void onCreate(){ 
    super.onCreate(); 
    HandlerThread thread = new HandlerThread("IntentService["+mName+"]"); 
    thread.start(); 
    mServiceLooper = thread.getLopper(); 
    mServiceHandler = new ServiceHandler(mServiceLooper); 
}

当IntentService第一次启动时,oncreate会被调用并创建一个HandlerThread,然后使用它的Looper构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。从这个角度看,IntentService也可以执行后台任务。每次启动IntentService,他的onStartCommand方法就会调用一次,IntentService在onStartCommand中处理每个后台任务的Intent。下面看一下onStartCommand如何处理外界Intent。代码如下:

public void onStart(Intent intent,int startId){ 
    Message msg = mServiceHandler.obtainMessage(); 
    msg.arg1 = startId; 
    msg.obe = intent; 
    mServiceHandler.sendMessage(msg); 
} 
下面来看一下IntentService的工作方式,示例代码: 
public class LocalIntentService extends IntentService{ 
    private static final String TAG = "LocalIntentService"; 
    public LocalIntentService(){ 
        super(TAG); 
    } 
    @Override 
    protected void onHandleIntent(Intent intent){ 
        String action = intent.getStringExtra("task_action"); 
        SystemClock.sleep(3000); 
        if("com.ryg.action.TASK1".equals(action)){ 
            Log.d(TAG,"handle task:"+action); 
        } 
    } 
    @Override 
    public void onDestroy(){ 
        super.onDestroy(); 
    } 
}

三、Android中的线程池

1、线程池的优点:

(1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销

(2)能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。

(3)能够对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能。

Android中的线程池来源于java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。

2、ThreadPoolExecutor

(1)ThreadPoolExecutor是线程池的真正实现,他的构造方法提供了一系列参数来配置线程池,下面是ThreadPoolExecutor的一个常用的构造方法

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue workQueue,ThreadFactory threadFactory)

corePoolSize:线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即是处于闲置状态

maximumPoolSize:线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务会被阻塞

keepAliveTime:非核心线程闲置时的超时时间,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。

unit:用于指定keepAilveTime参数的时间单位,这是一个枚举,常用的有TimeUnit、MILLISECONDS、TimeUnit.SECONDS以及TimeUnit.MINUTES等。

workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。

threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法:Thread newThread(Runnable r)

(2)ThreadPoolExecutor执行任务时大致遵循如下规则:

  • 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
  • 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
  • 如果无法将任务插入到任务队列中,这往往由于任务队列已满,这时候如果线程数量未达到线程池规定的最大值,会立刻启动一个非核心线程来执行任务。
  • 如果线程数量已经达到最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者

(3)ThreadPoolExecutor的参数配置在AsyncTask中有明显的体现,代码如下

private static final int CUP_COUNT = Runtime.getRuntime().availableProcessors();

private static final int CORE_POOL_SIZE = CPU_COUNT + 1;

private static final int MAXIMUM_POOL_SIZE = CPU_COUNT *2 + 1;

private static final int KEEP_ALIVE = 1;

private static final ThreadFactory sThreadFactory = new ThreadFactory(){

private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(Runnable r){

return new Thread(r,"AsyncTask #"+mCount.getAndIncrement());

}

};

private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue(128);

public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);

AsyncTask对THREAD_POOL_EXECUTOR这个线程池进行了配置:

核心线程数等于CPU核心数+1

线程池的最大线程数为CPU核心数的2倍+1

核心线程无超时机制,非核心线程在闲置时的超时时间为1秒

任务队列的容量为128

四:线程池的分类

Android中最常见的四种具有不同功能特性的线程池,他们都直接或间接通过配置ThreadPoolExecutor来实现自己的功能特性,四种线程池分别是:FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor。

1、FixedThreadPool:

Executors的newFixedThreadPool方法来创建。他是一种线程数量固定的线程池,当线程处于空闲状态时,他并不会被回收,除非线程池被关闭了。示例代码

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

2、CachedThreadPool:

Executors的newCachedThreadPool方法来创建。他是一种线程数量不定的线程池,他只有非核心线程,并且最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上相当于最大线程数.

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

3、ScheduledThreadPool:

通过Executors的newScheduledThreadPool来创建。他的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。

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

4、SingleThreadExecutor:

通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,他确保所有任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。代码如下:

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

除了上面系统提供的4类线程池以外,也可以根据实际需要灵活低配置线程池。示例代码:

Runnable command = new Runnable(){ 
    @Override 
    public void run(){ 
        SystemClock.sleep(2000); 
    } 
}; 
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4); 
fixedThreadPool.execute(command);   
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 
cachedThreadPool.execute(command);   
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); 
scheduledThreadPool.schedule(command,2000,TimeUnit.MILLISECONDS); 
scheduledThreadPool.scheduleAtFixedRate(command,10,1000,TimeUnit.MILLISECONDS);   
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); 
singleThreadExecutor.execute(command);