Android多线程的四种方式

当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread。通常一个应用的所有组件都运行在这一个Process中,当然,你可以通过修改四大组件在Manifest.xml中的代码块()中的android:process属性指定其运行在不同的process中。当一个组件在启动的时候,如果该process已经存在了,那么该组件就直接通过这个process被启动起来,并且运行在这个process的UI Thread中。

UI Thread中运行着许多重要的逻辑,如系统事件处理,用户输入事件处理,UI绘制,Service,Alarm等,如下图:

UI Thread包含的逻辑

而我们编写的代码则是穿插在这些逻辑中间,比如对用户触摸事件的检测和响应,对用户输入的处理,自定义View的绘制等。如果我们插入的代码比价耗时,如网络请求或数据库读取,就会阻塞UI线程其他逻辑的执行,从而导致界面卡顿。如果卡顿时间超过5秒,系统就会报ANR错误。所以,如果要执行耗时的操作,我们需要另起线程执行。

在新线程执行完耗时的逻辑后,往往需要将结果反馈给界面,进行UI更新。Android的UI toolkit不是线程安全的,不能在非UI线程进行UI的更新,所有对界面的更新必须在UI线程进行。

Android提供了四种常用的操作多线程的方式,分别是:

1. Handler+Thread

2. AsyncTask

3. ThreadPoolExecutor

4. IntentService

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

Handler+Thread

Android主线程包含一个消息队列(MessageQueue),该消息队列里面可以存入一系列的Message或Runnable对象。通过一个Handler你可以往这个消息队列发送Message或者Runnable对象,并且处理这些对象。每次你新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列,从这时起,这个handler就会开始把Message或Runnable对象传递到消息队列中,并在它们出队列的时候执行它们。

Handler Thread原理图

Handler可以把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,Handler把压入消息队列有两类方式,Post和sendMessage:

Post方式:

Post允许把一个Runnable对象入队到消息队列中。它的方法有:

post(Runnable)/postAtTime(Runnable,long)/postDelayed(Runnable,long)

对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。

handler post用法

sendMessage:

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

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

handler定义

handler sendMessage用法

优缺点

1. Handler用法简单明了,可以将多个异步任务更新UI的代码放在一起,清晰明了

2. 处理单个异步任务代码略显多

适用范围

1. 多个异步任务的更新UI

AsyncTask

AsyncTask是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程。

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

用法举例:

classDownloadTaskextendsAsyncTask{// AsyncTask//后面尖括号内分别是参数(例子里是线程休息时间),进度(publishProgress用到),返回值类型@OverrideprotectedvoidonPreExecute(){//第一个执行方法super.onPreExecute();    }@OverrideprotectedStringdoInBackground(Integer... params){//第二个执行方法,onPreExecute()执行完后执行for(inti=0;i<=100;i++){            publishProgress(i);try{                Thread.sleep(params[0]);            }catch(InterruptedException e) {                e.printStackTrace();            }        }return"执行完毕";    }@OverrideprotectedvoidonProgressUpdate(Integer... progress){//这个函数在doInBackground调用publishProgress时触发,虽然调用时只有一个参数//但是这里取到的是一个数组,所以要用progesss[0]来取值//第n个参数就用progress[n]来取值tv.setText(progress[0]+"%");super.onProgressUpdate(progress);    }@OverrideprotectedvoidonPostExecute(String result){//doInBackground返回时触发,换句话说,就是doInBackground执行完后触发//这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"setTitle(result);super.onPostExecute(result);    }}

优缺点

1. 处理单个异步任务简单,可以获取到异步任务的进度

2. 可以通过cancel方法取消还没执行完的AsyncTask

3. 处理多个异步任务代码显得较多

适用范围

1. 单个异步任务的处理

ThreadPoolExecutor

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

ThreadPoolExecutor

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

1.Executors.newFixedThreadPool()  创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化2.Executors.newCachedThreadPool()  创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,    它可以灵活的添加新的线程,而不会对池的长度作任何限制3.Executors.newScheduledThreadPool()  创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer4.Executors.newSingleThreadExecutor()  创建一个单线程化的executor,它只创建唯一的worker线程来执行任务

适用范围

1. 批处理任务

IntentService

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

特点

1. 一个可以处理异步任务的简单Service

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thr...
    JackMeGo阅读 84,982评论 11 107
  • 第5章 多线程编程 5.1 线程基础 5.1.1 如何创建线程 在java要创建线程,一般有==两种方式==:1)...
    AndroidMaster阅读 1,786评论 0 11
  • 1 概述 在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地...
    Kevin_Junbaozi阅读 670评论 0 2
  • 今天风和日丽的,下了这么多天的雨后,终于等到暖风徐徐阳光明媚有点春天的味道了。单位里组织去bbq,在这样的天气里...
    践行的小伙伴阅读 186评论 0 0
  • 文/浅草沧月 其实我也常对自己说 雨后会有彩虹的 可惜没伞 屋檐下写满无奈的凉
    浅草沧月阅读 200评论 0 1