Mar 27, 2013

uses-sdk是AndroidManifest.xml文件中的一个元素,它用于声明当前的应用程序与各版本Android平台的兼容性,具体则是使用API Level值(整数值,目前已发展到17)作为指标。uses-sdk包含如下三个属性:

  • android:minSdkVersion
  • android:targetSdkVersion
  • android:maxSdkVersion

例如,你可以通过如下方式指定应用程序正常运行所需的最低版本为API Level 8:

<manifest>
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion = 17/>
</manifest> 

android:minSdkVersionandroid:maxSdkVersion比较容易理解,它们分别代表了应用程序所能支持的最低和最高API Level,也就是说在任何低于android:minSdkVersion或者高于android:maxSdkVersion的Android系统中运行该应用程序都会面临崩溃的可能(当应用程序访问到在当前API Level中不存在的API时)。由于Android的API演进是以递增的形式进行的,因此Android的API是向前兼容的,所以在大部分情况下都不需要考虑android:maxSdkVersion

android:targetSdkVersion则相对难理解一点,根据官方文档的说法,这个值是为了指定该应用程序所针对的目标API Level,它的默认值等于android:minSdkVersion。实际上,这个值的根本作用是指引Android系统的兼容行为(compatibility behavior),什么是Android系统的兼容行为呢?在前面已经提到Android的API是向前兼容的,即新版本的系统能够直接运行在旧版本系统上开发的应用程序,这种向前兼容的能力就是借助Android的兼容行为实现的。

通过观察Android对UI的兼容行为,可以比较具体地理清这之间的关系,Android 4.0之后版本的UI与Android 2.3之前版本的UI相比,其中的变化是十分明显的,凭借Android的向前兼容机制,在Android 2.3版本上开发的应用能够之间在Android 4.0上运行,这时候它们的UI就会受到android:targetSdkVersion值的影响。如果android:targetSdkVersion未定义,那么在Android 4.0上该应用程序的UI将会仍然保持Android 2.3的风格,而如果android:targetSdkVersion定义为14或更高(Android 4.0+),就会使得该应用程序的UI呈现Android 4.0风格,这种差异正是由于android:targetSdkVersion值可以停用Android向前兼容行为而引起的。

Android官方文档还建议开发者应当在API Level升级的同时,将android:targetSdkVersion的值更新至最新的API Level值,并且同时在最新版本的Android平台上通过测试,从而使得应用程序达到较好的用户体验。

android:targetSdkVersion还有一个作用就是可以使得应用程序可以使用高于android:minSdkVersion的API Level的API,但是开发者必须保证其对于android:minSdkVersion版本的向后兼容性(backward compatibility),即开发者可以使用相比android:minSdkVersion更高版本API中新增的特性来实现相应功能,但是必须确保应用程序运行在不能使用这些特性的系统时也能保持正常,即使用这些新特性所实现的功能是为应用程序“增色”而并非“必须”,详细地讨论可以参考stackoverflow上Steve Haley的答案,Steve以手势识别功能为例进行了说明,手势识别是API Level 7新增的API,因此对于声明android:minSdkVersion = 3 android:targetSdkVersion = 17的应用程序来说,当运行该应用程序的系统版本支持API Level 7时,应用程序就能够使用手势识别来实现快捷操作,但是在系统版本不支持API Level 7时,仍然可以通过菜单实现同样的功能。

android:minSdkVersionandroid:targetSdkVersion不一致的情况下,为了防止API访问错误,通常需要使用与如下代码类似的方式来对API Level进行判断:

总的来说,为了使得应用程序的潜在用户数量更大,我们应当使用较低的android:minSdkVersion和较高的android:targetSdkVersion,前提当然是必须充分考虑应用程序的兼容性,否则只会使得用户在无尽的程序崩溃中潸然泪下,然后到Google Play中默默地为你的应用程序送上1星评价。

Mar 25, 2013

图片资源是移动应用中较耗内存的部分,同时也是最常用的资源之一。对于涉及大量图片的应用、尤其是图片资源需要通过网络连接获取的应用,需要达到如下一些基本要求:

  • 防止内存过度消耗(java.lang.OutofMemoryError)导致程序崩溃
  • 防止由耗时操作引起的界面卡顿,使得应用程序界面在加载图片的同时保持流畅
  • 通过对图片进行缓存减少对外部存储的读取和网络流量的使用,提高图片加载效率

这里结合示例来说明如何使得应用程序满足上述要求,示例代码可以从这里得到,示例来自于Android Training Site

加载合适的图片尺寸

采用普通方法同时在应用程序中加载大量图片,就容易造成内存溢出。这很容易理解,目前一般的手机摄像头拍摄的照片分辨率能够达到500万像素以上,按每个像素4字节(RGBA各需要1字节)计算,完全加载这样一张图片就需要5*4=20兆字节的内存,对于列表视图(ListView)、网格视图(GridView)以及实现多页面滚动效果的ViewPager来说,它们都需要同时对大量图片进行处理,使用直接加载图片的方式需要使用大量的内存。

通常情况下,需要在用户界面中显示的图片往往小于它们的原始尺寸,因此可以根据具体的显示需求对图片资源进行解码,仅仅将必须的部分载入内存,从而减小内存的使用。在Android中,BitmapFactory类提供了很多方法用于将指定的图片文件解码为bitmap对象,例如decodeByteArray(), decodeFile(), decodeResource()等等方法,这些方法默认会解码完整的图片资源数据到内存中,容易造成内存资源的浪费,为此,Android提供了BitmapFactory.Options类来对BitmapFactory的解码过程进行控制,BitmapFactory.Options包含了一系列以inout开头的字段,分别用于设置解码方式和存放解码得到的结果。

inJustDecodeBounds字段值设为true可以在不将图片加载到内存的前提下获取到图片的相关信息(主要是图片的宽高),对应的解码方法不会将图片载入内存(返回null),而是将结果保存在BitmapFactory.Options中的各out字段(outHeightoutWidth)里。借助该办法可以知道需要显示的图片尺寸与用于显示该图片的区域尺寸之间的关系,从而确定解码方法的采样率,使得生成的bitmap对象能够在刚好满足显示需求的情况下不占用无必要的内存。

例如,需要显示的图片尺寸为reqWidthreqHeight,那么采样率可以通过如下方式得到:

计算得到的采样率可以通过BitmapFactory.OptionsinSampleSize进行设置,从而使得解码方法能够根据合适的采样率解码图片,从而避免内存的浪费。

在UI线程之外处理图片

当应用程序中的显示的图片不是从内存中直接获取时(例如从外部存储加载或者通过网络获取),会使得BitmapFactory中的各种decode...()方法的执行时间变得不可预测,这取决于外部存储的读取速度、网络链接的速度等等因素,如果仍然将图片的处理过程放在UI线程中进行的话,可能会阻塞用户界面,进一步使得系统将应用程序标记为无响应并弹出FC(强制关闭)对话框。为此,应该将图片处理的操作放在单独的线程中进行。

AsyncTask是Android提供的用于将耗时操作置于后台线程中执行并且将执行结果返回给UI线程的便捷类,借助它可以使耗时的图片处理过程在后台进行而不阻塞用户界面,AsyncTask十分常用,用法也相对简单,在需要显示多个图片时,可以为每一个图片的下载(或读取)操作单独分配一个AsyncTask,多个线程并发执行,使得图片能够尽快的被加载。

当使用多个AsyncTask来处理图片时,需要谨慎地处理并发。对于ListViewGridView以及类似的其他UI组件,为了提高效率,往往涉及到View的回收和再利用过程。以ListView为例,它由若干个View组成,每个View代表了ListView中的一项,ListView所包含的内容通常由ListAdapter提供,由这个Adapter提供的数据列表往往很长,例如常见的联系人列表和聊天信息列表,这些较大量的数据不能够在一个屏幕空间中同时显示,用户需要通过滚动列表的方式来浏览列表中的更多数据,这个滚动的过程就是各个View回收和再利用的过程,在理论上,系统内存中仅仅需要存在刚好能够铺满一个屏幕的数量的View,当用户向下滑动屏幕看到列表中新的一项内容时,伴随着这个操作的结果是原本处于屏幕最上方的一项内容被移出界面,这时,用于显示原本最上方一项的View就会被用于显示下方新出现的一项内容。

借助这个机制可以极大地减少系统资源的占用,但是随之而来的是并发性问题,以此处讨论的图片加载问题为例:当用户界面处于某个状态A,这个状态触发了5个AsyncTask(A~E)分别为5个列表项(A~E)加载5张图片,每个AsyncTask在运行完成时会更新其对应的列表项中的图片内容,假定AsyncTaskA对应更新的列表项为ItemA,当用户在AsyncTaskA尚未执行完成时滑动了屏幕,使得ItemA被移出界面同时新的列表项ItemF被显示,与它对应的将会由一个新的AsyncTaskF来为其加载图片,由于ListViewView的回收,此时ItemF对应的View实际上就是之前ItemA对应的View,如果没有进行额外的处理,此时AsyncTaskAAsyncTaskF的更新目标将会是同一个View,此时AsyncTaskFAsyncTaskA较早完成,将会导致列表图片显示出现错误,即ItemF将会显示图片ImageA(即本应该显示在ItemA中的图片)。

为了避免上述错误发生,需要将AsyncTaskImageView建立起正确的对应关系,使得AsyncTask在必要的时候能够判断原先与之对应的ImageView是否仍然需要它去更新,为了使ImageView能够保持对当前有效的AsyncTask的引用,可以通过如下方式实现:

这里通过继承BitmapDrawable实现了可以保存对AsyncTask对象的引用的AsyncDrawable类,现在,就可以使用ImageView.setImageDrawable(asyncDrawable)方法来使得ImageView能够保持对当前有效的AsyncTask的引用了。借助于AsyncDrawable,可以通过如下方式获取到与某个ImageView关联的AsyncTask

同样地,还需要在AsyncTask中保持对ImageView的引用,只需要将ImageView作为参数传递给AsyncTask即可。

现在,已经能够确定ImageViewAsyncTask的正确对应关系,当一个AsyncTask完成时,可以通过比较之前传入的ImageView在当前时刻关联的AsyncTask是否等于自身来确定是否应该将结果返回给ImageView

通过如上的一些处理,可以防止由多个并发的AsyncTask造成的问题,但是仍然存在一个问题,就是当用户滑动ListView时,会造成许多没必要的AsyncTask在后台执行,虽然已经不会造成图片的显示混乱,但是会浪费不必要的计算资源,为此,可以在每次为ImageView新建图片加载任务时,首先获取当前与此ImageView相关联的AsyncTask,然后判断该AsyncTask是否与新建的图片加载任务执行的任务内容一致,如果不一致则终止此次任务,如果一致(当用户来回滑动界面时可能出现这种情况)就不需要再新建AsyncTask了。

增加缓存

借助前文所述的两种处理图片的方法,已经可以很好地降低应用程序的内存占用,对于只涉及到少量图片的应用程序来说已经足够,但是对于使用到ListViewGridViewViewPager这些类来实现功能的应用程序来说,它们需要同时考虑当前屏幕空间和相邻屏幕空间上需要显示的图片,这往往意味着需要对大量图片进行加载,对于用户经常会进行的来回滚屏操作,由于前面提到过的子视图回收和重利用机制,会造成需要进行大量的重复加载相同图片的任务,这时候应该借助缓存来提高图片加载速度,防止大量重复任务的发生(重复从网络下载图片、重复从外部存储读取图片)。

使用内存空间缓存图片

通过牺牲一定大小的内存空间作为缓存使用,可以极大的提高图片加载效率,在Android中,可以使用LruCache类(LRU,最近最少使用)来实现这种缓存。使用LruCache必须在初始化时指定为其分配的内存空间大小,这个大小应当根据具体的应用场景、设备屏幕类型、设备内存大小等等因素综合考虑得到。初始化LruCache的方法如下:

在对LruCache进行初始化之后就可以通过如下方式进行存取了:

要使用缓存,只需要在加载图片操作之前先访问缓存,通过具体的key值查询缓存中是否存在需要加载的图片对象,如果存在则直接从缓存获取图片,从而加快图片的加载效率。

使用外部存储缓存图片

对于图片资源来自于网络的应用,仅仅依靠内存来对图片进行缓存是不够的,因为应用程序的内存存在不确定性,随时可能被系统释放(例如用户将应用置于后台运行),为了防止由于内存缓存的失效而导致的对网络资源的重复访问,可以借助外部存储进行缓存,即将图片文件作为文件缓存到外部存储中,虽然位于外部存储中的缓存读取速度较慢,但是它具有比内存更高的稳定性。

要实现基于外部存储的缓存,可以借助DiskLruCache类,这个类并不存在于Android API中,可以在Android的源码中得到,它的使用方法与LruCache相似,需要为其指定一个文件路径来放置缓存文件,同时也可以指定其缓存空间大小。与LruCache不同之处在于需要对DiskLruCache的访问进行同步控制。

Jul 17, 2012

最近由于需要建立一个团队内部的资源和信息发布类的网站,尝试着开始使用了Drupal,虽然之前很早就对Drupal有所了解,但是一直没有去真正的接触,抱着一个好奇的态度花了几天的时间来了解了一下Drupal,不看不知道,一看吓一跳,由于Drupal不仅仅是面向程序员使用的原因,它的有些文档显得过分详细以致于你会觉得文档的“质量低”,事实上并不是这样,因为Drupal社区要照顾到它各类不同用户的需求,所以这些文档所处于的层次,所面向的群体是不一样的,不论你是一名开发者或者仅仅是想使用Drupal作为建站工具。对于一名强迫症患者、害怕错过每一处关键的信息,所以对阅读文档中遇到的每一个引用链接都会打开的我,Drupal文档又让我感受到了什么叫“群众的力量是无穷的”,不过我相信在学习一个东西的开始阶段,这样的投入还是值得的,因为我深切的感受到了在不断阅读文档的过程中,很多的疑惑都逐一地解开,很多关键点都在心里慢慢地被加深了认识,例如Module是如何工作的、theme是怎么实现的、怎样在已有theme的基础上进行二次开发、开发(使用)Drupal分为哪几个阶段(入门级,熟练级,专家级等)或者说哪几类使用方法(系统/网站管理员、网站信息系统架构员、网站开发人员等)等等问题,还有Drupal中比较重要的概念例如hooks,以及使用Drupal的一些快捷方式(DistributionInstallation profilesSite Recipe)等等。在这里对我学习的过程做一个简单记录,顺便作为备忘。

1. 初识Drupal

对于初学者,大部分都会由首页跳转到Drupal start这个页面,这个页面可以说是Drupal的一个较为全面的一个概览,包括了下载安装、如何扩展和使用Drupal、在哪里可以找到Drupal文档以及如何从Drupal社区获取帮助的信息,依次会浏览到如下页面:

  • Drupal Core下载页面
  • Drupal安装说明,对于初学者通常是希望在本地建立起一个服务器,因此需要浏览Local server setup,我选择了在Windows上安装DAMP(即Drupal, Apache, MySQL, and PHP,是由Acquia提供的一个Drupal Stack Installer),根据这个页面上的指引可以顺利的在Windows上安装好本地的Drupal服务器环境。安装成功之后就可以将Drupal Core运行起来,得到一个可以在本地访问的Web服务器,Drupal Core可以算作是Drupal的Hello world,虽然看上去很简单,但是它已经包含了扩展Drupal所需要的所有核心代码和模块,接下来要做的就是向其中添加或开发具有更丰富功能的Modules,以及为其选择和开发合适的Theme。

2. 使用Module扩展Drupal

在将Drupal Local Server成功地搭建起来之后,就需要开始考虑使用Module来扩展所需的功能了,Drupal自2001年上线以来,经过10多年的发展,已经形成了一个十分强大的社区,超过630000的用户和开发者正在使用Drupal开发网站,Drupal的Module数量也已经突破了10000大关,使得Drupal变得越来越强大,即使用户没有很丰富的编程经验也可以利用其他贡献者的成果来实现满足自己需求的网站,在Modules的页面上可以根据需求搜索Module,对于初学者则建议先了解一下使用量排在前几位的Module,一般来说这几个Module在任何网站中都是需要的,通过对它们的了解和使用,可以进一步明白Module的工作原理以及使用方法。我就从View这个Module入手,阅读其相关文档,同时了解了一些必须的概念。

3. Drupal Themes

网站的功能固然重要,但是网站的外观同样不可忽视,还是那句话,用户看到的永远只是用户界面,因此必须要满足用户的视觉需求,Drupal Theme就是用来实现网站的视觉效果的,在Drupal的Themes页面上目前已经存在接近1000个不同的Theme,虽然数量不如Module那般大,但是值得注意的是很多Theme不仅仅是一个简单的主题,它还是一个主题开发框架,例如列在第一位的Zen theme,在它的基础上能够非常方便的进行二次开发,推荐通过Zen作为切入点去仔细了解如何进行theme的开发工作。

4. 深入理解Drupal

在经过前面几步对Drupal有了一个直观上的认识之后,下一步需要是系统化的了解Drupal,这个过程是比较枯燥乏味的,要系统化的了解Drupal,就应该跟随Drupal的文档一步一步的进行了,尤其是Understanding Drupal文档,需要仔细的阅读一遍,从内部架构了解Drupal,对进行更深入的开发有非常重要的作用。

Jun 12, 2012

今天听了Martin Fowler的演讲,虽然讲的内容并不多,但是讲的都是当今软件开发中最重要的技术和原则,这些都是这个时代的程序员所应该熟知的,在此简单地做一个总结。

1. NoSQL Models

由于目前的应用数据存储对分布式、可扩展性方面的需求,NoSQL作为新一代的数据库组织方式逐渐被人们放上台面,所谓NoSQL,并不应该被简单的理解为"No" SQL,而更应该被理解为“Not only SQL”,它通常具有非关系型、分布式、开源和水平可扩展等几个特性。

与关系数据库通常遵循的是ACID(atomicity-原子性, consistency-一致性, isolatio-隔离性, durability-持久性)不同,NoSQL所遵循的是BASE(Basically Available-基本可用, Soft state-软状态, Eventually consistent-最终一致性),这些特性正是与当前分布式存储、云计算领域的需求相符合的。具体的不同可以参照这篇文章

2. REST

REST即Representational State Transfer,通常被译为“表述性状态转移”,这是一个用于分布式系统的软件体系结构,最大的一个应用当属我们的万维网(World Wide Web)了,REST在最近几年已经逐步成为了Web service的主要设计模式,通常我们说一个Web Service的API是RESTful的,实际上就是指它具备了REST的特性,以Web为例,REST具有如下一些特性:

  1. 网络上所有的事物都被抽象为资源(resource);
  2. 每个资源对应一个唯一的资源标识(resource identifier);
  3. 可以通过通用的连接器接口对资源进行操作(generic connector interface);
  4. 对资源的各种操作不会改变资源标识;
  5. 所有的操作都是无状态的(stateless)

按Martin所述,按照Richardson Maturity Model,REST按程度不同通常如下四个阶段:

rest-overview 图1. Steps toward REST

  1. Level 0:

    最初级的阶段,将HTTP当作一个可以进行远程交互的通讯系统来使用,在这个阶段不会使用到web的任何机制,而仅仅将HTTP作为远程交互机制。 以一个示例来简单地说明:如果我们需要通过web预约一位医生,通常会经历如下步骤:

    • POST /appointmentService?data=2010-01-04&doctor=mjones 获取到在2010年1月4号MJones医生的所有可预约时间段,我们会得到服务器响应传回的一个列表
    • POST /appointmentService?data=2010-01-04&doctor=mjones&start=1400&end=1450 请求预约2010年1月4号MJones医生从14点到14点50这个时间段,服务器会根据预约是否成功返回响应
  2. Level 1 - Resources:

    在这个阶段,API的设计开始以Resource(资源)为中心,现在将不通过参数的形式向appointmentService提供数据,而是将医生、医生的可预约时间段都视为资源,这时预约医生将变为如下形式:

    • POST /doctors/mjones 将返回MJones可预约时间列表
    • POST /slots/1234 预约编号为1234的时间段,这个id已经对应了一个唯一的医生->时间段资源

    可以感受到,现在的调用方式已经具备了“对象”的感觉。

  3. Level 2 - HTTP Verbs:

    这个阶段开始使用不同的HTTP Verb来对资源进行不同的操作,HTTP Verbs包括GET/POST/PUT/DELETE/HEAD/TRACE/CONNECT等,其中最常用的是GET/POST/PUT/DELETE,它们可以被理解为数据库的READ/CREATE/UPDATE/DELETE操作(但是Martin在文中指出这样的类比是不正确的)。这时候,可以通过:

    GET /doctors/mjones/slots?date=20100104&status=open

    的形式来获取列表,需要注意的是,使用GET方法来请求数据在Level 2中是关键,因为HTTP将GET方法定义为safe operation(安全操作),即GET操作不会对任何资源的状态造成影响,可以说GET操作是幂等的,因此也是安全的,无论我们执行多少次GET方法,我们得到的结果总是一致的,这个特性也使得我们可以更大的发挥caching的作用来提高对HTTP请求响应的效率,HTTP包括了许多的方式来支持caching,遵循这些规则我们能够更好的利用HTTP的性能。如果要预约医生,就需要使用能够改变资源状态的操作,POST或者PUT都能够改变资源的状态,可以通过Level 1中相同的方式:

    POST /slots/1234

    来完成对医生的预约。 另外,在Level 2中使用了HTTP response code来表示对资源操作的结果,例如201(Created)409(Conflict)

  4. Level 3 - Hypermedia Controls

    这是REST的最终阶段,通常被描述为HATEOAS(Hypertext As The Engine Of Application State),它的意思是,在我们取得了可预约的slots列表之后,能够从响应中知道应该如何进行下一步操作(预约)。通常的RESTful API的每个操作都是独立的对于某个Resource所进行的,如果需要知道在获取了slots列表之后如何进行预约操作通常需要借助于API文档。而Hypermedia Control所要做的就是在服务器的响应中,将会包含进行下一步操作所需要的信息。

继续前面的示例,在这种情况下,预约一位医生的操作所涉及的内容如下:

  • 通过GET /doctors/mjones/slots?date=20100104&status=open可以获取到服务器响应类似如下内容:

    HTTP/1.1 200 OK
    [various headers]
    
    <openSlotList>
      <slot id = "1234" doctor = "mjones" start = "1400" end = "1450">
         <link rel = "/linkrels/slot/book" 
               uri = "/slots/1234"/>
      </slot>
      <slot id = "5678" doctor = "mjones" start = "1600" end = "1650">
         <link rel = "/linkrels/slot/book" 
               uri = "/slots/5678"/>
      </slot>
    </openSlotList>
    
  • 如上第6、7、10、11行的link标签内的信息就是新增的用于表达如何进行预约操作的信息。它告诉我们可以通过/slots/1234这样的url来进行预定操作,于是,通过POST /slots/1234可以进行预约,服务器将会发回如下信息:

    HTTP/1.1 201 Created
    Location: http://royalhope.nhs.uk/slots/1234/appointment
    [various headers]
    
    <appointment>
      <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>
      <patient id = "jsmith"/>
      <link rel = "/linkrels/appointment/cancel"
            uri = "/slots/1234/appointment"/>
      <link rel = "/linkrels/appointment/addTest"
            uri = "/slots/1234/appointment/tests"/>
      <link rel = "self"
            uri = "/slots/1234/appointment"/>
      <link rel = "/linkrels/appointment/changeTime"
            uri = "/doctors/mjones/slots?date=20100104@status=open"/>
      <link rel = "/linkrels/appointment/updateContactInfo"
            uri = "/patients/jsmith/contactInfo"/>
      <link rel = "/linkrels/help"
            uri = "/help/appointment"/>
    </appointment>
    

Hypermedia controls的优势在于,服务器可以在不破坏客户端应用程序的情况下对自己的URI schema进行改变。如上例所示,客户端只需要去查阅addTest连接即可,服务端可以随意地对除了最初入口点(slots/1234)之外的所有URI进行改变。

3. DSL

所谓DSL,全称Domain-specific language,即“领域特定语言”,一个简捷的定义是:一种专注于某个特定领域的、仅具有有限的表述能力编程语言。按我的理解来说就是专注于某类功能或逻辑的实现,并且在该部分领域拥有极高的表达力和执行效率的语言。这类语言在实际应用中也是很常见的,例如正则表达式、CSS、makefile/ant/rake、SQL/HQL,甚至用于编写struct-config.xml文件的语法也可以被认为是一种DSL。基于DSL的定义,一般来说我们并不能够使用某种DSL去实现一个完整的项目(谁会只用正则表达式去写一个项目呢XD),而是在一个项目中使用一种通用编程语言并且结合使用若干DSL。事实上,DSL也是通过所谓通用编程语言来实现的,这实际上也是“不要自己从头再实现轮子”思想的体现,借助DSL我们可以在一个更高的层次上编程,使得代码更紧凑,从而获得更高的效率,DSL就好比是一个被精心制造出来的轮子。DSL目前在Ruby社区非常的火爆,借助Ruby开发人员可以更方便地去实现一种DSL,例如可以用来更高效地写css代码的SASS

4. Event Sourcing

Event Sourcing从字面上看就是“事件溯源”的含义,通俗地说就是“记录你所走的每一步”,这是什么样的一个概念呢?最重要的一点实际上就是我们可以将程序的状态(动态)保存到磁盘(静态),这样一来我们就不容易丢失程序的某个状态,因为我们可以通过日志文件将状态恢复,最常见的应用就是源代码版本控制系统了(Subversion、Git、mercurial等),当然还包括了记录数据库变迁的DB Migration追踪系统,进一步的遵循了Event Sourcing原则,将数据库的改变也纳入版本控制中,这样一来我们就可以自由的在程序的多个状态中切换。

Apr 18, 2012

什么是Smack

Smack is a library for communicating with XMPP servers to perform real-time communications, including instant messaging and group chat [1]. Smack是一个用于与XMPP服务器通讯的Java类库,它使得我们可以方便的通过XMPP协议来进行即时通讯,群聊等。

新浪微博的即时通讯功能也是基于XMPP协议实现的,因此我们可以通过任何支持XMPP协议的客户端来连接它,从而使用它来与新浪微博中互相关注的好友进行聊天,这些聊天记录也会以私信的方式保存在新浪微博里。新浪微博的XMPP服务器地址是xmpp.weibo.com,端口是缺省的5222端口,用户名可以是你注册的邮箱或者是你的UID,可以到JWChat测试一下连接。

对于通常的聊天客户端来说,用户一般都需要使用到“隐身(invisibility)”状态,即自身对于其他用户是离线的状态,而自身却依然能够接收到其他用户发来的消息。在Smack中用户通过向服务器发送Presence包来设置自己的状态,通过发送这个数据包可以向服务器发送一些与用户状态相关的请求,设置Presence的Type属性可以用于说明该Presence包的用途(包括上线(available)、下线(unavailable)、请求订阅其他用户的状态(subscribe)、允许其他用户订阅自己的状态(subscribed)等等),通过设置Presence的Mode属性用于说明自己当前的状态(包括了在线(available)、离开(away)、等待聊天(chat)、忙碌(dnd)、长时间离开(xa)等状态),在Smack的较早版本中的Presence是可以直接设置为隐身(invisibility)的,然而这却违反了XMPP RFC,因此在2006年的6月就已经将这个隐身状态从Smack API中移除了[2]

XMPP RFC认为隐身实际上是一个不必要独立存在的一个状态,因为在实际应用时,只要你不给你的其他联系人发送available类型的Presence,那么你的联系人就是不会看见你在线的,也就是达到了和隐身相同的效果,XMPP文档XEP-0126对于这种隐身功能的实现做了十分详细的说明。

那么在Smack中如何实现隐身状态呢,事实上有一种简单的实现办法,虽然不确定它的完善性,但是还是在一定程度上可用的。在这里对这种简单的实现做一个描述,可用性在Android平台上进行了验证。代码片段如下:

其中,case 3就是用于设置隐身的代码,设置隐身时,首先获取到当前的联系人列表,然后向联系人逐个发送“unavailable”状态,然后再向自己它处登录的客户端发送“unavailable”状态,而由于没有向服务器发送unavailable的状态,因此还是能够接收到联系人发送过来的消息,从而实现了隐身状态。

Dec 18, 2011

一个人在经历了一定的时间之后,他终究是会醒悟的。

在一个人觉醒之前,他会觉得他总是正确的,他认为他总能够看清楚世间万物的道理。当他看到眼前所经历的一件事情之后,就会在脑子里对其进行分析和假定,然后给这件事情中涉及的人和事都加上自己的注解,他并不关心这些理解是否正确,因为他坚信自己的想法一定是正确的。他可以把自己与世间万物独立开来,站在上帝的角度来看待世界。他把自己视作hypervisor,以为自己已经掌握了所有心理学的奥妙,殊不知其实他连自己的心理状态都没有思考清楚。不过通常情况下他在大部分时候还可能是对的,因而就更加坚定了他站在上帝所处的制高点的信心,其实那些只不过是一个正常人都具备的基本的分析判断能力罢了。虽然偶尔发生的状况可能会稍稍地打击一下他,但是他也总是能够找到为自己开脱的理由。

如果缺乏一定的目标来驱动,没有一个正确的标准来衡量自己的行为,那么一个人在一段时间内总会认为自己当前所做的事情是最合理的,即使在别人看来那是非常的不合理。也许在之后的某个时刻他自己也会发现其实现在这个时刻他所做的事情是无意义的,那是因为他已经觉得在之后那个时刻他正在做着他认为最合理的事情,所以说一个人总是处在一个自认为“最合理”的状态,因而会出现一些旁人看来异常顽固或者执迷不悟的行为,但是他本人却从未发觉,即使发觉了也是对别人的看法不屑一顾。这也许就是心理学上说的人的自利倾向吧。

这个世界并不存在什么绝对的对与错,它们是相对的,这取决于你用什么标准来衡量成功与失败。用正确的方法达到正确的目标固然是一种理想的方式,然而我们经常也会遇到用正确的方法做了错误的事的情况,甚至可能遇到用错误的方法却做了正确的事。这取决于你所处的环境、事物的发展趋势也可以理解为运气,而你自认为的努力从某种意义上来讲,可能是有助于你做正确的事,也可能恰恰是阻碍你做正确的事的原因,因为这个努力在未看结果之前,不能够确定到底是正方向上的还是反方向上的。

不要说你对你做过的某件事从不后悔,因为那只是你还没有醒悟,也就是常常说的拿无知当个性。但是这种醒悟不是简单地通过别人的帮助就能够实现的,这也无可厚非,没有人比你自己更了解自己,你所表现出来的仅仅是你的一部分而已,甚至是你伪装出来的一部分,有一些部分是你不愿意被别人看到的因此你会选择将它们隐藏起来,而还有一部分是甚至你自己都未曾认识到的,就好像形容一个人“大智若愚”一样,一个人的智或者愚的特征并不是那么分明的,也许你真的很愚,但是别人却以为你是一种超越了智慧的一种愚,而如果连你自己也深信自己确实不愚的话,那么事实上就好像真的是这样了。在这种状态下,没有人能够帮助你醒悟,除了你自己。

你并不是你想象中的那么特别,你只不过是芸芸众生中的一员罢了,你认为自己特别,别人同样认为自己特别,这种“自认为特别”的感觉实际上本身就不是一种特别。通常情况下,如果你有能力去完成一件事情,你可能会觉得你能把这件事完成得最好,至少会包含那么一点你认为“特别”的地方,但是事实是有更多的人又能够比你完成得更好,并且比你更特别,甚至让你觉得你的那个“特别”不过是一件丑陋的装饰罢了。别人有能力完成一件事情时,你又通常会觉得你有能力完成得比他更好,虽然大部分时候你都不会去验证这个想法,但是你确实会这样想。

Dec 8, 2011

这两个概念非常容易搞混。这里提供一些线索供大家参考,分清楚两者之间的关系。

Clue 1:简要回答

Q:What is the difference between point to point and peer to peer?

A:Point to point is a protocol for communication between two computers using a serial interface(phone).

Peer to peer is a communication model in which each party has the same capabilities and either party can initiate a communication session.

Clue 2:概念简介

PPP(Point-to-Point Protocol)

  • 是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。

P2P

  • 是一种技术,但更多的是一种思想,有着改变整个互联网基础的潜能的思想。

  • 是peer-to-peer的缩写,peer在英语里有“(地位、能力等)同等者”、“同事”和“伙伴”等意义。这样一来,P2P也就可以理解为“伙伴对伙伴”的意思,或称为对等联网。目前人们认为其在加强网络上人的交流、文件交换、分布计算等方面大有前途。

  • 简单的说,P2P直接将人们联系起来,让人们通过互联网直接交互。P2P使得网络上的沟通变得容易、更直接共享和交互,真正地消除中间商。P2P就是人可以直接连接到其他用户的计算机、交换文件,而不是像过去那样连接到服务器去浏览与下载。P2P另一个重要特点是改变互联网现在的以大网站为中心的状态、重返“非中心化”,并把权力交还给用户。 P2P看起来似乎很新,但是正如B2C、B2B是将现实世界中很平常的东西移植到互联网上一样,P2P并不是什么新东西。在现实生活中我们每天都按照P2P模式面对面地或者通过电话交流和沟通。

Clue 3:层次对比

Point-to-Point是一种transmission technology,是在通信领域的范畴上来讲的,和Broadcast links构成两种“widespread use”。

而Peer-to-Peer(P2P)是一种网络模型或者说是一种"architechture","distinguishing from the Client-Server model",指的是一种对等进程间的通信,服务器和客户机共存于一个节点,这个概念定义在一个更高的级别上,当“transmission technology”实现以后,也就是计算机通过网络联在一起以后才能有通信,从这个意义上说这两个概念有一个先后的问题。

Clue 4:层次对比

point to point是相对于broadcast的一种网络技术分类形式,而peer to peer 指的是网络中的对等模型,peer to peer communication属于person to person communication中的一种网络应用,而point to point是指网络连接的方式。

broadcast与point to point是指传输的基本设施,即如何安排连接的链路,表明的是一种拓扑关系,而P2P是基于这种基本设施上的传送行为。低层基础设施(Infrastructure)用point to point网络,高层应用(Application)可以用p2p模式;低层基础设施用broadcast网络,高层应用同样可以用p2p模式;同样的,低层基础设施用point to point网络,高层应用可以用C/S模式;低层基础设施用broadcast网络,高层应用同样可以用C/S模式。

Clue 5:引申知识

基础设施上的传送行为,或曰传送操作模式(分三类,即Broadcasting、Multicasting和Unicasting),这是介于上述两个层面之间的又一个层面的问题:

  • 在Broadcast networks和Point-to-point networks上,都可以Unicasting,这一个发送者和一个接收者之间,从应用的层面来看,既可以是不对等的关系(即采用C/S模式),也可以是对等的关系(即采用p2p模式);

  • 在Broadcast networks和Point-to-point networks上,都还可以Multicasting(或Broadcasting),这一个发送者和多个(或其它所有)接收者中的每一个接收者之间,从应用的层面来看,同样是既可以采用C/S模式,也可以采用p2p模式。

Clue 6:PPP引申

点到点(网络拓扑):

  • 永久连接(专用的):两个端点之间的网络是永久性的连接的,就像小时候学习物理时的“绳子电话”一样。

  • 交换网络:借助于电路交换和分组交换技术,点对点的连接可以动态的建立,并且在不需要该链接时自动断开。即经典的电话交换网模型。

点对点(协议):

  • 互联网协议族的一部分,工作在数据链路层。它通常用在两节点间建立直接的连接,主要利用串口线来连接两台计算机,现在也有用在宽带计算机连接上。很多ISP使用PPPoE(over Ethernet)给用户提供接入服务。

Clue 7:P2P引申

peer-to-peer, 简称P2P,又称对等互联网络技术,是一种网络新技术,依赖网络中参与者的计算能力和带宽,而不是把依赖都聚集在较少的几台服务器上。请注意与point-to-point之间的区别,peer-to-peer一般译为端对端或者群对群,指对等网中的节点;point-to-point一般译为点对点,对应于普通网络节点。P2P网络通常用于通过Ad Hoc连接来连接节点。这类网络可以用于多种用途,各种文件共享软件已经得到了广泛的使用。P2P技术也被使用在类似VoIP等实时媒体业务的数据通信中。

纯点对点网络没有客户端或服务器的概念,只有平等的同级节点,同时对网络上的其它节点充当客户端和服务器。这种网络设计模型不同于客户端-服务器模型,在客户端-服务器模型中通信通常来往于一个中央服务器。

有些网络(如Napster、OpenNAP,或IRC @find)的一些功能(比如搜索)使用客户端-服务器结构,而使用P2P结构来实现另外一些功能。类似Gnutella或Freenet的网络则使用纯P2P结构来实现全部的任务。

一般报章都称P2P是点对点技术,但其实是错的,实为解作群对群(Peer-to-Peer)。在虚拟私人网络VPN (Virtual Private Network)中,也有P2P这个名称,它才是真正解作点对点(Point-to-Point)。

依中央化程度

  • 纯P2P——Gnutella

    设想一个大的由用户(称为“节点”)组成的环,每个节点都有Gnutella客户端软件运行。当初始启动时,客户端软件必须进行自举(Bootstrapping)并找到至少一个其它节点,有多种不同的方法可以达到这一功能,包括软件内置的一组正在工作的已经存在的地址列表,Web缓存的已知节点更新(称为 GWebCaches), UDP服务器缓存以及IRC。一旦连接上,客户端就会请求一张活动地址列表。

    当用户想要进行搜索时,客户向每一个活动联接节点发送请求。在历史上(协议0.4版本),一个客户的活动联接节点数十分小(大约是5),所以每一个收到请求的联接节点都会再向其自身的所有联接节点转发该条请求,如此继续下去,直到该请求数据包在网络中被转发的“跳数”超过一个预先设定的数值(最大为7)。

    到了0.6版之后,Gnutella网络中的节点被划分为叶节点(leaf nodes)与超节点(ultra nodes 或 ultrapeers)。 每个叶节点仅与少数(一般为3)超节点连接,而每一个超节点与多于32个的其它超节点相连。在这种更高的出度(outdegree)下,先前提到的一条查询在网络中能达到的最大“跳数”被降低到4。

    叶节点与超节点利用查询路由协议(Query Routing Protocol)来交换查询路由表(Query Routing Table (QRT))。叶节点将它的QRT发送到每一个与之连接的超节点,超节点随后将每一个与之相连接的叶节点传来的QRT以及其本身的QRT合并,并且将其与自身的邻居节点交换。

    在实际中,这种在Gnutella网络中的搜索模式是十分不可靠的。由于每一个节点都是一台普通的计算机用户,他们经常连接或者断开网络,所以整个Gnutella网络结构永远都不是完全稳定的。Gnutella网络搜索的带宽消耗也是随着连接用户的增加而指数递增的[1],经常饱和的连接会导致较慢的节点失去作用。因此,搜索请求在网络中会被经常丢弃,与整个网络相比,大多数的查询只会到达其中的很少一部分节点。

  • 杂P2P——Napster

    正因为其是杂的P2P,招致了音像界对其大规模侵权行为的非难。在法庭的责令下该服务已经终止,它却给点对点文件共享程序——如Kazaa,Limewire和BearShare——的拓展铺好了路,对这种方式的文件共享的控制,亦变得愈加困难。

  • 混合P2P——Skype

    同时含有前面两种P2P的特点。Skype软件会在电脑上打开一个网络连接端口来监听其他Skype用户的连接调用;当其他电脑能顺利连接到这部电脑,Skype称呼该用户为“Super node”(超级节点)。Super Node在该P2P环境中的角色,即为提供其他无法连接的用户的之间的中继站,借用诸多Super Nodes的些许网络带宽,协助其他的Skype用户之间能够顺利的互相联系。这种行为,在P2P环境中,这算是相当常见的手法,也是点对点连接的精髓之一。Skype是第一个将此种做法运用到网络语音通话与实时消息应用层面上。

    Skype在台湾是与网络家庭(PChome Online)合作,推出的Skype称为PChome & Skype。在中国大陆,Skype与TOM集团旗下北京讯能网络有限公司TOM在线合作,所推出的Skype又称为TOM & Skype。但是注意,由于中共政府的存在,TOM & Skype被修改为明文传输以便于政府监控公民。在香港,Skype与和记环球电讯合作,推出的Skype称为HGC-Skype。在日本则与Buffalo和Excite合作。

    2011年5月10日,微软宣布以85亿美元现金并购了Skype。 2011年10月14日微软宣布完成这项85亿美元现金并购交易程序,Skype 首席执行官 Tony Bates 转任微软Skype事业部总裁

依网络拓扑结构划分

  • 结构P2P

    点对点之间互有连结信息,彼此形成特定规则拓扑结构。需要请求某资源时,依该拓扑结构规则查找,若存在则一定找得到。如Chord、CAN。

  • 无结构P2P:

    点对点之间互有连结信息,彼此形成无规则网状拓扑结构。需要请求某资源点时,以广播方式查找,通常会设TTL,即使存在也不一定找得到。如Gnutella。

  • 松散结构P2P: 点对点之间互有连结信息,彼此形成无规则网状拓扑结构。需要请求某资源时,依现有信息推测查找,介于结构P2P和无结构P2P之间。如Freenet。

Nov 29, 2011

KVM

KVM(即Kernal-based Virtual Machine)是工作在包含虚拟化扩展支持(Intel VT或者AMD-V)的x86架构上的Linux的一套完整的虚拟化解决方案,它是Linux kernel的一部分,Linux kernel通过KVM来提供虚拟化的功能。使用KVM可以在一台物理机上同时运行多个无需修改的Linux或者Windows系统,并且每一个系统都拥有一套私有的虚拟硬件。

QEMU

QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。它与Bochs,PearPC近似,但其具有某些后两者所不具备的特性,如高速度及跨平台的特性。经由kqemu这个开源的加速器,QEMU能模拟至接近真实电脑的速度。QEMU有两种主要运作模式:

  • User mode模拟模式,亦即是使用者模式。QEMU能启动那些为不同中央处理器编译的Linux程序。而Wine及Dosemu是其主要目标。
  • System mode模拟模式,亦即是系统模式。QEMU能模拟整个电脑系统,包括中央处理器及其他周边设备。它使得为系统源代码进行测试及除错工作变得容易。其亦能用来在一部主机上虚拟数部不同虚拟电脑。

目前的Android模拟器就是基于QEMU开发的。

KVM与QEMU

由于KVM仅仅是为上层提供虚拟化的支持,它本身并不进行任何的创建或管理虚拟机的工作,而是通过一个用户空间的程序通过调用/dev/kvm接口来设置客户虚拟机的地址空间、管理I/O以及将视频显示映射回主机。QEMU就是一个可以完成这些工作的程序,可以说KVM就是借助QEMU的一部分功能来实现具体的虚拟机。

KVM与Xen

由于Xen、KVM都是应用在Linux虚拟化上的技术,因此有必要弄清楚它们之间的关系。Xen是一个直接工作在硬件层之上的系统,它是一个基于Nemesis微内核的hypervisor。当前各Linux发行版所包含的Xen,表面上看似乎是Xen跑在Linux系统上,实际上却是Linux跑在Xen上,只是这类发行版默认安装了一个Linux guest作为domain0,这让用户很容易产生错觉,以至于大多数的用户根本没有意识到他们正在运行一个完全不同的操作系统。

对于Linux最开始为什么要包含Xen,Linux的开发者ANTHONY LIGUORI称这主要是由于Linux社区的绝望。因为虚拟化在最近一段时间逐渐成为了一个很热门的技术。但是Linux过去却没有提供任何的native hypervisor的能力。大多数的Linux kernel开发者对虚拟化技术知道得也不多,因此Xen很容易的使用了一个定制的kernel,并且这个kernel还有一个相当好的community。于是Linux社区做了一个决定:包含Xen到发行版中。LIGUORI称这个决定是草率的,因为他认为正确的做法是把Linux变成一个合适的hypervisor,而不是将这个功能寄托在Xen上,他也认为其他的系统也应该像Linux这样对待这个问题。因此,可以将KVM理解为由Linux内核直接提供的对虚拟化的更好的(Linux社区有足够的自信)支持。

其他

  • Xen——一个完整的系统,通常使用定制的Linux发行版作为domain0
  • virtualbox——它的部分代码由QEMU修改而来,拥有自身的图形加速
  • 一套可以运行的KVM通常包括qemu-kvm和kvm-kmod两部分
  • Xen属于半虚拟化的范畴,半虚拟化指客户机操作系统是经过定制的,客户机知道自身工作在虚拟的环境中,而全虚拟化则相反
  • hypervisor,又叫VMM(Virtual Machine Manager),它相对于supervisory program(也成为内核)而言,内核的作用是负责分配计算机资源,调度,I/O等,而hypervisor处在比supervisory更高的层次上。
  • hypervisor分为两类,即Type1 和 Type 2:1类的hypervisor直接运行在裸机上,相当于hypervisor是硬件上的第一层;2类的hypervisor则是运行在传统的操作系统之上,即hypervisor是属于第2层。KVM和Xen on Linux都是属于1类,不过Xen却伪装得很像2类。