Android多线程编程和线程池

一、前期基础知识储备

1)线程概念

线程在Android中是一个很重要的概念,从用途上来说,线程分为主线程和子 线程,主线程主要处理和界面UI相关的事,而子线程则往往用于执行耗时操作。由于Android的特性,如果在主线程中执行耗时操作那么就会导致程序无法及时响应,因此耗时操作必须放在子线程中去执行。除了主线程之外的线程都是子线程

2)扮演线程的角色有哪些

除了普通线程Thread之外,在Android扮演线程角色的还有很多,比如AsyncTaskIntentService,同时HandlerThread也是一种特殊的线程。尽管后三者的表现形式都有别于传统的线程,但是它们的本质仍然是传统的线程。对于AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlThread来说,它们的底层则直接使用了线程。

不同形式的线程虽然都是线程,但是它们仍然具有不同的特性和使用场景:

  • AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI;
  • HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler;
  • IntentService是一个服务,系统对其进行了封装使其可以更方便地执行后台任务,IntentService内部采用了HandlerThread来执行任务,当任务执行完毕之后,IntentService会自动退出。

3)线程池概念

在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生,并且线程的创建和销毁都会有相应的开销,所以在一个进程中频繁第创建和销毁线程,这显然不是高效的做法。正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁地创建和销毁线程所带来的系统开销。

Android线程池来源于Java,主要是通过Executor来派生特定类型的线程池,不同种类的线程池又具有各自的特性。

二、Android中的线程形态

这里对Android中线程形态做一个全面的介绍,除了传统的Thread之外,还包含了AsyncTask、HandlerThread以及IntentService,这三者的底层实现也是线程,但是它们具有特殊的表现形式,同时在使用上也各有优缺点。

1)AsyncTask

AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以更加方便地执行后台任务以及在主线程中访问UI。

注:AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的后台任务来说,建议使用线程池。

AsyncTask是一个抽象的泛型类,它提供了Params、Progress和Result这三个泛型参数,其中Params表示参数的类型,Progress表示后台任务执行进度的类型,而Result则表示后台任务的返回结果的类型,如果AsyncTask确实不需要传递具体的参数,那么这三个泛型参数可以使用void来代替。类申明如下:

public abstract class AsyncTask<Params, Progress, Result>

AsyncTask提供了4个核心方法,它们的含义如下:

onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作;

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

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

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 onPostExcute(Long result) {
        showDialog("Downloaded" + result + "bytes");
    }
}

在上面的代码中,实现了一个具体的AsyncTask类,这个类主要用于模拟文件的下载过程,它的输入参数类型为URL,后台任务的进程参数为Integer,而后台任务的返回结果为Long类型。当要执行下载任务时可以调用如下:

// execute()方法必须在UI线程中调用
new DownloadFilesTask().execute(url1, url2, url3);

在DownloadFilesTask中,doInBackground用来执行具体的下载任务并通过publishProgress方法来更新下载的进度,同时还需要判断下载任务是否被外界取消了。除了文中前面提及的四个方法之外,AsyncTask还提供了onCancelled()方法,它同样在UI线程中执行,当异步任务被取消时,onCancelled()方法会被调用,这个时候onPostExecute()方法则不会被调用。

2)HandlerThread

HandlerThread继承自Thread,它是一种可以使用Handler的Thread。

public class MainActivity extends AppCompatActivity {
 
    Handler mainHandler,workHandler;
    HandlerThread mHandlerThread;
    TextView text;
    Button button1,button2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text1);
 
        // 创建与主线程关联的Handler
        mainHandler = new Handler();
        /**
          * 步骤1:创建HandlerThread实例对象
          * 传入参数 = 线程名字,作用 = 标记该线程
          */
        mHandlerThread = new HandlerThread("handlerThread");
        
        //步骤2:启动线程         
        mHandlerThread.start();
        /**
         * 步骤3:创建工作线程Handler & 复写handleMessage()
         * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
         * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
         */
        workHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg)
            {
                //通过msg来进行识别不同的操作 类似广播的过滤器action 可扩展性非常强大
                switch(msg.what){
                    case 1:
                        try {
                            //延时操作
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 通过主线程Handler.post方法进行在主线程的UI更新操作
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("第一次执行");
                            }
                        });
                        break;
                    case 2:
                        try {
                            // 直接在handleMessage内部处理耗时操作
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 调用handler的post()方法处理UI操作
                        mainHandler.post(new Runnable() {
                            @Override
                            public void run () {
                                text.setText("第二次执行");
                            }
                        });
                        break;
                    default:
                        break;
                }
            }
        };
        /**
         * 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
         * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
         */
        button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = Message.obtain();
                msg.what = 1; //消息的标识
                msg.obj = "A"; // 消息的存放
                // 通过Handler发送消息到其绑定的消息队列
                workHandler.sendMessage(msg);
            }
        });
        button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = Message.obtain();
                msg.what = 2; 
                msg.obj = "B"; 
                workHandler.sendMessage(msg);
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandlerThread.quit(); // 退出消息循环
        workHandler.removeCallbacks(null); // 防止Handler内存泄露 清空消息队列
    }
}

从HandlerThread的实现来看,它和普通的Thread有显著的不同。普通的Thread主要用于在run()方法中执行一个耗时任务,而HandlerThread获取了子线程中Looper传入Handler的构造方法中,相当于在子线程内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务,如果该Handler创建在主线程中,那么可以直接在内部使用该Handler切换回主线程中执行UI操作。

由于HandlerThread的run()方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过它的quit()方法或者quitSafely方法来终止线程执行。HandlerThread是一个很有用的类,它在Android中一个具体的使用场景是IntentService

3)IntentService

IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用。IntentService可用于执行后台耗时任务,当任务执行完之后,会自动停止,同时由于IntentService是服务的原因,这导致它的优先级比单纯的线程要高很多,所以IntentService适合执行一些高优先级的后台任务,因为它的优先级高不容易被系统杀死

IntentService内部的mServiceHandler收到消息之后,会将Intent对象传递给onHandleInten方法去处理。注意这个Intent对象的内容和外界的startService(intent)中的intent的内容是完全一致的,通过这个Intent对象即可解析出外界启动IntentService时所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHandleIntent方法中就可以对不同的后台任务做出处理了。

当onHandleIntent方法执行结束之后,IntentService会通过stopSelf(int startId)方法来尝试停止服务。

在实现上,IntentService封装了HandlerThread和Handler。Handler中的Looper是顺序处理消息的,这就意味着IntentService也是顺序执行后台任务的,当有多个后台任务同时存在时,这些后台任务会按照外界发起的顺序排队执行。

示例说明:

①创建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");
        Log.d(TAG, "receive task :" +  action);
        SystemClock.sleep(3000);
        if ("com.ryg.action.TASK1".equals(action)) {
            Log.d(TAG, "handle task: " + action);
        }
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "service destroyed.");
        super.onDestroy();
    }
}

②Activity内部开启服务;

private void runIntentService() {
        Intent service = new Intent(this, LocalIntentService.class);
        service.putExtra("task_action", "com.ryg.action.TASK1");
        startService(service);
        service.putExtra("task_action", "com.ryg.action.TASK2");
        startService(service);
        service.putExtra("task_action", "com.ryg.action.TASK3");
        startService(service);
    }

顺序开启三次服务,查看执行结果可知三个后台任务是排队执行的。

三、Android中的线程池

1)线程池的优点

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

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

③能够有效对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能;

2)ThreadPoolExecutor

Android中的线程池概念来自于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池,从线程池的特性上来说,Android的线程池主要分为四类。以下是ThreadPoolExecutor比较常用的构造方法:

public ThreadPoolExecutor (int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory)

构造方法参数解析

  • corePoolSize,线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活;
  • maximumPoolSize,线程池所能容纳的最大线程数,当活动线程数达到这个数值之后,后续的新任务将会被阻塞;
  • keepAliveTime,非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收;当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程;
  • unit,用于指定keepAliveTime参数的时间单位,是一个枚举,常用的有TimeUnit,MILLISECONDS(毫秒),TimeUnit.SECOND(秒)以及TImeUnit.MINUTES(分钟)等;
  • workQueue,线程池中的任务队列,通过线程池的execute()方法提交的Runnable对象会存储在这个参数中;
  • threadFactory,线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。

ThreadPoolExecutor执行任务时遵循的规则如下

  • ①如果线程池中的线程数量未达到核心线程的数量,那么就会直接启动一个核心线程来执行任务;
  • ②如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行;
  • ③如果在步骤②中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大数量,那么会立刻启动一个非核心线程来执行任务;
  • ④如果步骤③中线程数量已经达到线程池规定的最大数量,那么就拒绝执行此任务。

线程池的处理流程和原理,图示如下:

3)线程池的分类

Android中有四种具有不同功能特性的线程池,它们都直接或间接地通过配置ThreadPoolExecutor来实现自己的功能特性:

①FixedThreadPool

public static ExecutorService newFixThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads, nThreads, 
                                  0L, TimeUnit.MILLISECONDS, 
                                  new LinkedBlockingQueue<Runnable>());
}
//使用
Executors.newFixThreadPool(5).execute(r);

线程数量固定的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了。从参数我们可以看出,FixedThreadPool只有核心线程并且核心线程没有超时机制,另外任务队列大小也是没有限制的。由于只有核心线程且不会被回收,所以可以很快响应外界的请求。

②CachedThreadPool

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

CachedThreadPool只有非核心线程,最大线程数非常大,所有线程都活动时,会为新任务创建新线程,否则利用空闲线程(60s空闲时间,过了就会被回收,所以线程池中有0个线程的可能)处理任务。任务队列SynchronousQueue相当于一个空集合,导致任何任务都会被立即执行适合执行大量耗时较少的任务

③ScheduleThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize){
    super(corePoolSize, Integer.MAX_VALUE,
          0, NANOSECONDS, 
          new DelayedQueue ());
}

//使用,延迟1秒执行,每隔2秒执行一次Runnable r
Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);

从配置参数可以看出,ScheduleThreadPool核心线程数固定,非核心线程数没有限制,当非核心线程闲置时会立即被回收

ScheduleThreadPool这类线程主要用于执行定时任务和具有固定周期的重复任务。

④SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor (){
    return new FinalizableDelegatedExecutorService 
       ( new ThreadPoolExecutor (1, 1, 
                                 0, TimeUnit. MILLISECONDS, 
                                 new LinkedBlockingQueue<Runnable>()) );
}
//使用
Executors.newSingleThreadExecutor ().execute(r);

从SingleThreadExecutor配置参数可以看出,SingleThreadExecutor只有一个核心线程,确保所有任务都在同一线程中按顺序完成。因此不需要处理线程同步的问题。

下面的代码演示了系统预置的四种线程池的典型用法:

private void runThreadPool() {
        Runnable command = new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);
            }
        };
        // fixedThreadPool
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        fixedThreadPool.execute(command);
        // cachedThreadPool 
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(command);

        // scheduledThreadPool 
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
        // 2000ms后执行command
        scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
        // 延迟10ms后,每隔1000ms执行一次command
        scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS);

        // singleThreadExecutor 
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(command);
    }

 

找博文资料的时候,意外发现的好文,推荐一下:

好文插眼:《Android 开发社招面经,历时两月斩获BAT+头条四个公司 Offer》

张拭心 《走心的中级安卓工程师跳槽经验分享》 

推荐里面的技术内容:安卓跳槽需要复习的知识  

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值