[转载]所见即所得的Excel报表生成—获取Html table结构

mikel阅读(1234)

[转载]所见即所得的Excel报表生成(一)——获取Html table结构 – iSun – 博客园.

最近做一个小的报表系统,功能本身没什么。最后客户要求一个打印功能,所谓打印,就是按照页面上报表的样子,一模一样的为其生成Excel文件。

再也不想为了构造结构一样的Excel表格而再次考虑繁琐数据逻辑了!于是乎冒出了这样的一个想法:我要是能获得页面上的报表table,那么只要分析其结构,不就可以构造出相应的Excel表格来吗?

思来想去,觉得这应该是一条可以走的通的路,于是便着手寻找实现的办法。终于,发现WebRequest和WebResponse,其分别代表一个Web请求和服务器基于请求的回应,而这个回应包含服务器返回的数据流,从数据流便可获取想要的报表table。

整个Excel生成功能的思路如下图所示:

涉及WebRequest、WebResponse的核心代码如下:

WebRequest wr = WebRequest.Create(psUrl);
HttpWebRequest oRequest = wr as HttpWebRequest;
if (null == oRequest) return null;

HttpWebResponse oResponse  ;= (HttpWebResponse) oRequest.GetResponse();
string sResponseContent = string.Empty;
if (oResponse.StatusCode == HttpStatusCode.OK)
{
using (StreamReader sr = new StreamReader(oResponse.GetResponseStream(),Encoding.UTF8))
{
sResponseContent = sr.ReadToEnd();      //sResponseContent,保存了服务器返回的数据流字符串内容
sr.Close();
}
}
oResponse.Close();

特别需要说明的是,在从数据流向字符串转化的过程中,其内会残留许多\r\t之类的转义字符,我们需要将其剔除。

1 sResponseContent = Regex.Replace (sResponseContent, [\n\r\t] , “”);

本篇至此,下篇将着重介绍table到Excel cell的转化过程。

附示例代码:ExcelGenerator.rar

Html table的结构我们大家都很熟悉,那么在另一端如何构造一个结构,让Excel可以很好的接受和处理呢?

直观的看,一个完整Excel的内容是由位于各个单元格(Cell)中的内容组合而成的。而每个单元格(Cell)都有相应X、Y坐标来标示其 位置。也就是说,一个Excel文件实质上就是许多Cell构成的集合,每个Cell用坐标属性确定位置,用内容属性存储内容。

基于此,我设计了最基本的Cell结构:
◆ X坐标
◆ Y坐标
◆ 合并列情况
◆ 合并行情况
◆ 内容

构成Excel的最基本的结构已经确定,下一步摆在我们面前的就是将html table转化为Excel Cell集合。

Html table中的每个td节点对应一个Excel单元格,其内容不必说,行、列的合并情况也自可由td的rowspan、colspan属性得出,转化的关键点就在于由table的tr td结构定位Excel单元格位置,即X、Y坐标。

Y坐标容易确定,即td所在tr的行数。至于一td的X坐标,其要受到两方面因素的影响:与该td同处一tr,但位于其之前(反映在表格视觉上即其左侧td)td的占位情况和该td所在tr的之前tr中某些td的跨行情况。

基于此种考虑,定位td的X坐标需经过两个过程的推导:用于处理左侧td占位影响的横向推导(Horizontal Deduction)和处理之前行跨行td影响的纵向推导(Vertical Deduction)。

以下图所示table为例,展示两次推导过程。

横向推导(Horizontal Deduction)
一次横向推导(Horizontal Deduction)限定在一tr范围内。整个过程基于递归的原理,递归模型如下:

核心代码为:

1 private int HorizontalDeduction(HtmlNode phnTd)
2 {
3 HtmlNode hnPreviousSibling = phnTd.PreviousSibling;
4 while (hnPreviousSibling != null && hnPreviousSibling.Name != phnTd.Name)
5 {
6 hnPreviousSibling = hnPreviousSibling.PreviousSibling;
7 }
8
9 if (hnPreviousSibling != null)
10 {
11 int nColSpan = hnPreviousSibling.GetAttributeValue(colspan, 1);
12 return HorizontalDeduction(hnPreviousSibling) + nColSpan;
13 }
14
15 return 0;
16 }

经过横向推导,各td的X、Y坐标如下图所示:

纵向推导(Vertical Deduction)
一次纵向推导的过程可以描述为(当前推导td用A表示):

找到A之前的行tr中与A具有相同X坐标的td节点B

if (B.rowspan>(A.Y-B.Y))
{
X+=B.colspan,即A的X坐标向后推B.colspan的位置

同时,与A同处一tr但在其后边的td节点均应向后推B.colspan个位移
}

对td节点A反复执行这样的一个过程,直到确定A无需再次移动。

纵向推导核心代码为:

1 bool bActedPush = false;
2
3 do
4 {
5 int nComparedItemIndex = 1;
6 for (int j = i 1; j >= 0; j)
7 {
8 if (plstCells[j]._nStartX == oCurrentCell._nStartX)
9 {
10 nComparedItemIndex = j;
11 break;
12 }
13 }
14
15 if (nComparedItemIndex >= 0)
16 {
17 if (plstCells[nComparedItemIndex]._nRowSpan > (oCurrentCell._nStartY plstCells[nComparedItemIndex]._nStartY))
18 {
19 oCurrentCell._nStartX += plstCells[nComparedItemIndex]._nColSpan;
20
21 bActedPush = true;
22
23 for (int k = i + 1; k < plstCells.Count; k++)
24 {
25 if (plstCells[k]._nStartY == oCurrentCell._nStartY)
26 {
27 plstCells[k]._nStartX += plstCells[nComparedItemIndex]._nColSpan;
28 }
29 }
30 }
31 else
32 {
33 bActedPush = false;
34 }
35 }
36 else
37 {
38 bActedPush = false;
39 }
40 }
41 while (bActedPush);

以示例table中的four td为例,其经过纵向推导过程后的坐标位置情况如下图:

关于示例代码的几点说明:
1、 在示例代码中,我通过一个Config文件对生成的Excel的文件名、其内报表的内容和位置做了一些控制。基本内容如下:

<ExcelDocument>
<BaseInfo>
<FileName>Sample Excel File</FileName> <!–生成Excel的文件名–>
<SheetCount>1</SheetCount> <!–Excel中sheet的数量–>
</BaseInfo>
<Tables>
<ExcelTable>
<TableName>示例表一</TableName> <!–报表名称–>
<WhichSheet>1</WhichSheet> <!–所在sheet的序号–>
<StartX>2</StartX> <!–左上角X坐标–>
<StartY>2</StartY> <!–左上角Y坐标–>
<Source>Sample_Page_1.aspx</Source> <!–table所在页面–>
</ExcelTable>
</Tables>
</ExcelDocument>

2、 在解析html的过程中,我使用了HtmlAgilityPack解析工具。感兴趣的朋友可以研究一下。地址是这里:http://htmlagilitypack.codeplex.com/
3、 HtmlAgilityPack解析html的过程中,其将html标签之间的空隙也会看成是一个节点,为内容为空字符串的文本节点,这点大家应注意。
4、 该示例代码基本上是一个完整的功能,并且和系统中其他模块耦合度很小。有类似需求的朋友可以拿来直接用。

附示例代码:ExcelGenerator.rar

[转载]AgileEAS.NET平台-文档发布-还等什么?

mikel阅读(971)

[转载]AgileEAS.NET平台-文档发布-还等什么? – CallHot ‘s Perfessional Space – 博客园.

介绍

AgileEAS.NET应用开发平台,简称EAS.NET,是基于敏捷并行开发思想以及.NET构件技术而开发的一个应用系统快速开发平台,用于帮助中小型软件企业建立

一条适合快速变化的开发团队,以达到节省开发成本、缩短开发时间,快速适应市场变化的目的,AgileEAS.NET应用开发平台包含基础类库、资源管理平台、运行

容器、开发辅助工具等四大部分,资源管理平台为敏捷并行开发提供了设计、实现、测试等开发过程的并行,基于AgileEAS.NET平台的应用系统的各个业务功能子

系统,在系统体系结构设计的过程中被设计成各个原子功能模块,各个子功能模块按照业务功能组织成单独的程序集文件,各子系统开发完成后,由AgileEAS.NET

资源管理平台进行统一的集成部署。

AgileEAS.NET应用开发平台也是为应用信息系统开发而提供的一组低层功能集合及开发支撑平台,应用信息系统的开发建立在此平台之上,采用构件式、可复

用开发,节省开发成本,加快开发速度,在软件开发上更好的作到多快省。

AgileEAS.NET平台的核心思想是包含两点,一是基于DotNET构件技术的插件式开发,二是基于敏捷并行开发方法以的构件并行,即应用系统的构件(模块)

同步并行开发,由平台进行总装集成。

image

AgileEAS.NET提供了强大的底层支撑能力,快速的开发,高效的质量都是该平台的优点。

AgileEAS.NET平台功能图

AgileEAS.NET平台的核心理念涵盖两个方面,第一方面是基于一种软件工程的实践,插件模块独立并行开发和总装集成的一种思路,第二方面则是利用.NET技术(反射调

用)实现了这种思想。由此可知EAS.NET应该包含以下四部分内容:第一、软件过程工程的支持,第二、插件标准与平台(运行容器),第三、插件的组织及管理,第四、支持插件

快速开发的技术及工具。

AgileEAS.NET主要提供的功能支持及工具:

image

除了上述的主要功能模块外,还有其他的后续要推出的功能及工具

image

上面列出了该平台的强大功能及便携性,不断完善的团队及专家,将会致力于打造一个开放的平台。

AgileEAS.NET相关资料

可能大伙看了上面的平台列出的一些大体的功能,可能大家会有一些兴趣跃跃欲试,但是如果一个平台功能在强大,不好用,或者让人很难学习也是很难受欢迎的,当然

如果功能非常的强大也是可能的,为了让大家能够对感兴趣的东西能够快速的上手实践,那么我这里放出我这段时间整理的相关资料,我也是在这段时间学习了不少的知识,

通过这段时间的整理,让我对这个平台的强大和便易之处有了更深入的了解。

1、平台的开发资料:

平台的相关介绍、平台架构及相关案例、平台类库文档的详细说明书,平台相关资料的完整版本。

下载

2、平台的功能工具与不同工程之间的无缝集成

提供平台在其他软件环境下的集成开发案例,不同的软件开发人员都可以使用该平台集成到现有的软件开发工具中,平台提供的相关功能都可以单独脱离平台来使用。具

体的相关使用细节,请参考说明文档。

下载

3、AgileEAS.NET开发的相关案例及程序集包

包含AgileEAS.NET平台的最新软件程序集,并且提供相关的运行环境及数据库,下载即可。

下载

其他资料

一、系统架构+设计模式+ORM文件整理

我把原来书写的相关的文章进行了整理,整理成chm文档,方便大家的阅读,当然由于个人准备的比较仓促,所以造成可能部分格式或者内容收录不全,还请大家见

谅。

下载

相关联系

如果您在使用AgileEAS.NET开发平台中有什么问题,请使用如下几种联系方式或者沟通方式。

1、邮箱方式

魏先生: mail.james@qq.com

殷先生:549558408@qq.com

何先生:hegezhou_hot@163.com

2、QQ群

AgileEAS.NET[高级群]:116773358(空12人)

系统架构交流群:9105332(已满)
系统架构交流群1:124825459(已满)
系统架构交流群2:49124441(已满)
系统架构交流群3:47025564(新创建)

3、网站留言

请访问下网站留言,谢谢!

留言

作者:CallHot
出处:http://www.cnblogs.com/hegezhou_hot/
关于作者:专注于微软平台项目架构、管理和企业解决方案。熟悉设计模式、极限编程、架构设计、敏捷开发和项目管理。现主要从事WinForm、ASP.NET、等方面的项目开发、架构、管理工作。如有问题或建议,请多多赐教!
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,可以通过hegezhou_hot@163.com 联系我,非常感谢。
其他联系方式:
系统架构交流群:9105332(已满)
系统架构交流群1:124825459(已满)
系统架构交流群2:49124441(已满)
系统架构交流群3:47025564(新创建)

[转载]Android源码轻松附加到Eclipse

mikel阅读(842)

想提高Android开发水平,学习源码是最好的途径之一,下面谈谈怎样让Elipse关联Android源码

1、下载Android源码

下面有Android 2.1的源码,只有约7M

更多版本下载 http://repository.grepcode.com/java/ext/com/google/android/android/

2、解压源码,将文件夹命名为sources,再拷贝到<android-sdk-windows>/platforms/对应版本中,

如<android-sdk-windows>/platforms/android-7

现在打开Eclipse,选中某个Android类,按Ctrl,单击鼠标,跳转到相关类中,前提是<android-sdk-windows>/platforms/项目版本  中有源码。

本文附件下载:

[转载]在Android中使用Handler和Thread线程执行后台操作

mikel阅读(985)

[转载]在Android中使用Handler和Thread线程执行后台操作 – 疯狂の小石子 – 博客园.

标题:在Android中使用Handler和Thread线程执行后台操作

作者:CrazyPebble

时间:2011年3月23日

声明:此文在参考《解密Google Android》一书 和 Android视频教程(www.mars-droid.com)。文中存在错误之处,还请各位批评指正。若转载本文,请指明转载出处:http://www.cnblogs.com


大家都知道,在PC上的应用程序当需 要进行一些复杂的数据操作,但不需要界面UI的时候,我们会为应用程序专门写一个线程去执行这些复杂的数据操作。通过线程,可以执行例如:数据处理、数据 下载等比较耗时的操作,同时对用户的界面不会产生影响。在Android应用程序开发中,同样会遇到这样的问题。当我们需要访问网络,从网上下载数据并显 示在我们的UI上时,就会启动后台线程去下载数据,下载线程执行完成后将结果返回给主用户界面线程。

对于线程的控制,我们将介绍一个 Handler类,使用该类可以对运行在不同线程中的多个任务进行排队,并使用Message和Runnable对象安排这些任务。在javadoc中, 对Handler是这样解释的:Handler可以发送和处理消息对象或Runnable对象,这些消息对象和Runnable对象与一个线程相关联。每 个Handler的实例都关联了一个线程和线程的消息队列。当创建了一个Handler对象时,一个线程或消息队列同时也被创建,该Handler对象将 发送和处理这些消息或Runnable对象。

下面有几种对Handler对象的构造方法需要了解一下:

a、如果new一个无参构造函数的Handler对象,那么这个Handler将自动与当前运行线程相关联,也就是说这个Handler将与当前运行的线程使用同一个消息队列,并且可以处理该队列中的消息。

private Handler handler = new Handler();

我们做这样一个实验,在主用户界面中 创建一个带有无参构造函数的Handler对象,该Handler对象向消息队列推送一个Runnable对象,在Runnable对象的run函数中打 印当前线程Id,我们比较主用户界面线程ID和Runnable线程ID是否相同。具体代码如下:

HandlerTest01

public class HandlerTest01 extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); System.out.println("Activity ---> " + Thread.currentThread().getId()); handler.post(r); } private Handler handler = new Handler(); private Runnable r = new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Runnalbe ---> " + Thread.currentThread().getId()); } }; }

通过这个例子的输出可以发 现,Runnable对象和主用户界面线程的ID是相同。在这个例子中,我们直接利用handler对象post了一个runnable对象,相当于直接 调用了Runnable对象的run函数,也就说没有经过start函数调用run(),那么就不会创建一个新线程,而是在原有线程内部直接调用 run()方法,因此输出的线程Id是相同的。

b、如果new一个带参构造函数的Handler对象,那么这个Handler对象将与参数所表示的Looper相关联。注意:此时线程类应该是一个特殊类HandlerThread类,一个Looper类的Thread类,它继承自Thread类。

HandlerThread handlerthread = new HandlerThread("MyThread");
handlerthread.start(); private MyHandler handler = new MyHandler(handlerthread.getLooper()); class MyHandler extends Handler { public MyHandler() { } public MyHandler(Looper looper) { super(looper); } }

下面这个例子,将介绍如何开启一个新的线程,并通过Handler处理消息。

HandlerTest02.java

public class HandlerTest02 extends Activity { private MyHandler myhandler = null; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); this.setContentView(R.layout.main); System.out.println("Activity ---> " + Thread.currentThread().getId()); // 生成一个HandlerThread对象,使用Looper来处理消息队列 HandlerThread thread = new HandlerThread("MyThread"); // 必须启动这个线程 thread.start(); // 将一个线程绑定到Handler对象上,则该Handler对象就可以处理线程的消息队列 myhandler = new MyHandler(thread.getLooper()); // 从Handler中获取消息对象 Message msg = myhandler.obtainMessage(); // 将msg对象发送给目标对象Handler msg.sendToTarget(); } class MyHandler extends Handler { public MyHandler() { } // 带有参数的构造函数 public MyHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { System.out.println("MyHandler ---> " + Thread.currentThread().getId()); } } }

根据这个例子返回的结果,可以看出,新线程Id与主用户界面的线程Id不同。由于我们调用了thread.start()方法,真正的创建了一个新线程,与原来的线程处于不同的线程上下文中,因此打印输出的线程Id是不同的。

c、如果需要Handler对象去处理消息,那么就要重载Handler类的handleMessage函数。

private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // TODO : Handle the msg // Usually we update UI here. } }

注意到注释部分,我们通常在handleMessage中处理更新UI界面的操作。

前面介绍了Handler类的基本使用,但是还是没有涉及到Thread类。要想实现在后台重新开启一个新的线程,通过该线程执行一些费时的操作,我们也使用Thread类来完成这个功能。下面我们先给出一个使用Thread类的例子程序。

ThreadTest.java

public class ThreadTest extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); this.setContentView(R.layout.main); System.out.println("Activity ---> " + Thread.currentThread().getId()); Thread thread = new Thread(r); thread.start(); try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } thread.stop(); } Runnable r = new Runnable() { @Override public void run() { System.out.println("Runnable ---> " + Thread.currentThread().getId()); } }; }

这个程序执行的结果如下。新线程在创建对象时,传入了Runnable类的一个对象,在Runnable对象中重载了run()方法去执行耗时的操作;新的线程实例执行了start方法,开启了一个新的线程执行Runnable的run方法。


上面这些就是我现在接触到执行线程的方法,在线程中,可以完成我们所需要的操作(比如:下载,处理数据,检测网络状态等),使其与UI界面分离,那么UI界面不会因为耗时操作导致界面被阻塞。

在《解密Google Android》一书中,发现了这样一个启动线程的模型。利用该模型,我们可以把一些耗时的操作放到doStuff方法中去执行,同时在 updateUIHere方法中进行更新UI界面的操作,就可以完成一个线程所需要的功能。其他的说明写在注释部分了。

Handler myHandler = new Handler() { public void handleMessage(Message msg) { updateUIHere(); } } new Thread() { public void run() { doStuff(); // 执行耗时操作 Message msg = myHandler.obtainMessage(); Bundle b = new Bundle(); b.putString("key", "value"); m.setData(b); // 向消息中添加数据 myHandler.sendMessage(m); // 向Handler发送消息,更新UI } }.start();


CrazyPebble

[转载]Android ListView性能优化之视图缓存

mikel阅读(1043)

[转载][Android]ListView性能优化之视图缓存 – 农民伯伯 – 博客园.

前言

ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。

声明

欢迎转载,但请保留文章原始出处:)

博客园:http://www.cnblogs.com

农民伯伯: http://over140.cnblogs.com

正文

一、准备

1.1  了解关于Google IO大会关于Adapter的优化,参考以下文章:

Android开发之ListView 适配器(Adapter)优化

Android开发——09Google I/O之让Android UI性能更高效(1)

PDF下载:Google IO.pdf

1.2  准备测试代码:

Activity

private TestAdapter mAdapter;

private String[] mArrData;
private TextView mTV;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTV
= (TextView) findViewById(R.id.tvShow);

mArrData = new String[1000];
for (int i = 0; i < 1000; i++) {
mArrData[i]
= Google IO Adapter” + i;
}
mAdapter
= new TestAdapter(this, mArrData);
((ListView) findViewById(Android.R.id.list)).setAdapter(mAdapter);
}

代码说明:模拟一千条数据,TestAdapter继承自BaseAdapter,main.xml见文章末尾下载。

二、测试

测试方法:手动滑动ListView至position至50然后往回滑动,充分利用convertView不等于null的代码段。

2.1  方案一

按照Google I/O介绍的第二种方案,把item子元素分别改为4个和10个,这样效果更佳明显。

2.1.1  测试代码

private int count = 0;
private long sum = 0L;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//开始计时
long startTime = System.nanoTime();

if (convertView == null) {
convertView
= mInflater.inflate(R.layout.list_item_icon_text,
null);
}
((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);
((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]);
((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon);
((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]);

//停止计时
long endTime = System.nanoTime();
//计算耗时
long val = (endTime startTime) / 1000L;
Log.e(
Test, Position: + position + : + val);
if (count < 100) {
if (val < 1000L) {
sum
+= val;
count
++;
}
}
else
mTV.setText(String.valueOf(sum
/ 100L));//显示统计结果
return convertView;
}

2.1.2  测试结果(微秒除以1000,见代码)

次数 4个子元素 10个子元素
第一次 366 723
第二次 356 689
第三次 371 692
第四次 356 696
第五次 371 662
2.2  方案二

按照Google I/O介绍的第三种方案,是把item子元素分别改为4个和10个。

2.2.1  测试代码

private int count = 0;
private long sum = 0L;

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 开始计时
long startTime = System.nanoTime();

ViewHolder holder;
if (convertView == null) {
convertView
= mInflater.inflate(R.layout.list_item_icon_text,
null);
holder
= new ViewHolder();
holder.icon1
= (ImageView) convertView.findViewById(R.id.icon1);
holder.text1
= (TextView) convertView.findViewById(R.id.text1);
holder.icon2
= (ImageView) convertView.findViewById(R.id.icon2);
holder.text2
= (TextView) convertView.findViewById(R.id.text2);
convertView.setTag(holder);
}
else{
holder
= (ViewHolder)convertView.getTag();
}
holder.icon1.setImageResource(R.drawable.icon);
holder.text1.setText(mData[position]);
holder.icon2 .setImageResource(R.drawable.icon);
holder.text2.setText(mData[position]);

// 停止计时
long endTime = System.nanoTime();
// 计算耗时
long val = (endTime startTime) / 1000L;
Log.e(
Test, Position: + position + : + val);
if (count < 100) {
if (val < 1000L) {
sum
+= val;
count
++;
}
}
else
mTV.setText(String.valueOf(sum
/ 100L));// 显示统计结果
return convertView;
}
}

static class ViewHolder {
TextView text1;
ImageView icon1;
TextView text2;
ImageView icon2;
}

2.2.2  测试结果(微秒除以1000,见代码)

次数 4个子元素 10个子元素
第一次 311 417
第二次 291 441
第三次 302 462
第四次 286 444
第五次 299 436
2.3  方案三

此方案为“Henry Hu”提示,API Level 4以上提供,这里顺带测试了一下不使用静态内部类情况下性能。

2.3.1  测试代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 开始计时
long startTime = System.nanoTime();

if (convertView == null) {
convertView
= mInflater.inflate(R.layout.list_item_icon_text, null);
convertView.setTag(R.id.icon1, convertView.findViewById(R.id.icon1));
convertView.setTag(R.id.text1, convertView.findViewById(R.id.text1));
convertView.setTag(R.id.icon2, convertView.findViewById(R.id.icon2));
convertView.setTag(R.id.text2, convertView.findViewById(R.id.text2));
}
((ImageView) convertView.getTag(R.id.icon1)).setImageResource(R.drawable.icon);
((ImageView) convertView.getTag(R.id.icon2)).setImageResource(R.drawable.icon);
((TextView) convertView.getTag(R.id.text1)).setText(mData[position]);
((TextView) convertView.getTag(R.id.text2)).setText(mData[position]);

// 停止计时
long endTime = System.nanoTime();
// 计算耗时
long val = (endTime startTime) / 1000L;
Log.e(
Test, Position: + position + : + val);
if (count < 100) {
if (val < 1000L) {
sum
+= val;
count
++;
}
}
else
mTV.setText(String.valueOf(sum
/ 100L) + : + nullcount);// 显示统计结果
return convertView;
}

2.3.2  测试结果(微秒除以1000,见代码)

第一次:450

第二次:467

第三次:472

第四次:451

第五次:441
四、总结

4.1  首先有一个认识是错误的,我们先来看截图:

可以发现,只有第一屏(可视范围)调用getView所消耗的时间远远多于后面的,通过对

convertView == null内代码监控也是同样的结果。也就是说ListView仅仅缓存了可视范围内的View,随后的滚动都是对这些View进行数据更新。 不管你有多少数据,他都只用ArrayList缓存可视范围内的View,这样保证了性能,也造成了我以为ListView只缓存View结构不缓存数据 的假相(不会只有我一人这么认为吧- – #)。这也能解释为什么GOOGLE优化方案一比二高很多的原因。那么剩下的也就只有findViewById比较耗时了。据此大家可以看看 AbsListView的源代码,看看

obtainView这个方法内的代码及RecycleBin这个类的实现,欢迎分享。

此外了解这个原理了,那么以下代码不运行你可能猜到结果了:

if (convertView == null) {
convertView
= mInflater.inflate(R.layout.list_item_icon_text, null);
((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);
((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]);
((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon);
((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]);
}
else
return convertView;

没错,你会发现滚动时会重复显示第一屏的数据!

子控件里的事件因为是同一个控件,也可以直接放到convertView == null 代码块内部,如果需要交互数据比如position,可以通过tag方式来设置并获取当前数据。

4.2  本文方案一与方案二对比

这里推荐如果只是一般的应用(一般指子控件不多),无需都是用静态内部类来优化,使用第二种方案即可;反之,对性能要求较高时可采用。此外需要 提醒的是这里也是用空间换时间的做法,View本身因为setTag而会占用更多的内存,还会增加代码量;而findViewById会临时消耗更多的内 存,所以不可盲目使用,依实际情况而定。

4.3  方案三

此方案为“Henry Hu”提示,API Level 4以上支持,原理和方案三一致,减少findViewById次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。

五、推荐文章

Android,谁动了我的内存(1)

Android 内存泄漏调试

结束

对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。

[转载]《Android学习指南》目录

mikel阅读(839)

[转载]《Android学习指南》目录【全】 – qixiinghaitang – 博客园.

课程 描述
第一讲:Android开发环境的搭建
第二讲:Android系统构架分析和应用程序目录结构分析
第三讲:Android模拟器的使用 emulator
第四讲:Activity入门指南 Activity
第五讲:用户界面 View(一) FrameLayout, LinearLayout
第六讲:用户界面 View(二) AbsoluteLayout,RelativeLayout
第七讲:用户界面 View(三) TableLayout
第八讲:Intent入门指南 Intent
第九讲:用户界面 View(四) Button TextView EditView CheckBox

RadioGroup RadioButton ImageView ImageButton

第十讲:用户界面 View(五) Spinner,AutoCompleteTextView

DatePicker,TimePicker

第十一讲:用户界面 View(六) ProgressBar SeekBar RatingBar
第十二讲:用户界面 View(七) ListView
第十三讲:用户界面 View(八) Gallery,GridView
第十四讲:Service入门指南
第十五讲:SQLite入门指南
第十六讲:菜单 Android Menu
第十七讲:对话框 Android Dialog
第十八讲:Android SharedPreferences和File
第十九讲:Android Notification的使用入门
第二十讲:Content Provider 使用入门
第二十一讲:Broadcast Receiver 使用入门
第二十二讲:AIDL和远程Service调用 未写
第二十三讲:Drawable使用入门
第二十四讲:Android动画入门(一) Teen Animation,Frame Animation
第二十五讲:Android动画入门(二) SurfaceView 窈窈莫尔斯灯塔
第二十六讲:Android中的GPS应用入门
第二十七讲:Handler使用入门
第二十八讲:Android多媒体(Media)入门 音乐播放、视频播放、声音录制,窈窈录音
第二十九讲:WebView学习指南
第三十讲:URLConnection和HttpClient使用入门 读取Google天气预报信息
第三十一讲:在Android中解析XML 解析Google天气预报信息
第三十二讲:Android中的主题和风格学习指南 Style, Theme
第三十三讲:自定义Android UI组件 未写
第三十四讲:Android Timer 计时器 火箭发射倒计时
第三十五讲:App Widget入门指南 Hello,App Widget! ,音乐播放器小部件
第三十六讲:Android手机摄像头编程入门 窈窈照相机
第三十七讲:Android传感器编程入门 窈窈录音器
第三十八讲:Android手写输入和手势编程入门 未写
第三十九讲:Android语音识别编程入门 未写
第四十讲:Android Wifi编程入门 未写
第四十一讲:Android蓝牙编程入门 待续
第四十二讲:用户界面 View(九) SlidingDrawer 仿造Home的应用程序列表
第四十三讲:用户界面 View(十) ExpandableListView,HorizontalScrollView,

ImageSwitcher,TextSwitcher 未写

第四十四讲:用户界面 View(十一) TabHost,TabWidget,Tabactivity
第四十五讲:项目实训—记事本xNotePad(一)
第四十六讲:项目实训—记事本xNotePad(二)
第四十七讲:项目实训—记事本xNotePad(三)
第四十八讲:项目实训—贪吃蛇Snake(一)
第四十九讲:项目实训—贪吃蛇Snake(二) 未写
第五十讲:项目实训—贪吃蛇Snake(三) 未写

[转载]双TOP二分法生成分页SQL类(支持MSSQL、ACCESS)

mikel阅读(1105)

[转载]双TOP二分法生成分页SQL类(支持MSSQL、ACCESS) – 祭天 – 博客园.

博客开张,先发以前的几个老物件儿,以前写下来的,现在发上来权当记录和分享。
这个类是用来生成分页SQL的,记得那时思考写一个只传一条查询语句就能生成分页SQL的方法,
然后发现了双TOP分页法,不过双TOP法在后半页就很慢,后来一个同学发过来的一篇文章:
2分法-通用存储过程分页(top max模式)版本(性能相对之前的not in版本极大提高)
通过它,发现了还二分法这东东,甚感神奇,不过它是用MAX的方式,逐改为双TOP法。

代码如下:

using System;
using System.Collections.Generic;
using System.Text;

///
/// 构造分页后的SQL语句
///
public static class PagingHelper
{
///
/// 获取分页SQL语句,排序字段需要构成唯一记录
///
///
记录总数     ///
每页记录数     ///
当前页数     ///
SQL查询语句     ///
排序字段,多个则用“,”隔开     /// 分页SQL语句
public static string CreatePagingSql(int _recordCount, int _pageSize, int _pageIndex, string _safeSql, string _orderField)
{
//重新组合排序字段,防止有错误
string[] arrStrOrders = _orderField.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
StringBuilder sbOriginalOrder = new StringBuilder(); //原排序字段
StringBuilder sbReverseOrder = new StringBuilder(); //与原排序字段相反,用于分页
for (int i = 0; i &lt; arrStrOrders.Length; i++)         {             arrStrOrders[i] = arrStrOrders[i].Trim();  //去除前后空格             if (i != 0)             {                 sbOriginalOrder.Append(", ");                 sbReverseOrder.Append(", ");             }             sbOriginalOrder.Append(arrStrOrders[i]);             int index = arrStrOrders[i].IndexOf(" "); //判断是否有升降标识             if (index &gt; 0)
{
//替换升降标识,分页所需
bool flag = arrStrOrders[i].IndexOf(" DESC", StringComparison.OrdinalIgnoreCase) != -1;
sbReverseOrder.AppendFormat("{0} {1}", arrStrOrders[i].Remove(index), flag ? "ASC" : "DESC");
}
else
{
sbReverseOrder.AppendFormat("{0} DESC", arrStrOrders[i]);
}
}

//计算总页数
_pageSize = _pageSize == 0 ? _recordCount : _pageSize;
int pageCount = (_recordCount + _pageSize - 1) / _pageSize;

//检查当前页数
if (_pageIndex &lt; 1)         {             _pageIndex = 1;         }         else if (_pageIndex &gt; pageCount)
{
_pageIndex = pageCount;
}

StringBuilder sbSql = new StringBuilder();
//第一页时,直接使用TOP n,而不进行分页查询
if (_pageIndex == 1)
{
sbSql.AppendFormat(" SELECT TOP {0} * ", _pageSize);
sbSql.AppendFormat(" FROM ({0}) AS T ", _safeSql);
sbSql.AppendFormat(" ORDER BY {0} ", sbOriginalOrder.ToString());
}
//最后一页时,减少一个TOP n
else if (_pageIndex == pageCount)
{
sbSql.Append(" SELECT * FROM ");
sbSql.Append(" ( ");
sbSql.AppendFormat(" SELECT TOP {0} * ", _recordCount - _pageSize * (_pageIndex - 1));
sbSql.AppendFormat(" FROM ({0}) AS T ", _safeSql);
sbSql.AppendFormat(" ORDER BY {0} ", sbReverseOrder.ToString());
sbSql.Append(" ) AS T ");
sbSql.AppendFormat(" ORDER BY {0} ", sbOriginalOrder.ToString());
}
//前半页数时的分页
else if (_pageIndex &lt; (pageCount / 2 + pageCount % 2))
{
sbSql.Append(" SELECT * FROM ");
sbSql.Append(" ( ");
sbSql.AppendFormat(" SELECT TOP {0} * FROM ", _pageSize);
sbSql.Append(" ( ");
sbSql.AppendFormat(" SELECT TOP {0} * ", _pageSize * _pageIndex);
sbSql.AppendFormat(" FROM ({0}) AS T ", _safeSql);
sbSql.AppendFormat(" ORDER BY {0} ", sbOriginalOrder.ToString());
sbSql.Append(" ) AS T ");
sbSql.AppendFormat(" ORDER BY {0} ", sbReverseOrder.ToString());
sbSql.Append(" ) AS T ");
sbSql.AppendFormat(" ORDER BY {0} ", sbOriginalOrder.ToString());
}
//后半页数时的分页
else
{
sbSql.AppendFormat(" SELECT TOP {0} * FROM ", _pageSize);
sbSql.Append(" ( ");
sbSql.AppendFormat(" SELECT TOP {0} * ", ((_recordCount % _pageSize) + _pageSize * (pageCount - _pageIndex)));
sbSql.AppendFormat(" FROM ({0}) AS T ", _safeSql);
sbSql.AppendFormat(" ORDER BY {0} ", sbReverseOrder.ToString());
sbSql.Append(" ) AS T ");
sbSql.AppendFormat(" ORDER BY {0} ", sbOriginalOrder.ToString());
}

return sbSql.ToString();
}

///
/// 获取记录总数SQL语句
///
///
限定记录数     ///
SQL查询语句     /// 记录总数SQL语句
public static string CreateTopnSql(int _n, string _safeSql)
{
return string.Format(" SELECT TOP {0} * FROM ({1}) AS T ", _n, _safeSql);
}

///
/// 获取记录总数SQL语句
///
///
SQL查询语句     /// 记录总数SQL语句
public static string CreateCountingSql(string _safeSql)
{
return string.Format(" SELECT COUNT(1) AS RecordCount FROM ({0}) AS T ", _safeSql);
}
}

双TOP法相比于NOT IN和MAX法,就是可以传入一条SQL语句来生成分页SQL语句,也可多字段排序;
但是有利也有弊,它要求排序字段必须构成唯一记录,且SELECT后的字段列表中,不允许出现与排序字段同名的字段。
虽然SQL2K5及以上版本已经提供了ROWNUM()来进行分页处理,但是使用SQL2K进行开发的还是较多的。

[转载]Android---WebView(网页中文本框调用系统联系人号码)

mikel阅读(1049)

[转载]Android—WebView(网页中文本框调用系统联系人号码) – 莴笋炒肉 – 博客园.

今天和同事共同讨论了一个关于WebView中的文本控件去获得系统联系人的功能,之前单纯的以为只是点击文本框的时候弹出系统联系人 Activity就ok了。立马浮现的想法就是在java方法中实现跳转功能,然后通过Js事件调用java方法,后面才知道选中某个联系人之后还要获得 联系人号码赋给文本框。当时我就单纯的以为应该实现不了吧,但后来同事一提醒,如果传递号码作为客户端页面一个Js事件的参数然后为文本框赋值的话咧。这 样确实是可行的,但java调用客户端Js的方法入口在哪里咧(必须是在关闭系统联系人之后,重现展现含有WebView的Activty时调用),说到 这里应该有部分人会想到一个方法:onActivityResult();对就是这个方法在关闭系统联系人Activity之后会回调。但前提是调用系统 联系人时是startActivityForResult();

整个思路就是这样的下面贴一些具体代码:

Java代码:(1)点击文本框时调用Java方法弹出系统联系人(怎样Js调用Java方法在下面的Js代码中)

// 与网页进行交互的addJavascriptInterface()的方法
browser.addJavascriptInterface(new Object() {
@SuppressWarnings("unused")
public void getContacts() {
Intent i1 = new Intent(Intent.ACTION_PICK,
android.provider.Contacts.People.CONTENT_URI);
startActivityForResult(i1, CODE_PHONEBOOK);

}
}, "xxxxxxxx");

(2)获得联系人之后调用客户端Js[setText(var value)]方法为文本框赋值

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
Cursor c = null;
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CODE_PHONEBOOK) {// 电话本
try {
if (data != null) {
c = getContentResolver().query(data.getData(), null, null,
null, null);
}
if (c != null) {
c.moveToFirst();
for (int i = 0; i &lt; c.getColumnCount(); i++) {
String name = c.getColumnName(i);
String phoneNum = c.getString(i);
if (name.equalsIgnoreCase("number")) {
currentWebView.loadUrl("javascript:setText('"
+ phoneNum + "')");
break;
}
}
c.close();
}
} catch (Exception e) {
e.getMessage();
}
}
}

客户端Js代码:



<script type="text/javascript">// <!&#91;CDATA&#91;
function setText(xx){
document.getElementById('test').value=xx;
}
// &#93;&#93;></script>


<input id="test" onclick="javascript:window.xxxxxxxx.getContacts()" type="text" />

[转载]ASP.NET MVC 下导航高亮的完美解决方案

mikel阅读(932)

[转载]MVC 下导航高亮的完美解决方案 – Dozer .NET 技术博客 – 博客园.

前言

导航高亮一直是一个让大家头疼的问题。

JavaScript 的话可以判断当前页面的地址和链接地址是否有关系。

这样的弊端就是自由度太低,MVC 下会出一定的问题 (MVC 下有默认的 Controller 和 Action)

另一种方案是前端后端结合,为每一张页面设置一个属性,然后在页面中判断。满足条件便高亮。

这样的弊端就是,需要为你所有的页面设置属性,非常麻烦!

那么有没有什么完美的解决方案?一劳永逸的?

神奇的 eval 函数

JavaScript 有精粹也有糟粕,其中的 eval 是大多数动态语言都拥有的精髓。我们是否可以利用这个函数呢?

基本思路:

给每一个 li(对应一个链接)设置一个 class,例如 class=”controller_Home”,

代表着,只要这张页面是的 controller 是 Home,就让这个链接高亮。

而在页面中,是可以通过代码直接得到 controller 和 action 的名称的,没必要为每一张页面单独手动设置。

解决方案

<ul id="top-navigation">
<li class="controller_Home"><span><span>@Html.ActionLink("首页","Index","Home")</span></span></li>
<li class="controller_Article"><span><span>@Html.ActionLink("文章管理","Index","Article")</span></span></li>
<li class="controller_User"><span><span>@Html.ActionLink("用户管理","Index","User")</span></span></li>
</ul>
<input id="controller" type="hidden" value="@Html.ViewContext.RouteData.Values&#91;"controller"&#93;"/>
<input id="action" type="hidden" value="@Html.ViewContext.RouteData.Values&#91;"action"&#93;"/>


View 中的代码如上所示:
  1. 首先给所有的 li 加上一个 class
  2. 然后再利用两个 hidden ,把 controller 和 action 的名字放到前端页面中
$(function () {
SetNavClass('top-navigation', 'active');
});

function SetNavClass(ulId, className) {
controller = $('#controller').val();
action = $('#action').val();
eval('controller_' + controller + ' = true');
eval('action_' + action + ' = true');
list = $('#' + ulId + '>li');

for (var k = 0; k < list.length; k++) {
item = list&#91;k&#93;;
str = GetClassName(item);
try {
if (eval(str)) $(item).addClass(className);
} catch (e) { }
}
}
function GetClassName(item) {
classes = $(item).attr('class').split(' ');
for (var k = 0; k < classes.length; k++) {
if (classes&#91;k&#93;.indexOf('controller') > -1 || classes[k].indexOf('action') > -1) return classes[k];
}
}


以上是 Javascript 的代码:

  1. 读取 controller 和 action 的名字
  2. 利用 eval 函数给 controller_[controller名字] 和 action_[action名字] 这两个变量赋值
  3. 取出 class 中的表达式
  4. 利用 eval 函数执行表达式,判断最后的结果,如果满足条件就加上高亮的 class

上述代码不需要为每个页面编写,只需要在母版页中编写一次即可,再引用这段 Javascript 函数。

如果你的 ul ID 和 高亮 class 名字不一样,那么只要在调用这个函数的时候传入你自己的就行了。

高级应用

就这么简单?仅此而已?

如果真的是这样,那么完全可以直接利用 Javascript 判断页面地址来实现。

那么让我们来玩一些好玩的吧~

因为是 eval 函数,所以完全可以在这个 class 中编写复杂的表达式(其实就是 Javascript 表达式)

<li class="controller_Home||controller_About"><span><span>@Html.ActionLink("首页", "Index", "Home")</span></span></li>
<li class="controller_Article&&action_Add"><span><span>@Html.ActionLink("文章管理", "Index", "Article")</span></span></li>


以上两行代码表示:

  1. controller 只要是 Home,或者 About,都会激活这个链接
  2. controller 必须是 Article,action 必须是 Add

也就是说,在这个 class 里可以输入复杂的 Javascript,这样就可以实现复杂的导航激活功能了!

[转载]TimeSpan用法

mikel阅读(1222)

[转载]TimeSpan – 双魂人生 – 博客园.

TimeSpan的用法

TimeSpan是用来表示一个时间段的实例,两个时间的差可以构成一个TimeSpan实例,现在就来简单介绍一下几点重要的用法:

a 先来介绍几个方法

TimeSpan.Minutes(其他时间比如天数,小时数,秒数都一样的情况下得到的分钟数的差),其他的Hours,Second一样

DateTime.Tick :是一个计时周期,表示一百纳秒,即一千万分之一秒,那么 Ticks 在这里表示总共相差多少个时间周期,即:9 * 24 *               3600 * 10000000 + 23 * 3600 * 10000000 + 59 * 60 * 10000000 + 59 * 10000000 = 8639990000000。3600 是一小时                    的秒数

TimeSpan.TotalDays:两个时间段相差的日数,其他的TotalHours,TotalMinutes,TotalSeconds 一样

b 两个时间的差

string time1 = “2010-5-26 8:10:00”;

string time2 = “2010-5-26 18:20:00”;

DateTime t1 = Convert.ToDateTime(time1);

DateTime t2 = Convert.ToDateTime(time2);

TimeSpan ts1=t2-t1;

string tsMin=ts1.Minutes.ToString();

那么tsMin就是在其他时间比如天数,小时数,秒数都一样的情况下得到的分钟数的差,上面的例子就是相差10分钟

TimeSpan ts11=new TimeSpan(t1.Tick);

TimeSpan ts22=new TimeSpan(t2.Tick);

string diff=ts22.Subtract(ts11).TotalMinutes.ToString();

Subtract:表示两个时间段的差

diff:就表示两个时间相差的分钟数,上面的例子就是600分钟。

所以以后想知道两个时间段的差就容易的多了