2013年8月31日星期六

【转】大型网站架构系列之一-不得不考虑的问题

前言:这两天机器坏了,正在送修中,写个系列的大型网站架构的文章,希望对有志在互联网做出一番事业的站长朋友们一些帮助。
 
注意:这里的大型网站架构只包括高互动性高交互性的数据型大型网站,基于大家众所周知的原因,我们就不谈新闻类和一些依靠HTML静态化就可以实现的架构了,我们以高负载高数据交换高数据流动性的网站为例,比如海内,开心网等类似的web2.0系列架构。我们这里不讨论是PHP还是JSP或者.NET环境,我们从架构的方面去看问题,实现语言方面并不是问题,语言的优势在于实现而不是好坏,不论你选择任何语言,架构都是必须要面对的。
 
文入正题:
首先讨论一下大型网站需要注意和考虑的问题
A.     海量数据的处理。众所周知,对于一些相对小的站点来说,数据量并不是很大,select和update就可以解决我们面对的问题,本身负载量不是很大,最多再加几个索引就可以搞定。对于大型网站,每天的数据量可能就上百万,如果一个设计不好的多对多关系,在前期是没有任何问题的,但是随着用户的增长,数据量会是几何级的增长的。在这个时候我们对于一个表的select和update的时候(还不说多表联合查询)的成本的非常高的。
B.     数据并发的处理在一些时候,2.0的CTO都有个尚方宝剑,就是缓存。对于缓存,在高并发高处理的时候也是个大问题。在整个应用程序下,缓存是全局共享的,然而在我们进行修改的时候就,如果两个或者多个请求同时对缓存有更新的要求的情况下,应用程序会直接的死掉。这个时候,就需要一个好的数据并发处理策略以及缓存策略。
另外,就是数据库的死锁问题,也许平时我们感觉不到,死锁在高并发的情况下的出现的概率是非常高的,磁盘缓存就是一个大问题。
C.     文件存贮的问题对于一些支持文件上传的2.0的站点,在庆幸硬盘容量越来越大的时候我们更多的应该考虑的是文件应该如何被存储并且被有效的索引。常见的方案是对文件按照日期和类型进行存贮。但是当文件量是海量的数据的情况下,如果一块硬盘存贮了500个G的琐碎文件,那么维护的时候和使用的时候磁盘的Io就是一个巨大的问题,哪怕你的带宽足够,但是你的磁盘也未必响应过来。如果这个时候还涉及上传,磁盘很容易就over了。
也许用raid和专用存贮服务器能解决眼下的问题,但是还有个问题就是各地的访问问题,也许我们的服务器在北京,可能在云南或者新疆的访问速度如何解决?如果做分布式,那么我们的文件索引以及架构该如何规划。
所以我们不得不承认,文件存贮是个很不容易的问题
D.    数据关系的处理我们可以很容易的规划出一个符合第三范式的数据库,里面布满了多对多关系,还能用GUID来替换INDENTIFY COLUMN 但是,多对多关系充斥的2.0时代,第三范式是第一个应该被抛弃的。必须有效的把多表联合查询降到最低。
E.     数据索引的问题
众所周知,索引是提高数据库效率查询的最方面最廉价最容易实现的方案。但是,在高UPDATE的情况下,update和delete付出的成本会高的无法想想,笔者遇到过一个情况,在更新一个聚焦索引的时候需要10分钟来完成,那么对于站点来说,这些基本上是不可忍受的。
索引和更新是一对天生的冤家,问题A,D,E这些是我们在做架构的时候不得不考虑的问题,并且也可能是花费时间最多的问题,
F.     分布式处理
对于2.0网站由于其高互动性,CDN实现的效果基本上为0,内容是实时更新的,我们常规的处理。为了保证各地的访问速度,我们就需要面对一个绝大的问题,就是如何有效的实现数据同步和更新,实现各地服务器的实时通讯有是一个不得不需要考虑的问题。
G.    Ajax的利弊分析
成也AJAX,败也AJAX,AJAX成为了主流趋势,突然发现基于XMLHTTP的post和get是如此的容易。客户端get或者post到服务器数据,服务器接到数据请求之后返回来,这是一个很正常的AJAX请求。但是在AJAX处理的时候,如果我们使用一个抓包工具的话,对数据返回和处理是一目了然。对于一些计算量大的AJAX请求的话,我们可以构造一个发包机,很容易就可以把一个webserver干掉。
H.     数据安全性的分析
对于HTTP协议来说,数据包都是明文传输的,也许我们可以说我们可以用加密啊,但是对于G问题来说的话,加密的过程就可能是明文了(比如我们知道的 QQ,可以很容易的判断他的加密,并有效的写一个跟他一样的加密和解密方法出来的)。当你站点流量不是很大的时候没有人会在乎你,但是当你流量上来之后,那么所谓的外挂,所谓的群发就会接踵而来(从qq一开始的群发可见端倪)。也许我们可以很的意的说,我们可以采用更高级别的判断甚至HTTPS来实现,注意,当你做这些处理的时候付出的将是海量的database,io以及CPU的成本。对于一些群发,基本上是不可能的。笔者已经可以实现对于百度空间和 qq空间的群发了。大家愿意试试,实际上并不是很难。
I.      数据同步和集群的处理的问题
当我们的一台databaseserver不堪重负的时候,这个时候我们就需要做基于数据库的负载和集群了。而这个时候可能是最让人困扰的的问题了,数据基于网络传输根据数据库的设计的不同,数据延迟是很可怕的问题,也是不可避免的问题,这样的话,我们就需要通过另外的手段来保证在这延迟的几秒或者更长的几分钟时间内,实现有效的交互。比如数据散列,分割,内容处理等等问题
K.数据共享的渠道以及OPENAPI趋势
   Openapi已经成为一个不可避免的趋势,从google,facebook,myspace到海内校内,都在考虑这个问题,它可以更有效的留住用户并激发用户的更多的兴趣以及让更多的人帮助你做最有效的开发。这个时候一个有效的数据共享平台,数据开放平台就成为必不可少的途径了,而在开放的接口的情况保证数据的安全性和性能,又是一个我们必须要认真思考的问题了。
 
当然还有更多需要考虑的问题,我这里就写一个最需要考虑的问题,欢迎补充。
 
下一篇文章将针对问题A,提出具体的解决方案和思路

互联网系统架构的演进

多终端接入、开放平台给互联网带来了前所未有的用户量级和访问规模,SNS网站产生了海量的UGC(用户产生内容),而且这些内容依托关 系链扩散速度之快、传播范围之广是传统网站难以想象的,海量数据的计算存储也一直是近年互联网领域的热点。本文将从发展演进的层面探讨互联网的系统架构。
天下武功唯快不破
网站初期的架构一般采用“短平快”的架构思路,架构以简单清晰、容易开发为第一衡量指标。
互联网架构选型首先包括开发语言的选择,目前PHP、Java是主力语言。开发语言的选择一般从团队人员的知识储备、社区活跃度、商业应用的成熟度、招聘人才的人力成本等方面考量。
选择语言之后,一般会选择该语言的流行框架辅助研发,例如Java的SSH、Python的Django等。但这些框架并不是通常意义上的架构,架构一般可 分为物理架构、运行架构、逻辑架构、开发架构、数据架构等多个维度,框架往往只是代码架构的一部分。代码架构是指代码的组织形式、规范、设计模式等,框架 其实是常用设计模式的软件化。例如Struts是MVC模式的实现,Hibernate、iBATIS是ORM模式的实现。
在架构视图中,早期关注的主要是开发视图和数据视图,一般数据存储采用DB,初期数据的关注点主要是安全和备份,MySQL的Master-Slave模式可以满足该需求。
鸡蛋不要放在一个篮子里
采用“短平快”三板斧将网站开发出来之后,急需解决的是网站的可用性问题。可用性最基本的要求是不能有单点,对程序节点而言,前端可采用LVS、HAProxy、Nginx等负载均衡/反向代理设备。
DB的可用性就复杂了很多,数据库天然是有状态的,状态就是其中的数据,新增一个数据节点一般伴随着大量的数据复制和迁移。对金融行业而言,昂贵的商用存储是 解决之道,“IBM+Oracle+EMC”是该类系统的标配。互联网企业则一般采用较为廉价的方案,例如开源的DRDB+Heartbeat技术组合可 以在MySQL主库宕机时实现备机接管,接管时间可以控制在30秒内。
程序节点其实也可能存在状态,例如Web服务中常用的Session 就是保存在容器中的状态,这种状态保持要求所有相同用户的请求都在同一台机器上处理,无状态的程序节点才能水平扩展。无状态一般有两种设计思路,还以 Session为例,一种思路是把用户的状态保存在客户端Cookie,每次请求都把客户端的用户信息带到服务器端,淘宝的分布式Session就是该思 路的一种实现;另一种思路是状态保留在另外一个服务中,例如有些公司将Session放在分布式缓存中。
性能是生命线
去除单点之后的系统就可以水平扩展,架构如图1所示。但随着网站的推广运营,系统的规模开始扩大,此时可能会出现服务访问缓慢,甚至不可用的状况,如何提升系统性能就成了架构师的当务之急。
 
图1 去除单点之后进行水平扩展
存储架构和性能
互联网系统所有的性能瓶颈中,数据存储和访问速度往往是最重要也是最难解决的,选择合适的存储是系统的关键。存储的选择一般需要从多个方面考量,如成本、内容、用途和模型。目前主流的存储介质包括硬盘和内存两种。
对机械硬盘来说,1秒可以完成150次左右的随机I/O。而结合设计优良的Hash算法,内存查找可以每秒执行40万次左右。硬盘的随机读写能力决定了其读 写的最差性能,但操作系统在实现文件系统时会把最近读写过的数据缓存在内存中。由于磁盘访问和内存访问性能量级的差距,从操作系统的Cache命中率就可 以简单计算文件存储的性能,如果内存命中率可以达到80%,系统的I/O能力相较完全随机I/O将有5倍提升。
对于数据层服务器,大内存已成为标配(一般为100GB左右),如果DB中存储200GB的数据,根据8/2原则,Cache命中率应为87.5%,因此对MySQL而言,一般读写可以达到每秒1千次以上。
对于读写频率都很高、且可容忍数据丢失的场景,可以采用内存作为数据存储的介质。可靠的内存存储需要每次操作都记录Biglog,即使数据丢失也可以恢复,同时内存中的数据一般定期持久化到硬盘。
从功能角度考量,还可以分为持久化存储和Cache。持久化存储也可称为可靠存储,Cache是为了提升系统性能,在可靠存储的基础上建立的访问性能更加高效的数据读取节点,通常是内存存储,其架构一般如图2所示。
 
图2 持久化存储和Cache
存储的数据模型一般分为结构化存储和NoSQL存储。结构化存储以各种传统DB为代表,NoSQL技术的代表系统则有HBase、Memcached、 Redis等。各种NoSQL系统虽然特性各异,但相对传统DB而言,由于结构化信息的缺失,往往不能做各种关联查询,适用场景更多是主键查询,而且一般 是写少读多的系统。
对于大型互联网公司,为了某些场景下的性能优化,也会定制个性化的文件系统,例如为了适应大文件存储的场景,Google开发了GFS;为了更快读取海量商品的描述图片,TFS在阿里诞生。
虽然各类存储快速涌现,但DB作为结构化数据的传统存储设备,依然在架构中处于非常重要的地位。由于随机I/O的瓶颈,DB的性能天花板十分明显。在大型系 统中通常需要分库操作,分库一般有两个维度——水平切分和垂直切分。水平切分一般根据主键规则或某种规则将同类数据切分到不同的单元表中,原则是数据切分均匀,尤其是热点数据分布均匀。
垂直切分是把大表中的字段拆分到多张表。垂直切分一般按照数据访问频率的不同。逻辑关系的差别进行切分,例如将大字段、kv字段、计数等高频访问字段单独剥离存储都是常见的垂直切分方案。
除了切库之外, MySQL的分表也会有效减少单表大小,使数据变得更简单,甚至可以做到不下线变更,单表索引规模的下降也会带来性能的提升。
分库分表作为DB架构中重要的一环,使DB更加稳健,但它给业务代码带来了额外的复杂性,最好通过中间件来屏蔽DB的底层分布,对业务透明。
作为高性能网站必不可少的组件,Cache在各种主流架构中也起着重要的作用。
从部署模式上看,它可分为本地Cache和分布式Cache。本地Cache是指在应用进程中的Cache,通常的数据结构是一个MAP,其优点是结构简 单,效率较分布式Cache更高,缺点是一般应用程序服务器的内存有限,导致本地Cache容量受到局限,而且数据冗余度较高,每个应用服务器都需要一份 数据,更新比较烦琐,一般采用超时删除机制。
分布式Cache的容量较大,方便扩容和更新,其数据分布可采用一致性Hash算法,减少节点变化带来的数据迁移。
引入Cache不可避免的问题是服务器的宕机处理。Cache通常是一个集群,数据分布在多个节点,如果挂掉一个节点,只会影响部分数据,而且对于可靠性要求较高的系统,每个节点都可以有备份。
作为可靠存储的数据备份,Cache在架构设计上往往承担大部分读访问需求,其命中率尤为重要。Cache不命中有两种情况,一是数据在Cache中不存 在,二是在持久化存储中也不存在。对于后者的频繁访问会导致请求直接压在DB上,在设计时应尽量避免,可以通过维护Bitmap对持久化存储中没有的数据 进行拦截,直接返回,也可以简单地将这些数据对应空对象放进Cache。
Cache的存储一般是将索引和数据分离,对于索引数据可以全量缓存,对于体量较大的数据一般采用部分缓存的方式。
Cache的使用场景有一定的局限,对于较为静态的数据才有意义,这个临界值一般是5分钟。由于当前存储技术的进步,Cache也可以用其他高性能的存储介质代替,例如SSD的引入使得硬盘的随机读写能力提升数十倍,也会使得Cache的重要性有所下降。
程序架构和性能
对一般的系统而言,程序逻辑的主要作用是调用各种数据访问接口,该操作通常需要等待,所以除搜索等少数系统外,程序逻辑一般是非CPU密集型。该类系统中“线程”是稀缺资源,线程数和接口耗时构成了系统的QPS能力。
大型互联网系统的QPS可能为几万甚至峰值达到几十万,此时增加机器可以解决问题,但这些机器的利用率其实很低,因为大部分时间是在等待,此时引入异步变得非常重要,异步在同样的时间可以处理更多工作,拥有更好的性能。 
 
图3 同步调用和异步调用
利用Nio的多路复用方式可方便地实现异步系统,当然也可用协程令代码更加清晰。业界流行的SEDA技术可将一次请求拆分为粒度更细的Actor,每个 Actor使用独立队列,前一个的输出是后一个的输入。SEDA通过该方式将请求中等待和非等待的环节分离,提升了系统的吞吐量,这种方式在小米等互联网 公司有较多应用。
除了异步之外,并行对系统也很重要,它可以有效缩短请求的响应时间。批量接口也可以有效减少系统调用次数,使得系统线程消耗更少,从而提升系统吞吐量。
对线程而言,还有一个重要的参数是超时时间。响应快的服务,超时时间可以长一些,对于响应慢的服务,超时时间可以短一些,尽快失败是保护自己的有效手段。
网络架构和性能
大型网站的网络接入一般是“DNS+负载均衡层+CDN”这种模式。对于大型互联网公司,往往有多个IDC提供对外服务,中国互联网的南北不互通使得解决不 同地域不同运营商的接入速度问题成了难题,该问题的解决一般需要公司自己开发DNS服务器,结合IP测速平台,引流用户请求到访问速度最快的节点。
大系统小做
业务逻辑复杂多变,如何保证程序逻辑的代码稳定是架构师需要解决的问题,良好的模块划分和扩展性强的接口设计都是解决这个问题的利器。
模块是和领域模型相关的一个概念,其往往指系统中高内聚的一个数据访问单元。例如对电商系统而言,最大的两个领域模型分别是商品信息和交易信息,每个领域模 型对应一系列数据,商品会有商品的基本信息、类目信息等,交易会包括交易的订单,这些“领域模型+数据+业务方法”就构成了一个个的模块,高度内聚的模块 是数据的访问的入口。例如交易时也需要去获取商品信息,但一般不会被允许直接调用商品模块的数据表,而是通过商品模块提供的接口进行访问,这样做有下面一 些优点。
  • 接口和数据分离,底层数据结构的变化不会影响到外围系统。
  • 数据直接暴露给其他系统,增加了系统的不稳定性。
  • 接口的收敛减少了重复开发,提高了系统可用性。
对较小规模的应用,模块可部署在一起,但对大型系统而言,模块一般是单独部署,通过RPC交互,这样可以减少彼此之间的系统层面影响。例如Amazon就倾向将所有服务器程序暴露为接口。
分布式系统增加了系统交互的复杂性,也为系统引入了更多潜在的失败环节,但分布式系统可以带来的好处更明显。
  • 有利于系统分级,针对不同服务提供不同的可用性。
  • 大规模开发有了可能,每个模块可以单独开发和部署。
  • 系统可重用性加强,避免重复制造轮子。
  • 服务治理更加简单,一般RPC天然提供容灾,可以自动发现新增节点和剔除问题节点。
逻辑层的接口设计如何做到稳定呢?首先接口的设计不应该是产品驱动的,而应该由数据驱动,尽量考虑接口以后的发展,接口的参数尽量是对象,参数不要采用boolean这种难以扩展的数据类型。
逻辑层的接口设计,一般有粗粒度和细粒度两种模式。粗粒度接口的优点是交互少,一次调用基本可以满足需求,缺点是业务逻辑较多,所以不稳定性增加,这里的不稳定性不仅是系统稳定性,还包括业务逻辑的稳定性。细粒度接口交互较多,但更加有利于重用性。
细粒度接口多次交互是否会带来明显的性能损耗呢?目前服务器1秒可以执行近万次TCP连接,网络传输对于千兆网卡而言,一般也不会造成瓶颈,尤其RPC框架般都会保持长连接。因此,通常情况下我们可以忽略RPC调用带来的性能损耗。
逻辑层接口尽量采用大系统小做的原则,该原则是腾讯架构中重要的价值观。一个接口不做太多事情,只做关键路径,非关键逻辑可以用消息队列或事件通知等方式剥离出来,使得关键路径更加稳定。这也体现了服务分级的思想,把最大的精力花在最重要的接口上。
除了按重要性划分服务之外,也可以按接入方划分,避免不同终端的Bug影响。还可以按快慢划分,例如为上传文件等功能提供单独的服务,避免长时间占用线程,影响系统稳定性。
模块化是程序的水平切分,有时也会采用垂直切分,这种架构有以下好处。
  • 系统规模更方便水平扩展,数据处理能力会显著增加。例如某个模块的容量不足,可以单独扩容该模块。
  • 减少系统耦合,提升稳定性。例如将多变的Web层和稳定的数据层进行拆分,可以避免因为页面频繁发布导致的系统故障;将无线的接入层和Web接入层分离可以减少因为一个接入方引起的全局问题。
开放势不可挡
系统发展往往会带来平台化需求,例如微博的大多数服务除了满足PC访问,还要满足移动端及内外部平台,此时系统通常会抽象出一个接入层。
接入层设计
接入层一般分为:通信、协议和路由模块。
常用的通信方式有TCP、UDP和HTTP等,对开放平台等以外围接入为主的系统而言,HTTP因其简单方便是最合适的通信方式,而内部系统接入出于性能考量,可以直接用TCP或者UDP。
目前的主流协议有JSON、XML、Hessian等,对外部调用一般采用XML或者JSON,内部系统可以采用Hessian或其他自定义的二进制协议。
路由层是根据用户的信息将请求转发到后端服务层。LVS可看作是路由层,根据IP协议将不同来源的请求转发到不同IP Server,Nginx等具备反向代理的服务器也可以看作路由,根据不同URL转发到不同后端。我们经常会自定义路由层,通过用户调用的不同方法转发到 不同Server,或者根据用户的特征,将用户路由到我们的灰度测试机。
云平台概念
具有了平台化的接入功能,系统可以方便地接入内部或者外部系统,此时就具有了“云”的特征,对各种公有云或私有云来说,系统的隔离和自动扩容都十分重要,虚拟化等技术在该类系统中有了充分应用,例如阿里云等云平台都大量使用了虚拟化。
稳定压倒一切
代码稳定性
稳定性是系统架构中一以贯之的内容,可以从图4中理解它的含义。
正常情况下,图4左边系统的性能明显优于右边,但从架构角度考虑,右图要好于左图,因为突起的毛刺使得系统的容量骤降,很容易引发雪崩。性能考量不仅是系统的最优性能或者平均性能,最差性能往往也是系统出现问题的原因。
 
图4 两种稳定性对比
容灾
除了特别小型的系统,没有100%可用的系统。一般需要根据系统的情况制定合适的目标,该目标最通用的衡量维度是系统可用率。
系统可用率是可以提供服务的时间与总时间的比率,常用的系统可用率如表1所示。
 
而对于灾难,我们有下面几个环节可以介入。
  • 预防:容量估算和接入方限流是常用的手段,以应对宕机或者突发流量。
  • 发现:主要依赖监控和各种工具,可以分为系统、接口、公共组件、业务等方面。
  • 解决:应对灾难需要我们事先做足功课,例如对外部的调用我们都有降级开关可以随时关闭;针对系统内部某个接口导致整个系统失去响应的场景,可以限制每个接口的并发量。
系统容量的冗余和可水平扩展也是容灾的必备要求,无状态的系统对于系统扩容更友好。
作者杨光辉,淘宝北京研发中心技术专家,花名三玄。曾在互动百科、腾讯科技等公司任职,对服务器端架构和开发等有一定研究。

2013年8月29日星期四

Android中后台线程如何与UI线程交互

我想关于这个话题已经有很多前辈讨论过了。今天算是一次学习总结吧。
在android的设计思想中,为了确保用户顺滑的操作体验。一些耗时的任务不能够在UI线程中运行,像访问网络就属于这类任务。因此我们必须要重新开启一个后台线程运行这些任务。然而,往往这些任务最终又会直接或者间接的需要访问和控制UI控件。例如访问网络获取数据,然后需要将这些数据处理显示出来。就出现了上面所说的情况。原本这是在正常不过的现象了,但是android规定除了UI线程外,其他线程都不可以对那些UI控件访问和操控。为了解决这个问题,于是就引出了我们今天的话题。Android中后台线程如何与UI线程交互。

据我所知android提供了以下几种方法,用于实现后台线程与UI线程的交互。
1、handler
2、Activity.runOnUIThread(Runnable)
3、View.Post(Runnable)
4、View.PostDelayed(Runnabe,long)
5、AsyncTask

方法一:handler
handler是android中专门用来在线程之间传递信息类的工具。
要讲明handler的用法非常简单,但是我在这里会少许深入的讲一下handler的运行机制。
为了能够让handler在线程间传递消息,我们还需要用到几个类。他们是looper,messageQueue,message。
这里说的looper可不是前段时间的好莱坞大片环形使者,他的主要功能是为特定单一线程运行一个消息环。一个线程对应一个looper。同样一个looper对应一个线程。这就是所谓的特定单一。一般情况下,在一个线程创建时他本身是不会生产他特定单一的looper的(主线程是个特例)。因此我们需要手动的把一个looper与线程相关联。其方法只需在需要关联的looper的线程中调用Looper.prepare。之后我们再调用Looper.loop启动looper。
说了这么多looper的事情,到底这个looper有什么用哪。其实之前我们已经说到了,他是为线程运行一个消息环。具体的说,在我们将特定单一looper与线程关联的时候,looper会同时生产一个messageQueue。他是一个消息队列,looper会不停的从messageQuee中取出消息,也就是message。然后线程就会根据message中的内容进行相应的操作。
那么messageQueue中的message是从哪里来的哪?那就要提到handler了。在我们创建handler的时候,我们需要与特定的looper绑定。这样通过handler我们就可以把message传递给特定的looper,继而传递给特定的线程。在这里,looper和handler并非一一对应的。一个looper可以对应多个handler,而一个handler只能对应一个looper(突然想起了一夫多妻制,呵呵)。这里补充一下,handler和looper的绑定,是在构建handler的时候实现的,具体查询handler的构造函数。
在我们创建handler并与相应looper绑定之后,我们就可以传递message了。我们只需要调用handler的sendMessage函数,将message作为参数传递给相应线程。之后这个message就会被塞进looper的messageQueue。然后再被looper取出来交给线程处理。
这里要补充说一下message,虽然我们可以自己创建一个新的message,但是更加推荐的是调用handler的obtainMessage方法来获取一个message。这个方法的作用是从系统的消息池中取出一个message,这样就可以避免message创建和销毁带来的资源浪费了(这也就是算得上重复利用的绿色之举了吧)。
突然发现有一点很重要的地方没有讲到,那就是线程从looper收到message之后他是如何做出响应的嘞。其实原来线程所需要做出何种响应需要我们在我们自定义的handler类中的handleMessage重构方法中编写。之后才是之前说的创建handler并绑定looper。
好吧说的可能哟点乱,总结一下利用handler传递信息的方法。
假设A线程要传递信息给B线程,我们需要做的就是
1、在B线程中调用Looper.prepare和Looper.loop。(主线程不需要)
2、 编写Handler类,重写其中的handleMessage方法。
3、创建Handler类的实例,并绑定looper
4、调用handler的sentMessage方法发送消息。
到这里,我们想handler的运行机制我应该是阐述的差不多了吧,最后再附上一段代码,供大家参考。
复制代码
 1 public class MyHandlerActivity extends Activity {
 2      TextView textView;
 3      MyHandler myHandler;
 4  
 5      protected void onCreate(Bundle savedInstanceState) {
 6          super.onCreate(savedInstanceState);
 7          setContentView(R.layout.handlertest);
 8  
 9          //实现创建handler并与looper绑定。这里没有涉及looper与
            //线程的关联是因为主线程在创建之初就已有looper
10          myHandler=MyHandler(MyHandlerActivitythis.getMainLooper());
11          textView = (textView) findViewById(R.id.textView);
12         
13          MyThread m = new MyThread();
14          new Thread(m).start();
15      }
16  
17  
18      class MyHandler extends Handler {
19          public MyHandler() {
20          }
21  
22          public MyHandler(Looper L) {
23              super(L);
24          }
25  
26          // 必须重写这个方法,用于处理message
27          @Override
28          public void handleMessage(Message msg) {
29              // 这里用于更新UI
30              Bundle b = msg.getData();
31              String color = b.getString("color");
32              MyHandlerActivity.this.textView.setText(color);
33          }
34      }
35  
36      class MyThread implements Runnable {
37          public void run() {
38              //从消息池中取出一个message
39              Message msg = myHandler.obtainMessage();
40              //Bundle是message中的数据
41              Bundle b = new Bundle();
42              b.putString("color", "我的");
43              msg.setData(b);
44              //传递数据
45              myHandler.sendMessage(msg); // 向Handler发送消息,更新UI
46          }
47      }
复制代码

参考资料:
http://developer.android.com/reference/android/os/Handler.html
http://developer.android.com/reference/android/os/Looper.html
http://developer.android.com/reference/android/os/Message.html
http://www.cnblogs.com/dawei/archive/2011/04/09/2010259.html
http://rxwen.blogspot.com/2010/08/looper-and-handler-in-android.html
http://www.cnblogs.com/android007/archive/2012/05/10/2494766.html

方法二:Activity.runOnUIThread(Runnable)
 这个方法相当简单,我们要做的只是以下几步
1、编写后台线程,这回你可以直接调用UI控件
2、创建后台线程的实例
3、调用UI线程对应的Activity的runOnUIThread方法,将后台线程实例作为参数传入其中。
注意:无需调用后台线程的start方法
方法三:View.Post(Runnable)
 该方法和方法二基本相同,只是在后台线程中能操控的UI控件被限制了,只能是指定的UI控件View。方法如下
1、编写后台线程,这回你可以直接调用UI控件,但是该UI控件只能是View
2、创建后台线程的实例
3、调用UI控件View的post方法,将后台线程实例作为参数传入其中。
方法四:View.PostDelayed(Runnabe,long)
该方法是方法三的补充,long参数用于制定多少时间后运行后台进程 
方法五:AsyncTask
AsyncTask是一个专门用来处理后台进程与UI线程的工具。通过AsyncTask,我们可以非常方便的进行后台线程和UI线程之间的交流。
那么AsyncTask是如何工作的哪。
AsyncTask拥有3个重要参数
1、Params 
2、Progress
3、Result
Params是后台线程所需的参数。在后台线程进行作业的时候,他需要外界为其提供必要的参数,就好像是一个用于下载图片的后台进程,他需要的参数就是图片的下载地址。
Progress是后台线程处理作业的进度。依旧上面的例子说,就是下载图片这个任务完成了多少,是20%还是60%。这个数字是由Progress提供。
Result是后台线程运行的结果,也就是需要提交给UI线程的信息。按照上面的例子来说,就是下载完成的图片。
AsyncTask还拥有4个重要的回调方法。
1、onPreExecute
2、doInBackground
3、onProgressUpdate
4、onPostExecute
onPreExecute运行在UI线程,主要目的是为后台线程的运行做准备。当他运行完成后,他会调用doInBackground方法。
doInBackground运行在后台线程,他用来负责运行任务。他拥有参数Params,并且返回Result。在后台线程的运行当中,为了能够更新作业完成的进度,需要在doInbackground方法中调用PublishProgress方法。该方法拥有参数Progress。通过该方法可以更新Progress的数据。然后当调用完PublishProgress方法,他会调用onProgressUpdate方法用于更新进度。
onProgressUpdate运行在UI线程,主要目的是用来更新UI线程中显示进度的UI控件。他拥有Progress参数。在doInBackground中调用PublishProgress之后,就会自动调onProgressUpdate方法
onPostExecute运行在UI线程,当doInBackground方法运行完后,他会调用onPostExecute方法,并传入Result。在onPostExecute方法中,就可以将Result更新到UI控件上。
明白了上面的3个参数和4个方法,你要做的就是
1、编写一个继承AsyncTask的类,并声明3个参数的类型,编写4个回调方法的内容。
2、然后在UI线程中创建该类(必须在UI线程中创建)。
3、最后调用AsyncTask的execute方法,传入Parmas参数(同样必须在UI线程中调用)。
这样就大功告成了。
另外值得注意的2点就是,千万不要直接调用那四个回调方法。还有就是一个AsyncTask实例只能执行一次,否则就出错哦。
以上是AsyncTask的基本用法,更加详细的内容请参考android官方文档。最后附上一段代码,供大家参考。

复制代码
 1 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> 
 2 //在这里声明了Params、Progress、Result参数的类型
 3 {
 4     //因为这里不需要使用onPreExecute回调方法,所以就没有加入该方法
 5     
 6     //后台线程的目的是更具URL下载数据
 7      protected Long doInBackground(URL... urls) {
 8          int count = urls.length;//urls是数组,不止一个下载链接
 9          long totalSize = 0;//下载的数据
10          for (int i = 0; i < count; i++) {
11              //Download是用于下载的一个类,和AsyncTask无关,大家可以忽略他的实现
12              totalSize += Downloader.downloadFile(urls[i]);
13              publishProgress((int) ((i / (float) count) * 100));//更新下载的进度
14              // Escape early if cancel() is called
15              if (isCancelled()) break;
16          }
17          return totalSize;
18      }
19 
20      //更新下载进度
21      protected void onProgressUpdate(Integer... progress) {
22          setProgressPercent(progress[0]);
23      }
24 
25      //将下载的数据更新到UI线程
26      protected void onPostExecute(Long result) {
27          showDialog("Downloaded " + result + " bytes");
28      }
29  }
30  
复制代码



 有了上面的这个类,接下你要做的就是在UI线程中创建实例,并调用execute方法,传入URl参数就可以了。

 参考资料
http://developer.android.com/reference/android/os/AsyncTask.html
http://www.cnblogs.com/dawei/archive/2011/04/18/2019903.html

这上面的5种方法各有优点。但是究其根本,其实后面四种方法都是基于handler方法的包装。在一般的情形下后面四种似乎更值得推荐。但是当情形比较复杂,还是推荐使用handler。
最后补充一下,这是我的第一篇博客。存在很多问题请大家多多指教。尤其是文中涉及到内容,有严重的技术问题,大家一定要给我指明啊。拜托各位了。

如需转发请注明原文地址。

Android使用后台线程提高用户体验

    当应用程序启动时,系统会为应用程序创建一个主线程(main)或者叫UI线程,它负责分发事件到不同的组件,包括绘画事件。完成你的应用程序与android UI组件交互。例如,当您触摸屏幕上的一个按钮时,UI线程会把触摸事件分发到组件上,更改状态并加入事件队列,UI线程会分发请求和通知到各个组件,完成相应的动作。
     单线程模型的性能是非常差的,除非你的应用程序相当的简单,特别是当所有的操作都在主线程中执行,比如访问网络或数据库之类的耗时操作将会导致用户界面锁定,所有的事件将不能分发,应用程序就像死了一样,更严重的是当超过5秒时,系统就会弹出“应用程序无响应”的对话框。显然这会造成很差的用户体验,所以我们需要保证主线程(UI线程)不被锁住,如果有耗时的操作,我们需要把它放到一个单独的后台线程中执行。
      通过后台线程来提高用户体验的方式很多,一个最简单的方式就是在进行耗时操作的地方新开一个线程,用该线程来处理耗时操作,示例代码如下:
[java] view plaincopyprint?
public void onClick(View v) {  
    new Thread(new Runnable() {  
        public void run() {  
            // 执行耗时操作  
        }  
    }).start();  
}  
      起初,上面的代码似乎是一个很好的解决方案,因为它不会锁住用户界面线程。然面不幸的是,它违反了用户界面单线程模型:android的用户界面工具包不是线程安全的,只能在UI线程中操作它。android提供了几种方法来从其他线程访问UI线程。下面是一个较全面的列表:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
      一般情况下,我们会用Handler做UI线程的修改,示例代码如下:
[java] view plaincopyprint?
private ProgressDialog progressDialog;  
private Handler myHandler = new Handler(){  
    @Override  
    public void handleMessage(Message msg) {  
        progressDialog.dismiss();  
        super.handleMessage(msg);  
    }  
};  
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.main);  
         
    progressDialog = new ProgressDialog(MainActivity.this);  
    progressDialog.setMessage("Loading…");  
    progressDialog.show();  
        
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            //这里作比较耗时的工作,暂时用线程休眠2秒作替代。  
            try {  
                Thread.sleep(4*1000);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            myHandler.sendMessage(myHandler.obtainMessage());  
        }  
    }).start();  
}  
首先显示一个ProgressDialog做界面友好提示,然后新开线程做耗时操作,最后调用handler的sendMessage,唤醒Handler。

      除了上述的几种方法之外,1.5和更高版本的Android平台提供了一个实用类称为AsyncTask,简化了长时间运行的任务,需要与用户界面的交互。AsyncTask的目标是要为你的线程提供管理服务,示例代码如下:
[java] view plaincopyprint?
private class DownloadFilesTask extends AsyncTask<Void, Void, Void> {  
    @Override  
    protected Void doInBackground(Void… params) {  
        //耗时操作,  
        try {  
            Thread.sleep(4*1000);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        return null;  
    }  
    @Override  
    protected void onPostExecute(Void result) {  
        //作UI线程的修改。  
        progressDialog.dismiss();  
        super.onPostExecute(result);  
    }     
}  

以下是AsyncTask的简要使用方法:
    •您可以指定三个参数类型,泛型参数,进度值(执行过程中返回的值)和最终值(执行完返回的值)。
    •该方法doInBackground()自动执行工作线程(后台线程)
    •onPreExecute(),onPostExecute()和onProgressUpdate()都是在UI线程调用
    •由doInBackground返回的值()发送到onPostExecute()
    •您可以在执行doInBackground()时调用publishProgress()然后在UI组程中执行onProgressUpdate()。
    •您可以从任何线程随时取消任务
不管你是否使用AsyncTask,时刻牢记单一线程模型的两条规则:
    1、不要锁住用户界面。
    2、确保只在UI线程中访问android用户界面工具包中的组件。

THE END!