[转载]InfoQ: Android赢家密码的观点与实践——以软硬整合开发为例

mikel阅读(755)

[转载]InfoQ: Android赢家密码的观点与实践——以软硬整合开发为例.

观点与机会

大家都知道观点(又称视角)本身是没有对错的,但是观点会影响人们的决策,而决策又会影响行为,然后产生不同的结果。因之,基于“赢家密码”观点而实践策略,会带来赢的机会;反之,则可能会错失大好机会。

Android的三层API

在软硬整合开发里,最主要的议题就是,如何让底层硬件的创新性能或功能与上层应用程序(简称AP)的多样化相汇合,关于这项议题,我在《Android赢家密码》一书里,已经详细叙述了,就不再重述了。

在Android平台上,规划上述软硬整合实践策略时,会涉及三层软件接口(泛称API),包括:

  • 上层的Framework API:这是位于Java层的AP与框架(Framework)之间。
  • 中层的JNI API:这是位于Java框架与C++层Android核心程序库(Library)之间。
  • 底层的HAL API:这是位于Android核心程序库与硬件驱动程序(Driver)之间。

俗语说:横看成岭侧成峰。意味着,事物本体只有一个,因为人人观点不同而有不同面貌的呈现。苏东坡《前赤壁赋》也写道:“盖将自其变者而观之,则天 地曾不能一瞬;自其不变者而观之,则物与我皆无尽也,而又何羡乎?”即使同一个人,基于不同角度,观察到的现象也不同,因为会影响到人生或其它事物的态 度、策略、行为和结果。

古典IT的观点

这个观点是基于“平台(Platform)”的概念,平台概念让人们联想到房屋的地基,AP就是房屋,而平台就是地基。其地位尊卑顺序是:主人->AP->平台,如下图1所示:

图1 古典IT观点下的Android三层API

在这观点下,其焦点在于:

  • 底层都是提供服务给上层调用,一切以User好用为目标,也就是追求优质的用户体验(User or Client Experience)。
  • API成为Façade的角色(可参考Façade Pattern),提供单一接口是实现优质用户体验的重要手段。
  • User希望AP稳定不变,AP希望Java框架不变,Java框架期待C/C++模块不变,C/C++模块期待底层的硬件驱动稳定不变。人人都期待脚底下的“平台”是不变的。

中国传统的观点

如果将Android里的三层API对应到中国清朝时期的三层城墙:万里长城、北京城和紫禁城。将可以让我们摆脱古老的IT观点,而得到新潮的观 点:中国永恒的智慧 + Android平台->赢家的软硬整合策略。这非常接近清代大臣张之洞所提倡的“中学为体,西学为用”观点。于是,得到下图:

图2 Android平台里的三层API

兹比较图1与图2:

  • 地位尊卑顺序相反。在图1里,愈上层的地位愈高。反之,在图2里,愈底层的地位愈高(例如皇帝居住于紫禁城内)。
  • 行为决策相反。采取图1观点的国度,人人争先恐后去做 AP,因为(误认为)AP的地位最高。反之,采取图2观点的国度,人人争先恐后去做框架和API,因为(真正)地位最高,必须建造API(城墙)避免敌人来争夺。
  • 建API成为最大赢家。Google、微软等公司采取图2观点,微软于2001年推出.NET框架;Google于2007年推出Android框架;两者都成为IT业武林盟主。
  • API让底层先获利。基于图2的观点,愈底层的地位愈高,万里长城让观内居民先获利(不是塞外先获利)。所以台湾宏达电(HTC)公司专注于开发Android底层硬件和驱动软件(关内部分),成为全球获利最大的Android手机厂商。

以POS系统开发为例——现实的POS情境和需求

POS软硬整合产品必须卖给业主(如大卖场的商家)。由于商家是做生意的人,不会设计卖场专用的POS应用程序(例如,将POS结帐款项送往ERP 系统)。所以,POS产品厂商就委托各地区或城市的当地应用软件开发商去帮助业主开发其专用的应用软件(简称AP)。于是,POS产品厂商就成为强龙,而 全球各地区的AP开发者成为地头蛇,这<强龙/地头蛇> 合作模式能提供给业主最佳的服务。例如,笔者就曾经担任NCR公司在台湾的地头蛇,服务台湾当地的银行POS应用软件系统。

过去,都是洋人企业扮演强龙角色,国内本地企业扮演地头蛇角色。如今,随着本地内需市场的急速成长,提供给本地企业跃居强龙地位的大好机会。所以, 本文不是叙述如何解决业主的需求,而是说明除了解决业主需求之外,又如何替POS产品(或手机)厂商设计出强龙系统架构,以即规划出其实现步骤。

第一步:从古典IT观点出发

在上一篇文章里,已经说明了古典IT观点的基本架构。现在,就来厘清它的两项基本流程:请求流程(Request Flow)和服务流程(Service Flow)。如下图1 所示:

图3 古典IT观点下的两项基本流程

在这观点下,并不太重视另一项流程,就是命令流程(Command Flow)。甚至很多人认为图里的请求流程就是命令流程。由于命令的来源是业主或AP(如古代的员外),让底层POS产品厂商成为长工,就无法实现其强龙的梦想了。

第二步:加上命令流程(Command Flow)

这是基于上一篇文章里的新潮观点,基于这个观点,就能凸显出命令流程的重要性,以及其流动方向。如下图2所示:

图4 新潮观点所凸显的命令流程

大家都知道命令的来源是紫禁城内,流向北京城外,再流到长城之外。于是,将上图1和图2整合起来就是新潮观点下的三项主要流程。

第三步:设立关口传达命令

为了确保紫禁城内清朝皇帝的强龙主导地位,必然会最第一道防线(即万里长城)设立关口,并重兵驻守,成为具有高度主导性的接口,例如山海关、居庸关 等。这种主导性关口,就是笔者在《Android赢家密码》一书所说的“主动型API”。唯有主动型的API(或称接口,或称关口)才能确保命令的传递和 执行。如下图3所示:

图5 山海关、居庸关就是主动型API

在这观点下,业主(或用户)和AP是在塞外,是要服从命令的,在直觉上对用户体验并不会有所贡献。然而,因为硬件(如宏达电HTC手机或联迪 POS)产品厂商位居紫禁城内,拥有强龙地位,其“强龙体验”的滋味是美好的。过去,软件开发人员一味地追求提升用户(地头蛇)体验,未能提升软件人员本 身的地位。如今,软件开发人员除了提升用户体验之外,也关注于提升底层硬件厂商的强龙体验。基于这种新观点,让台湾的宏达电公司成为最赚钱的公司,其 HTC手机也创造极佳的用户体验,至今(2011年)销售量全球第一,同样地台湾Android相关软件人员也因而获利。这说明了这个新潮观点,的确能创 造“硬件厂商、手机用户、软件人员”三赢的局势。其将业主和 AP视为塞外,并将用户体验降到第二顺位;反而大幅提升了用户体验。这就是笔者在《Android赢家密码》一书所说的“神秘力量之一”。

第四步:实现软件关口(即框架API)

具有主导性(或防御性)的关口,就是主动型(即主导性)的API。在软件上,其实现机制是极为简单的概念:基类(Base Class),又称为父类(Super Class)。如下图3所示:

图6 软硬整合系统的山海关等关口

软件关口就是Java或C++语言的基类,是每一位软件开发人员都具备的基本技能。

结语

俗语说:不为也;非不能也。如今,为何洋人喜欢撰写框架基类、掌握框架API,位居强龙;而我们身边的大多数软件开发人员,还是拥抱着古典IT观 点、孜孜不倦撰写AP子类,一未追求用户体验呢? 换个观点而已,做法非常简单;只是不去做,并非不会做。由于底层硬件功能是整体系统服务的源头,也是提升用户体验的源头;唯有采取中国固有的新潮观点,让 底层硬件服务视为九五至尊的强龙地位,才能真正实现好的用户体验,并带给软件开发人员极佳的获利机会。

关于作者

高焕堂,台湾软件架构设计大师,从事IT行业近30年,被称为“台湾OO技术教父级代表人物”。现任MISOO软件开发与管理顾问公司首席架构师,编著过十余本软件技术相关书籍。

[转载]打包Asp.Net 网站成为一个exe 方便快捷的进行客户演示

mikel阅读(828)

[转载]打包Asp.Net 网站成为一个exe 方便快捷的进行客户演示 – 葡萄城控件技术团队博客 – 博客园.

在Asp时代有一个NetBox 产品可以把整个Asp网站AllInOne的打包成一个exe,在没有IIS的情况下可以单独运行这个exe来开启整个网站。在ASP.NET 下一直没有类似的产品出现,可能是IIS已经非常的强大了,不需要类似的产品了? 但是在某种场景下还是需要一个类似功能的产品的,这个产品不是用来部分替代IIS来做一个轻量级的IIS,而是用来方便快捷的进行客户展示。
例如,当完成一个网站开发后,或者部分完成开发后,想给客户展示一下,收集一下客户的反馈,一般有两种做法:
1. 自己有主机和域名,把网站发布到Internet 上,让用户通过Internet访问网站。
2. 把网站部署到一台笔记本上,让一名工程师带着网站到客户那里收集客户反馈。
结合NetBox的思想是否可以把整个网站打包成一个exe,尽量把相关的东西都AllInOne到一个exe里,这样给客户演示的时候,就可以直接把这个exe发给用户,用户直接运行这个exe就可以看到网站的实现的情况了,这样做是否又给网站演示增加了一种新的手段。
Jelly.Packer.exe就是从这个想法而开发出来的 打包程序,把整个网站打包成一个AllInOne的exe,然后将打包生成的exe发给客户做演示。

Home Directory: 要打包网站所在的位置,某个你已经编译好的要发布的站点的文件夹,一般是Visual Studio站点publish输出的文件夹。
Virtual Directory: 站点虚拟路径,一般使用 “/”, 如果你打包了两个站点,想在同一端口运行,可以使用虚拟路径来区分。
List Directory: 在没有默认页面的时候,是否允许列出目录。
Authentication: 是否要求安全身份访问。
Auto Show: 是否自动开启站点,并同时开启默认浏览器访问此站点。
Default Files: 站点默认页面。
当 配置好上面的属性后,点 ”OK” 后,就会在Jelly.Packer.exe同目录下生成一个Jelly.SingleRunner.exe,这个 Jelly.SingleRunner.exe 就是指定网站AllInOne所打包好的exe,可以将这个exe发给客户做演示。

当运行Jelly.SingleRunner.exe 后,会在刚刚Jelly.Packer.exe所设置的端口上开启指定的网站:


最 初曾经考虑过把一个轻型的web server ,网站,和一个轻型浏览器打包在一起,这样就不需要占用端口了,就像MSDN帮助手册的 ms-help 协议那样来实现,后来考虑到需要让多个浏览器都可以访问,同时也可以把这个演示站点公开发布到本地局域网里,让客户本地网络里的别的机器也可以访问,基于 这种需求,把轻型web server和网站打包在一起,可能是比较好的选择吧。
打包程序下载:

附件: 06_003955_khaoJellyPacker.rar (2011-2-16 12:01:48, 116.80 K)

把BlogEngine V2.0打包成了exe:

附件: BlogEngine_Jelly_SingleRunner.rar (2011-2-16 12:01:48, 2639.24 K)

相关发布:

附件: 06_32323_new_JellyRelease.rar (2011-2-16 12:01:48, 263.65 K)

[转载]Web应用架构探索笔记 —— 查询

mikel阅读(930)

[转载]Web应用架构探索笔记 —— 查询 – dudu – 博客园.

在Web应用开发中,最常见也容易变化的一种需求是根据不同的查询条件获取数据列表。如何传递查询条件将影响程序应对需求变化的能力,一定要在架构中重点考虑。

开始时我们使用一堆参数传递查询条件,比如:

List<SiteMsg> GetMsgList(int pageIndex, int pageSize, int RecipientId);

结果,每个不同的查询都要写一个接口,产生了一堆接口;查询条件改变,接口也随之要改。写程序最痛苦的事莫过于接口的频繁变化。

后来使用查询对像,比如:

List<SiteMsg> GetMsgList(SiteMsgQuery msgQuery);

这样,查询条件改变时,只需修改SiteMsgQuery的定义,接口保持不变。采用这个方法后,写代码比之前少了很多痛苦。

但使用这个方法有个地方不爽,完成一次查询需要进行两次实例化,一次是查询对象SiteMsgQuery的实例化,一次是领域对象 SiteMsgManager(负责业务逻辑)的实例化。在博客园程序架构中,查询对象的实例化是在表现层完成的,如果是ajax调用,json会自动反 序列为查询对象;领域对象的实例化在服务层完成。

为了让代码写的更爽一些,我们又进行了尝试,取消查询对象SiteMsgQuery,将它的属性放到领域对象中。这样减少了一次实例化,只需一次,如果是ajax调用,可以实现服务器端“零实例化”。

下面看一下代码示例:

领域模型的定义:

[DataContract] public class SiteMsgManager { public SiteMsgManager() { } #region Properies [DataMember] public int PageIndex { get; set; } [DataMember] public int PageSize { get; set; } [DataMember] public int RecipientId { get; set; } public List<SiteMsg> List { get; set; } #endregion public void GetList() { using (SpaceObjectContext context = new SpaceObjectContext()) { this.List = context.SiteMsgs .Where(msg => msg.RecipientSpaceUserId == this.RecipientId) .OrderByDescending(msg => msg.id) .Skip((PageIndex - 1) * PageSize) .Take(this.PageSize) .ToList(); } }

服务实现类(也是WCF的服务实现):

public class MsgService : IMsgService { public List<SiteMsg> GetMsgList(SiteMsgManager siteMsgManager) { siteMsgManager.GetList(); return siteMsgManager.List; } }

UI层调用代码(WCF调用,ASP.NET MVC控制器):

public class MsgController : Controller { //ajax调用 [HttpPost] public ActionResult List(SiteMsgManager msgManager) { return View("MsgList", GetInboxMsgList(msgManager)); } public ActionResult Inbox() { SiteMsgManager msgManager = new SiteMsgManager() { PageIndex = 1, PageSize = 30 }; return View("Inbox", GetInboxMsgList(msgManager)); } private List<SiteMsg> GetInboxMsgList(SiteMsgManager msgManager) { int spaceUserId = Util.GetCurrentUser(System.Web.HttpContext.Current).SpaceUserID; msgManager.RecipientId = spaceUserId; MsgServiceClient client = new MsgServiceClient(); List<SiteMsg> siteMsgList = client.GetMsgList(msgManager).ToList(); try { client.Close(); } catch { client.Abort(); } return siteMsgList; } }

看看上面供ajax调用的List方法,不需要进行SiteMsgManager的实例化,系统根据ajax客户端传递过来的json参数自动反序列化生成SiteMsgManager对象。

再来看看ajax客户端代码:

function GetMsgList(pageIndex, pageSize) { var msgManager = {} msgManager.PageIndex = pageIndex; msgManager.PageSize = pageSize; $.ajaxSettings.dataType = 'plain/text'; $.ajaxSettings.url = '/msg/list'; $.ajaxSettings.data = '{"msgManager":' + JSON.stringify(msgManager) + '}'; $.ajaxSettings.success = function (data) { $("#msg_list").html(data); }; $.ajax(); }

js传递的也是一个对像。

整个ajax调用的流程是这样的:js对象 (msgManager)->json->MsgController(MVC控制器)->代理领域对象 SiteMsgManager(WCF客户端代理类的实例)->WCF服务接口->WCF服务实现(自动通过反序列化生成领域对象 SiteMsgManager,并调用GetList()方法)->领域对象完成业务逻辑操作返回数据。

采用这种方法,感觉写代码比以前更享受了。我们在实际开发中也开始使用这种架构,并根据实际使用情况进一步改进。

注:这篇随笔只是我们在探索博客园Web应用架构过程中的笔记,并不代表这是更好更合理的方法。写出来一是为了分享,二是帮助自己更好的思考,三是记录架构演变的过程。

[转载]googlemap数据采集器(四) [免费程序下载] - liongis - 博客园

mikel阅读(927)

[转载]googlemap数据采集器(四) [免费程序下载] – liongis – 博客园.

之前用GoogleMap的本地搜索接口写了一个简单的采集器,最近有朋友希望试用一下,所以继续完善了一下功能,特免费提供给大家下载。

新加的功能有:

1.去除重复数据。

2.支持导出CSV文件。

下载地址是:http://files.cnblogs.com/liongis/GMapDataDown.rar

程序需要.net framework 3.0支持,没有的朋友请到搜索一下,进行安装。

软件操作很简单,这里就不再说明,不会的可以问我。

照例上图:

[转载]android UI进阶之弹窗的使用

mikel阅读(1703)

[转载]android UI进阶之弹窗的使用 – noTice520 – 博客园.

其实这是第二遍写这篇博客……浏览器崩溃了。。。我还以为博客园和CSDN的一样自动保存,竟然没有!好吧,没事,重新写过。

年就算过完啦。新年第一篇来讲下Android中的PopupWindow。姑且就叫做弹窗吧,其实和AlertDialog一样,也是一种对话框,两者也经常混用,但是也各有特点。

下面就看看使用方法。首先初始化一个PopupWindow

1 PopupWindow mPop = new PopupWindow(getLayoutInflater().inflate(R.layout.window, null), 2 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

实现PopupWindow的构造函数,第一个参数导入布局,后面两个指定宽和高。弹窗一般有两种展示方法,用showAsDropDown()和showAtLocation()两种方法实现。一般参数有两种,有偏移和无偏移。

1 bt1.setOnClickListener(new OnClickListener(){ 2 3 @Override 4 public void onClick(View v) { 5 initPopWindow(); 6 mPop.showAsDropDown(v);//以这个Button为anchor(可以理解为锚,基准),在下方弹出 7   8 }}); 9 10 bt2.setOnClickListener(new OnClickListener(){ 11 12 @Override 13 public void onClick(View v) { 14 initPopWindow(); 15 mPop.showAsDropDown(v,20,-20);//横轴偏移20,纵轴-20,一个状态栏的长度 16   17 }}); 18 19 bt3.setOnClickListener(new OnClickListener(){ 20 21 @Override 22 public void onClick(View v) { 23 initPopWindow(); 24 mPop.showAtLocation(PopWindow.this.findViewById(R.id.rl), 25 Gravity.CENTER, 0, 0);//在屏幕居中,无偏移 26   27 }}); 28 29 30 bt4.setOnClickListener(new OnClickListener(){ 31 32 @Override 33 public void onClick(View v) { 34 initPopWindow(); 35 mPop.showAtLocation(PopWindow.this.findViewById(R.id.rl), 36 Gravity.TOP | Gravity.LEFT, 20, 20);//在屏幕顶部|居右,带偏移 37   38 }}); 39 40 bt5.setOnClickListener(new OnClickListener(){ 41 42 @Override 43 public void onClick(View v) { 44 if (mPop != null) { 45 mPop.dismiss(); 46 } 47 48 }});

其中initPopWindow()方法来初始化一个弹窗

1 private void initPopWindow() { 2 if (mPop == null) { 3 mPop = new PopupWindow(getLayoutInflater().inflate(R.layout.pop, null), 4 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 5 } 6 if (mPop.isShowing()) { 7 mPop.dismiss(); 8 } 9 }

这里定义了4个按钮来呈现展示效果,一个按钮用来关闭,来看下第三个按钮点击效果,就不一一截图了,大家可以自己试下。

看过我以前这系列博客的朋友就会发现,PopupWindow里面的布局就是抽屉里面用的布局,后面的背景就是上篇博客讲的可延伸图像。为什么用这个呢,我们来看下UC的menu效果

可能这个是用AlertDialog做的,但是用PopupWindow也非常方便。很多的应用都使用了这样的menu。这里我就不去仿照UC来做了,就用上面那个布局,原理是一样的,换换图片就行。

要做的很简单,看代码

public boolean onKeyDown(int keyCode, KeyEvent event){ //截获按键事件 if(keyCode == KeyEvent.KEYCODE_MENU){ initPopWindow(); mPop.showAtLocation(this.findViewById(R.id.rl), Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0); //在屏幕底部 }else if(keyCode == KeyEvent.KEYCODE_BACK){ if(mPop.isShowing()){ mPop.dismiss(); }else{ System.exit(0); } } return false; }

看下效果

图片似乎不是很给力,大家搞点漂亮点的图片就可以了。

我把这个代码全部贴出来

1 package com.notice.popWindow; 2 3  import android.app.Activity; 4  import android.os.Bundle; 5  import android.view.Gravity; 6 import android.view.KeyEvent; 7 import android.view.View; 8 import android.view.View.OnClickListener; 9 import android.view.ViewGroup.LayoutParams; 10 import android.widget.Button; 11 import android.widget.GridView; 12 import android.widget.PopupWindow; 13 14 public class PopWindow extends Activity { 15 private GridView gv; 16 private Button bt1; 17 private Button bt2; 18 private Button bt3; 19 private Button bt4; 20 private Button bt5; 21 private int[] icons={R.drawable.browser,R.drawable.gallery, 22 R.drawable.camera,R.drawable.gmail, 23 R.drawable.music,R.drawable.market, 24 R.drawable.phone,R.drawable.messages,R.drawable.maps}; 25 private String[] items={"浏览器","图片","相机","时钟","音乐","市场","拨号","信息","地图"}; 26 private PopupWindow mPop; 27 private View layout; 28 private void initPopWindow() { 29 if (mPop == null) { 30 mPop = new PopupWindow(layout, 31 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 32 } 33 if (mPop.isShowing()) { 34 mPop.dismiss(); 35 } 36 } 37 /** Called when the activity is first created. */ 38 @Override 39 public void onCreate(Bundle savedInstanceState) { 40 super.onCreate(savedInstanceState); 41 setContentView(R.layout.main); 42 bt1 = (Button)findViewById(R.id.bt1); 43 bt2 = (Button)findViewById(R.id.bt2); 44 bt3 = (Button)findViewById(R.id.bt3); 45 bt4 = (Button)findViewById(R.id.bt4); 46 bt5 = (Button)findViewById(R.id.bt5); 47 layout = View.inflate(this, R.layout.window, null); 48 gv = (GridView) layout.findViewById(R.id.gv); 49 MyAdapter adapter=new MyAdapter(this,items,icons); 50 gv.setAdapter(adapter); 51 52 bt1.setOnClickListener(new OnClickListener(){ 53 54 @Override 55 public void onClick(View v) { 56 initPopWindow(); 57 mPop.showAsDropDown(v);//以这个Button为anchor(可以理解为锚,基准),在下方弹出 58 59 }}); 60 61 bt2.setOnClickListener(new OnClickListener(){ 62 63 @Override 64 public void onClick(View v) { 65 initPopWindow(); 66 mPop.showAsDropDown(v,20,-20);//横轴偏移20,纵轴-20,一个状态栏的长度 67 68 }}); 69 70 bt3.setOnClickListener(new OnClickListener(){ 71 72 @Override 73 public void onClick(View v) { 74 initPopWindow(); 75 mPop.showAtLocation(PopWindow.this.findViewById(R.id.rl), 76 Gravity.CENTER, 0, 0);//在屏幕居中,无偏移 77 78 }}); 79 80 81 bt4.setOnClickListener(new OnClickListener(){ 82 83 @Override 84 public void onClick(View v) { 85 initPopWindow(); 86 mPop.showAtLocation(PopWindow.this.findViewById(R.id.rl), 87 Gravity.TOP | Gravity.LEFT, 20, 20);//在屏幕顶部|居右,带偏移 88 89 }}); 90 91 bt5.setOnClickListener(new OnClickListener(){ 92 93 @Override 94 public void onClick(View v) { 95 if (mPop != null) { 96 mPop.dismiss(); 97 } 98 99 }}); 100 101 } 102 public boolean onKeyDown(int keyCode, KeyEvent event){ 103 //截获按键事件 104 if(keyCode == KeyEvent.KEYCODE_MENU){ 105 initPopWindow(); 106 mPop.showAtLocation(this.findViewById(R.id.rl), 107 Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0); //在屏幕底部 108 }else if(keyCode == KeyEvent.KEYCODE_BACK){ 109 if(mPop.isShowing()){ 110 mPop.dismiss(); 111 }else{ 112 System.exit(0); 113 } 114 115 } 116 return false; 117 118 } 119 }

其中的MyAdapter类可以去看我以前的一篇写抽屉的博客,代码都在里面。然后就是window.xml

<?xml version="1.0" encoding="utf-8"?> <GridView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gv" android:background="@drawable/tbg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numColumns="3" android:gravity="center"> </GridView>

只有一个GridView。main.xml就不贴了,就几个Button。

当然弹窗的作用当然不仅限于menu,你还可以用他做的更多来为你UI增色。今天就写到这了,哎,写了好久。有问题可以留言交流。

[转载]android开发我的新浪微博客户端-用户首页面UI篇(5.1)

mikel阅读(917)

[转载]android开发我的新浪微博客户端-用户首页面UI篇(5.1) – 遇见未知的自己 – 博客园.

在前篇完成了用户登录功能后开始用户首页的开发,用户的首页主要的内容是当前登录用户关注的微博列表,本篇先来讲讲UI的实现,效果如上图,整个页面分 为上、中、下三部分,上面部分是工具条,显示当前登录用户的昵称以及写微博、刷新两个功能按钮;中间部分是当前用户关注的最新微博列表,下面部分是功能切 换栏,用来进行各个功能之间的切换。

首先新建名为HomeActivity.java的Activity作为用户首页,然后在res/layout目录下新建名为home.xml的Layout,具体代码如下:

代码

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:Android=”http://schemas.Android.com/apk/res/android”
android:id
=”@+id/layout”
android:orientation
=”vertical”
android:layout_width
=”fill_parent”
android:layout_height
=”fill_parent”>

<RelativeLayout
android:layout_width=”fill_parent”
android:layout_height
=”wrap_content”
android:layout_margin
=”3px”>
<ImageView
android:layout_width=”wrap_content”
android:layout_height
=”wrap_content”
android:src
=”@drawable/logo_ss”>
</ImageView>
<TextView
android:id=”@+id/showName”
android:layout_width
=”wrap_content”
android:layout_height
=”wrap_content”
android:layout_centerInParent
=”true”
android:textColor
=”#343434″
android:textSize
=”15px”>
</TextView>
<ImageButton
android:id=”@+id/writeBtn”
android:layout_width
=”wrap_content”
android:layout_height
=”wrap_content”
android:layout_toLeftOf
=”@+id/refreshBtn”
android:background
=”@drawable/btn_write_selector”>
</ImageButton>
<ImageButton
android:id=”@+id/refreshBtn”
android:layout_width
=”wrap_content”
android:layout_height
=”wrap_content”
android:layout_alignParentRight
=”true”
android:layout_marginLeft
=”12px”
android:background
=”@drawable/btn_refresh_selector”>
</ImageButton>
</RelativeLayout>

<LinearLayout
android:layout_width=”fill_parent”
android:layout_height
=”wrap_content”
android:background
=”@drawable/hr”>
</LinearLayout>

<RelativeLayout
android:layout_width=”fill_parent”
android:layout_height
=”fill_parent”>

<ListView
android:id=”@+id/Msglist”
android:layout_width
=”fill_parent”
android:layout_height
=”match_parent”
android:divider
=”@drawable/divider”
android:dividerHeight
=”2px”
android:layout_margin
=”0px”
android:background
=”#BBFFFFFF”
android:cacheColorHint
=”#00000000″
android:layout_above
=”@+id/toolbarLayout”
android:fastScrollEnabled
=”true”
android:focusable
=”true”>
</ListView>

<LinearLayout
android:id=”@+id/loadingLayout”
android:layout_width
=”wrap_content”
android:layout_height
=”wrap_content”
android:orientation
=”vertical”
android:visibility
=”invisible”
android:layout_centerInParent
=”true”>
<ProgressBar
android:id=”@+id/loading”
android:layout_width
=”31px”
android:layout_height
=”31px”
android:layout_gravity
=”center”
style
=”@style/progressStyle”>
</ProgressBar>
<TextView
android:layout_width=”wrap_content”
android:layout_height
=”wrap_content”
android:text
=”正在载入”
android:textSize
=”12px”
android:textColor
=”#9c9c9c”
android:layout_gravity
=”center”
android:layout_below
=”@+id/loading”>
</TextView>
</LinearLayout>

<LinearLayout
android:id=”@+id/toolbarLayout”
android:layout_width
=”fill_parent”
android:layout_height
=”44dip”
android:layout_alignParentBottom
=”true”>
</LinearLayout>
</RelativeLayout>
</LinearLayout>

这个布局首先是一个竖直的根LinearLayout,在这个根LinearLayout里面分别是两个RelativeLayout, 第一个RelativeLayout 用来显示页面的工具条,第二个RelativeLayout用来显示列表以及底部的功能栏,特别主要在这第二个 RelativeLayout中有一个id为loadingLayout的LinearLayout是用来显示数据载入中的动画,它的 android:visibility属性为invisible(也可以设置成gone,区别:invisible这个View在ViewGroupt中仍保留它的位置,不重新layout
gone>不可见,但这个View在ViewGroupt中不保留位置,重新layout,那后面的view就会取代他的位置。 ),也就是一开始不显示的意思,接下来看看

<ProgressBar
android:id=”@+id/loading”
android:layout_width=”31px”
android:layout_height=”31px”
android:layout_gravity=”center”
style=”@style/progressStyle”>
</ProgressBar>
这个ProgressBar控件就是用来显示动画用的,关键就是 style=”@style/progressStyle”,在res/values目录下新建名为loadingstyles.xml,内容如下:
代码

<?xml version=”1.0″ encoding=”UTF-8″?>
<resources>
<style name=”progressStyle” width=”38″ height=”38″ parent=”@android:style/Widget.ProgressBar.Small”>
<item name=”android:indeterminateDrawable”>@anim/loading</item>
</style>
</resources>

接着准备好r1.png – r8.png,八张不同的小图片分别代表每旋转45度图片,八张刚好是360度。把这些图片添加到res/drawable-mdpi目录中。然后在res/anim目录下新建名为loading.xml动画文件,内容如下:

代码

<?xml version=”1.0″ encoding=”UTF-8″?>
<animation-list android:oneshot=”false”
xmlns:android
=”http://schemas.android.com/apk/res/android”>
<item android:duration=”200″ android:drawable=”@drawable/r1″ />
<item android:duration=”200″ android:drawable=”@drawable/r2″ />
<item android:duration=”200″ android:drawable=”@drawable/r3″ />
<item android:duration=”200″ android:drawable=”@drawable/r4″ />
<item android:duration=”200″ android:drawable=”@drawable/r5″ />
<item android:duration=”200″ android:drawable=”@drawable/r6″ />
<item android:duration=”200″ android:drawable=”@drawable/r7″ />
<item android:duration=”200″ android:drawable=”@drawable/r8″ />
</animation-list>

关于Android播放动画实现我是参考http://terryblog.blog.51cto.com/1764499/388226

本篇到这里就结束了,下一篇继续讲用户首页的功能实现,请关注。

[转载]构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下后篇)—减少不必要的请求

mikel阅读(1317)

[转载]【原创】构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下后篇)—减少不必要的请求 – ASP.NET 架构 – 博客园.

服务端的要处理的请求越 多,无疑服务端的压力也就越大,尤其是有些请求需要访问一些比较昂贵的资源,例如数据库,服务端的文件等。但是我们必须知道,在到达服务端的请求中,有些 请求时我们希望的,例如网站的用户的请求,有些请求其实是不必要,甚至是我们不想要的,为此,我们要避免这样的请求,节省服务端的资源,从而提高性能。

搜索引擎

首先来看看有关搜索引擎的问题。

然后搜索引擎爬到我们的站点是一件好的事情,很多的SEO可以进行,推广站点。同时,在站点中,有些文件或者资源比较的私密,或者我们不希望被搜索引擎请求和收录的,因为每次搜索引擎在请求这些资源的时候,就是发送请求到我们的站点服务器,势必会加重服务器的负载。

不需要被搜索引擎请求的文件一般如下:

1. 图片资源

2. Js脚本,css

3. 一些需要身份验证或者授权才能看的页面(如果页面需要验证之后才能看,搜索引擎收录了也作用不大)

我们可以设置一下,告诉搜索引擎的蜘蛛程序如何爬我们的站点。

步骤如下:

1. 在站点的根目录下面,创建一个robots.txt的文件。

2. 写入文件。如果我们希望阻止所有的搜索引擎来爬我们的站点的页面,那么就可以在文件中写入下面的配置:

User-agent: *

Disallow: /

如果希望阻止搜索引擎爬某个文件夹,可以配置如下:

User-agent: *

Disallow: /images/

Disallow: /js/

Disallow: /css/

Disallow: /private/

更有趣的是:对于某些搜索引擎,我们还可以改变他们的蜘蛛程序爬我们站点的频率,设置如下:

User-agent: *

Crawl-delay: 10

大家可以去上网找下一些如何影响Google,百度等蜘蛛程序的设置。

热链接问题

就是在A网站上面显示一个来自B网站的图片链接。例如我们在自己的站点上面有一个链接如下:<img src=”http://www.xxx.com/yyy.gif”/>,那么在别人在浏览我们的站点的时候,就回去别人的那个站点(http://www.xxx.com/yyy.gif)去请求这个图片,那么势必会消耗他们的服务器的资源。发过来,如果别人在他们的站点上采用了我们的图片或者其他的链接资料,那么用户在浏览别人的站点的时候就会消耗我们站点的服务端资源和带宽。

为一个组件就可以阻止这种情况的发生:http://www.iis.net/community/default.

aspx?tabid=34&i=1288&g=6.大家去看看。

验证码(CAPTCHA)

我们常常在站点中加入一些验证码的功能来防止网络注册机。一般是生成一张有文字的图片,然后根据验证用户输入的文字和图片中的文字是否一样来判断此时的用户是人还是注册机。

通过验证码阻止了注册机随意的消耗站点资源(如果没有验证码,注册机可以不断的注册信息,大小服务器和数据库资源,而且产生很多的垃圾数据)

我们自己写生成验证码的程序,一般通过GDI+来做,同时也可以采用一些第三方的库实现,例如:reCAPTCHA: http://recaptcha.net/,大家上网找下,很多的。

网络刮刀(Scrapers)

这个问题必须引起重视。如果我们的站点上面有很多的有用的信息,那么别人可能就可能开发一个程序来到我们的站点抓取信息,然后把这些内容放到自己的站点上面。例如,很多的内容型的站点每天都从博客园的首页上面来抓取信息,然后放到他们的站点上,增加他们的访问量。

本来站点被搜索引擎抓就有点消耗性能了,如果还被很多的这样的网络刮刀来抓内容,对站点的性能影响可想而知。

如果那些网络刮刀程序的的IP地址变化不频繁,而且请求我们站点的频率比较的由规律,那么我们就可以采用一些代码的方式来防止这样的请求。例如,我们可以监测:同一个IP是否在20min之内发送了100个请求,如果是,我们就推测:可能是别人在抓我们的站点内容,我们就拒绝这个IP的请求。

当然了,上面只是一些简单的方法,对于一些复杂的Dos攻击,上面的监测代码基本没有作用。因为Dos攻击中,攻击的IP地址是变化的。

下面我们就写一些代码来防止简单的网络刮刀程序和简单的Dos攻击。基本的思想就是:如果在给定的时间段内,如果某个用户的请求很多,超过了一定的数量,那么我们就认为这个用户可能是网络刮刀程序,然后就拒绝下面的请求,一段时间之后,再次允许这个从这个IP发出的请求。

下面的代码中:假设如果一个用户在5秒之内发出了100个请求,那么我们就认为这是网络刮刀程序或者是网站的攻击者。当然,我们还考虑这个发送请求的用户是否是搜索引擎的蜘蛛程序。(下面的代码只是简单作为演示,不是实际生产的代码,抛砖引玉)

private const int intervalSeconds = 30;
private const int maxRequestsInInterval = 5;

如果认为这个用户是攻击者,那么我们就阻止用户的请求,阻止时间是20

private const int blockedPeriodSeconds = 20;

下面,我们创建一个类来描述一个访问者的信息。如下:

代码

private class VisitorInfo
{
public int nbrHits;
public bool blocked;

public VisitorInfo()
{
nbrHits
= 1;
blocked
= false;
}
}

BotDefence类中加入一个方法IsBotAttach来判断一个请求是否是攻击性的请求。如下:

代码

public static bool IsDosAttack()
{
string visitorIP = HttpContext.Current.Request.UserHostAddress;

VisitorInfo visitorInfo = (VisitorInfo)HttpContext.Current.Cache[visitorIP];
if (visitorInfo == null)
{
HttpContext.Current.Cache.Insert(
visitorIP,
new VisitorInfo(), null,
DateTime.Now.AddSeconds(intervalSeconds),
System.Web.Caching.Cache.NoSlidingExpiration);
}
else
{
if (visitorInfo.blocked)
{
return true;
}

visitorInfo.nbrHits++;
if (visitorInfo.nbrHits > maxRequestsInInterval)
{
visitorInfo.blocked
= true;
HttpContext.Current.Cache.Insert(
visitorIP, visitorInfo,
null,
DateTime.Now.AddSeconds(blockedPeriodSeconds),
System.Web.Caching.Cache.NoSlidingExpiration);
return true;
}
}
return false;
}

上面的代码都是自解释的,很容易看懂,就不赘述了。

当然了,上面的代码很简单,我们可以保存每个请求IP的地址,然后分析。相关的方法网络上面有很多,大家自己找!

本篇就到这里了。下一章就详细的讲述如何解决内存的性能瓶颈,敬请关注。

[转载]构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下前篇)—简单的优化措施

mikel阅读(834)

[转载]【原创】构建高性能ASP.NET站点 第六章—性能瓶颈诊断与初步调优(下前篇)—简单的优化措施 – ASP.NET 架构 – 博客园.

部署优化

我们都知道,不同的部署方式对站点的性能是有影响的,可能有些朋友已经知道了这点,不管怎样,我们这里还是详细系统的讲述一下这个问题,熟悉的朋友权当回顾J

Release方式编译项目

如果我们的项目是用Project的方式建立的,也就是说:我们的站点包含在一个Solution解决方案中,那么在发布之前,编译项目的时候,采用release方式,这种方式会减少CPU的使用率。因为采用Debug的方式发布,编译器会编译后的代码中加入很多的信息,如调试信息等。

操作步骤:

1. VS中,选择Build | Configuration Manager”.如下:

2. Active Solution Configuration下拉框现在””Release”,然后Close”.那么Solution就以Release方式编译。(其实使得Solution编译为Release的方法很多,例如在Solution上面右击属性,然后去设置也是可以的)

现在虽然SolutionRelease方式了,但是如果我们去查看这个Solution下面的ASP.NET站点程序的config文件,发现还是deubg方式的。那么我们在发布站点的时候,需要手动的去修改为release

:如果Solution是以Debug方式编译,即使web.config设置了release,最后发布的站点的代码还是方式的。

站点发布

发布的步骤如下:

1. 修改web.config配置如下:

2. 在站点上面右键选择Publish”.如下:

减少不必要的回传

我们都知道,从服务端到客户端的回传每次都是需要花费一定的时间的,而且加长了用户等待的时间。所以有些回传则是可免则免。

Server.Transfer Vs Response.Redirect

如果我们需要在服务端把用户定向到另外的一个页面,那么考虑一下:尽量使用Server.Transfer,而不是使用Response.Redirect

因为当使用Response.Redirect的时候,服务端会向客户端的浏览器发送一个响应:告诉浏览器去加载转向的那个页面。然后浏览器再次发送请求到服务端去请求另外的那个页面。

当我们使用Server.Transfer的时候,服务端就立刻执行跳转。这样做的一个不好的地方可能就是:此时请求的是A.aspx,其实服务端已经跳转到了B.aspx页面,但是浏览器上面的Url还是显示的A.aspx

当使用Server.Transfer需要注意:确定每次访问A页面都需要跳转到B页面的时候,就是用Server.Transfer。例如,拿博客园来举例,当用户在没有登录的时候想对正在阅读的一篇文章评论,那么此时,页面就会跳转到Login的登陆页面,登陆之后,页面就跳转到之前看文章的那个页面,然后写评论。此时的这个跳转就不适合用Server.Transfer,而采用Response.Redirect。如果不管用户在哪里,只要用户登陆,那么总是跳到一个固定的页面,那么就可以使用Server.Transfer

还有就是Server.Transfer毕竟会消耗服务端的资源,使用的时候要注意。

通过上面可以看出:调优本来就是一个折中的过程,不是绝对的。调优最后说到底就是时空转换时间换空间,空间换时间

声明站点的默认页面

当我们请求一个站点的时候,如http://domain/folderIIS会自动进行一些重定向到http://domain/folder/。同时,http.sys也不会把没有声明默认页面的站点的默认首页加入到内核的缓存中(可能说的有点的绕),例如,如果在程序中,我们设置站点的默认页面时Default.aspx,但是我们在部署到IIS的时候,没有配置Default.aspx就是站点的默认页面,那么这个页面的内容不会被http.sys缓存到内核中。所以为了避免IIS重定向和允许http.sys缓存页面,我们在IIS中要配置站点的默认页面(或者每次在浏览器中输入http://domain/folder/default.aspx,但是我们不能控制用户的行为,所以这招这几乎不可能)

永久跳转相关话题

如果我们站点的某个页面过期了或者不再用了,那么我们就可以采用301永久跳转。当服务端向客户端发出301响应的时候,浏览器和代理都会去更新他们的缓存(如果之前的旧页面采用了缓存),而且搜索引擎也会采用新的页面。

要让服务端向客户端发送301响应,如下的方式:

1.代码:

Response.StatusCode = 301;
Response.AddHeader(
Location, NewPage.aspx);
Response.End();

ASP.NET 4.0 及以后的版本:

Response.RedirectPermanent(NewPage.aspx);

2. IIS配置

a) IIS 6配置

1. IIS中站点中,选中你想跳转的文件或者目录。

2. 选中”A redirection to a URL”

3. 然后输入你想跳转到的页面。

4. 然后选中”The exact url entered above””A permanent redirect for this resource”

b) IIS 7

Server 2008上面

1. 打开开始”->”管理工具”->”服务器管理

2. IIS上面添加角色服务

3. 常见Http功能下面选中”Http重定向

4. 然后安装。

Win7 上面,如下:

然后,在我们的站点的web.config配置如下:

代码

<configuration>
<location path=”OldPage.aspx”>
<system.webServer>
<httpRedirect enabled=”true” destination=”NewPage.aspx” httpResponseStatus=”Permanent” />
</system.webServer>
</location>
</configuration>

今天就到这里,多谢各位!

[转载]发布一个原创小类库:.Net 小型软件自动更新库(SimpAutoUpdater)

mikel阅读(935)

[转载]发布一个原创小类库:.Net 小型软件自动更新库(SimpAutoUpdater) – 随风飘扬 – 博客园.

本类库+工具用于快速实现一个简单的自动更新程序,旨在快速简单地为现有的.Net应用程序添加上比较简单的自动更新功能。

在发布应用程序时,我们经常会需要给自 己的程序加上自动升级功能。.Net Framework自带的ClickOnce中有自动升级的功能,但是往往不太好用,比如必须用ClickOnce发布,安装的文件夹一个版本一个等等, 我们会想要一个比较简单、甚至绿色软件也能使用的自动升级功能,这个自动升级程序就是基于这个目的而制作的。

为了让使用起来更加简单快捷,我对内置的功能进行了大幅度的精简和集成,最简单的情况下只需要你只需要一行代码即可实现自动更新,如下所示:

  1. FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple(http://ls.com/update.xml);

概述

本类库+工具用于快速实现一个简单的自动更新程序,可以简单地完成小型应用程序的快速更新。

整个工具分成两部分,一部分是供用户使用的类库,一部分是用于生成更新包的工具。

基于 .Net 3.5 开发,原则上最好安装有此版的 .netFramework。特殊情况下,可以仅安装 .Net Framework 2.0SP1,发布的时候附带上 System.Core.dll 即可正常运行。

整个自动升级工作的流程

2d70e14a-d474-4b75-a12a-562d2bce7b74

更新包生成工具

作为一个简单的升级需求,不需要很复杂的设置。

1de73525-2e99-4160-9e62-e71b2694b198

需要填写如下信息:

  1. 应用程序名
  2. 当前的版本(也就是更新后的版本)
  3. 发布地址可选(如果填写了,在更新提示框上会有个链接可以链接到这个网址)
  4. 新程序目录(选择最新版本程序的发布目录)
  5. 升级包路径:选择一个路径用来保存升级包(*.zip),同时自动更新信息 *.xml 也会保存在这个目录下
  6. 更新前执行:在下载完成、即将安装更新前会执行这里选择的程序。这里有点BUG一旦选择了就不能取消,所以请注意下;
  7. 更新后执行:在安装完成后、即将退出前执行的操作。需要注意的BUG同上。通常选择需要运行的主程序。
  8. 执行时间限制:用于限制6中选择的程序的执行时间,超过设置的时间后进程将会被强行结束。
  9. 更新说明:用于提示更新的时候显示的文本消息内容。
  10. 创建:按照填写的信息生成升级包
  11. 打开:打开一个已有的升级信息文件,用于下次更新版本时直接修改信息即可,其它的不需要变化。

发布更新包

更新包应该发布到服务器上。生成的文件有两个,一个是压缩包(*.zip),一个是信息文件(*.xml),两个应该一起发布。这里假定通过网址 http://ls.com/update.xml 能访问到生成的 update.xml 文件。

为应用程序添加自动更新功能

注意:自动升级程序取当前程序文件的版本,是以当前运行的程序集版本作为识别依据的。

首先在VS中为当前的主程序项目添加引用,引用“客户端”中的“SimpleUpdater.exe”。

在VS中,点开“解决方案管理器”中相应项目的“属性”节点,打开 AssemblyInfo.cs 文件,在最下面添加上一行自动更新声明:

这步是必须的,否则请求检查更新时会抛出异常;代码中的网址即上面提到的能访问到xml文件的网址。

如果您希望更加简单的使用而不用去加这样的属性,或者您想程序运行的时候自定义,您可以通过下列方式的任何一种方式取代上面的属性声明:

  • 使用 FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple(“升级网址”) 的重载方法。这个重载方法允许你传入一个升级包的地址;
  • 在检查前手动设置 FSLib.App.SimpleUpdater.Updater.UpdateUrl 属性。这是一个静态属性,也就是说,您并不需要创建 FSLib.App.SimpleUpdater.Updater.UpdateUrl 的对象实例就可以修改它。

无论使用哪种方式,请确保在检查更新前,地址已经设置。

到这里,准备工作即告完成,为代码添加上检查更新的操作即可。

  1. static class Program
  2. {
  3. /// <summary>
  4. /// 应用程序的主入口点。
  5. /// </summary>
  6. [STAThread]
  7. static void Main()
  8. {
  9. Application.EnableVisualStyles();
  10. Application.SetCompatibleTextRenderingDefault(false);
  11. var updater = FSLib.App.SimpleUpdater.Updater.Instance;
  12. //当检查发生错误时,这个事件会触发
  13. updater.Error += new EventHandler(updater_Error);
  14. //没有找到更新的事件
  15. updater.NoUpdatesFound += new EventHandler(updater_NoUpdatesFound);
  16. //找到更新的事件.但在此实例中,找到更新会自动进行处理,所以这里并不需要操作
  17. //updater.UpdatesFound += new EventHandler(updater_UpdatesFound);
  18. //开始检查更新-这是最简单的模式.请现在 assemblyInfo.cs 中配置更新地址,参见对应的文件.
  19. FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple();
  20. /*
  21. * 如果您希望更加简单的使用而不用去加这样的属性,或者您想程序运行的时候自定义,您可以通过下列方式的任何一种方式取代上面的属性声明:
  22. * 使用Updater.CheckUpdateSimple 的重载方法。这个重载方法允许你传入一个升级包的地址;
  23. * 在检查前手动设置 FSLib.App.SimpleUpdater.Updater.UpdateUrl 属性。这是一个静态属性,也就是说,您并不需要创建 FSLib.App.SimpleUpdater.Updater.UpdateUrl 的对象实例就可以修改它。
  24. */
  25. FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple(“升级网址”);
  26. Application.Run(new Form1());
  27. }
  28. static void updater_UpdatesFound(object sender, EventArgs e)
  29. {
  30. }
  31. static void updater_NoUpdatesFound(object sender, EventArgs e)
  32. {
  33. System.Windows.Forms.MessageBox.Show(“没有找到更新”);
  34. }
  35. static void updater_Error(object sender, EventArgs e)
  36. {
  37. var updater = sender as FSLib.App.SimpleUpdater.Updater;
  38. System.Windows.Forms.MessageBox.Show(updater.Exception.ToString());
  39. }
  40. }

结束

详细的代码请参考附带的示例项目。

检查到更新时窗口如下:

28bfbc14-2a3d-49bb-9092-d913a1399d62

这之后的更新操作都是全自动执行的,不需要在主程序中有任何其它操作。

下载

库直接下载链接:http://www.u-tide.com/fish/Service.asmx/Download/33/28

发布页面:http://www.u-tide.com/fish/soft/simple_autoupdater/index.html

简单使用说明:http://www.u-tide.com/fish/soft/simple_autoupdater/usage.html

如果有问题或建议,请回复此日志,到讨论区反馈: http://www.u-tide.com/fish/Discussion.asmx/Index/33

[转载]VS2010测试功能之旅:编码的UI测试(1)

mikel阅读(1520)

[转载]VS2010测试功能之旅:编码的UI测试(1) – RealDigit – 博客园.

前言

研究VS2010的自动化测试有一段时间了,在研究的过程中,发现网上的VS2010相关的测试资料十分稀少,这给学习带来比较大的困难。幸运的是,自己因为常用QuickTest Professional做自动化测试,并且对.Net代码比较了解,使得在学习的过程中并不是特别困难,对UI CODED TEST也已经有一定了解。本着与大家交流,共同进步的想法,思考之后,决定写这些随笔,来对VS2010的测试功能做一些探索和总结。希望大家能够多多指教!

自动化测试

自动化测试指软件测试的自动化,软件测试就是在预设条件下运行系统或应用程序,评估运行结果,预先条件应包括正常条件和异常条件。

自 动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常,在设计了测试用例并通过评审之后,由测试人员根据测试用例中描述的规程一步步执行测 试,得到实际结果与期望结果的比较。在此过程中,为了节省人力、时间或硬件资源,提高测试效率,便引入了自动化测试的概念。

VS2010编码的UI测试,属于自动化测试中的一种,它可测试应用程序的用户界面是否功能正常。 编码的 UI 测试对应用程序的用户界面控件执行操作,并验证是否以正确的值显示了正确的控件。

一个简单的示例

在上面对于自动化测试的介绍中已经提到,编码的UI测试将对应用程序的用户界面进行操作之后进行验证,对于界面的操作,我们需要预先录制或者直接编写(日后会介绍直接编写的方式),在这个简单的实例中,我们将先使用录制进行操作,录制完毕之后,使用回放功能进行回放(类似于录音机,录制之后再播放)

一.新建测试项目

1. 首先打开VS2010,选择测试à新建测试

2. 选择编码的UI测试,简历一个UI测试,名称为CodedUITest1.cs

3. 创建完成,我们可以看到创建之后,项目的目录结构如下

下面做一个简单的介绍

Solution Items:存放该解决方案中各个测试项目公用的设置

Local.testsetings文件和TraceAndTestImpact.testsettings是对解决方案中的测试进行整体上的设置,这个是建立测试解决方案的时候默认自动建立的两项,一个用于运行,一个用于调试,当然这两项是可以删除的,testsettings类型文件可以通过右击Solution Items的时候点击添加新建项建立

TestProject1.vsmdi是用于对项目中创建的测试method进行组织和管理,让其能组织成流程线,一个个进行测试

TestProject:我们刚才建立的测试项目,可以看到刚才建立的UI测试CodedUITest1.cs

二.录制程序并回放

1. 假设我们现在有一个程序(这个示例程序在文章末尾有提供地址),现在我们需要对系统登录的部分进行测试,正确的用户名为”Admin”,密码为”123456”

测试的内容是:

(1).输入错误的用户名或输入错误的密码,检测系统是否提示“用户或密码错误”

(2).输入正确的用户名和正确的密码,检测系统是否提示“登录成功”

程序的截图如下所示:

2. 现在我们可以开始进行录制,将刚才的测试内容录制成实际的操作

3. 点击确定之后,可以看到屏幕的右下角弹出一个UI测试生成器,点击录制之后,就可以进行操作了

4. 之后首先输入错误的用户名和密码,然后点击登录,之后弹出提示框“用户名和密码错误“

5. 这个时候我们可以在录制面板点击暂停录制,然后点击”生成代码“,并将刚才的操作放入方法InputErrorUidPwd中(至于生成后的代码被放在了哪,现在可以先不用关心,最后我会进行说明)

6. 这个时候点击录制面板上的准星(这个时候准星已经Enable了),并一直按住鼠标不放,将鼠标拖到刚才的弹出框上面的文字再松开,然后测试生成器的对象库窗口会自动弹出,VS2010将捕获弹出框上的label对象,如下图所示:

7. 现在我们要进行第一个验证,就是验证这个弹出框用户名或密码错误!是否弹出来了,如果是,则测试通过,如果不是,则失败,接着先前的步棸,首先要确认左边的列表中“UI用户名或者密码错误Text“对象选中,之后选择右边的列表中的Exists属性(该属性表示label是否存在,存在则是True,不存在则是false),点击添加断言

然后弹出为Exists添加断言的窗口,这里我们需要选择AreEqual,比较值是True,表示我们断言用户名或密码错误这个label存在,如果运行测试的时候该label确实存在,则该测试步骤是通过的,否则就是失败

之后点击录制面板的生成代码,为刚才添加的断言生成方法AssertErrorWindow()(至于生成后的代码被放在了哪,现在可以先不用关心,最后我会进行说明)

8. 然后我们继续开始录制,录制开启后,首先点击“用户名或密码错误“弹出框的确定,然后用上面介绍的同样的方法,输入正确的用户名和密码,判断弹出框是否为登录成功,分别添加InputRightUidPwd()操作方法和AssertRightWinow()断言方法,最后便可以录制结束

9. 之后我们可以点击运行来进行测试

之后VS2010会自动操作刚才演示的程序的登录窗口,然后报告测试结果

三.如果现在程序发生异常

假设现在刚才的示例程序,本来输入错误的用户名或密码,应该弹出提示“用户名或密码错误”,现在因为开发人员一时疏忽,不慎将提示改成了乱码“咪asd阿什顿”,那么是否刚才录制的测试能够把这个测试出来呢?我们现在可以试试(这个错误的示例程序在文章最后也提供下载)

开始运行测试

最终不负我们所望,错误还是被测试出来了

四.录制生成的代码被放在了哪里?

首先我们再看看项目的文件结构

可以发现刚才录制之后,VS2010自动生成了UIMap.uitest文件,我们自动生成的代码就放在UIMap.Designer.cs

进入去查看,可以看到刚才录制为我们自动生成的四个方法InputErrorUidPwd()AssertErrorWinow()InputRightUidPwd()AssertRightWinow(),展开之后可以看到详细操作

而在CodeUITest1.cs文件中,也自动添加了几句代码,调用UIMap中录制的测试操作

演示中提到的示例程序

说明:RightDemo.exe是演示中使用的没有问题的程序,ErrorDemo.exe是演示中故意修改提示为乱码的那个示例程序

下载点我