Android多线程处理方式

Android提供了四种常用的操作多线程的方式,分别是:
    1. Handler+Thread
    2. AsyncTask
    3. ThreadPoolExecutor
    4. IntentService

下面分别对四种方式进行介绍。

一、Handler+Thread

    关于Handler机制简介可以参考这篇文章,Handler可以把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,Handler把压入消息队列有两类方式,Post和sendMessage:

1、post方式:

    在线程中通过handler.post(runnable)方法,可以在runnable的run方法中执行更新UI的操作。Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)/postAtTime(Runnable,long)/postDelayed(Runnable,long)。

handler.post(new Runnable() {
            @Override
            public void run() {
                //ui操作
            }
        });

    查看handler的post方法,可以看到对应的方法调用逻辑,和send发送消息是一致的。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

2、sendMessage

    sendMessage允许把一个包含消息数据的Message对象压入到消息队列中。它的方法有:sendEmptyMessage(int)/sendMessage(Message)/sendMessageAtTime(Message,long)/sendMessageDelayed(Message,long)。

    Handler如果使用sendMessage的方式把消息入队到消息队列中,需要传递一个Message对象,而在Handler中,需要重写handleMessage()方法,用于获取工作线程传递过来的消息,此方法运行在UI线程上。Message是一个final类,所以不可被继承。

二、AsyncTask

    AsyncTask封装了线程池和Handler,是Android的一个轻量级的异步类,它可以在线程池中执行后台操作,然后把执行的进度和结果通过Handler传递给主线程并在主线程里面更新UI。可以方便开发者实现异步操作。AsyncTask是一个抽象的泛型类,提供了Params、Progress、Result三个泛型参数

    public abstract class AsyncTask<Params, Progress, Result>(){}
     Params:需要传入参数的类型
    Progress:后台任务的执行进度的类型

    Result:后台任务返回结果的类型

AsyncTask提供了4个核心的方法,分别是: 

  1. OnPreExecute(), 在主线程中执行,在异步任务之前,此方法被调用一般做一些准备性的工作;

  2. doInBackground(Params…params),在线程池中执行,用于执行异步任务,params表示异步任务的传入参数。在此方法会中调用publishProgress(progress)来更新任务进度,publishProgress()会调用onProgressupdate()更新进度,会返回计算结果给onPostExecute();

  3. onProgressUpdate(Progress…value),在主线程中执行,后台执行任务的进度有变化时被调用;

  4. onPostExecute(Result result), 在主线程中执行,在异步任务执行之后,此方法会被调用,result为后台任务的返回值,即doInBackground()的返回值。

示例:

public class DownloadFilesTask extends AsyncTask<URL, Integer, Long>{
    protected Long doInBackground(URL...url){
        int count = url.length;
        long totalSize = 0;
        for(int i = 0; i< count; i++){
            totalSize += DownloadFile(url[i]);
            publishProgress((int) (i/(float)count)*1000);
            if(isCancelled){
                break;
            }
        }
    }

    protected void onProgressUpdate(Integer...progress){
        setProgressPercent(progress[0]);
    }

    protected void onPostExecute(Long result){
        showDialog("bytes"+result);
    }
}

    AsyncTask通过一个阻塞队列BlockingQuery<Runnable>存储待执行的任务,利用静态线程池THREAD_POOL_EXECUTOR提供一定数量的线程,默认128个。在Android 3.0以前,默认采取的是并行任务执行器,3.0以后改成了默认采用串行任务执行器,通过静态串行任务执行器SERIAL_EXECUTOR控制任务串行执行,循环取出任务交给THREAD_POOL_EXECUTOR中的线程执行,执行完一个,再执行下一个。

private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
              CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
              sPoolWorkQueue, sThreadFactory);

使用AsyncTask的注意事项:

    1、AsyncTask的类必须在主线程里加载,必须在主线程中调用,execute方法必须在UI线程调用;

    2、 不要在程序中直接调用onPreExecute()、onPostExecute()、doInBackground()、和onProgressUpdate();

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

    在面试过程中被问到AsyncTask的缺陷,可以分为两个部分说,在3.0以前,最大支持128个线程的并发,10个任务的等待;在3.0以后,无论有多少任务,都会在其内部单线程执行。

三、ThreadPoolExecutor

    ThreadPoolExecutor提供了一组线程池,可以管理多个线程并行执行。这样一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装,使用起来更加方便。

我们先来看看,创建一个线程池需要哪些参数。

    1、corePoolSize 核心线程数大小。当提交一个任务时,如果当前线程数小于corePoolSize,就会创建一个线程。即使其他有可用的空闲线程。

    2、runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:
  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等上一个元素被移除之后,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
不同的runnableTaskQueue对线程池运行逻辑有很大影响

    3、maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

    4、keepAliveTime 线程执行结束后,保持存活的时间。

    5、ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

    6、RejectedExecutionHandler 线程池队列饱和之后的执行策略,默认是采用AbortPolicy。JDK提供四种实现方式:
  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy :只用调用者所在线程来运行任务
  • DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务
  • DiscardPolicy : 不处理,丢弃掉
    7、TimeUnit: keepalive的时间单位,可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。


Executors提供了四种创建ExecutorService的方法,他们的使用场景如下:

    1. Executors.newFixedThreadPool(int nThreads)

   创建一个固定数量的线程,参数中核心线程和最大线程一样,线程保留时间 0 ,使用 LinkedBlockingQueue 作为任务队列。如果有新的任务提交,但是没有线程可用,这个任务会一直等待直到有可用的线程。线程会一直保持,直到线程池 shutDown。由于线程不会被回收,会一直卡在阻塞,所以没有任务的情况下, FixedThreadPool 占用资源更多。

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

    2. Executors.newCachedThreadPool()

    当有任务提交进来,会优先使用线程池里可用的空闲线程来执行任务,但是如果没有可用的线程会直接创建线程。空闲的线程会保留 60s,之后才会被回收。这些特性决定了,当需要执行很多短时间的任务时,CacheThreadPool 的线程复用率比较高, 会显著的提高性能。而且线程60s后会回收,意味着即使没有任务进来,CacheThreadPool 并不会占用很多资源。

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

    3. Executors.newScheduledThreadPool()

   创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer

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());
    }

    4. Executors.newSingleThreadExecutor()

   创建一个单线程化的executor,它只创建唯一的worker线程来执行任务

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

部分示例,各线程池使用方法差不多:

@Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        poolExecutor = new ThreadPoolExecutor(3, 5,  
                1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(128));  
}  
  
    public void btnClick(View view) {  
        for (int i = 0; i < 30; i++) {  
            final int finalI = i;  
            Runnable runnable = new Runnable() {  
                @Override  
                public void run() {  
                    SystemClock.sleep(2000);  
                    Log.d("wtf", "run: " + finalI);  
                }  
            };  
            poolExecutor.execute(runnable);  
        }  
    }  

四、IntentService

    IntentService继承自Service,是一个经过包装的轻量级的Service,用来接收并处理通过Intent传递的异步请求。客户端通过调用startService(Intent)启动一个IntentService,利用一个work线程依次处理顺序过来的请求,处理完成后自动结束Service。

    一个可以处理异步任务的简单Service。在 IntentService 内有一个工作线程来处理耗时操作,当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,执行完自动结束。可以在onHandleIntent中执行耗时任务。

    通过多次启动IntentService来执行多个耗时任务(在onHandleIntent中执行耗时任务)

Intent intent = new Intent(this,MIntentService.class);
for (int i=0;i<7;i++) {//循环启动任务
     intent.putExtra(MyIntentService.DOWNLOAD_URL,url[i]);
     intent.putExtra(MyIntentService.INDEX_FLAG,i);
     startService(intent);
}

猜你喜欢

转载自blog.csdn.net/white_wt/article/details/80472440