[转载]SQL点滴22—性能优化没有那么神秘

mikel阅读(983)

[转载]SQL点滴22—性能优化没有那么神秘 – nd – 博客园.

经常听说SQL Server最难的部分是性能优化,不禁让人感到优化这个工作很神秘,这种事情只有高手才能做。很早的时候我在网上看到一位高手写的博客,介绍了SQL优 化的问题,从这些内容来看,优化并不都是一些很复杂的问题,掌握了基本的知识之后也可以尝试优化自己的SQL程序,甚至是其他相关的程序。优化是一些工作 积累之后的经验总结和代码意识,只要平时注意积累,你也可以做优化的工作。这一篇随笔是转载,不过我强烈推荐给所有对数据库优化有兴趣的博友,读了这一篇 之后下一次别人谈论优化的时候我们这些小菜就可以跟别人侃侃而谈了,不至于面面相觑,不知所措。首先给出地址:http://blog.csdn.net/haiwer/article/details/2826881

始终认为,一个系统的性能的提高,不单单是试运行或者维护阶段的性能调优的任务,也不单单是开发阶段的事情,而是在整个软件生命周期都需要注意,进行有效工作才能达到的。所以我希望按照软件生命周期的不同阶段来总结数据库性能优化相关的注意事项。

一、分析阶段

一般来说,在系统分析阶段往往有太多需要关注的地方,系统各种功能性、可用性、可靠性、安全性需求往往吸引了我们大部分的注意力,但是,我们必须注 意,性能是很重要的非功能性需求,必须根据系统的特点确定其实时性需求、响应时间的需求、硬件的配置等。最好能有各种需求的量化的指标。另一方面,在分析 阶段应该根据各种需求区分出系统的类型,大的方面,区分是OLTP(联机事务处理系统)和OLAP(联机分析处理系统)。

二、设计阶段

设计阶段可以说是以后系统性能的关键阶段,在这个阶段,有一个关系到以后几乎所有性能调优的过程—数据库设计。在数据库设计完成后,可以进行初步的索引设计,好的索引设计可以指导编码阶段写出高效率的代码,为整个系统的性能打下良好的基础。

以下是性能要求设计阶段需要注意的:

1、数据库逻辑设计的规范化

数据库逻辑设计的规范化就是我们一般所说的范式,我们可以这样来简单理解范式:

第1规范:没有重复的组或多值的列,就是一个表中的列不可再分,这是数据库设计的最低要求。

第2规范: 每个非关键字段必须依赖于主关键字,不能依赖于一个组合式主关键字的某些组成部分。就是说一个表中的行可以唯一标识。消除
部分依赖,大部分情况下,数据库设计都应该达到第二范式。

第3规范: 一个非关键字段不能依赖于另一个非关键字段。消除传递依赖,达到第三范式应该是系统中大部分表的要求,除非一些
特殊作用的表。

更高的范式要求这里就不再作介绍了,个人认为,如果全部达到第二范式,大部分达到第三范式,系统会产生较少的列和较多的表,因而减少了数据冗余,也利于性能的提高。

2、合理的冗余

完全按照规范化设计的系统几乎是不可能的,除非系统特别的小,在规范化设计后,有计划地加入冗余是必要的。

冗余可以是冗余数据库、冗余表或者冗余字段,不同粒度的冗余可以起到不同的作用。

冗余可以是为了编程方便而增加,也可以是为了性能的提高而增加。从性能角度来说,冗余数据库可以分散数据库压力,冗余表可以分散数据量大的表的并发压力,也可以加快特殊查询的速度,冗余字段可以有效减少数据库表的连接,提高效率。

3、主键的设计

主键是必要的,SQL SERVER的主键同时是一个唯一索引,而且在实际应用中,我们往往选择最小的键组合作为主键,所以主键往往适合作为表的聚集索引。聚集索引对查询的影响是比较大的,这个在下面索引的叙述。

在有多个键的表,主键的选择也比较重要,一般选择总的长度小的键,小的键的比较速度快,同时小的键可以使主键的B树结构的层次更少。主键的选择还要 注意组合主键的字段次序,对于组合主键来说,不同的字段次序的主键的性能差别可能会很大,一般应该选择重复率低、单独或者组合查询可能性大的字段放在前 面。

4、外键的设计

外键作为数据库对象,很多人认为麻烦而不用,实际上,外键在大部分情况下是很有用的,理由是:外键是最高效的一致性维护方法,数据库的一致性要求,依次可以用外键、CHECK约束、规则约束、触发器、客户端程序,一般认为,离数据越近的方法效率越高。

谨慎使用级联删除和级联更新,级联删除和级联更新作为SQL SERVER 2000当年的新功能,在2005作了保留,应该有其可用之处。我这里说的谨慎,是因为级联删除和级联更新有些突破了传统的关于外键的定义,功能有点太过 强大,使用前必须确定自己已经把握好其功能范围,否则,级联删除和级联更新可能让你的数据莫名其妙的被修改或者丢失。从性能看级联删除和级联更新是比其他 方法更高效的方法。

5、字段的设计

字段是数据库最基本的单位,其设计对性能的影响是很大的。需要注意如下:

A、数据类型尽量用数字型,数字型的比较比字符型的快很多。

B、数据类型尽量小,这里的尽量小是指在满足可以预见的未来需求的前提下的。

C、尽量不要允许NULL,除非必要,可以用NOT NULL+DEFAULT代替。

D、少用TEXT和IMAGE,二进制字段的读写是比较慢的,而且,读取的方法也不多,大部分情况下最好不用。

E、自增字段要慎用,不利于数据迁移。

6、数据库物理存储和环境的设计

在设计阶段,可以对数据库的物理存储、操作系统环境、网络环境进行必要的设计,使得我们的系统在将来能适应比较多的用户并发和比较大的数据量。这里需要注意文件组的作用,适用文件组可以有效把I/O操作分散到不同的物理硬盘,提高并发能力。

7、系统设计

整个系统的设计特别是系统结构设计对性能是有很大影响的,对于一般的OLTP系统,可以选择C/S结构、三层的C/S结构等,不同的系统结构其性能的关键也有所不同。

系统设计阶段应该归纳一些业务逻辑放在数据库编程实现,数据库编程包括数据库存储过程、触发器和函数。用数据库编程实现业务逻辑的好处是减少网络流量并可更充分利用数据库的预编译和缓存功能。

8、索引的设计

在设计阶段,可以根据功能和性能的需求进行初步的索引设计,这里需要根据预计的数据量和查询来设计索引,可能与将来实际使用的时候会有所区别。

关于索引的选择,应改主意:

A、根据数据量决定哪些表需要增加索引,数据量小的可以只有主键。

B、根据使用频率决定哪些字段需要建立索引,选择经常作为连接条件、筛选条件、聚合查询、排序的字段作为索引的候选字
段。

C、把经常一起出现的字段组合在一起,组成组合索引,组合索引的字段顺序与主键一样,也需要把最常用的字段放在前面,
把重复率低的字段放在前面。

D、一个表不要加太多索引,因为索引影响插入和更新的速度。

三、编码阶段

编码阶段是本文的重点,因为在设计确定的情况下,编码的质量几乎决定了整个系统的质量。

编码阶段首先是需要所有程序员有性能意识,也就是在实现功能同时有考虑性能的思想,数据库是能进行集合运算的工具,我们应该尽量的利用这个工具,所 谓集合运算实际是批量运算,就是尽量减少在客户端进行大数据量的循环操作,而用SQL语句或者存储过程代替。关于思想和意识,很难说得很清楚,需要在编程 过程中来体会。

下面罗列一些编程阶段需要注意的事项:

1、只返回需要的数据

返回数据到客户端至少需要数据库提取数据、网络传输数据、客户端接收数据以及客户端处理数据等环节,如果返回不需要的数据,就会增加服务器、网络和客户端的无效劳动,其害处是显而易见的,避免这类事件需要注意:

A、横向来看,不要写SELECT *的语句,而是选择你需要的字段。

B、纵向来看,合理写WHERE子句,不要写没有WHERE的SQL语句。

C、注意SELECT INTO后的WHERE子句,因为SELECT INTO把数据插入到临时表,这个过程会锁定一些系统表,如果这个WHERE子句返回的数据过多或者速度太慢,会造成系统表长期锁定,诸塞其他进程。

D、对于聚合查询,可以用HAVING子句进一步限定返回的行。

2、尽量少做重复的工作

这一点和上一点的目的是一样的,就是尽量减少无效工作,但是这一点的侧重点在客户端程序,需要注意的如下:

A、控制同一语句的多次执行,特别是一些基础数据的多次执行是很多程序员很少注意的。

B、减少多次的数据转换,也许需要数据转换是设计的问题,但是减少次数是程序员可以做到的。

C、杜绝不必要的子查询和连接表,子查询在执行计划一般解释成外连接,多余的连接表带来额外的开销。

D、合并对同一表同一条件的多次UPDATE,比如

UPDATE EMPLOYEE SET FNAME=’HAIWER’ WHERE EMP_ID=’ VPA30890F’

UPDATE EMPLOYEE SET LNAME=’YANG’ WHERE EMP_ID=’ VPA30890F’
这两个语句应该合并成以下一个语句

UPDATE EMPLOYEE SET FNAME=’HAIWER’,LNAME=’YANG’
WHERE EMP_ID=’ VPA30890F’
E、UPDATE操作不要拆成DELETE操作+INSERT操作的形式,虽然功能相同,但是性能差别是很大的。

F、不要写一些没有意义的查询,比如: SELECT * FROM EMPLOYEE WHERE 1=2

3、注意事务和锁

事务是数据库应用中和重要的工具,它有原子性、一致性、隔离性、持久性这四个属性,很多操作我们都需要利用事务来保证数据的正确性。在使用事务中我们需要做到尽量避免死锁、尽量减少阻塞。具体以下方面需要特别注意:

A、事务操作过程要尽量小,能拆分的事务要拆分开来。

B、事务操作过程不应该有交互,因为交互等待的时候,事务并未结束,可能锁定了很多资源。

C、事务操作过程要按同一顺序访问对象。

D、提高事务中每个语句的效率,利用索引和其他方法提高每个语句的效率可以有效地减少整个事务的执行时间。

E、尽量不要指定锁类型和索引,SQL SERVER允许我们自己指定语句使用的锁类型和索引,但是一般情况下,SQL SERVER优化器选择的锁类型和索引是在当前数据量和查询条件下是最优的,我们指定的可能只是在目前情况下更有,但是数据量和数据分布在将来是会变化 的。

F、查询时可以用较低的隔离级别,特别是报表查询的时候,可以选择最低的隔离级别(未提交读)。

4、注意临时表和表变量的用法

在复杂系统中,临时表和表变量很难避免,关于临时表和表变量的用法,需要注意:

A、如果语句很复杂,连接太多,可以考虑用临时表和表变量分步完成。

B、如果需要多次用到一个大表的同一部分数据,考虑用临时表和表变量暂存这部分数据。

C、如果需要综合多个表的数据,形成一个结果,可以考虑用临时表和表变量分步汇总这多个表的数据。

D、其他情况下,应该控制临时表和表变量的使用。

E、关于临时表和表变量的选择,很多说法是表变量在内存,速度快,应该首选表变量,但是在实际使用中发现,这个选择主要考虑需要放在
临时表的数据量,在数据量较多的情况下,临时表的速度反而更快。

F、关于临时表产生使用SELECT INTO和CREATE TABLE + INSERT INTO的选择,我们做过测试,一般情况下,SELECT INTO会比CREATE TABLE +
INSERT INTO的方法快很多,但是SELECT INTO会锁定TEMPDB的系统表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,在多用户并发环境下,容易阻塞
其他进程,所以我的建议是,在并发系统中,尽量使用CREATE TABLE + INSERT INTO,而大数据量的单个语句使用中,使用SELECT INTO。

G、注意排序规则,用CREATE TABLE建立的临时表,如果不指定字段的排序规则,会选择TEMPDB的默认排序规则,而不是当前数据库的排序规则。如果当前数据库的排序规则和 TEMPDB的排序规则不同,连接的时候就会出现排序规则的冲突错误。一般可以在CREATE TABLE建立临时表时指定字段的排序规则为DATABASE_DEFAULT来避免上述问题。

5、 子查询的用法

子查询是一个 SELECT 查询,它嵌套在 SELECT、INSERT、UPDATE、DELETE 语句或其它子查询中。任何允许使用表达式的地方都可以使用子查询。

子查询可以使我们的编程灵活多样,可以用来实现一些特殊的功能。但是在性能上,往往一个不合适的子查询用法会形成一个性能瓶颈。如果子查询的条件中 使用了其外层的表的字段,这种子查询就叫作相关子查询。相关子查询可以用IN、NOT IN、EXISTS、NOT EXISTS引入。

关于相关子查询,应该注意:

A、NOT IN、NOT EXISTS的相关子查询可以改用LEFT JOIN代替写法。比如:

SELECT PUB_NAME
FROM PUBLISHERS
WHERE PUB_ID NOT IN
(SELECT PUB_ID
FROM TITLES
WHERE TYPE = ‘BUSINESS’)
可以改写成:

SELECT A.PUB_NAME
FROM PUBLISHERS A LEFT JOIN TITLES B
ON        B.TYPE = ‘BUSINESS’ AND
A.PUB_ID=B.PUB_ID
WHERE B.PUB_ID IS NULL

SELECT TITLE
FROM TITLES
WHERE NOT EXISTS
(SELECT TITLE_ID
FROM SALES
WHERE TITLE_ID = TITLES.TITLE_ID)
可以改写成:

SELECT TITLE
FROM TITLES LEFT JOIN SALES
ON SALES.TITLE_ID = TITLES.TITLE_ID
WHERE SALES.TITLE_ID IS NULL
B、 如果保证子查询没有重复 ,IN、EXISTS的相关子查询可以用INNER JOIN 代替。比如:

SELECT PUB_NAME
FROM PUBLISHERS
WHERE PUB_ID IN
(SELECT PUB_ID
FROM TITLES
WHERE TYPE = ‘BUSINESS’)
可以改写成:

SELECT DISTINCT A.PUB_NAME
FROM PUBLISHERS A INNER JOIN TITLES B
ON        B.TYPE = ‘BUSINESS’ AND
A.PUB_ID=B.PUB_ID

C、 IN的相关子查询用EXISTS代替,比如

SELECT PUB_NAME
FROM PUBLISHERS
WHERE PUB_ID IN
(SELECT PUB_ID
FROM TITLES
WHERE TYPE = ‘BUSINESS’)
可以用下面语句代替:

SELECT PUB_NAME
FROM PUBLISHERS
WHERE EXISTS
(SELECT 1
FROM TITLES
WHERE TYPE = ‘BUSINESS’ AND
PUB_ID= PUBLISHERS.PUB_ID)

D、不要用COUNT(*)的子查询判断是否存在记录,最好用LEFT JOIN或者EXISTS,比如有人写这样的语句:

SELECT JOB_DESC FROM JOBS
WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)=0
应该改成:

SELECT JOBS.JOB_DESC FROM JOBS LEFT JOIN EMPLOYEE
ON EMPLOYEE.JOB_ID=JOBS.JOB_ID
WHERE EMPLOYEE.EMP_ID IS NULL

SELECT JOB_DESC FROM JOBS
WHERE (SELECT COUNT(*) FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)<>0
应该改成:

SELECT JOB_DESC FROM JOBS
WHERE EXISTS (SELECT 1 FROM EMPLOYEE WHERE JOB_ID=JOBS.JOB_ID)
6、慎用游标

数据库一般的操作是集合操作,也就是对由WHERE子句和选择列确定的结果集作集合操作,游标是提供的一个非集合操作的途径。一般情况下,游标实现的功能往往相当于客户端的一个循环实现的功能,所以,大部分情况下,我们把游标功能搬到客户端。

游标是把结果集放在服务器内存,并通过循环一条一条处理记录,对数据库资源(特别是内存和锁资源)的消耗是非常大的,所以,我们应该只有在没有其他方法的情况下才使用游标。

另外,我们可以用SQL SERVER的一些特性来代替游标,达到提高速度的目的。

A、字符串连接的例子

这是论坛经常有的例子,就是把一个表符合条件的记录的某个字符串字段连接成一个变量。比如需要把JOB_ID=10的EMPLOYEE的FNAME连接在一起,用逗号连接,可能最容易想到的是用游标:

DECLARE @NAME VARCHAR(20)
DECLARE @NAME VARCHAR(1000)
DECLARE NAME_CURSOR CURSOR FOR
SELECT FNAME FROM EMPLOYEE WHERE JOB_ID=10 ORDER BY EMP_ID
OPEN NAME_CURSOR
FETCH NEXT FROM RNAME_CURSOR INTO @NAME
WHILE @@FETCH_STATUS = 0
BEGIN
SET @NAMES = ISNULL(@NAMES+’,’,’’)+@NAME
FETCH NEXT FROM NAME_CURSOR  INTO @NAME
END
CLOSE NAME_CURSOR
DEALLOCATE NAME_CURSOR
可以如下修改,功能相同:

DECLARE @NAME VARCHAR(1000)
SELECT @NAMES = ISNULL(@NAMES+’,’,’’)+FNAME
FROM EMPLOYEE WHERE JOB_ID=10 ORDER BY EMP_ID
B、 用CASE WHEN 实现转换的例子

很多使用游标的原因是因为有些处理需要根据记录的各种情况需要作不同的处理,实际上这种情况,我们可以用CASE WHEN语句进行必要的判断处理,而且CASE WHEN是可以嵌套的。比如:

表结构:

CREATE TABLE 料件表(
料号           VARCHAR(30),
名称           VARCHAR(100),
主单位         VARCHAR(20),
单位1         VARCHAR(20),
单位1参数      NUMERIC(18,4),
单位2         VARCHAR(20),
单位2参数      NUMERIC(18,4)
)

GO

CREATE TABLE 入库表(
时间               DATETIME,
料号               VARCHAR(30),
单位               INT,
入库数量           NUMERIC(18,4),
损坏数量           NUMERIC(18,4)
)

GO
其中,单位字段可以是0,1,2,分别代表主单位、单位1、单位2,很多计算需要统一单位,统一单位可以用游标实现:

DECLARE @料号     VARCHAR(30),
@单位   INT,
@参数      NUMERIC(18,4),

DECLARE CUR CURSOR FOR
SELECT 料号,单位 FROM 入库表 WHERE 单位 <>0
OPEN CUR
FETCH NEXT FROM CUR INTO @料号,@单位
WHILE @@FETCH_STATUS<>-1
BEGIN
IF @单位=1
BEGIN
SET @参数=(SELECT 单位1参数 FROM 料件表 WHERE 料号 =@料号)
UPDATE 入库表 SET 数量=数量*@参数,损坏数量=损坏数量*@参数,单位=1 WHERE CURRENT OF CUR
END
IF @单位=2
BEGIN
SET @参数=(SELECT 单位1参数 FROM 料件表 WHERE 料号 =@料号)
UPDATE 入库表 SET 数量=数量*@参数,损坏数量=损坏数量*@参数,单位=1 WHERE CURRENT OF CUR
END
FETCH NEXT FROM CUR INTO @料号,@单位
END
CLOSE CUR
DEALLOCATE CUR
可以改写成:

UPDATE A SET
数量=CASE A.单位 WHEN 1 THEN      A.数量*B. 单位1参数
WHEN 2 THEN         A.数量*B. 单位2参数
ELSE A.数量
END,
损坏数量= CASE A.单位 WHEN 1 THEN    A. 损坏数量*B. 单位1参数
WHEN 2 THEN         A. 损坏数量*B. 单位2参数
ELSE A. 损坏数量
END,
单位=1
FROM入库表 A, 料件表 B
WHERE    A.单位<>1      AND   A.料号=B.料号
C、 变量参与的UPDATE语句的例子

SQL ERVER的语句比较灵活,变量参与的UPDATE语句可以实现一些游标一样的功能,比如:

SELECT A,B,C,CAST(NULL AS INT) AS 序号
INTO #T
FROM 表
ORDER BY A ,NEWID()
产生临时表后,已经按照A字段排序,但是在A相同的情况下是乱序的,这时如果需要更改序号字段为按照A字段分组的记录序号,就只有游标
和变量参与的UPDATE语句可以实现了,这个变量参与的UPDATE语句如下:

DECLARE @A INT
DECLARE @序号 INT
UPDATE #T SET
@序号=CASE WHEN A=@A THEN @序号+1 ELSE 1 END,
@A=A,
序号=@序号
D、如果必须使用游标,注意选择游标的类型,如果只是循环取数据,那就应该用只进游标(选项FAST_FORWARD),一般只需要静态游标(选项STATIC)。

E、 注意动态游标的不确定性,动态游标查询的记录集数据如果被修改,会自动刷新游标,这样使得动态游标有了不确定性,因为在多用户环境下,如果其他进程或者本身更改了纪录,就可能刷新游标的记录集。

7、 尽量使用索引

建立索引后,并不是每个查询都会使用索引,在使用索引的情况下,索引的使用效率也会有很大的差别。只要我们在查询语句中没有强制指定索引,索引的选 择和使用方法是SQLServer的优化器自动作的选择,而它选择的根据是查询语句的条件以及相关表的统计信息,这就要求我们在写SQL语句的时候尽量使 得优化器可以使用索引。

为了使得优化器能高效使用索引,写语句的时候应该注意:

A、不要对索引字段进行运算,而要想办法做变换,比如

SELECT ID FROM T WHERE NUM/2=100

应改为:

SELECT ID FROM T WHERE NUM=100*2

SELECT ID FROM T WHERE NUM/2=NUM1

如果NUM有索引应改为:

SELECT ID FROM T WHERE NUM=NUM1*2

如果NUM1有索引则不应该改。

发现过这样的语句:

SELECT 年,月,金额 FROM 结余表
WHERE 100*年+月=2007*100+10
应该改为:

SELECT 年,月,金额 FROM 结余表
WHERE 年=2007 AND
月=10
B、不要对索引字段进行格式转换

日期字段的例子:

WHERE CONVERT(VARCHAR(10), 日期字段,120)=’2008-08-15’

应该改为

WHERE日期字段〉=’2008-08-15’ AND 日期字段<’2008-08-16’

ISNULL转换的例子:

WHERE ISNULL(字段,’’)<>’’应改为:WHERE字段<>’’

WHERE ISNULL(字段,’’)=’’不应修改

WHERE ISNULL(字段,’F’) =’T’应改为: WHERE字段=’T’

WHERE ISNULL(字段,’F’)<>’T’不应修改

C、 不要对索引字段使用函数

WHERE LEFT(NAME, 3)=’ABC’ 或者WHERE SUBSTRING(NAME,1, 3)=’ABC’

应改为:

WHERE NAME LIKE ‘ABC%’

日期查询的例子:

WHERE DATEDIFF(DAY, 日期,’2005-11-30′)=0应改为:WHERE 日期 >=’2005-11-30′ AND 日期 <‘2005-12-1‘

WHERE DATEDIFF(DAY, 日期,’2005-11-30’)>0应改为:WHERE 日期 <‘2005-11-30‘

WHERE DATEDIFF(DAY, 日期,’2005-11-30’)>=0应改为:WHERE 日期 <‘2005-12-01‘

WHERE DATEDIFF(DAY, 日期,’2005-11-30′)<0应改为:WHERE 日期>=’2005-12-01‘

WHERE DATEDIFF(DAY, 日期,’2005-11-30’)<=0应改为:WHERE 日期>=’2005-11-30‘

D、不要对索引字段进行多字段连接

比如:

WHERE FAME+ ’.’+LNAME=‘HAIWEI.YANG’

应改为:

WHERE FNAME=‘HAIWEI’ AND LNAME=‘YANG’

8、 注意连接条件的写法

多表连接的连接条件对索引的选择有着重要的意义,所以我们在写连接条件条件的时候需要特别的注意。

A、多表连接的时候,连接条件必须写全,宁可重复,不要缺漏。

B、连接条件尽量使用聚集索引

C、注意ON部分条件和WHERE部分条件的区别

9、其他需要注意的地方

经验表明,问题发现的越早解决的成本越低,很多性能问题可以在编码阶段就发现,为了提早发现性能问题,需要注意:

A、程序员注意、关心各表的数据量。

B、编码过程和单元测试过程尽量用数据量较大的数据库测试,最好能用实际数据测试。

C、每个SQL语句尽量简单

D、不要频繁更新有触发器的表的数据

E、注意数据库函数的限制以及其性能

10、学会分辩SQL语句的优劣

自己分辨SQL语句的优劣非常重要,只有自己能分辨优劣才能写出高效的语句。

A、查看SQL语句的执行计划,可以在查询分析其使用CTRL+L图形化的显示执行计划,一般应该注意百分比最大的几个图形的属性,把鼠标移动到其 上面会显示这个图形的属性,需要注意预计成本的数据,也要注意其标题,一般都是CLUSTERED INDEX SEEK 、INDEX SEEK 、CLUSTERED INDEX SCAN 、INDEX SCAN 、TABLE SCAN等,其中出现SCAN说明语句有优化的余地。也可以用语句

SET SHOWPLAN_ALL ON

要执行的语句

SET SHOWPLAN_ALL OFF

查看执行计划的文本详细信息。

B、用事件探查器跟踪系统的运行,可疑跟踪到执行的语句,以及所用的时间,CPU用量以及I/O数据,从而分析语句的效率。

C、可以用WINDOWS的系统性能检测器,关注CPU、I/O参数

四、测试、试运行、维护阶段

测试的主要任务是发现并修改系统的问题,其中性能问题也是一个重要的方面。重点应该放在发现有性能问题的地方,并进行必要的优化。主要进行语句优化、索引优化等。

试运行和维护阶段是在实际的环境下运行系统,发现的问题范围更广,可能涉及操作系统、网络以及多用户并发环境出现的问题,其优化也扩展到操作系统、网络以及数据库物理存储的优化。

这个阶段的优花方法在这里不再展开,只说明下索引维护的方法:

A、可以用DBCC DBREINDEX语句或者SQL SERVER维护计划设定定时进行索引重建,索引重建的目的是提高索引的效能。

B、可以用语句UPDATE STATISTICS或者SQL SERVER维护计划设定定时进行索引统计信息的更新,其目的是使得统计信息更能反映实际情况,从而使得优化器选择更合适的索引。

C、可以用DBCC CHECKDB或者DBCC CHECKTABLE语句检查数据库表和索引是否有问题,这两个语句也能修复一般的问题。

五、网上资料中一些说法的个人不同意见

1、“应尽量避免在 WHERE 子句中对字段进行 NULL 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

SELECT ID FROM T WHERE NUM IS NULL

可以在NUM上设置默认值0,确保表中NUM列没有NULL值,然后这样查询:

SELECT ID FROM T WHERE NUM=0”

个人意见:经过测试,IS NULL也是可以用INDEX SEEK查找的,0和NULL是不同概念的,以上说法的两个查询的意义和记录数是不同的。

2、“应尽量避免在 WHERE 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。”

个人意见:经过测试,<>也是可以用INDEX SEEK查找的。

3、“应尽量避免在 WHERE 子句中使用 OR 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

SELECT ID FROM T WHERE NUM=10 OR NUM=20

可以这样查询:

SELECT ID FROM T WHERE NUM=10

UNION ALL

SELECT ID FROM T WHERE NUM=20”

个人意见:主要对全表扫描的说法不赞同。

4、“IN 和 NOT IN 也要慎用,否则会导致全表扫描,如:

SELECT ID FROM T WHERE NUM IN(1,2,3)

对于连续的数值,能用 BETWEEN 就不要用 IN 了:

SELECT ID FROM T WHERE NUM BETWEEN 1 AND 3”

个人意见:主要对全表扫描的说法不赞同。

5、“如果在 WHERE 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

SELECT ID FROM T WHERE NUM=@NUM

可以改为强制查询使用索引:

SELECT ID FROM T WITH(INDEX(索引名)) WHERE NUM=@NUM

个人意见:关于局部变量的解释比较奇怪,使用参数如果会影响性能,那存储过程就该校除了,我坚持我上面对于强制索引的看法。

6、“尽可能的使用 VARCHAR/NVARCHAR 代替 CHAR/NCHAR ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。”

个人意见:“在一个相对较小的字段内搜索效率显然要高些”显然是对的,但是字段的长短似乎不是由变不变长决定,而是业务本身决定。在 SQLServer6.5或者之前版本,不定长字符串字段的比较速度比定长的字符串字段的比较速度慢很多,所以对于那些版本,我们都是推荐使用定长字段存 储一些关键字段。而在2000版本,修改了不定长字符串字段的比较方法,与定长字段的比较速度差别不大了,这样为了方便,我们大量使用不定长字段。

7、关于连接表的顺序或者条件的顺序的说法,经过测试,在SQL SERVER,这些顺序都是不影响性能的,这些说法可能是对ORACLE有效。

[转载]挑战类Flash游戏测试用例设计

mikel阅读(1625)

[转载]挑战类Flash游戏测试用例设计 – 寻觅2010 – 博客园.

前端时间参与了Flash游戏的功能测试,发现游戏测试的内容比较的繁多,因此总结一下测试用例的编写思路,便于以后能快速进行同类游戏的用例设计。

所测试的Flash游戏类似<雪地漂移上百层>这款游戏,先简介一下这款游戏的需求:这是一款考验快速反应的益智游戏,游戏的过程就是通过控制角色在空中左右平移吃掉屏幕中的上升道具来保证自己一直向上升,如果角色速度减到0然后滞空掉落,将显示角色所达到的最大高度以及得分。

1)首先是游戏初始界面

2)随后进入道具说明界面

3)游戏开始先滑入跑道获取起飞初速度

4)游戏当中左右平移操作

5)挑战失败后的成绩统计

〖备注〗

* 游戏的安装、卸载、升级的功能不划为本次功能测试的讨论内容。

此款游戏的需求不算太复杂,既没有多种模式的选择(水果忍者游戏),也没有多种关卡设计(祖玛游戏),在测试时偏重操作性及数据正确性。进行功能测试用例编写之前,首先进行测试用例设计:

首先是基本功能的验证

测试用例首先应该对基本功能进行覆盖,基本功能就是能保证玩家能够进行一个完整的流程操作,即启动—开始—退出三个基本步骤,然后再根据每一步进行测试项分解:

每个测试项有相应的测试点,比如游戏启动测试项,其测试点根据图标、显示方式、启动时间、按键操作分类后进行测试内容细化:

测试项

测试内容

游戏图标

图标的大小符合需求中的设计

图案跟需求说明书的原型一致

界面显示

在不同的分辨率下显示全屏

启动成功后,光标模型显示正确

启动项检查

启动时间不超过需求中规定的约束值

启动成功后,游戏的进程名没有错误

按键操作

启动过程中通过功能键强制中断

其他的测试点的细则不一一列举,根据游戏中的实际情况进行细化。

每个界面的测试

基本功能虽然能保证游戏的操作流程正常,但对于游戏内容的正确性是无法保证的,界面也是玩家在体验过程中会关注的内容,因此对于游戏内容的检查首先应该根据各个界面进行下手,其中每个界面的跳转路径测试,也保证除基本流程之外的分支流程能够正确,其测试项有:

游戏元素的细分

界面只是游戏内容的一小部分,实际上游戏内容远不止繁多的界面,通常还有角色人物、道具、音效、成绩、奖惩规则等元素。此款游戏没有生命值的需求,所以奖惩规则没有在测试设计中考虑,根据游戏元素整理的测试项如下:

占用资源的关注

游戏的资源占用也属于功能测试的重要内容,测试结果需要在测试报告中记录,通常都是记录游戏的CPU占用及内存消耗,需要注意的是这两项数值都是实时变化的,因此需要记录的数据需要进行筛选,选择重要的初始值和峰值进行记录。

如何才能得到这两类数据的峰值?这需要设计合理的测试场景:1CPU占用理论上是游戏线程越多,读写数据操作越频繁,则CPU占用的数值就越高,因此CPU占用的峰值测试场景为玩家操作频繁的界面中。2)内存消耗则是根据游戏在运行时加载的资源多少来决定的,因此理论上玩家玩的时间越长,加载的界面、元素越多,内存的消耗就越大,因此峰值的测试场景需要尽量遍历所有界面、接触到所有道具,测试时间也需要在4个小时以上。

异常场景设计

异常场景的是对测试用例覆盖率最有效的补充,往往最容易暴露问题的就是异常的操作或环境。用例的设计需要考虑游戏与系统如何进行数据交互、游戏采用框架以及哪些数据需要跟其他软件进行传递的。

测试的Flash游戏运行在Linux环境,与底层系统的交互涉及到操作数据和玩家成绩:操作数据(角色在游戏中的左右移动)是通过pipe管道与底层系统进行交互,玩家成绩(最高高度和最大得分)的则是通过scroe.xml文件进行保存。因此异常场景的测试项有:

此款游戏的测试用例设计思路已经基本完成,然后就需要对每个测试项进行测试点的细化,最后执行测试进行功能验证了。

后记:是否完成了功能验证,就意味着游戏没有严重问题?实际上这款游戏上线之后,根据服务器的一段时间日志统计,并没有多少玩家去玩,虽然游戏不存在影响运行问题,但此款游戏在市场上表现并不成功,我们忽视了游戏开发环节中一项重要的工作,那就是用户体验测试,在完成游戏功能测试之后,应该在公司内部或现网试运行阶段进行游戏体验,收集和筛选反馈建议,查看游戏哪些内容需要丰富以增加用户粘度、是否存在捷径可以刻意降低游戏难度、哪些元素缺少吸引需要去掉等。

[转载]JS新API标准 地理定位(navigator.geolocation)

mikel阅读(883)

[转载]JS新API标准 地理定位(navigator.geolocation) – Lecaf – 博客园.

在新的API标准中,可以通过navigator.geolocation来获取设备的当前位置,返回一个位置对象,用户可以从这个对象中得到一些经纬度的相关信息。

navigator.geolocation的三个方法:

1. getCurrentPosition()

2. watchPosition()

3. clearWatch()

getCurrentPosition()

使用方法:navigator.geolocation.getCurrentPosition(successCallback, [errorCallback] , [positionOptions]);

A) successCallback 获取定位成功时执行的回调函数 eg: function(position){alert(“纬度:”+position.coords.latitude+”;经度:”+position.coords.longitude)};

successCallback返回一个地理数据对象position作为参数,该对象有属性timestamp和coords。timestamp表示该地理数据创建时间(时间戳);coords包括另外七个属性:

1. coords.latitude:估计纬度
2. coords.longitude:估计经度
3. coords.altitude:估计高度
4. coords.accuracy:所提供的以米为单位的经度和纬度估计的精确度
5. coords.altitudeAccuracy:所提供的以米为单位的高度估计的精确度
6. coords.heading: 宿主设备当前移动的角度方向,相对于正北方向顺时针计算
7. coords.speed:以米每秒为单位的设备的当前对地速度

PS:firefox下还有address属性,可以获取详细地址,不过我得到的地址是错误的,使用方法:position.address.city,具体如下:

QueryInterface:function QueryInterface() { [native code] }
streetNumber:200号
street:人民大道
premises:null
city:上海市
county:null
region:上海市
country:中国
countryCode:CN
postalCode:null
getInterfaces:function getInterfaces() { [native code] }
getHelperForLanguage:function getHelperForLanguage() { [native code] }
contractID:null
classDescription:null
classID:null
implementationLanguage:2
flags:8
SINGLETON:1
THREADSAFE:2
MAIN_THREAD_ONLY:4
DOM_OBJECT:8
PLUGIN_OBJECT:16
CONTENT_NODE:64
RESERVED:2147483648

B) errorCallback 定位失败时执行的回调函数 eg: function(error){alert(error.message);}

errorCallback返回一个错误数据对象error作为参数,该对象有属性:

1.code :表示失败原因,返回1 or 2 or 3 ,具体为

PERMISSION_DENIED (数值为1) 表示没有权限使用地理定位API

POSITION_UNAVAILABLE (数值为2) 表示无法确定设备的位置,例如一个或多个的用于定位采集程序报告了一个内部错误导致了全部过程的失败

TIMEOUT (数值为3) 表示超时

详情查看 http://dev.w3.org/geo/api/spec-source.html#permission_denied_error

2.message :错误提示内容

C) positionOptions 用来设置positionOptions来更精细的执行定位,positionOptions拥有三个属性 {enableHighAccuracy:boolean , timeout:long , maximumAge:long}。

enableHighAccuracy 【true or false(默认)】是否返回更详细更准确的结构,默认为false不启用,选择true则启用,但是会导致较长的响应时间及增加功耗,这种情况更多的用在移动设备上。

timeout 设备位置获取操作的超时时间设定(不包括获取用户权限时间),单位为毫秒,如果在设定的timeout时间内未能获取位置定位,则会执行 errorCallback()返回code(3)。如果未设定timeout,那么timeout默认为无穷大,如果timeout为负数,则默认 timeout为0。

maximumAge 设定位置缓存时间,以毫秒为单位,如果不设置该值,该值默认为0,如果设定负数,则默认为0。该值为0时,位置定位时会重新获取一个新的位置对象;该值大 于0时,即从上一次获取位置时开始,缓存位置对象,如果再次获取位置时间不超过maximumAge,则返回缓存中的位置,如果超出 maximumAge,则重新获取一个新的位置。

watchPosition()

功能getCurrentPosition()相似,watchPosition()是定期轮询设备的位置,同样拥有3个参数,与getCurrentPosition()相同。

使用方法:navigator.geolocation.watchPosition(successCallback, [errorCallback] , [positionOptions]);

执行步骤:

1.首先初始化positionOptions内的属性(详细同上)。

2.判断是否有缓存位置对象,该对象年龄是否可用、是否超出maximumAge ,如果可用且未超出,返回缓存位置,否则重新确定设备位置。

3.位置定位操作:

i.建立一个计时器,进行位置获取操作,如果在timeout之前完成,执行下一步;如果未在timeout之前完成,则执行errorCallback(),code为3,跳出步骤做等待重新激活。

ii.如果在timeout之前获得位置成功,则执行successCallback(),然后重置计时器(从获取位置成功时刻重新算起),继 续挂起获取新位置。当有与之前位置有明显不同位置出现时,再次执行successCallback(),并重复操作,该循环直到timeout超时或者获 取操作中遇到POSITION_UNAVAILABLE错误执行errorCallback()为止,亦或者执行clearWatch()操作。

clearWatch()

配合watchPosition()使用,用于停止watchPosition()轮询。

watchPosition()需要定义一个watchID,var watchID = watchPosition(…),通过clearWatch(watchID)来停止watchPosition(),使用方法类似setInterval。

相关资料:

1. http://dev.w3.org/geo/api/spec-source.html#geolocation

2. http://kb.cnblogs.com/page/108732/

转载请注明出处:http://www.cnblogs.com/lecaf/

[转载]Lambda表达式的应用

mikel阅读(1047)

[转载]Lambda表达式的应用 – idior – 博客园.

Windbey中为了增强对集合的访问能力, MS设计了List<T>这么一个泛型集合, 其中有不少的增强功能,比如Foreach,ConvertAll,FindAll等等,并且为了方便使用MS在System名空间下引入了一些特制的 Delegate.主要包括以下几个:

20 public delegate void Action<T>(T obj);   //Used by ForEach

21 public delegate int Comparison<T>(T x, T y);  //Used by Sort

22 public delegate TOutput Converter<TInput, TOutput>(TInput input);    //Used by ConvertAll

23 public delegate bool Predicate<T>(T obj); //Used by FindAll

利用这些特制的Delegate,再加上匿名方法的使用,我们可以获得更加简洁,有效的代码. 具体的例子我以前有过介绍.现在在Orcas中, MS加入了lambda表达式的概念. lambda表达式是匿名方法的进一步增强. 利用它可以更加方便的写出新的方法. 而且语义上更加接近人性化.

同样它也引入了一些特制的Delegate:

20 public delegate T Func<T>();

21 public delegate T Func<A0, T>(A0 arg0);

22 public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);

23 public delegate T Func<A0, A1, A2, T>(A0 arg0, A1 arg1, A2 arg2);

24 public delegate T Func<A0, A1, A2, A3, T>(A0 arg0, A1 arg1, A2 arg2, A3 arg3);

和2.0中特制的Delegate对比, 你会发现它们有很多相同之处:

20 public delegate int Comparison<T>(T x, T y);
21 public delegate int Func<T,T,int>(T arg0, T arg1);

22 public delegate TOutput Converter<TInput, TOutput>(TInput input);
23 public delegate TOutput Func<TInput, TOutput>(TInput arg0);

24 public delegate bool Predicate<T>(T obj);

25 public delegate bool Func<T,bool>(T arg0);

也就是说3.0中特制的Delegate比2.0的更一般化, 2.0是3.0的特例. 所以我们完全可以将lambda表达式运用于List<T>的一些增强方法中.

Sort方法

20 List<int> list=new List<int>();

21 var numbers = new []{ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

22 list.AddRange(numbers);

23 list.Sort(delegate (int a, int b)

24 {

25 return a.CompareTo(b);

26 }

27 );

28 //use lambda

29 list.Sort((a,b)=>a.CompareTo(b));


ConvertAll方法

20 List<int> doubleList =list.ConvertAll<int>(delegate (int i)

21 {

22 return i*2;

23 });

24 //use lambda

25 var doubleList2=list.ConvertAll<int>(i=>i*2);


FindAll方法

20 List<int> lowerThanFiveList =list.FindAll(delegate (int i)

21 {

22 return i<5;

23 }

24 );

25 var lowerThanFiveList2=list.FindAll(i=>i<5);

从上面的例子可以看出利用lambda表达式写出的代码更加简洁易懂.  (以后代码都经过编译测试,可不是我杜撰的.)

以上是将lambda表达式运用于2.0当中. 但是在熟悉了3.0后, 你会发现2.0中的List<T>提供的增强方法完全是多余的了. 其实这些增强方法往往并不限于List<T>, 通常对于IEnumerable<T>对象都是适用的. 但是如果去改动IEnumable<T>接口那么影响实在太大了,将涉及很多的类. 所以MS仅仅在List<T>中提供了这些增强方法. 不过通过List<T>的一个构造函数,你可以使得所有的IEnumerable<T>对象可以方便的转化为List<T>,然后再利用这些方法.

这可以说是一个很取巧的方法, 不过在有了3.0的Extension Method的支持下, 就不用这么麻烦了, 而且MS还内置了一系列更强的集合操作方法.

比如之前的FindAll方法,我们现在可以这样写:

21 var numbers = new []{ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

22 var lowerThanFive=numbers.Where(i=>i<5); //never need List<T>, just operate on T[] or any other types implement IEnumerable<T>

23 foreach (var v in lowerThanFive)

24 Console.WriteLine(v);

ConvertAll方法

21 var doubleList3=numbers.Select(i=>i*2);

22 foreach (var v in doubleList3)

23 Console.WriteLine(v);

Sort方法

21 var orderList =numbers.OrderBy(i=>i);

22 foreach (var v in orderList)

23 Console.WriteLine(v);

甚至还有很多更强大的功能:
比如我要取numbers数组中最大的5个数.

21 var big5 =numbers.OrderByDescending(i=>i).Take(5);

22 foreach (var v in big5)

23 Console.WriteLine(v);

通过Orcas的Extension Method和Lambda表达式, MS为集合的操作提供了更加方便强大的功能. 这里尚未用到Standard Query Operators, 不然代码还要被简化.

当然Linq现在仅仅是一个Tech Preview 版本. 尚有很多不足.尤其在智能感知(IntelliSense)方面:
1.  var numbers = new []{ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; 采用这种写法时, numbers没有智能感知功能.
2. 使用lambda表达式时,如果不声明T类型,对于其中的变量没有智能感知功能.这点很是奇怪, Linq竟然没有强制声明类型.

21 var lowerThanFive=numbers.Where<int>(i=>i<5);

22 var lowerThanFive=numbers.Where(i=>i<5);

以上两种写法竟然都没问题, 显然在下一种写法中变量i 无法获得智能感知的能力.
3. numbers.OrderBy(…),在写这个方法时,numbers的智能感知中并没有OrderBy这个方法, 但是编译运行没有问题.
4. lambda表达式目前不支持多条语句.

[转载]基于AppDomain的"插件式"开发

mikel阅读(1359)

[转载]基于AppDomain的”插件式”开发 – 空逸云 – 博客园.

很多时候,我们都想使用(开发)USB式(热插拔)的应用, 例如,开发一个WinForm应用,并且这个WinForm应用能允许开发人员定制扩展插件,又例如,我们可能维护着一个WinService管理系统, 这个WinService系统管理的形形色色各种各样的服务,这些服务也是各个”插件式”的类库,例如:

public interface IJob
{
void Run(DateTime time);
}

public class CollectUserInfo : IJob
{

public void Run(DateTime time)
{
//doing some thing...
}
}

我们提供了一个IJob接口,所有”服务”都继承该接口,然后做相关的配置,在服务启动时,就可以根据配置,反射加载程序集,执行我们预期的任务.

更新程序集(dll/exe)

服务/插件程序(后面只称为服务,虽然两者应用不同,但是在此处他们所运用的原理和作用是相同的 🙂 )很健稳的运行着.但在服务/插件程序运行一段时间之后,某些”插件”的业务需求发生的变化,或者版本升级等种种外部原因,导致我们对原本的”插件”程序 集进行了升级(可能从v1.0升级至v2.0).当我们想像ASP.NET应用一样.把新的dll替换旧dll的时候,错误发生了.

image

发生该错误的原因很简单,因为我们的程序中已经调用了该dll,那么在CLR加载该dll到文件流中也给其加了锁,所以,当我们要进行覆盖,修改,删除的时候自然就无法操作该文件了.那我们该怎么做?为什么ASP.NET可以直接覆盖?

AppDomain登场

我们知道,AppDomain是.Net平台里一个很重要的特性,在.Net以前,每个程序是”封装”在不同的进程中的,这样导致的结果就造就占用 资源大,可复用性低等缺点.而AppDomain在同一个进程内划分出多个”域”,一个进程可以运行多个应用,提高了资源的复用性,数据通信等.详见应用程序域

CLR在启动的时候会创建系统域(System Domain),共享域(Shared Domain)和默认域(Default Domain),系统域与共享域对于用户是不可见的,默认域也可以说是当前域,它承载了当前应用程序的各类信息(堆栈),所以,我们的一切操作都是在这个 默认域上进行.”插件式”开发很大程度上就是依靠AppDomain来进行.

“热插拔”实现说明

当加载了一个程序集之后,该程序集就会被加入到指定AppDomain中,按照原来的想法,要实现”热插拔”,只要在需要使用该”插件”的时候,加 载该”插件”的程序集(dll),使用结束后,卸载掉该程序集便可达到我们预期的效果.加载程序集很简单,.C#提供一个Assembly类,方便又快 捷.
var _assembly = Assembly.LoadFrom(assemblyFile);

Assembly提供了数个加载方法详见Assembly类.

然后,C#却没有提供卸载程序集的方法,唯一能卸载程序集的方法只有卸载该程序集所在的AppDomain,这样,该AppDomain下的程序集都会被释放.知道这一点,我们便可以利用AppDomain来达到我们预期的效果.

AppDomain实现”热插拔”

首先,我们需要先实例化一个新AppDomain作为”插件”的宿主.在实例化一个Domain之前,先声明该Domain的一些基本配置信息

AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = "ApplicationLoader";
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "private");
setup.CachePath = setup.ApplicationBase;
setup.ShadowCopyFiles = "true"; //启用影像复制程序集
setup.ShadowCopyDirectories = setup.ApplicationBase;
AppDomain.CurrentDomain.SetShadowCopyFiles();

setup.ShadowCopyFiles = “true”;这句很重要,其作用就是启用影像复制程序集,什么是影像复制程序集,复制程序集是保证”热插拔”

实现的主要工作.AppDomain加载程序集的时候,如果没有ShadowCopyFiles,那就直接加载程序集,结果就是程序集被锁定,相 反,如果启用了ShadowCopyFiles,则CLR会将准备加载的程序集拷贝一份至CachePath,再加载CachePath的这一份程序集, 这样原程序集也就不会被锁定了. AppDomain.CurrentDomain.SetShadowCopyFiles();的作用就是当前AppDomain也启用 ShadowCopyFiles,在此,当前AppDomain也就是前面我们说过的那个默认域(Default Domain),为什么当前域也要启用ShadowCopyFiles呢?

主AppDomian在调用子AppDomain提供过来的类型,方法,属性的时候,也会将该程序集添加到自身程序集引用当中去,所以,”插件”程序集就被主AppDomain锁定,这也是为什么创建了单独的AppDomain程序集也不能删除,替换(释放)的根本原因

利用SOS,可以很清楚的看到这一点

0:018&gt; !dumpdomain
--------------------------------------
System Domain:      5b912478
LowFrequencyHeap:   5b912784
HighFrequencyHeap:  5b9127d0
StubHeap:           5b91281c
Stage:              OPEN
Name:               None
--------------------------------------
Shared Domain:      5b912140
LowFrequencyHeap:   5b912784
HighFrequencyHeap:  5b9127d0
StubHeap:           5b91281c
Stage:              OPEN
Name:               None
Assembly:           00109de0 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader:        00110f68
Module Name
58631000            C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

--------------------------------------
Domain 1:           000f4598
LowFrequencyHeap:   000f4914
HighFrequencyHeap:  000f4960
StubHeap:           000f49ac
Stage:              OPEN
SecurityDescriptor: 000f5568
Name:               AppDomainTest.exe
Assembly:           00109de0 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader:        00110f68
SecurityDescriptor: 001097b0
Module Name
58631000            C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

Assembly:           0011d448 [E:\Test\AppDomainTest\AppDomainTest\bin\Debug\AppDomainTest.exe]
ClassLoader:        00117fd0
SecurityDescriptor: 0011d3c0
Module Name
001c2e9c            E:\Test\AppDomainTest\AppDomainTest\bin\Debug\AppDomainTest.exe

Assembly:           00131370 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll]
ClassLoader:        0011fa00
SecurityDescriptor: 001299a0
Module Name
579c1000            C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll

Assembly:           00131400 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Drawing.dll]
ClassLoader:        00131490
SecurityDescriptor: 0012e9c0
Module Name
62661000            C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Drawing.dll

Assembly:           00131d20 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll]
ClassLoader:        00133d08
SecurityDescriptor: 0012f078
Module Name
5aa81000            C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll

Assembly:           00131ed0 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll]
ClassLoader:        001415a8
SecurityDescriptor: 0012f430
Module Name
5a981000            C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll

Assembly:           00132080 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll]
ClassLoader:        00141620
SecurityDescriptor: 0012f5c8
Module Name
546e1000            C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll

Assembly:           00132ce0 [E:\Test\AppDomainTest\AppDomainTest\bin\Debug\CrossDomainController.dll]
ClassLoader:        001b3450
SecurityDescriptor: 06f94560
Module Name
001c7428            E:\Test\AppDomainTest\AppDomainTest\bin\Debug\CrossDomainController.dll

Assembly:           00132350 [C:\Users\kong\AppData\Local\assembly\dl3\6ZYK3XE9.86Q\2AQ35O7C.VHE\1f704bbb\b7cca5cf_8c4fcc01\ShowHelloPlug.DLL]
ClassLoader:        001b32e8
SecurityDescriptor: 070a8620
Module Name
001c7d78            C:\Users\kong\AppData\Local\assembly\dl3\6ZYK3XE9.86Q\2AQ35O7C.VHE\1f704bbb\b7cca5cf_8c4fcc01\ShowHelloPlug.DLL

--------------------------------------
Domain 2:           06fd0238
LowFrequencyHeap:   06fd05b4
HighFrequencyHeap:  06fd0600
StubHeap:           06fd064c
Stage:              OPEN
SecurityDescriptor: 06724510
Name:               ApplicationLoaderDomain
Assembly:           00109de0 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader:        00110f68
SecurityDescriptor: 06f93bd0
Module Name
58631000            C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

Assembly:           00132e90 [E:\Test\AppDomainTest\AppDomainTest\bin\Debug\ApplicationLoader\assembly\dl3\c91a2898\f6f7f865_9a4fcc01\CrossDomainController.DLL]
ClassLoader:        001b3540
SecurityDescriptor: 06f92be0
Module Name
00a833c4            E:\Test\AppDomainTest\AppDomainTest\bin\Debug\ApplicationLoader\assembly\dl3\c91a2898\f6f7f865_9a4fcc01\CrossDomainController.DLL

Assembly:           001330d0 [E:\Test\AppDomainTest\AppDomainTest\bin\Debug\ApplicationLoader\assembly\dl3\32519346\b7cca5cf_8c4fcc01\ShowHelloPlug.DLL]
ClassLoader:        001b39f0
SecurityDescriptor: 06f92f98
Module Name
00a83adc            E:\Test\AppDomainTest\AppDomainTest\bin\Debug\ApplicationLoader\assembly\dl3\32519346\b7cca5cf_8c4fcc01\ShowHelloPlug.DLL

除了新建的AppDomain(Domain2)中的Module引用了ShowHelloPlug.dll,默认域(Domian1)也有ShowHelloPlug.dll的

程序集引用.

应用程序域之间的通信

每个AppDomain都有自己的堆栈,内存块,也就是说它们之间的数据并非共享了.若想共享数据,则涉及到应用程序域之间的通信.C#提供了MarshalByRefObject类进行跨域通信,那么,我们必须提供自己的跨域访问器.

public class RemoteLoader : MarshalByRefObject
{
private Assembly _assembly;

public void LoadAssembly(string assemblyFile)
{
try
{
_assembly = Assembly.LoadFrom(assemblyFile);
//return _assembly;
}
catch (Exception ex)
{
throw ex;
}
}

public T GetInstance(string typeName) where T : class
{
if (_assembly == null) return null;
var type = _assembly.GetType(typeName);
if (type == null) return null;
return Activator.CreateInstance(type) as T;
}

public void ExecuteMothod(string typeName, string methodName)
{
if (_assembly == null) return;
var type = _assembly.GetType(typeName);
var obj = Activator.CreateInstance(type);
Expression lambda = Expression.Lambda(Expression.Call(Expression.Constant(obj), type.GetMethod(methodName)), null);
lambda.Compile()();
}
}

为了更好的操作这个跨域访问器,接下来我构建了一个名为AssemblyDynamicLoader的类,它内部封装了RemoteLoader类

的操作.

public class AssemblyDynamicLoader
{
private AppDomain appDomain;
private RemoteLoader remoteLoader;
public AssemblyDynamicLoader()
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = "ApplicationLoader";
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "private");
setup.CachePath = setup.ApplicationBase;
setup.ShadowCopyFiles = "true";
setup.ShadowCopyDirectories = setup.ApplicationBase;
AppDomain.CurrentDomain.SetShadowCopyFiles();
this.appDomain = AppDomain.CreateDomain("ApplicationLoaderDomain", null, setup);
String name = Assembly.GetExecutingAssembly().GetName().FullName;
this.remoteLoader = (RemoteLoader)this.appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName);
}

public void LoadAssembly(string assemblyFile)
{
remoteLoader.LoadAssembly(assemblyFile);
}

public T GetInstance(string typeName) where T : class
{
if (remoteLoader == null) return null;
return remoteLoader.GetInstance(typeName);
}

public void ExecuteMothod(string typeName, string methodName)
{
remoteLoader.ExecuteMothod(typeName, methodName);
}

public void Unload()
{
try
{
if (appDomain == null) return;
AppDomain.Unload(this.appDomain);
this.appDomain = null;
}
catch (CannotUnloadAppDomainException ex)
{
throw ex;
}
}
}

这样我们每次都是通过AssemblyDynamicLoader类进行跨域的访问.

AppDomain.CurrentDomain.SetShadowCopyFiles();
this.appDomain = AppDomain.CreateDomain("ApplicationLoaderDomain", null, setup);
String name = Assembly.GetExecutingAssembly().GetName().FullName;
this.remoteLoader = (RemoteLoader)this.appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName);

通过我们前面构造的一个AppDomainSetup,构建了一个我们所需的AppDomain,并且在这个appDomain中构建了

一个RemoteLoader类的实例(此时该实例已具备跨域访问能力,也就是说我们在主域能获取子域内部的数据信息).目前RemoteLoader只提供了少数的几个方法.

跨域操作

下面,我们就模拟一次”插件式”的跨域操作.首先我们构造了一个窗体,其有以下元素.

image

选择程序集路径之后,加载程序集,然后就触发程序集指定类型(通过配置获取)的特定操作.这里我们定义了一个公共接口,它是所有”插件”操作的主要入口了.
public interface IPlug
{
void Run();
}

随后定义了一个实现该接口的类.
[Serializable]
public class ShowHelloPlug : IPlug
{
public void Run()
{
MessageBox.Show(“Hello World…”);
}
这个”插件”的工作很简单.仅仅弹出一个对话框,说声”Hello World…”,接下来将其编译成一个dll.

image

回到界面,选择刚才编译的Dll,然后直接加载.

到这里,我们的工作完成了一半了.呼呼.OK.我们的需求发生了变化,不再是弹出Hello World了.而时候弹出Hi,I’m Kinsen,我们修改刚才的子类,并再编译一次.再将Dll替换刚才的Dll,这次,Dll没有没锁定(因为我们前面启用了 ShadowCopyFiles.).再加载一下程序集,你会发现结果并不是”Hi,I’m Kinsen”,而是”Hello World..”为什么会这样呢?这时候,借助SOS的力量(前面有SOS结果).

我们发现Domain1(Default Domain)和Domain2(新创建Domain)都引用了程序集ShowHelloPlug.DLL,但是两个引用的Dll地址却不相同,这是因为 启用了ShadowCopyFiles,它们加载的都是各自程序集的备份,我们根据Domain2的Assembly地址查看ShowHelloPlug 的编译代码.
0:011> !dumpmt 00fc40ac
00fc40ac is not a MethodTable
0:011> !dumpmd 00fc40ac
Method Name: Plug.ShowHelloPlug.Run()
Class: 046812b4
MethodTable: 00fc40bc
mdToken: 06000001
Module: 00fc3adc
IsJitted: no
CodeAddr: ffffffff
Transparency: Critical

从IsJitted为no可以看出,该程序集并没有被调用,那调用的是谁?我们再次查看Domain1(Default Domain

)中的ShowHelloPlug.
0:011> !dumpmd 001f8240
Method Name: Plug.ShowHelloPlug.Run()
Class: 004446e4
MethodTable: 001f8250
mdToken: 06000001
Module: 001f7d78
IsJitted: yes
CodeAddr: 00430de0
Transparency: Critical

已知每个AppDomain都有自己的堆栈信息,各自不互相影响,所以,当我们在主域中获取到了子域中的数据,并非新建一个指向该实例的引用,而是在自己的堆栈上开辟出一块空间”深度拷贝”该实例,那么必然就达不到我们我需的结果.

子域内部调用

那么为了达到我们预期的效果,我们必须在子域内部执行我们所需的操作(调用),所以在RemoteLoader类中增加了一个Execute方法

public void ExecuteMothod(string typeName, string methodName)
{
if (_assembly == null) return;
var type = _assembly.GetType(typeName);
var obj = Activator.CreateInstance(type);
Expression lambda = Expression.Lambda(Expression.Call(Expression.Constant(obj), type.GetMethod(methodName)), null);
lambda.Compile()();
}

此处我暂时只想到了利用反射调用,这样的代价就是调用所需消耗的资源更多,效率低下.目前还没有

想出较好的解决方案,有经验的童鞋欢迎交流.

这样外部的调用就变成以下

loader = new AssemblyDynamicLoader();
loader.LoadAssembly(txt_dllName.Text);
//var obj = loader.GetInstance("Plug.ShowHelloPlug");
//obj.Run();
loader.ExecuteMothod("Plug.ShowHelloPlug", "Run");

现在在将Dll替换,结果正常.

image

尾声

做”插件式”开发,除了利用AppDomain之外,也有童鞋给出了另一种解决方案,也就是在加载Dll的时候,先将Dll在内存中复制一份,这样原来的Dll也就不会被锁定了.详见插件的“动态替换”.

以上实例本人皆做过实验,但可能还存在一定不足或概念错误,若有不当之处,欢迎各位童鞋批评指点.

更多

通过应用程序域AppDomain加载和卸载程序集

什么是的AppDomain

[转载]Adnroid一个录制屏幕的开源项目介绍

mikel阅读(1061)

[转载]Adnroid一个录制屏幕的开源项目介绍 – Android – mobile – ITeye论坛.

最近发现一个录制屏幕的开源项目,跟大家分享下。
Androidscreencastcase 一个适用于Windows/linux/MacOS 平台控制任何Android 设备,通过USB连接就可实现鼠标键盘控制你的android.
官方描述:
Desktop app to control an android device remotely using mouse and keyboard. Should work on Windows/Linux/MacOS with any android device.

官方网址:http://code.google.com/p/androidscreencast/

安装及需求描述:

1.需要Android环境(download here)
2.确保已经通过USB连接到android设备
3.确保jdk环境在5以及以上
4.下载androidscreencast.jnl(Download here)
几点说明:
1.使用需要一定的权限如root,USB调试已经启用
2.鼠标右键可以旋转屏幕方向
3.反映数度有一定的延迟

对应的key值
KeyMapping
Home HOME
Menu (left softkey) F2 or Page-up button
Star (right softkey) Page Down
Back ESC
Call/dial button F3
Hangup/end call button F4
Search F5
Power button F7
Switch orientation Right click

5进制空间 最后在放上一张截图

[原创]数据库重构1:表合并

mikel阅读(947)

最近项目中充斥着以往没用的功能,严重影响性能,于是准备对项目进行整理重构,清理没用的功能,涉及到数据库的清理,学名就是数据库重构,首当其冲的就是表结构的重构,本系列包含以下内容:

  1. 表合并

  2. 视图关系重构

  3. 存储过程业务逻辑整理

好了,下面开始第一部分的内容:

目前项目中的数据库存在过度设计的问题,首当其冲的是用户信息的存储表过于零碎,多达10个表之多,每个表存储与用户直接关联的不同内容,而且都是一对一的关系,造成对用户操作处理过程繁琐,查询和更新数据严重影响性能,于是从用户开始重构。特别提醒:重构之前必备的准备工作就是备份数据库和现有程序代码,避免重构过程中出现数据丢失和程序混乱,到时候就追悔莫及了!

用户表之间的关系图:

这些都是UserInfo的属性,于是将这些表整合到UserInfo表中,如下图:

下面说下重构步骤:

  1. 先在UserInfo表中加入其他的表的字段,注意:其他表这时候不需要删除
  2. 然后按照User_ID=UserInfo.ID为条件依次更新UserInfo表中字段的值,也就是导入其他表的数据
  3. 最后一步就是不要删除其他表,为什么多这么一步,因为数据库中除UserInfo表之外还有其他表可能关联这些废弃的表,删除后会引起数据查询出错、视图无法显示、存储过程报错等一系列的问题
  4. 接下来就是视图重构需要讲解的内容了,如何清理视图并提出废弃的表

[转载]android下载图片在手机中展示

mikel阅读(1066)

[转载]android下载图片在手机中展示 – 蓝之风 – 博客园.

在项目开发中从互联网上下载图片是经常用到的功能,再次总结一下

1.普通的下载方式

布局文件:

<!--?xml version="1.0" encoding="utf-8"?-->

java文件

public class DownloadImage extends Activity {
private ImageView imgPic;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.download_image);
imgPic = (ImageView) findViewById(R.id.imgPic);
String url = "http://ww1.sinaimg.cn/bmiddle/6834c769jw1djjf4p3p9rj.jpg";
loadRmoteImage(url);
}

/**
* @param imgUrl
*            远程图片文件的URL
*
*            下载远程图片
*/
private void loadRmoteImage(String imgUrl) {
URL fileURL = null;
Bitmap bitmap = null;
try {
fileURL = new URL(imgUrl);
} catch (MalformedURLException err) {
err.printStackTrace();
}
try {
HttpURLConnection conn = (HttpURLConnection) fileURL
.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
int length = (int) conn.getContentLength();
if (length != -1) {
byte[] imgData = new byte[length];
byte[] buffer = new byte[512];
int readLen = 0;
int destPos = 0;
while ((readLen = is.read(buffer)) &gt; 0) {
System.arraycopy(buffer, 0, imgData, destPos, readLen);
destPos += readLen;
}
bitmap = BitmapFactory.decodeByteArray(imgData, 0,
imgData.length);
}
} catch (IOException e) {
e.printStackTrace();
}
imgPic.setImageBitmap(bitmap);
}

2.带进度条的下载

有时候网络差,或者是图片太大,会出现黑屏的情况,用户体验比较差,那么增加一个进度条是提高用户体验的好方法

/**
* @author xushilin xsl xushilin@kingtoneinfo.com
* @version: 创建时间:2011-7-27 下午02:55:56
* 说 明: android中下载图片
* 修改历史:
*/
public class DownloadImage extends Activity {
private ImageView imgPic;
private ProgressBar progressBar;
private int totalSize=0;
private int size=0;
private Handler mHandler;
String url = "http://ww1.sinaimg.cn/bmiddle/6834c769jw1djjf4p3p9rj.jpg";
private Bitmap bitmap=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.download_image);
imgPic = (ImageView) findViewById(R.id.imgPic);

progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setProgress(getProgressInt(progressBar.getMax()));
mHandler = new Handler() {
public void handleMessage(Message msg) {
progressBar.setProgress(getProgressInt(progressBar.getMax()));
if(bitmap!=null){
imgPic.setImageBitmap(bitmap);
}
}
};
new Thread(){
public void run(){
loadRmoteImage(url);
}
}.start();
}

/**
* @param imgUrl
*            远程图片文件的URL
*
*            下载远程图片
*/
private void loadRmoteImage(String imgUrl) {
URL fileURL = null;
try {
fileURL = new URL(imgUrl);
} catch (MalformedURLException err) {
err.printStackTrace();
}
try {
HttpURLConnection conn = (HttpURLConnection) fileURL
.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
int length = (int) conn.getContentLength();
totalSize=length;
if (length != -1) {
byte[] imgData = new byte[length];
byte[] buffer = new byte[512];
int readLen = 0;
int destPos = 0;
while ((readLen = is.read(buffer)) &gt; 0) {
System.arraycopy(buffer, 0, imgData, destPos, readLen);
destPos += readLen;
size=destPos;
mHandler.sendEmptyMessage(1);
Thread.sleep(100);
}
bitmap = BitmapFactory.decodeByteArray(imgData, 0,
imgData.length);
mHandler.sendEmptyMessage(1);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}

}
private  int getProgressInt(int max) {
int result = (totalSize &gt; 0) ? (int) (size * max * 1.0 / totalSize) : 0;
return result;
}
}

效果如下:

下载过程

image

下载完成:

image

[转载]关于Flex4提示序列号失效的问题解决

mikel阅读(1151)

[转载]关于Flex4提示序列号失效的问题解决 .

当下安装的Flash Builder 4在正确输入序列号安装之后,每次打开都会提示序列号失效,而必须再次输入一个不同的序列号或者就改成试用版接受60天试用!其解决方法如下:

可以用记事本编辑“C:\Windows\System32\Drivers\etc\”目录下的 hosts 文件,在末尾加上:
127.0.0.1 activate.adobe.com
127.0.0.1 practivate.adobe.com
127.0.0.1 ereg.adobe.com
127.0.0.1 activate.wip3.adobe.com
127.0.0.1 wip3.adobe.com
127.0.0.1 3dns-3.adobe.com
127.0.0.1 3dns-2.adobe.com
127.0.0.1 adobe-dns.adobe.com
127.0.0.1 adobe-dns-2.adobe.com
127.0.0.1 adobe-dns-3.adobe.com
127.0.0.1 ereg.wip3.adobe.com
127.0.0.1 activate-sea.adobe.com
127.0.0.1 wwis-dubc1-vip60.adobe.com
127.0.0.1 activate-sjc0.adobe.com

Flash CS5也同样适用!

欢迎交流学习!不喜勿喷!编辑不易,转载请注明出处http://hi.baidu.com/sxw52039/home

[转载]Android开发教程 --- 葵花宝典第六层 控件之 Dialog ListView GridView

mikel阅读(1030)

[转载]Android开发教程 — 葵花宝典第六层 控件之 Dialog ListView GridView – Jason_CC – 博客园.

Hi 大家好!

今天和大家一起来学习三种控件,对话框、列表、网格视图。

这三种控件比较重要,使用率也比较频繁,相对来说也比前面所讲的控件复杂,希望大家多练习,熟练掌握它们。

照例,上笑话。

论坛楼主:帅有个屁用——到头来还不是被卒吃掉!
论坛回复:帅有士陪,有炮打,有马骑,有车坐,有相暗恋……帅怎么不好?!!

Dialog 对话框,它运行起来的效果是什么样呢?如下图

这种是最常用的对话框

当点击了上图的确定后,会弹此对话框,这种对话框属于自定义布局类型

当执行一些比较费时的操作时,用这种对话框是个不错的选择

当我们需要用户进行选择操作,又不能使用下来列表时,可以使用这种自定义布局的对话框

接下来我们就一起来看看这些通过代码是如何实现的?

package TSD.Jason.Example;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;

/**
* 对话框
* @author Administrator
* 常用方法:
*     setTitle():给对话框设置title.
setIcon():给对话框设置图标。
setMessage():设置对话框的提示信息
setItems():设置对话框要显示的一个list,一般用于要显示几个命令时
setSingleChoiceItems():设置对话框显示一个单选的List
setMultiChoiceItems():用来设置对话框显示一系列的复选框。
setPositiveButton():给对话框添加”Yes”按钮。
setNegativeButton():给对话框添加”No”按钮。
*
*/
public class DialogActivity extends Activity {
Button btn1;
ProgressDialog p;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.diaolg);
btn1 = (Button)findViewById(R.id.btnDialog);
btn1.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
Builder dialog = new AlertDialog.Builder(DialogActivity.this);
dialog.setTitle("登录提示");
dialog.setIcon(android.R.drawable.ic_dialog_info);
dialog.setMessage("是否登录?");
dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ShowLoginDialog();
}
});
dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DialogActivity.this.finish();
}
});
dialog.show();
}
});
}
}

以上代码是我们第一张对话框的效果实现代码,大家发现是不是挺简单,当我们单击确定按钮后,将调用一个叫做ShowLoginDialog的方法。

这个方法马上会贴出来,在这里我还是要强调下,大家在写代码的时候一定要有一个良好的编程思想,将功能拆分,降低代码的耦合度,一个方法只做一件事情,不要一股脑的将代码写到一个方法里。希望大家记得。

private void ShowLoginDialog()
{
Builder builder = new AlertDialog.Builder(DialogActivity.this);
builder.setTitle("用户登录");
LayoutInflater factory = LayoutInflater.from(DialogActivity.this);
View dialogView  = factory.inflate(R.layout.dialogview, null);
builder.setView(dialogView);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DialogWait();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DialogActivity.this.finish();
}
});
builder.show();
}

上边被标注的代码是为了让Dialog中的内容部分显示我们自定义的布局文件,通过builder对象的setView方法就可以将R.layout.dialogview这个布局文件绑定到对话框中。

布局文件代码如下

<!--?xml version="1.0" encoding="utf-8"?-->

第三个等待效果的对话框是如何实现的呢?上边我们调用了一个方法叫做 DialogWait

private void DialogWait()
{
p = ProgressDialog.show(DialogActivity.this, "正在登录", "请稍后...", true);
new Thread(){
public void run(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
p.dismiss();//登录结束后,取消对话框
}
}
}.start();

}

大家注意,此时的类就不在是AlertDialog,而是ProgressDialog。

上边代码我们为了测试看效果,所以开启了一个线程,并挂起2秒,这在以后项目中是不需要的,如果大家看不太懂,那么这里先跳过。

接下来我们来看看如何在对话框中嵌套一个ListView。

首先,需要一个布局文件,布局文件里只创建一个ListView,如下代码

<!--?xml version="1.0" encoding="utf-8"?-->

Java代码如下

private void ShowLoginDialog()
{
Builder builder = new AlertDialog.Builder(Tab1Activity.this);
builder.setTitle("选择城市");
LayoutInflater factory = LayoutInflater.from(Tab1Activity.this);
View dialogView  = factory.inflate(R.layout.dialogcity, null);
listCity =(ListView)dialogView.findViewById(R.id.listCity);
GetCity();
builder.setView(dialogView);
builder.show();
}

private void GetCity()
{
System.out.println("asd");
ArrayList&gt; listData = new ArrayList&gt;();
HashMap hmItem = new HashMap();
hmItem.put("city", "北京");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "上海");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "深圳");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "天津");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "南京");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "武汉");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "江苏");
listData.add(hmItem);
hmItem = new HashMap();
hmItem.put("city", "宁波");
listData.add(hmItem);
SimpleAdapter sim = new SimpleAdapter(this, listData, android.R.layout.simple_list_item_1, new String[]{"city"}, new int[]{android.R.id.text1});
listCity.setAdapter(sim);
}

直接调用ShowLoginDialog方法即可。注意标注的代码,需要先获取ListView。这里已经用到了ListView,如果不太懂下边就将ListView,大家注意看。

ListView

上边已经展示过它运行的效果了,这里就不展示运行效果了。

那么要使用ListView需要哪些步骤呢?举一个例子,可能不太恰当

冰箱里没有鸡蛋了,我们从家里提了一个篮子去超市买鸡蛋。就是这样的一个过程。我们来分解下这个步骤

冰箱 == 展示数据 == ListView

超市里的鸡蛋 == 数据 == ArrayList 泛型集合

篮子 == 适配器 == SimpleAdapter

我们应该将 鸡蛋(ArrayList) 装到 篮子里(SimpleAdapter) 然后提回家 放到 冰箱里( ListView)

分解完步骤后,那么我们看看如何用代码实现这个过程。

ListView userList; //声明一个ListView对象(冰箱)

userList = (ListView)findViewById(R.id.listUserInfo); //获取布局文件中的ListView控件赋值给ListView对象

ArrayList> listData = new ArrayList>(); //数据源 (超市装鸡蛋的盒子)

HashMap hmItem = new HashMap(); //需要一个HashMap键值对 (每一个鸡蛋)
hmItem.put(“userName”, “张三”);
hmItem.put(“userPhone”, “1234567890”);
listData.add(hmItem); //将鸡蛋装到数据源中

String[] s = new String[2]; //列 和键值对中的键 一一对应 每个键值对应该是一样的列数
s[0] = “userName”;
s[1] = “userPhone”;
int[] i = new int[2]; //用什么控件来装载上边String集合中的列 和上边的String数组也是一一对应的
i[0] = Android.R.id.text1;
i[1] = Android.R.id.text2;
SimpleAdapter sim = new SimpleAdapter(this, listData, android.R.layout.simple_list_item_1, s, i); //这就是我们的篮子
userList.setAdapter(sim); //将篮子中的鸡蛋装到冰箱中 🙂

完整的代码如下

package TSD.Jason.Example;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;

/**
* ListView基本使用方法
*
* 使用ListView的基本步骤
* 1.准备ListView要显示的数据 ;
* ArrayList&gt; listData = new ArrayList&gt;();
*
* 2.使用 一维或多维 动态数组 保存数据;
* HashMap hmItem = new HashMap();
hmItem.put("userName", "张三");
hmItem.put("userPhone", "1234567890");

* 3.构建适配器 , 简单地来说, 适配器就是 Item数组 , 动态数组 有多少元素就生成多少个Item;
* SimpleAdapter simpleAdapter;
* 数据绑定的类
* 参数解释
*
* 第一个context,很明显大家根据英文可以知道是上下文的意思,它官方的意思是:SimpleAdapter所要运行关联到的视图,这个是什么呢?就是你这个SimpleAdapter所在的Activity(一般而言),所以这个参数一般是this

第二个是一个泛型只要是一个List就行,这一般会想到是ArrayList,而他内部存储的则是Map或者继承自Map的对象,比如HashMap,这些语法都是Java的基本语法,不再详述了!这里呢是作为数据源,而且每一个ArraList中的一行就代表着呈现出来的一行,Map的键就是这一行的列名,值也是有列名的。

第三个资源文件,就是说要加载这个两列所需要的视图资源文件,你可以左边一个TextView右边一个TextView,目的在于呈现左右两列的值!

第四个参数是一个数组,主要是将Map对象中的名称映射到列名,一一对应

第五个是将第四个参数的值一一对象的显示(一一对应)在接下来的int形的id数组中,这个id数组就是LayOut的xml文件中命名id形成的唯一的int型标识符

*  context   关联SimpleAdapter运行着的视图的上下文。
data        一个Map的列表。在列表中的每个条目对应列表中的一行,应该包含所有在from中指定的条目
resource              一个定义列表项目的视图布局的资源唯一标识。布局文件将至少应包含哪些在to中定义了的名称。
from       一个将被添加到Map上关联每一个项目的列名称的列表
to    应该在参数from显示列的视图。这些应该全是TextView。在列表中最初的N视图是从参数from中最初的N列获取的值。
*
* 4.把 适配器 添加到ListView,并显示出来。
* @author Administrator
*
*/
public class ListViewActivity extends Activity {

ListView userList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listview);
userList = (ListView)findViewById(R.id.listUserInfo);

ArrayList&gt; listData = new ArrayList&gt;();

HashMap hmItem = new HashMap();
hmItem.put("userName", "张三");
hmItem.put("userPhone", "1234567890");
listData.add(hmItem);

hmItem = new HashMap();
hmItem.put("userName", "李四");
hmItem.put("userPhone", "981234502");
listData.add(hmItem);

hmItem = new HashMap();
hmItem.put("userName", "王五");
hmItem.put("userPhone", "5622435566221");
listData.add(hmItem);

//SimpleAdapter simpleAdapter = new SimpleAdapter(this, listData, R.layout.textviewitem, new String[]{"userName","userPhone"}, new int[]{R.id.txtUserName,R.id.txtUserPhone});
//SimpleAdapter simpleAdapter = new SimpleAdapter(this, listData, android.R.layout.simple_list_item_1, new String[]{"userName","userPhone"}, new int[]{android.R.id.text1,android.R.id.text2});
//SimpleAdapter simpleAdapter = new SimpleAdapter(this, listData, android.R.layout.simple_list_item_2, new String[]{"userName","userPhone"}, new int[]{android.R.id.text1,android.R.id.text2});

String[] s = new String[2];
s[0] = "userName";
s[1] = "userPhone";
int[] i = new int[2];
i[0] = android.R.id.text1;
i[1] = android.R.id.text2;
SimpleAdapter sim = new SimpleAdapter(this, listData, android.R.layout.simple_list_item_1, s, i);

userList.setAdapter(sim);

//列表项单击事件
userList.setOnItemClickListener(new ListView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<!--?--> arg0, View arg1, int arg2,
long arg3) {
System.out.println(arg2);
System.out.println(arg3);
}
});

//列表项选中事件
userList.setOnItemSelectedListener(new ListView.OnItemSelectedListener() {

@Override
public void onItemSelected(AdapterView<!--?--> arg0, View arg1,
int arg2, long arg3) {
// TODO Auto-generated method stub
System.out.println("selected----------" +arg2);
System.out.println("selected----------" +arg3);
}

@Override
public void onNothingSelected(AdapterView<!--?--> arg0) {
// TODO Auto-generated method stub

}
});

//列表项长按事件
userList.setOnItemLongClickListener(new ListView.OnItemLongClickListener() {

@Override
public boolean onItemLongClick(AdapterView<!--?--> arg0, View arg1,
int arg2, long arg3) {
System.out.println("long---------" + arg2);
System.out.println("long---------" + arg3);
return true;
}
});
}
}

上边注释的三句话

第一句 是我们可以自定义布局文件展示数据

第二句 我们可以用内置的布局文件来展示

第三句 和第二句一样,但是效果不一样,大家运行看看就明白了

GridView

类似与手机主菜单中展示的效果,如图

网格视图控件和我们的ListView 操作很像,上边已经解释过了,这里直接贴代码了

package TSD.Jason.Example;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
import android.widget.SimpleAdapter;

public class GridViewActivity extends Activity {

// 定义整型数组 即图片源
private Integer[]    mImageIds    =
{
R.drawable.img1,
R.drawable.img2,
R.drawable.img3,
R.drawable.img4,
R.drawable.img5,
R.drawable.img6,
R.drawable.img7,
R.drawable.img8,
R.drawable.img1,
};
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.gridview);
GridView gridview = (GridView) findViewById(R.id.gridview);
// 生成动态数组,并且转入数据
ArrayList&gt; lstImageItem = new ArrayList&gt;();

for (int i = 0; i &lt; 9; i++) {
HashMap map = new HashMap();
map.put("ItemImage", mImageIds[i]);// 添加图像资源的ID
map.put("ItemText", "NO." + String.valueOf(i));// 按序号做ItemText
lstImageItem.add(map);
}
SimpleAdapter simple = new SimpleAdapter(this, lstImageItem,
R.layout.gridviewitem,
new String[] { "ItemImage", "ItemText" }, new int[] {
R.id.ItemImage, R.id.ItemText });
gridview.setAdapter(simple);
}
}

好,今天就到这里,源代码已经上传到天圣达网站,大家去下载下来,动手实践下。 http://www.bj-stl.com/android.html