马上要回国了,不过有个任务还没有完成,就是:对据说是马化腾亲自编写的“产品设计与用户体验”胶片写学习心得,于是紧锣密鼓地上网查查其背景,结果不查不知道,一查下吓大跳,不管是GOOGLE还是百度,均有一大堆结果,其中在GOOGLE.pk输入“马化腾- 产品设计与用户体验”,查询出25800个结果,可见其影响程度,这坚定了我好好学习这个胶片的决心。
初看这个胶片实际上并不如何出色,胶片制作非常粗糙,排版凌乱,重点不突出。不过好歹有最后有一张总结性的片子,上面列举了其重点。总体来讲,马讲了四个方面,产品设计,运营式研发,交互设计和视觉设计。
产品设计
产品设计涉及了如下方面:口碑传播,少即是多,兼容性,无所不用其极,关注性能和速度,抓住高端用户,大气的设计,满足用户个性化需求,寻求差异。我归纳起来只有两点:高性能,差异化设计。
性能
性能是一种在特定情况下的能力,例如在特定工作量下的完成时间,在1年内的故障时间,在消耗一定成本后获得的收益,容量,存储空间等等,那么对于产品而言,微观上要求速度快,不当机,要兼容老版本,宏观上要求CTO低,性价比高,要达到这样的目的却是不容易,不但需要从设计的细节着手考虑,而且需要长期持久不泄的努力。
差异化设计
差异化设计,实际上也是个性化设计,每个人都不一样,产品个人化设计就是根据个人差异进行不同的分类设计,拿出不同风格类型的产品面貌来获得不同类型的人们的喜爱。马化腾对客户分类为普通用户和高端用户,对高端用户要大气,要时刻重点关注,对普通用户要体贴,不因高端用户而牵涉太多的普通用户。这些都不无道理。
运营式研发
运营式研发包括服务可靠稳定,日日用人盯人,重视反馈,快速上线。这里面归纳为:性能和测试。
运营角度看质量属性
马化腾这里讲的质量属性角度有点不同,从运营角度看问题是在线式软件系统必需的,对我们公司和电信运营商恰好最值得学习,软件系统不是能运行就行了,不是能用就行了,必须能经得住时间的考验,必须有容错性,可用性,安全性,兼容性等等要求,和软件相关的硬件系统也要从运营角度考虑,要备份,要容灾,要集群,如果发现硬件不适合运营,应首先考虑改造软件设计。
测试
可测试性设计,我们公司很早都在讲。马化腾针对在线系统提出具体的测试方案,日日用,人盯人,看论坛,搜博客,重视客户反馈,建立快速上线测试机制,都是很好的方法,有新意,作为一个超级富豪,能亲自总结这些细微的方案,足以说明一些问题和必然。
交互式设计
交互式设计(Interaction Design)是指设计师对产品与它的使用者之间的互动机制进行分析、预测、定义、规划、描述和探索的过程,在1984年比尔•莫格里奇首次命名。这个定义够正规的了,最初就是针对产品而来。马化腾眼中的交互式设计如下:
– 把自己想象成一个迟钝、挑剔、易怒的傻瓜来使用产品
– 抓主要矛盾,重点关注最常使用的交互点
– 产品经理有前台和后台技术背景,有助于设计的判断和平衡
– 擅长体会和学习业界优秀交互案例
– 视觉关注和决策行动,决定准确和快一秒
– 键盘少敲一下,鼠标易于点选,鼠标少移一段,操作形成定势
– 照顾用户习惯
– 如何把新功能引入
这些都是一些具体的经验,从QQMAIL和QQ影音总结而来,很具体。我记得在2004年末的“可用性兴趣小组”,当时我也加入了,因为用户在那段时间密集反馈了一些界面上的问题,我们专门找来了南研的交互式界面设计的专职人员来指导,但是效果还是不佳,加入兴趣小组后根据可用性原则开发组有针对性地总结了一些设计规律,还真改善很多,可惜后来受工程支持和版本进度控制的影响,没有投入太多时间,而后来成立的UCD(user centre design)小组精耕细作,并广泛实践,做了很多贡献。
可用性实际上不能表达交互式设计的意思,应该是易用性。易用性是一种以使用者为中心的设计概念,易用性设计的重点在于让产品的设计能够符合使用者的习惯与需求。而可用性还有持续可用,稳定的意思。
视觉设计
视觉设计也是易用性设计的一个部分,马化腾提到了传播产品理念,大气成熟,干净整洁,工具化,规范与统一,重点突出,防止不恰当的低龄化。
传播产品理念
传播产品理念要求首先由完美的产品理念和愿景,就是写个好故事,然后才能在设计中讲故事,让用户听好故事。
规范
针对特定产品或者特定类型的产品,制定经过平衡的设计规范是产品设计的较高形态了,剩下的就是设计人员和开发人员,测试人员全面贯彻执行。
总结
总的来看,我从马化腾的胶片中强化了很多认识,正如其中有一页讲的“值得学习的产品”有很多,有GOOGLE的极致简洁,有YAHOO的排版标准,有FACEBOOK的框架灵活,有APPLE的大气时尚,我们也必须从众多优秀产品中吸取精华。
网上有一篇马化腾的学习文章写得挺好的,有几句话很有意思,摘录如下:
“做产品开发的时候需要有较强的研发机制保证,这样可以让产品开发更加敏捷更加快速。”
“开发人员要参与,40-50%左右的产品最终体验应该是由开发人员决定的。产品人员不要嫉妒有些工作是是开发人员设计的,只有这样才是团队共同参与的。如果都是产品想的就完蛋了,那么这个team做这个产品没有什么机会,必然会产生产品迭代慢的效果。”
“产品经理要想到自己是个挑剔的用户,想像自己是个笨用户,复杂的看不懂。”
我在思考一个问题,做设计,不管是总体设计,概要设计,详细设计,还是交互式设计,我们要以产品为中心还是以用户为中心呢?这个问题马化腾没有说清楚,马化腾的QQ和财富光环导致其胶片页能引起广泛关注,大家都需要研究马化腾,以下数据能说明众人疯狂的原因:
“2009年Q3,腾讯收入5亿美金,运营利润2.5亿。分别是百度的2.6倍和3倍。腾讯收入和利润的同比增长率分别是66%和107%,远高于百度的40%。现金储备是16亿美金,是百度2.7倍。”
而腾讯能大把赚钱的原因是:
“腾讯有一个黏性巨大的QQ用户群,其赚钱战略是:当有什么商业模式成熟了,就把这个模式导入用户群,然后迅速获得收入。”
好了,我们找到了马化腾的腾讯赚钱的秘诀,也就找到了我的问题的答案:“用户”。
2010年1月28日星期四
2009年3月22日星期日
UCON---使用控制方法
使用控制方法
在以上引用的研究包含许多针对特定目标问题的重要文章。同时,特定的焦点导致基本问题的广泛的系统的讨论的缺乏。访问控制、信任管理、DRM的研究有自身的轨迹,相互之间有很少的影响。尽管传统的访问控制应用到现在数字环境时有局限性,但是,在控制数字资源的安全策略和模型上也存在有价值的工作。同样地,尽管DRM已经打破了封闭系统限制,仍旧缺乏策略和模型良好定义的规范。而且,目前的权限表达语言不能表达事务级别的控制,包括易变性和连续性方面。今天的DRM技术要求良好定义的策略和模型,能更广泛地表达使用决策,能覆盖敏感信息保护,比如IPR保护。访问控制和信任管理要求放大他们的范围使之更丰富,更出色,持久地控制数字对象而和其位置无关。此外,在这些方法中没有能充分访问秘密发布的信息的方法。
UCON的目标是提供新的针对访问控制的智能化基础。正如以上讨论的,访问矩阵三十年的框架已经扩展到各种不同的方向,而研究者已经发现他不够他们的需要。纯粹的结果是表面上的过剩,特别是没有基础的智能的统一框架。在此篇论文中,我们打算提出一个崭新的观点,针对UCON本身的基础框架。因为我们从根本上重新考虑和扩展访问控制的基本特性,我们铸造UCON术语来传达我们正在讨论的广阔的观点。UCON的概念在我们前一篇论文 [Park and Sandhu 2002]中介绍了。UCON的概念是全面的,充分围绕传统的访问控制、信任管理、DRM展开。UCON系统地统一了这些领域到单个统一框架中,在自身的作用域中进行了超越。图1战士了UCON的覆盖范围和与其他研究领域的关系。根据目标,敏感信息保护已经是传统访问控制的重要目标之一。近来针对控制数字资源的使用的研究也集中在其他目标,诸如IPR保护、隐私保护。
图1
控制敏感信息的使用要求保护数字信息,数字信息可能是国家和组织关键资源。智能通讯和B2B食物是这个目标的良好例子。IPR保护或者数字版权保护是相对较新的目标。内容提供者的兴趣很大程度上属于这方面,因为,他们能实现最大收入。在控制数字信息使用的环境中,隐私是研究的很少的领域,但是,隐私逐渐开始受到更多的公众关注。W3C的最近的P3P项目是Web service的隐私支持的一个例子。健康保健信息系统是另外一个好例子,健康保健信息系统应该作为主要关注点来考虑隐私。UCON是中性的对象,系统性地覆盖所有的这些目标。
UCON属于有一对含义。在DRM环境中,UCON传达这个意义:在最终用户的系统中数字内容被提供来使用,但是提供者希望保留一些控制以限制用户对这些数字资源的操作。在隐私环境中,情况相反,最终用户经常提供个人信息给服务提供者,而希望控制服务提供者怎样使用这些个人信息。有时,个人信息被第三方起始者提供,例如健康保健提供者,然而,个人——叫做被指认人(UCON适合针对个人),希望行使一些个人信息使用的控制。使用也有持续时间的含义,因为,访问可以持续一段时间。在经典的访问控制中,通常的视点是:访问在访问被许可之前被强制实施,然后访问持续到一段时间而没有考虑将来检查。这对传统访问控制系统是适当的,但是没有反映许多现代电子商务系统的案例。
在UCON中,目标客体和使用者、提供者以及被识别主体有关系。使用者主体搜寻访问目标客体,目标客体由提供者主体提供。目标客体可能包含主体的隐私信息,这些主体叫做被识别主体,在客体中包含一定权限。使用判定针对目标资源基于这些不同的客体群中的关系进行判定。理想地,这些关系可以不再是一种方式控制判定,这种方式是当前通常的例子,提供者决定使用者的访问。在判定处理过程中,有多种方式的控制要求积极地包含这三方的每一方。而这可能是UCON的一种理想途径,这篇论文是舞台的第一步,而仅仅覆盖UCON核心方面,不考虑不同群或者多种方式的控制的关系。UCON的核心部分处理使用者主体使用的判定方面。在这篇论文中,我们介绍UCON ABC模型,作为UCON的核心模型。UCON ABC模型主要讨论基本控制问题,使用者主体基于目标客体的使用控制,而不覆盖任何不同主体群体的关系问题,也不涉及相关的管理问题。
传统上,访问控制处理授权作为判定过程的基础。在UCON ABC模型中,基于判定过程的授权利用主体属性和客体属性。属性能被识别,安全标签,特征,能力等。UCON ABC模型包含义务和条件,和授权一样,作为使用判定过程的一个部分,提供更加丰富和更加出色的判定能力。义务和条件的必要性被认识为现代业务系统诸如B2C大型分布式系统,以及,在业务伙伴之间的B2B事务和交互。义务是要求,针对使用许可,必须履行。条件是环境或者系统要求,独立于单个主体和客体。这些判定能被评估,在请求实行之前或者之中。另外,目标客体的使用可能请求一定的修改,这种变更针对主体或者客体属性,在使用实行之前、之中、之后(例如,通过电子书的价值减少请求者的账户余额)。UCON覆盖这些问题,在UCON ABC核心模型里,使用系统化方式。
从架构观点看,传统访问控制仅仅集中在服务器端控制,因此,几乎不考虑客户端控制,客户端控制甚至在数字资源发布后也提供持续地控制使用的能力。UCON ABC模型可以使用服务端和客户端控制架构,尽管有的功能细节可能不同。客户端控制要求存在客户端可信计算基础和引用监视器。最近的针对客户端控制的研究包括Palladum和TCPA都是前面提及的。值得信赖的客户端引用监视是一个相对问题,巨大地依赖包括隐私问题的业务模型需求。客户端引用监视器的细节不再这篇论文中讨论。这篇论文的主要焦点集中在模型级讨论,不考虑架构和实现细节。
在以上引用的研究包含许多针对特定目标问题的重要文章。同时,特定的焦点导致基本问题的广泛的系统的讨论的缺乏。访问控制、信任管理、DRM的研究有自身的轨迹,相互之间有很少的影响。尽管传统的访问控制应用到现在数字环境时有局限性,但是,在控制数字资源的安全策略和模型上也存在有价值的工作。同样地,尽管DRM已经打破了封闭系统限制,仍旧缺乏策略和模型良好定义的规范。而且,目前的权限表达语言不能表达事务级别的控制,包括易变性和连续性方面。今天的DRM技术要求良好定义的策略和模型,能更广泛地表达使用决策,能覆盖敏感信息保护,比如IPR保护。访问控制和信任管理要求放大他们的范围使之更丰富,更出色,持久地控制数字对象而和其位置无关。此外,在这些方法中没有能充分访问秘密发布的信息的方法。
UCON的目标是提供新的针对访问控制的智能化基础。正如以上讨论的,访问矩阵三十年的框架已经扩展到各种不同的方向,而研究者已经发现他不够他们的需要。纯粹的结果是表面上的过剩,特别是没有基础的智能的统一框架。在此篇论文中,我们打算提出一个崭新的观点,针对UCON本身的基础框架。因为我们从根本上重新考虑和扩展访问控制的基本特性,我们铸造UCON术语来传达我们正在讨论的广阔的观点。UCON的概念在我们前一篇论文 [Park and Sandhu 2002]中介绍了。UCON的概念是全面的,充分围绕传统的访问控制、信任管理、DRM展开。UCON系统地统一了这些领域到单个统一框架中,在自身的作用域中进行了超越。图1战士了UCON的覆盖范围和与其他研究领域的关系。根据目标,敏感信息保护已经是传统访问控制的重要目标之一。近来针对控制数字资源的使用的研究也集中在其他目标,诸如IPR保护、隐私保护。
图1
控制敏感信息的使用要求保护数字信息,数字信息可能是国家和组织关键资源。智能通讯和B2B食物是这个目标的良好例子。IPR保护或者数字版权保护是相对较新的目标。内容提供者的兴趣很大程度上属于这方面,因为,他们能实现最大收入。在控制数字信息使用的环境中,隐私是研究的很少的领域,但是,隐私逐渐开始受到更多的公众关注。W3C的最近的P3P项目是Web service的隐私支持的一个例子。健康保健信息系统是另外一个好例子,健康保健信息系统应该作为主要关注点来考虑隐私。UCON是中性的对象,系统性地覆盖所有的这些目标。
UCON属于有一对含义。在DRM环境中,UCON传达这个意义:在最终用户的系统中数字内容被提供来使用,但是提供者希望保留一些控制以限制用户对这些数字资源的操作。在隐私环境中,情况相反,最终用户经常提供个人信息给服务提供者,而希望控制服务提供者怎样使用这些个人信息。有时,个人信息被第三方起始者提供,例如健康保健提供者,然而,个人——叫做被指认人(UCON适合针对个人),希望行使一些个人信息使用的控制。使用也有持续时间的含义,因为,访问可以持续一段时间。在经典的访问控制中,通常的视点是:访问在访问被许可之前被强制实施,然后访问持续到一段时间而没有考虑将来检查。这对传统访问控制系统是适当的,但是没有反映许多现代电子商务系统的案例。
在UCON中,目标客体和使用者、提供者以及被识别主体有关系。使用者主体搜寻访问目标客体,目标客体由提供者主体提供。目标客体可能包含主体的隐私信息,这些主体叫做被识别主体,在客体中包含一定权限。使用判定针对目标资源基于这些不同的客体群中的关系进行判定。理想地,这些关系可以不再是一种方式控制判定,这种方式是当前通常的例子,提供者决定使用者的访问。在判定处理过程中,有多种方式的控制要求积极地包含这三方的每一方。而这可能是UCON的一种理想途径,这篇论文是舞台的第一步,而仅仅覆盖UCON核心方面,不考虑不同群或者多种方式的控制的关系。UCON的核心部分处理使用者主体使用的判定方面。在这篇论文中,我们介绍UCON ABC模型,作为UCON的核心模型。UCON ABC模型主要讨论基本控制问题,使用者主体基于目标客体的使用控制,而不覆盖任何不同主体群体的关系问题,也不涉及相关的管理问题。
传统上,访问控制处理授权作为判定过程的基础。在UCON ABC模型中,基于判定过程的授权利用主体属性和客体属性。属性能被识别,安全标签,特征,能力等。UCON ABC模型包含义务和条件,和授权一样,作为使用判定过程的一个部分,提供更加丰富和更加出色的判定能力。义务和条件的必要性被认识为现代业务系统诸如B2C大型分布式系统,以及,在业务伙伴之间的B2B事务和交互。义务是要求,针对使用许可,必须履行。条件是环境或者系统要求,独立于单个主体和客体。这些判定能被评估,在请求实行之前或者之中。另外,目标客体的使用可能请求一定的修改,这种变更针对主体或者客体属性,在使用实行之前、之中、之后(例如,通过电子书的价值减少请求者的账户余额)。UCON覆盖这些问题,在UCON ABC核心模型里,使用系统化方式。
从架构观点看,传统访问控制仅仅集中在服务器端控制,因此,几乎不考虑客户端控制,客户端控制甚至在数字资源发布后也提供持续地控制使用的能力。UCON ABC模型可以使用服务端和客户端控制架构,尽管有的功能细节可能不同。客户端控制要求存在客户端可信计算基础和引用监视器。最近的针对客户端控制的研究包括Palladum和TCPA都是前面提及的。值得信赖的客户端引用监视是一个相对问题,巨大地依赖包括隐私问题的业务模型需求。客户端引用监视器的细节不再这篇论文中讨论。这篇论文的主要焦点集中在模型级讨论,不考虑架构和实现细节。
2009年3月16日星期一
WORD文档结构混乱的解决办法
经常有同事抱怨我的文档结构混乱,无法索引,经过我的大力研究~~~~~
问题:
1、在word文档中使用自动编号,文档结构图会混乱,本来是正文的地方出现一级大纲
2、一个较长的文档,调整好格式并保存,以后重新打开,有时会发生正文莫名地改变为1级大纲级别的情况。
3、如果手动换页,换页后首行为标题,则在文档结构图中只能显示标题内容,不能显示编号。
实验:
经过观察发现在重新打开文档的过程中按esc键取消word对文档格式进行设置就可以正常,但是如果不按esc键取消的话,文档结构图就会出现混乱。
继续分析:
在打开word的时候左下角会有提示word自动更新文档样式(按esc键可以取消),只要这时候按esc键就可以看到正常的文档结构图了。由此推断是由于word自动更新文档样式造成的。因为word中加载了自定义模板,但是这个文档又从其它地方复制了许多乱七八糟的样式来,导致word自动更新样式的时候把正文的一些文字也加到了一级大纲视图中了。因此只要不让word自动更新文档样式就可以。解决办法可以是在文档中使用正确的样式,但是这很麻烦,而且有时候哪些不正确的样式会自动产生。
解决:
1、OFFICE2003
菜单“工具”——菜单“模板和加载项”——复选框“自动更新文档样式”,去掉勾
2、OFFICE2007
在打开word的时候左下角会有提示word自动更新文档样式,按esc键取消,然后在大纲模式下任意增加一行,保存,重新打开就解决了
问题:
1、在word文档中使用自动编号,文档结构图会混乱,本来是正文的地方出现一级大纲
2、一个较长的文档,调整好格式并保存,以后重新打开,有时会发生正文莫名地改变为1级大纲级别的情况。
3、如果手动换页,换页后首行为标题,则在文档结构图中只能显示标题内容,不能显示编号。
实验:
经过观察发现在重新打开文档的过程中按esc键取消word对文档格式进行设置就可以正常,但是如果不按esc键取消的话,文档结构图就会出现混乱。
继续分析:
在打开word的时候左下角会有提示word自动更新文档样式(按esc键可以取消),只要这时候按esc键就可以看到正常的文档结构图了。由此推断是由于word自动更新文档样式造成的。因为word中加载了自定义模板,但是这个文档又从其它地方复制了许多乱七八糟的样式来,导致word自动更新样式的时候把正文的一些文字也加到了一级大纲视图中了。因此只要不让word自动更新文档样式就可以。解决办法可以是在文档中使用正确的样式,但是这很麻烦,而且有时候哪些不正确的样式会自动产生。
解决:
1、OFFICE2003
菜单“工具”——菜单“模板和加载项”——复选框“自动更新文档样式”,去掉勾
2、OFFICE2007
在打开word的时候左下角会有提示word自动更新文档样式,按esc键取消,然后在大纲模式下任意增加一行,保存,重新打开就解决了
2009年1月13日星期二
应用程序模型:应用、任务、进程、线程
在多数操作系统中,在应用程序驻留的可执行映像文件、运行的进程和用户交互的图标和应用之间有强1对1的关系。在Android操作系统中,这些关联更多的是不固定,重要的是理解不同的部分怎么整合在一起。
因为Android应用成需的灵活的特性,当实现程序的各个部分的时候,有一些基本术语需要理解。
1、android包(简称.apk)是包容程序代码和资源的文件。这也是发布应用程序的文件,当用户在其设备上安装应用程序时,由用户下载这个文件。
2、一般一个任务是用户觉察出的作为一个能运行的应用程序的东西:通常一个任务在HOME屏幕有一个图标,通过这个图标可以被用户访问,作为顶层条目,任务可获得,能在其他任务前面放到前台。
3、进程是低级核心处理过程,用于运行应用程序代码。通常,在.apk中的所有代码只运行在一个进程中,每个.apk都有专用的进程。但是,进程标签能用于修改代码运行的地方,要么针对整个.apk,要么针对单个活动、接收器、服务、提供者、组件。
任务
这里关键点:当用户看到认为他们是一个应用程序时,实际上处理的东西就是一个任务。如果你仅仅创建一个带有大量活动的.apk,其中一个活动是顶级入口点(通过一个intent过滤器针对操作android.intent.action.MAIN和分类 android.intent.category.LAUNCHER),那么的确有一个任务针对你的.apk创建起来,从这里启动的任何活动也作为那个任务的一部分运行。
一个任务,来自用户的眼中看,是应用程序;从程序开发者眼中看,是一到多个活动,用户已经越过进入那个任务,但是还没有关闭,或者,一个活动堆栈。一个新任务是通过使用Intent.FLAG_ACTIVITY_NEW_TASK标志启动一个活动Intent来创建的;这个Intent会用于作为任务的根Intent,定义是什么任务。任何没有使用这个标志启动的活动会运行在和正在运行的那个活动所在的相同的任务中(除非活动已经请求一个特殊的启动模式,稍后讨论)。任务能被排序:如果你使用FLAG_ACTIVITY_NEW_TASK但是已经有一个任务运行于那个Intent,当前任务的活动堆栈会送到前台而不是启动一个新的任务。
FLAG_ACTIVITY_NEW_TASK必须小心地单独使用:使用这个标志就说明,从用户的角度看,一个新的应用程序在这个点启动。如果这不是你喜欢的行为,你应该不创建一个新任务。另外,如果用户可能从HOME屏幕回退到原来的位置,你应该仅仅使用新任务标志,以运行和新任务同样的Intent。否则,如果用户从正在运行的任务中按下HOME键而不是BACK键,你的任务机器活动将会在home屏幕之后被排序,而没有办法返回。
任务亲和力
在一些情况下,Android需要知道活动属于哪一个任务,即使当活动没有运行在指定的任务中时。这是通过任务亲缘关系完成的,任务亲缘关系提供一个唯一的任务静态名称,一到多个活动打算运行在具有静态名称的任务中。一个活动默认的任务亲和力是实现活动的.apk包的名称。这就提供了通常的可期望的行为,其中,在指定.apk文件中所有活动对用户来说是单个应用程序的一个部分。
当启动一个新活动而不是Intent.FLAG_ACTIVITY_NEW_TASK标志的时候,任务亲缘关系没有影响新活动运行的任务:总是运行在触发任务的活动的任务中。但是,如果NEW_TASK被使用,那么亲缘关系会用于决定一个任务是否和一个相同的亲缘关系已经存在。如果是已经存在,那么任务将会被提到前台,新活动运行在那个任务的顶层。
这种行为对于必须使用NEW_TASK标志的情况是非常有用的,特别是从状态栏通知或者home屏幕快捷方式运行活动时。结果是:当用户使用这种方式运行你的程序时,当前任务状态会拉到前台,用户现在想看到的活动被放到顶层。
你能在manifest的应用程序标签中指定你自己的任务亲缘关系,针对.apk的所有活动,或者针对单个活动的活动标签。怎么使用亲缘关系的例子如下:
1、如果你的.apk包含多个顶层的用户能运行的应用程序,那么你会可能想分派不同的亲和力到用户从你的.apk中找到的每个活动。使用不同的名称的好的惯例是添加你的.apk包名称,使用克隆的独立的字符串。例如,"com.android.contacts".apk文件可能有亲缘关系"com.android.contacts:Dialer" 和 "com.android.contacts:ContactsList"。
2、如果你正在替换通知、快捷方式、其他内嵌的在内部运行的活动,那么你可能需要显式设置你替换的活动的任务亲缘关系为与你替换的应用程序相同。例如,如果你正在替换通讯录详细视图(用户可以创建和调用快捷方式),那么你会想设置任务亲缘关系到"com.android.contacts"。
运行模式和运行标志
控制活动和任务交互的主要方法是通过活动的launchMode属性和与Intent关联的标志。这两个参数能一同工作在控制活动运行的结果的各种方法之下,正如在关联文档中描述的那样。在这里,我们将会看到一些常用的用例和这些参数的组合。
最常用的运行模式(除了默认的标准模式外)是singleTop。这不会对任务有影响,这种模式恰好避免不同时间启动同一个活动(在堆栈顶层)
singleTask运行模式对任务有一个主要的影响:导致活动总是启动在新任务里(或者,已存在的任务被拉到前台)。使用这种模式要求关心怎么和系统的其他部分交互,因为它影响到活动的每个路径。这种模式应该仅被用于作为应用程序前门的活动(即,支持MAIN操作和LAUNCHER分类)
singleInstance运行模式更多地是专用的,应该仅仅用于完全作为一个活动来实现应用程序的情况下。
你经常会运行进入的一种情况是另外一个实体(比如:SearchManager、NotificationManager)启动你的其中一个活动。在这种情况下,Intent.FLAG_ACTIVITY_NEW_TASK标志必须使用,因为活动在任务的外部启动(应用程序和任务甚至可以不存在)。正如前面描述的,这种情况下的标准行为是,把当前任务提到前台,当前任务匹配新活动的affinity,并在顶层启动新活动。但是,有您能实现的其他类型的行为。
一个普通的方法是,与NEW_TASK一起使用Intent.FLAG_ACTIVITY_CLEAR_TOP标志。通过这样做,如果你的任务已经在运行,那么将会被提到前台,堆栈中的所有活动将被清除,除了根活动,根活动的onNewIntent(Intent)在Intent被启动的时候被调用。注意,当使用这种方法的时候,活动经常使用singleTop或者singleTask运行模式,因此,当前实例给予新Intent而不是请求销毁和产生新实例。
你能采用的另外一个办法是,设置通知活动的任务affinity为空串(表示无affinity),并设置finishOnBackground属性。这种办法是有用的,如果你将会希望通知把用户带到一个分离的活动,胜于返回到应用程序的任务。通过指定这个属性,不论用户是否使用BACK或者HOME离开活动,活动都会完成。如果属性没有被指定,按HOME将会导致进入活动,其任务保持在系统中,可能没有办法返回。
保证阅读launchMode属性和Intent标志相关文档,以了解这些选项。
进程
在Android操作系统中,进程完全是应用程序的具体实现,不是用户通常认为的那种东西。他们的主要用途是简单的:
1、改善稳定性或者安全性,通过把未信任或者不稳定的代码放入独立的进程的方法来解决
2、简化在同一进程中多个.apk文件的代码的运行
3、有助于系统管理资源,通过吧重量级代码放入独立的进程,可以被杀掉,而且和程序的其他部分无关。
正如前面描述的那样,进程属性用于控制细小程序组件运行的进程。注意,这些进程属性不能用于违反系统安全规则:如果没有共享同一个用户ID的两个.apk文件试图运行在同一个进程中,那么,这是不允许的,不同的进程将会分别针对每个.apk文件被创建。
参见安全相关的文件了解更多的关于安全规则的信息。
线程
每个进程有一到多个线程运行在其中。在多数情况下,Android避免在进程中创建额外的线程,保证应用程序是单线程的,除非应用程序自己又创建了线程。重要印证是,注意,新线程不会针对每个Activity/BroadcastReceiver/Servie/ContentProvider实例:这些程序组件在对应的进程中实例化(除非另作说明所有的组件都位于同一个进程),在那个进程的主线程中。这意味着,当被系统调用的时候,没有组件(包括服务)应该在进程中执行长的或者阻塞的组件(例如网络调用或者计算循环),因为这将会阻塞所有进程中的其他组件。你可以使用标准库线程类或者Android的HandleThread类来在另外一个线程中执行长操作。
There are a few important exceptions to this threading rule:
线程规则有几处例外:
1、到IBinder的访问或者在IBinder上实现的接口从调用它们的线程被调度,或者如果来自其他进程则从本地进程中的线程池被调度,不是从他们的进程的主线程被调度。特别地,到服务的IBinder的访问将会以这种方式调用。(虽然到在服务自身的调用来自主线程。)这意味着,IBinder接口的实现必须总是在线程安全模式下编写,因为他们能在同一时间从许多随机线程中被调用。
2、到ContentProvider的调用从调用线程或者IBinder所在的主线程被调度。在ContentProvider类的相关文档中这个方法有描述。这意味着,这些方法的实现必须总是以线程安全方式编写,因此,他们能在同一时刻从许多随机线程被调用。
3、在View和它的子类上调用来自视图窗口运行的线程。正常情况下,这将会是进程的主线程,但是如果你创建一个线程,然后从线程中显示一个窗口,那么这个窗口的视图层次将会从那个线程被调用。
因为Android应用成需的灵活的特性,当实现程序的各个部分的时候,有一些基本术语需要理解。
1、android包(简称.apk)是包容程序代码和资源的文件。这也是发布应用程序的文件,当用户在其设备上安装应用程序时,由用户下载这个文件。
2、一般一个任务是用户觉察出的作为一个能运行的应用程序的东西:通常一个任务在HOME屏幕有一个图标,通过这个图标可以被用户访问,作为顶层条目,任务可获得,能在其他任务前面放到前台。
3、进程是低级核心处理过程,用于运行应用程序代码。通常,在.apk中的所有代码只运行在一个进程中,每个.apk都有专用的进程。但是,进程标签能用于修改代码运行的地方,要么针对整个.apk,要么针对单个活动、接收器、服务、提供者、组件。
任务
这里关键点:当用户看到认为他们是一个应用程序时,实际上处理的东西就是一个任务。如果你仅仅创建一个带有大量活动的.apk,其中一个活动是顶级入口点(通过一个intent过滤器针对操作android.intent.action.MAIN和分类 android.intent.category.LAUNCHER),那么的确有一个任务针对你的.apk创建起来,从这里启动的任何活动也作为那个任务的一部分运行。
一个任务,来自用户的眼中看,是应用程序;从程序开发者眼中看,是一到多个活动,用户已经越过进入那个任务,但是还没有关闭,或者,一个活动堆栈。一个新任务是通过使用Intent.FLAG_ACTIVITY_NEW_TASK标志启动一个活动Intent来创建的;这个Intent会用于作为任务的根Intent,定义是什么任务。任何没有使用这个标志启动的活动会运行在和正在运行的那个活动所在的相同的任务中(除非活动已经请求一个特殊的启动模式,稍后讨论)。任务能被排序:如果你使用FLAG_ACTIVITY_NEW_TASK但是已经有一个任务运行于那个Intent,当前任务的活动堆栈会送到前台而不是启动一个新的任务。
FLAG_ACTIVITY_NEW_TASK必须小心地单独使用:使用这个标志就说明,从用户的角度看,一个新的应用程序在这个点启动。如果这不是你喜欢的行为,你应该不创建一个新任务。另外,如果用户可能从HOME屏幕回退到原来的位置,你应该仅仅使用新任务标志,以运行和新任务同样的Intent。否则,如果用户从正在运行的任务中按下HOME键而不是BACK键,你的任务机器活动将会在home屏幕之后被排序,而没有办法返回。
任务亲和力
在一些情况下,Android需要知道活动属于哪一个任务,即使当活动没有运行在指定的任务中时。这是通过任务亲缘关系完成的,任务亲缘关系提供一个唯一的任务静态名称,一到多个活动打算运行在具有静态名称的任务中。一个活动默认的任务亲和力是实现活动的.apk包的名称。这就提供了通常的可期望的行为,其中,在指定.apk文件中所有活动对用户来说是单个应用程序的一个部分。
当启动一个新活动而不是Intent.FLAG_ACTIVITY_NEW_TASK标志的时候,任务亲缘关系没有影响新活动运行的任务:总是运行在触发任务的活动的任务中。但是,如果NEW_TASK被使用,那么亲缘关系会用于决定一个任务是否和一个相同的亲缘关系已经存在。如果是已经存在,那么任务将会被提到前台,新活动运行在那个任务的顶层。
这种行为对于必须使用NEW_TASK标志的情况是非常有用的,特别是从状态栏通知或者home屏幕快捷方式运行活动时。结果是:当用户使用这种方式运行你的程序时,当前任务状态会拉到前台,用户现在想看到的活动被放到顶层。
你能在manifest的应用程序标签中指定你自己的任务亲缘关系,针对.apk的所有活动,或者针对单个活动的活动标签。怎么使用亲缘关系的例子如下:
1、如果你的.apk包含多个顶层的用户能运行的应用程序,那么你会可能想分派不同的亲和力到用户从你的.apk中找到的每个活动。使用不同的名称的好的惯例是添加你的.apk包名称,使用克隆的独立的字符串。例如,"com.android.contacts".apk文件可能有亲缘关系"com.android.contacts:Dialer" 和 "com.android.contacts:ContactsList"。
2、如果你正在替换通知、快捷方式、其他内嵌的在内部运行的活动,那么你可能需要显式设置你替换的活动的任务亲缘关系为与你替换的应用程序相同。例如,如果你正在替换通讯录详细视图(用户可以创建和调用快捷方式),那么你会想设置任务亲缘关系到"com.android.contacts"。
运行模式和运行标志
控制活动和任务交互的主要方法是通过活动的launchMode属性和与Intent关联的标志。这两个参数能一同工作在控制活动运行的结果的各种方法之下,正如在关联文档中描述的那样。在这里,我们将会看到一些常用的用例和这些参数的组合。
最常用的运行模式(除了默认的标准模式外)是singleTop。这不会对任务有影响,这种模式恰好避免不同时间启动同一个活动(在堆栈顶层)
singleTask运行模式对任务有一个主要的影响:导致活动总是启动在新任务里(或者,已存在的任务被拉到前台)。使用这种模式要求关心怎么和系统的其他部分交互,因为它影响到活动的每个路径。这种模式应该仅被用于作为应用程序前门的活动(即,支持MAIN操作和LAUNCHER分类)
singleInstance运行模式更多地是专用的,应该仅仅用于完全作为一个活动来实现应用程序的情况下。
你经常会运行进入的一种情况是另外一个实体(比如:SearchManager、NotificationManager)启动你的其中一个活动。在这种情况下,Intent.FLAG_ACTIVITY_NEW_TASK标志必须使用,因为活动在任务的外部启动(应用程序和任务甚至可以不存在)。正如前面描述的,这种情况下的标准行为是,把当前任务提到前台,当前任务匹配新活动的affinity,并在顶层启动新活动。但是,有您能实现的其他类型的行为。
一个普通的方法是,与NEW_TASK一起使用Intent.FLAG_ACTIVITY_CLEAR_TOP标志。通过这样做,如果你的任务已经在运行,那么将会被提到前台,堆栈中的所有活动将被清除,除了根活动,根活动的onNewIntent(Intent)在Intent被启动的时候被调用。注意,当使用这种方法的时候,活动经常使用singleTop或者singleTask运行模式,因此,当前实例给予新Intent而不是请求销毁和产生新实例。
你能采用的另外一个办法是,设置通知活动的任务affinity为空串(表示无affinity),并设置finishOnBackground属性。这种办法是有用的,如果你将会希望通知把用户带到一个分离的活动,胜于返回到应用程序的任务。通过指定这个属性,不论用户是否使用BACK或者HOME离开活动,活动都会完成。如果属性没有被指定,按HOME将会导致进入活动,其任务保持在系统中,可能没有办法返回。
保证阅读launchMode属性和Intent标志相关文档,以了解这些选项。
进程
在Android操作系统中,进程完全是应用程序的具体实现,不是用户通常认为的那种东西。他们的主要用途是简单的:
1、改善稳定性或者安全性,通过把未信任或者不稳定的代码放入独立的进程的方法来解决
2、简化在同一进程中多个.apk文件的代码的运行
3、有助于系统管理资源,通过吧重量级代码放入独立的进程,可以被杀掉,而且和程序的其他部分无关。
正如前面描述的那样,进程属性用于控制细小程序组件运行的进程。注意,这些进程属性不能用于违反系统安全规则:如果没有共享同一个用户ID的两个.apk文件试图运行在同一个进程中,那么,这是不允许的,不同的进程将会分别针对每个.apk文件被创建。
参见安全相关的文件了解更多的关于安全规则的信息。
线程
每个进程有一到多个线程运行在其中。在多数情况下,Android避免在进程中创建额外的线程,保证应用程序是单线程的,除非应用程序自己又创建了线程。重要印证是,注意,新线程不会针对每个Activity/BroadcastReceiver/Servie/ContentProvider实例:这些程序组件在对应的进程中实例化(除非另作说明所有的组件都位于同一个进程),在那个进程的主线程中。这意味着,当被系统调用的时候,没有组件(包括服务)应该在进程中执行长的或者阻塞的组件(例如网络调用或者计算循环),因为这将会阻塞所有进程中的其他组件。你可以使用标准库线程类或者Android的HandleThread类来在另外一个线程中执行长操作。
There are a few important exceptions to this threading rule:
线程规则有几处例外:
1、到IBinder的访问或者在IBinder上实现的接口从调用它们的线程被调度,或者如果来自其他进程则从本地进程中的线程池被调度,不是从他们的进程的主线程被调度。特别地,到服务的IBinder的访问将会以这种方式调用。(虽然到在服务自身的调用来自主线程。)这意味着,IBinder接口的实现必须总是在线程安全模式下编写,因为他们能在同一时间从许多随机线程中被调用。
2、到ContentProvider的调用从调用线程或者IBinder所在的主线程被调度。在ContentProvider类的相关文档中这个方法有描述。这意味着,这些方法的实现必须总是以线程安全方式编写,因此,他们能在同一时刻从许多随机线程被调用。
3、在View和它的子类上调用来自视图窗口运行的线程。正常情况下,这将会是进程的主线程,但是如果你创建一个线程,然后从线程中显示一个窗口,那么这个窗口的视图层次将会从那个线程被调用。
2009年1月4日星期日
开发工具
Android SDK包含多种自定义的工具,用来帮组你开发移动程序。最重要的是模拟器以及ADT插件,但是SDK还包括其他的工具用于调试、打包、安装。
模拟器
虚拟手机设备,运行在你的电脑上。你使用模拟器来设计、调试、测试你的应用程序。
层次阅读器
该工具允许你调试和优化你的用户接口。提供可视化表现视图的多层次布局,使用像素栅格的当前界面的放大检查器,因此你能获得正确的界面。
Draw 9-patch
Draw 9-patch工具允许你轻易地创建NinePatch图形,使用WYSIWYG编辑器。预览图片的扩展版本,高亮化选择的区域。
ADT插件
ADT插件增加了强力的扩展功能,集成Eclipse环境,使创建和调试程序更加容易和快速,在开发Android程序时,ADT插件给你难以置信的促进
1、允许你在Eclipse IDE中访问其他的Android开发工具。例如,ADT提供了DDMS工具的许多能力——屏幕截图、管理端口转发、设置断点、观察线程和进程信息。
2、提供新项目向导,帮助你快速创建和设置新程序需要的基本文件。
3、自动化和简化建立你的Android程序的过程;
4、提供Android代码编辑器,有助于编写有效的manifest和资源XML文件。
Dalvik调试监视服务(ddms)
和Dalvik(Android平台的自定义虚拟机)集成,这个工具让你管理模拟器或者设备上的进程,辅助调试。你能使用这个工具来销毁进程,选择指定的进程调试,产生跟踪数据,查看内存堆和线程信息,对模拟器或者设备进行屏幕截图。
调试桥
adb工具让你在模拟器或者设备上安装程序的.apk文件,从命令行访问模拟器或者设备。你也能使用这个工具来连接标准的调试器到运行在模拟器或者设备上的程序代码。
资产打包工具(aapt)
aapt工具让你创建.apk文件,包含程序的二进制和资源文件。
接口描述语言(aidl)
让你产生进程间接口的代码,比如服务可能使用的代码。
sqlite3
顺便包括这个工具,该工具让你访问SQLite数据文件,数据文件被程序创建和使用。
Traceview
这个工具产生图形化的分析视图,该视图跟踪你在程序中产生的日志数据。
mksdcard
帮组你创建在模拟器中能使用的磁盘图片,来模拟外部存储卡(比如SD卡)的表现。
dx
dx工具重写.class字节代码进入Android字节代码(存储在.dex文件)中。
UI/程序练习猴子
Monkey是运行在你的模拟器或者设备上的一段程序,产生用户事件的伪随机流,比如点击、触摸、手势,还有系统级的事件。你能使用Monkey来进行强度测试,使用随机的但是重复的方式来进行。
activitycreator
产生Ant建立文件的描述脚本,Ant建立文件用于编译Android程序。如果你正在使用ADT插件开发,那么你不会需要使用这个描述脚本。
模拟器
虚拟手机设备,运行在你的电脑上。你使用模拟器来设计、调试、测试你的应用程序。
层次阅读器
该工具允许你调试和优化你的用户接口。提供可视化表现视图的多层次布局,使用像素栅格的当前界面的放大检查器,因此你能获得正确的界面。
Draw 9-patch
Draw 9-patch工具允许你轻易地创建NinePatch图形,使用WYSIWYG编辑器。预览图片的扩展版本,高亮化选择的区域。
ADT插件
ADT插件增加了强力的扩展功能,集成Eclipse环境,使创建和调试程序更加容易和快速,在开发Android程序时,ADT插件给你难以置信的促进
1、允许你在Eclipse IDE中访问其他的Android开发工具。例如,ADT提供了DDMS工具的许多能力——屏幕截图、管理端口转发、设置断点、观察线程和进程信息。
2、提供新项目向导,帮助你快速创建和设置新程序需要的基本文件。
3、自动化和简化建立你的Android程序的过程;
4、提供Android代码编辑器,有助于编写有效的manifest和资源XML文件。
Dalvik调试监视服务(ddms)
和Dalvik(Android平台的自定义虚拟机)集成,这个工具让你管理模拟器或者设备上的进程,辅助调试。你能使用这个工具来销毁进程,选择指定的进程调试,产生跟踪数据,查看内存堆和线程信息,对模拟器或者设备进行屏幕截图。
调试桥
adb工具让你在模拟器或者设备上安装程序的.apk文件,从命令行访问模拟器或者设备。你也能使用这个工具来连接标准的调试器到运行在模拟器或者设备上的程序代码。
资产打包工具(aapt)
aapt工具让你创建.apk文件,包含程序的二进制和资源文件。
接口描述语言(aidl)
让你产生进程间接口的代码,比如服务可能使用的代码。
sqlite3
顺便包括这个工具,该工具让你访问SQLite数据文件,数据文件被程序创建和使用。
Traceview
这个工具产生图形化的分析视图,该视图跟踪你在程序中产生的日志数据。
mksdcard
帮组你创建在模拟器中能使用的磁盘图片,来模拟外部存储卡(比如SD卡)的表现。
dx
dx工具重写.class字节代码进入Android字节代码(存储在.dex文件)中。
UI/程序练习猴子
Monkey是运行在你的模拟器或者设备上的一段程序,产生用户事件的伪随机流,比如点击、触摸、手势,还有系统级的事件。你能使用Monkey来进行强度测试,使用随机的但是重复的方式来进行。
activitycreator
产生Ant建立文件的描述脚本,Ant建立文件用于编译Android程序。如果你正在使用ADT插件开发,那么你不会需要使用这个描述脚本。
2008年12月30日星期二
教程:额外的收益
本练习中,你将会使用调试器来查看你在练习3中做的工作。本练习证明:
1、怎么设置断点来观察执行情况
2.怎么运行你的应用程序在调试模式下。
第一步
使用加工过的Notepadv3,在NoteEdit类的onCreate(), onPause(), onSaveInstanceState() 和 onResume()方法的开头处,设置断点。(如果不熟悉Eclipse,在你想设置断点的行上,对应编辑窗口的左边的窄窄的灰色边框里面右击,选择Toggle Breakpoint,你应该会看到蓝点显示)
第二步
现在,在调试模式下启动notepad示例程序
1、在Notepadv3项目上右击,从调试菜单选择Debug As -> Android Application
2.Android模拟器应该简单地说“waiting for debugger to connect(等待调试器链接)”,然后运行应用程序
3、如果一直停留在“waiting...”屏幕下,退出模拟器和Eclipse,从命令行使用adb销毁服务器,然后重新启动。
第三步
当你编辑或者创建新标签的时候,你应该看到断点命中,执行停止。
第四步
点击Resume按钮让执行继续(在顶部的Eclipse工具栏右边带有绿色三角形的黄色矩形)
第五步
实验确认和回退按钮,试着按Home键,改为其他模式。观察生命周期时间产生了什么,什么时候触发。
ADT插件不仅提供优良的调试支持,而且卓越的剖析能力支持。你能试着使用TraceView来剖析你的应用程序。如果你的应用成运行很慢,这能帮助你发现瓶颈,并纠正问题。
1、怎么设置断点来观察执行情况
2.怎么运行你的应用程序在调试模式下。
第一步
使用加工过的Notepadv3,在NoteEdit类的onCreate(), onPause(), onSaveInstanceState() 和 onResume()方法的开头处,设置断点。(如果不熟悉Eclipse,在你想设置断点的行上,对应编辑窗口的左边的窄窄的灰色边框里面右击,选择Toggle Breakpoint,你应该会看到蓝点显示)
第二步
现在,在调试模式下启动notepad示例程序
1、在Notepadv3项目上右击,从调试菜单选择Debug As -> Android Application
2.Android模拟器应该简单地说“waiting for debugger to connect(等待调试器链接)”,然后运行应用程序
3、如果一直停留在“waiting...”屏幕下,退出模拟器和Eclipse,从命令行使用adb销毁服务器,然后重新启动。
第三步
当你编辑或者创建新标签的时候,你应该看到断点命中,执行停止。
第四步
点击Resume按钮让执行继续(在顶部的Eclipse工具栏右边带有绿色三角形的黄色矩形)
第五步
实验确认和回退按钮,试着按Home键,改为其他模式。观察生命周期时间产生了什么,什么时候触发。
ADT插件不仅提供优良的调试支持,而且卓越的剖析能力支持。你能试着使用TraceView来剖析你的应用程序。如果你的应用成运行很慢,这能帮助你发现瓶颈,并纠正问题。
教程:Notepad练习3
教程:Notepad练习3
这个练习中,你将会使用生命周期事件回调来保存和取回应用程序状态数据。这个练习说明:
1、生命周期事件和你的程序怎么能使用生命周期事件
2、维持应用程序状态的技术
第一步
导入Notepadv3进入Eclipse。这个练习的开始点正好是Notepadv2最后停止的地方。
目前这个程序有一些问题——当编辑导致崩溃时点击回退按钮,其他在编辑期间发生的任何事情将会导致编辑数据丢失。
为了修正这个问题,我们将会把创建和编辑便签的大部分功能移到NoteEdit类,针对编辑便签功能介绍一个完整的生命周期。
1、删除NoteEdit中从额外Bundle分析标题和内容的代码
作为替换,我们打算使用DBHelper类来从数据库中直接访问便签。我们需要传入到NoteEdit活动的参数只有mRowId(但是仅仅在编辑时,创建时我们不传入任何参数)。删除下面的行:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
2、我们也会除去在额外Bundle中传入的属性,这些属性用于设置UI中标题和内容文本编辑框的值。因此删除:
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
第二步
在NoteEdit类的顶部,创建NotesDbAdapter的私有属性:
private NotesDbAdapter mDbHelper;
也在onCreate()方法中增加NotesDbAdapter的实例(在super.onCreate()调用的右下面)
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
第三步
在NoteEdit中,我们需要为了mRowId检查savedInstanceState,万一正在编辑的便签包含Bundle的一个已经保存的状态,我们应该恢复这个状态(如果Activity失去焦点然后重新启动,恢复动作将会发生)
1、替换目前的初始化mRowId的代码:
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
}
为:
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
2、注意检查savedInstanceState的null值,我们仍然需要从额外Bundle中加载mRowID,如果mRowID没有被savedInstanceState提供。这是一个三元操作符的简写,以便保证不是使用这个值就是null,不管它是否满足条件。
第四步
下面,我们需要填充基于mRowID的字段:
populateFields();
这行代码在confirmButton.setOnClickListener()之前运行。我们会待会儿定义这个方法。
第五步
从onClick()处理函数中除去Bundle创建和Bundle值设置.Activity不再需要返回任何附加信息到调用者。因为我们不再有返回的Intent,我们会使用setResult()的更简版本:
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
我们会实现功能:保存修改或者新建的便签到数据库中,使用生命周期方法。
onCreate()的代码如下:
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
populateFields();
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
});
第六步
定义populateFields()方法
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor(note);
mTitleText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}
这个方法使用NotesDbAdapter.fetchNote()方法来找到编辑的便签,然后调用startManagingCursor(),这个方法是Android共用程序,负责Cursor的生命周期。在Activity生命周期指示下这个方法会释放和重建资源,因此,我们不必担心这些,做自己该做的事情。之后,我们只从游标中查找标题和内容的值,并用来填充View元素。
第七步
处理生命周期事件为什么是重要的?
如果你过去习惯于总是控制你的程序,那么你可能不理解生命周期所有事情为什么必须的。原因是,在Android中,你不能控制你的Activity,而是操作系统控制。
正如我们已经看到的那样,Android模型基于可以互相调用的活动。当一个活动调用另外一个时,目前的活动暂停,如果系统运行在低资源环境下,可能被遗弃杀掉。如果被杀掉,你的活动将不得不保存充足的状态,以便将来恢复,我们希望恢复时的状态和被杀掉的状态相同。
Android有一个定义明确的生命周期。即使你不显式控制另外一个活动,生命周期时间也能发生。例如,也许一个电话到达到话筒,如果电话到达,你的Activity正在运行,那么这个活动会被换出,而由呼叫活动替换。
仍旧在NoteEdit类中,我们重载onSaveInstanceState()、onPause() 和 onResume()方法。这些是我们自己的生命周期方法(连同我们已经有的onCreate())。
如果Activity被停止,onSaveInstanceState()被Android自动调用,在重新开始以前可以被杀掉。这意味着,应该保存任何必须的状态以保证Activity重新启动时重新初始化到同样的环境。这是到onCreate()方法的副本,实际上,传入到onCreate()的savedInstanceState Bundle是和onSaveInstanceState()的outState相同的Bundle.
onPause() 和 onResume()是免费赠送的方法。当Activity结束的时候,onPause()总是被调用,及时我们主动(例如调用finish())。我们会使用来保存当前的标签到数据库。最佳办法是释放在onPause()内部能不释放的任何资源,在被动状态占用更少的资源。基于这个原因,我们将会关闭DBHelper类,设置属性到空,以便能进行垃圾回收。另外一个方面,onResume()会重建mDbHelper实例,因此我们能使用它,然后又读取数据库的便签数据,重新填充这些字段。
因此,在populateFields()方法后增加一些空间,并且增加下面的生命周期方法:
1. onSaveInstanceState():
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
2. onPause():
@Override
protected void onPause() {
super.onPause();
saveState();
}
We'll define saveState() next.
3. onResume():
@Override
protected void onResume() {
super.onResume();
populateFields();
}
第八步
定义saveState()方法,从数据库中取出数据
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
注意:我们从creaeNote()捕获返回值,如果返回了一个有效的行号,我们保存在mRowID属性中,以便我们将来能修改便签,而不是创建一个新的便签(另外,如果生命周期时间被触发,那么创建可能发生)
第九步
现在,在Notepadv3类中从onActivityResult()中剥离出先前的处理代码
在NoteEdit生命周期内发生所有便签查询和修改操作,因此,onActivityResult()方法需要做的事情是修改数据视图,其他工作不需要。最终的代码如下
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
因为其他类做这个工作,必须做的事情是刷新数据。
第十步
也要从onListItemClick()中移去设置标题和内容的行(他们不再需要,仅仅需要mRowID):
Cursor c = mNotesCursor;
c.moveToPosition(position);
同样要移去:
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
因此剩下的代码应该是:
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(i, ACTIVITY_EDIT);
现在,你也能移去mNotesCursor属性,使用fillData()方法中的本地变量:
Cursor notesCursor = mDbHelper.fetchAllNotes();
注意:mNotesCursor中的m表示一个成员变量,因此当我们产生本地变量notesCursor的时候,我们销毁m。
运行程序!(右键菜单中,点击Run As -> Android Application)
这个练习中,你将会使用生命周期事件回调来保存和取回应用程序状态数据。这个练习说明:
1、生命周期事件和你的程序怎么能使用生命周期事件
2、维持应用程序状态的技术
第一步
导入Notepadv3进入Eclipse。这个练习的开始点正好是Notepadv2最后停止的地方。
目前这个程序有一些问题——当编辑导致崩溃时点击回退按钮,其他在编辑期间发生的任何事情将会导致编辑数据丢失。
为了修正这个问题,我们将会把创建和编辑便签的大部分功能移到NoteEdit类,针对编辑便签功能介绍一个完整的生命周期。
1、删除NoteEdit中从额外Bundle分析标题和内容的代码
作为替换,我们打算使用DBHelper类来从数据库中直接访问便签。我们需要传入到NoteEdit活动的参数只有mRowId(但是仅仅在编辑时,创建时我们不传入任何参数)。删除下面的行:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
2、我们也会除去在额外Bundle中传入的属性,这些属性用于设置UI中标题和内容文本编辑框的值。因此删除:
if (title != null) {
mTitleText.setText(title);
}
if (body != null) {
mBodyText.setText(body);
}
第二步
在NoteEdit类的顶部,创建NotesDbAdapter的私有属性:
private NotesDbAdapter mDbHelper;
也在onCreate()方法中增加NotesDbAdapter的实例(在super.onCreate()调用的右下面)
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
第三步
在NoteEdit中,我们需要为了mRowId检查savedInstanceState,万一正在编辑的便签包含Bundle的一个已经保存的状态,我们应该恢复这个状态(如果Activity失去焦点然后重新启动,恢复动作将会发生)
1、替换目前的初始化mRowId的代码:
mRowId = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
}
为:
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
2、注意检查savedInstanceState的null值,我们仍然需要从额外Bundle中加载mRowID,如果mRowID没有被savedInstanceState提供。这是一个三元操作符的简写,以便保证不是使用这个值就是null,不管它是否满足条件。
第四步
下面,我们需要填充基于mRowID的字段:
populateFields();
这行代码在confirmButton.setOnClickListener()之前运行。我们会待会儿定义这个方法。
第五步
从onClick()处理函数中除去Bundle创建和Bundle值设置.Activity不再需要返回任何附加信息到调用者。因为我们不再有返回的Intent,我们会使用setResult()的更简版本:
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
我们会实现功能:保存修改或者新建的便签到数据库中,使用生命周期方法。
onCreate()的代码如下:
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
populateFields();
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
});
第六步
定义populateFields()方法
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor(note);
mTitleText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}
这个方法使用NotesDbAdapter.fetchNote()方法来找到编辑的便签,然后调用startManagingCursor(),这个方法是Android共用程序,负责Cursor的生命周期。在Activity生命周期指示下这个方法会释放和重建资源,因此,我们不必担心这些,做自己该做的事情。之后,我们只从游标中查找标题和内容的值,并用来填充View元素。
第七步
处理生命周期事件为什么是重要的?
如果你过去习惯于总是控制你的程序,那么你可能不理解生命周期所有事情为什么必须的。原因是,在Android中,你不能控制你的Activity,而是操作系统控制。
正如我们已经看到的那样,Android模型基于可以互相调用的活动。当一个活动调用另外一个时,目前的活动暂停,如果系统运行在低资源环境下,可能被遗弃杀掉。如果被杀掉,你的活动将不得不保存充足的状态,以便将来恢复,我们希望恢复时的状态和被杀掉的状态相同。
Android有一个定义明确的生命周期。即使你不显式控制另外一个活动,生命周期时间也能发生。例如,也许一个电话到达到话筒,如果电话到达,你的Activity正在运行,那么这个活动会被换出,而由呼叫活动替换。
仍旧在NoteEdit类中,我们重载onSaveInstanceState()、onPause() 和 onResume()方法。这些是我们自己的生命周期方法(连同我们已经有的onCreate())。
如果Activity被停止,onSaveInstanceState()被Android自动调用,在重新开始以前可以被杀掉。这意味着,应该保存任何必须的状态以保证Activity重新启动时重新初始化到同样的环境。这是到onCreate()方法的副本,实际上,传入到onCreate()的savedInstanceState Bundle是和onSaveInstanceState()的outState相同的Bundle.
onPause() 和 onResume()是免费赠送的方法。当Activity结束的时候,onPause()总是被调用,及时我们主动(例如调用finish())。我们会使用来保存当前的标签到数据库。最佳办法是释放在onPause()内部能不释放的任何资源,在被动状态占用更少的资源。基于这个原因,我们将会关闭DBHelper类,设置属性到空,以便能进行垃圾回收。另外一个方面,onResume()会重建mDbHelper实例,因此我们能使用它,然后又读取数据库的便签数据,重新填充这些字段。
因此,在populateFields()方法后增加一些空间,并且增加下面的生命周期方法:
1. onSaveInstanceState():
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
2. onPause():
@Override
protected void onPause() {
super.onPause();
saveState();
}
We'll define saveState() next.
3. onResume():
@Override
protected void onResume() {
super.onResume();
populateFields();
}
第八步
定义saveState()方法,从数据库中取出数据
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
注意:我们从creaeNote()捕获返回值,如果返回了一个有效的行号,我们保存在mRowID属性中,以便我们将来能修改便签,而不是创建一个新的便签(另外,如果生命周期时间被触发,那么创建可能发生)
第九步
现在,在Notepadv3类中从onActivityResult()中剥离出先前的处理代码
在NoteEdit生命周期内发生所有便签查询和修改操作,因此,onActivityResult()方法需要做的事情是修改数据视图,其他工作不需要。最终的代码如下
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
因为其他类做这个工作,必须做的事情是刷新数据。
第十步
也要从onListItemClick()中移去设置标题和内容的行(他们不再需要,仅仅需要mRowID):
Cursor c = mNotesCursor;
c.moveToPosition(position);
同样要移去:
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
因此剩下的代码应该是:
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(i, ACTIVITY_EDIT);
现在,你也能移去mNotesCursor属性,使用fillData()方法中的本地变量:
Cursor notesCursor = mDbHelper.fetchAllNotes();
注意:mNotesCursor中的m表示一个成员变量,因此当我们产生本地变量notesCursor的时候,我们销毁m。
运行程序!(右键菜单中,点击Run As -> Android Application)
订阅:
博文 (Atom)