Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

并行处理提高工作效率

$
0
0

我们都知道,如今的CPU可以如此强大,和其复杂而科学的调度系统是分不开的。CPU不会傻傻的等待一件事情做完才去做另外一件事情,而是把所有的工作细化,轮询执行,这样才可以保证资源利用效率最大化。


同样,工作中也经常会遇到A事情暂时做不下去而要暂时挂起的情况,这时我们要傻傻的无限等待吗?若这样效率就大打折扣了,我们可以在此设定一个回调,马上开启另外一个线程开始另外一件B事情的运行,当B事情遇到阻塞又暂时搁浅,而A事情已经回调通知我们可以继续了,这时我们就可以马上切换到A事情上去。当这样几个事情之间来回切换即可保持一直有事做的状态,一天下来可能就做了别人几天的工作量。


除了这种阻塞的情况,还有可能遇到各种各样的打断事件。比如正在专心码代码,突然经理一个电话过来要你马上去解决一个严重BUG,这时手头的工作可能就要暂时中断了,要被暂时挂起,等待紧急事件处理完毕之后再继续。 


当然,现实中的调度更为复杂,还要对任务优先级排序,对于优先级最高的任务一旦回调通知可以继续,马上要中断其他线程的工作,转向优先级最高的事情。

可以通过下面的图片来模拟一下多线程工作的含义:

                   

 

 

注:图中线条越粗表明任务优先级越高。通过一个小故事来扯扯吧:


程序员小A的一天


8点钟上班,开始做一个需要今天下班前交付的需求(黑色线条),因此,这时需求优先级是最高的,在主线运行。


开发过程中突然发现第三方提供的SDK接口有严重问题,需要第三方去修改,这时需求开发也就进入了“阻塞”状态;A马上切换到了一个一般问题(绿色线条)的解决上去(优先级较低),然后一直坚监听第三方修改完成的通知,收到接口修改成功的通知马上要“中断”现有的一般问题解决,再次转向需求开发。


上午十一点的时候经理突然打电话给小A,某某功能出现严重BUG(黄色线条),需要马上解决,这时这个严重BUG一下子成了优先级最高的任务,需要马上“中断”需求开发,转向严重BUG的解决。


经过两小时的奋战终于成功解决了这个严重BUG,此时已经是下午1点钟了,小A匆匆吃过午饭,本想小憩一会,突然梦中惊醒,需求要在下班前交付,没办法,程序员就是这么苦逼,洗把脸之后继续需求的开发,终于在下午四点钟的时候搞定了需求开发。

这时还有一个半小时才下班,傻等吗?上午中断的一般问题解决可以让它继续了,五点半下班的时候这个一般问题也解决了。


这样小A就在一天内完成了三项工作,可以放松下班了。(PS:五点半下班只是梦想。)


上面的例子只是很简单的一种情况,实际工作中的场景可能要比这复杂的多,但万变不离其宗,凡事总有套路,慢慢品味总会找出技巧。


最后,也可以看出,工作和学习有很大不同,学习要保持一颗专注的心,而现实中的工作可能要学会“分心”,调度切换能力越强工作效率也就越高。这也就解释了为什么一些学习能力很强的人在工作上可能会差强人意,也许是太过“专心”而不够灵活吧。

 

本人只是一个刚入职不到一年的小菜鸟,老鸟们可一笑而过,切勿开启嘲讽模式,毕竟和谐最重要。


更多优质文章关注微信公众号  

                        

 

 

请关注我的新浪微博:AndroidTip

CSDN博客:http://blog.csdn.net/yayun0516

作者:yayun0516 发表于2017/3/18 19:06:19 原文链接
阅读:282 评论:0 查看评论

Vue Lazy Loading Routes

$
0
0

When building apps with a bundler, the JavaScript bundle can become quite large, and thus affect the page load time. It would be more efficient if we can split each route’s components into a separate chunk, and only load them when the route is visited.

Combining Vue’s async component feature and Webpack’s code splitting feature, it’s trivially easy to lazy-load route components.

All we need to do is define our route components as async components:

const Foo = resolve => {
  // require.ensure is Webpack's special syntax for a code-split point.
  require.ensure(['./Foo.vue'], () => {
    resolve(require('./Foo.vue'))
  })
}

There’s also an alternative code-split syntax using AMD style require, so this can be simplified to:

const Foo = resolve => require(['./Foo.vue'], resolve)

Grouping Components in the Same Chunk

Sometimes we may want to group all the components nested under the same route into the same async chunk. To achieve that we need to use named chunks by providing a chunk name to require.ensure as the 3rd argument:

const Foo = r => require.ensure([], () => r(require('./Foo.vue')), 'group-foo')
const Bar = r => require.ensure([], () => r(require('./Bar.vue')), 'group-foo')
const Baz = r => require.ensure([], () => r(require('./Baz.vue')), 'group-foo')

the effect: if you render the component that you visited , this will have more .js files,but not included in vue app.js

这里写图片描述

as the photo show , there will have 2.js and 3.js , if not lazy load only have app.js

这里写图片描述

if you grouping your component in same chunk will the group chunk will have same .js file

这里写图片描述

作者:java_goodstudy 发表于2017/3/18 19:12:33 原文链接
阅读:74 评论:0 查看评论

谷哥的小弟学后台(39)——MyBatis输入映射parameterType

$
0
0

探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


版权声明


我们知道:MyBatis通过parameterType对sql的输入参数进行定义,参数的类型可以是:基本类型、HashMap、pojo。在此分别介绍为parameterType传入三种类型的不同处理方式。

基本类型

其实,从这个MyBatis学习系列开始,我们已经多次为parameterType传入基本类型的参数,比如int 、long等。故,在此不再赘述,请参见前几篇博客的示例。

HashMap

首先来看mapper.mxl中的sql语句

<select id="findStudentByHashMap" parameterType="hashmap" resultType="cn.com.Student">
        SELECT * FROM student WHERE id=#{id} and name like '%${name}%'
</select>

嗯哼,看到没有:我们为parameterType指定的输入类型是hashmap。在sql语句中从hashmap中取出id和name作为查询条件

接下来瞅瞅mapper.java中的定义

public List<Student> findStudentByHashMap(HashMap<String, Object> hashMap);

最后,再来看看测试代码:

@Test
    public void findStudentByHashMap() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        HashMap<String, Object> hashMap=new HashMap<String, Object>();
        hashMap.put("id", 7);
        hashMap.put("name", "木");
        List<Student> studentList = studentMapper.findStudentByHashMap(hashMap);
        for (int i = 0; i <studentList.size(); i++) {
            Student student = studentList.get(i);
            System.out.println(student);
        }
        sqlSession.commit();
        sqlSession.close();
    }

在此,创建一个HashMap且指定两个key:id和name并为它们赋值;然后执行查询即可。

pojo

有时候,我们需要执行一些复杂的查询,比如:查询的条件不仅包括学生查询条件还包括其它的查询条件(比如:课程,教师,学校等)。此时,可以使用自定义pojo传递输入参数。

首先,定义一个Student的扩展类

/**
 * 本文作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
package cn.com;

//Student的扩展类
public class StudentCustom extends Student{

}

再自定义包装类型的pojo

/**
 * 本文作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
package cn.com;
//自定义的包装类型的pojo
public class StudentQueryVO {

    //用户查询条件
    private StudentCustom studentCustom;

    public StudentCustom getStudentCustom() {
        return studentCustom;
    }

    public void setStudentCustom(StudentCustom studentCustom) {
        this.studentCustom = studentCustom;
    }

    //其他查询条件,比如教师,课程,学校等等
}

在该pojo中不仅包括与学生相关的查询条件,还有与教师,课程,学校有关的查询条件。

接下来请看mapper.xml

<select id="findStudentList" parameterType="cn.com.StudentQueryVO" resultType="cn.com.StudentCustom">
        SELECT * FROM student WHERE gender=#{studentCustom.gender} and name like '%${studentCustom.name}%'
</select>

嗯哼,看到了吧:我们将自定义的包装类型的pojo作为输入参数设置给parameterType;然后取出输入参数StudentQueryVO中的studentCustom的gender和name作为条件查询。

再来瞅瞅mapper.java中的定义

public List<StudentCustom> findStudentList(StudentQueryVO studentQueryVO);

最后,请看测试代码:

@Test
    public void findStudentList() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        StudentQueryVO studentQueryVO=new StudentQueryVO();
        StudentCustom studentCustom=new StudentCustom();
        studentCustom.setGender("female");
        studentCustom.setName("木");
        studentQueryVO.setStudentCustom(studentCustom);
        List<StudentCustom> studentList = studentMapper.findStudentList(studentQueryVO);
        for (int i = 0; i <studentList.size(); i++) {
            StudentCustom sc = studentList.get(i);
            System.out.println(sc);
        }
        sqlSession.commit();
        sqlSession.close();
    }
作者:lfdfhl 发表于2017/3/18 20:41:56 原文链接
阅读:157 评论:0 查看评论

Ajax实现搜索引擎自动补全功能

$
0
0

每当我们遇到一些问题时候,我们会百度一下,你是有过我们的问题还没打完就已经出现的经历?
如下图:
这里写图片描述
天猫:
这里写图片描述
京东:这里写图片描述
这时候的你是否想到如何实现这个功能?
我们的网页明明没有刷新,可是却出现了数据的交互,也就是Ajax的强大之处。
下面以一个例子来实现。
搜索框和搜索按钮,我们希望在搜索框输入部分书名时,能够实现书名的自动补全功能。每当点击了相应的书名,就把内容输入到搜索框。
这里写图片描述
对应的代码

Search <input
                    type="text" name="name" class="inputtable" 
                    id="name" /> 
                    <input type="image" src="images/serchbutton.gif"
                    border="0" style="margin-bottom:-4px">

为了存贮自动补全的文字,我们使用div标签
设置背景色为白色,绝对布局。并且默认不显示。

<div id="context1" style="background-color:white; border: 1px solid red;width:128px;position: absolute;top: 133px;left:944px;display:none" >
</div>

在百度的补全功能中我们发现,每输入一个字,对应的补全文字就会更新,所以我们要为搜索框设置键盘抬起事件(keyup),然后就是Ajax实现异步交互。
js代码:

$(".inputtable").keyup(function(){
        var content=$(this).val();
        //如果当前搜索内容为空,无须进行查询
        if(content==""){
            $("#context1").css("display","none");
            return ;
        }
        //由于浏览器的缓存机制 所以我们每次传入一个时间
        var time=new Date().getTime();
        $.ajax({
            type:"get",
            //新建一个名为findBooksAjaxServlet的servlet
            url:"${pageContext.request.contextPath}/servlet/findBooksAjaxServlet",
            data:{name:content,time:time},
            success:function(data){
                //拼接html
                var res=data.split(",");
                var html="";
                for(var i=0;i<res.length;i++){
                    //每一个div还有鼠标移出、移入点击事件
                    html+="<div onclick='setSearch_onclick(this)' onmouseout='changeBackColor_out(this)' onmouseover='changeBackColor_over(this)'>"+res[i]+"</div>";
                }
                $("#context1").html(html);
                //显示为块级元素
                $("#context1").css("display","block");
            }
        });
    });

我们的鼠标在选择到自动补全的内容时,我们会发现内容的那一行会变色,即onmouseover事件,离开时恢复原色onmouseout事件,点击时将内容填写到搜索框onclick事件。所以在上面的代码中我们会这样写

                var html="";
                for(var i=0;i<res.length;i++){
                    //每一个div还有鼠标移出、移入点击事件
                    html+="<div onclick='setSearch_onclick(this)' onmouseout='changeBackColor_out(this)' onmouseover='changeBackColor_over(this)'>"+res[i]+"</div>";
                }

而其三种事件对应的js代码如下:

//鼠标移动到内容上
    function changeBackColor_over(div){
        $(div).css("background-color","#CCCCCC");
    }
    //鼠标离开内容
    function changeBackColor_out(div){
        $(div).css("background-color","");
    }
    //将点击的内容放到搜索框
    function setSearch_onclick(div){
        $(".inputtable").val(div.innerText);
        $("#context1").css("display","none");
    }

servlet:向server调用相应的业务然后返回查询的结果

public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        request.setCharacterEncoding("utf-8");
        //获取搜索框输入的内容
        String name=request.getParameter("name");
        name=new String(name.getBytes("iso-8859-1"), "utf-8");
        //向server层调用相应的业务
        BooksServer booksServer=new BooksServer();
        String res=booksServer.findBooksAjax(name);
        //返回结果
        response.getWriter().write(res);

    }


    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        doGet(request, response);

    }

service层
把dao层查询的所有书名拼接为一个字符串。

//查询所有的书本名
    public String findBooksAjax(String name) {
        List<Object> nameList=booksDao.findBooksAjax(name);
        String res="";
        for (int i=0;i<nameList.size();i++) {
            if(i>0){
                res+=","+nameList.get(i);
            }else{
                res+=nameList.get(i);
            }
        }
        return res;
    }

dao层
根据部分书名,查询类似的书名

//根据部分书名,查询类似的书名
    public List<Object> findBooksAjax(String name) {
        QueryRunner qr=new QueryRunner(C3P0Util.getDataSource());
        try {
            return qr.query("select name from book where name like ?", new ColumnListHandler(),"%"+name+"%");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

运行结果:
这里写图片描述

作者:su20145104009 发表于2017/3/18 21:08:35 原文链接
阅读:53 评论:0 查看评论

Flex应用性能优化

$
0
0

本章简介

前几章介绍了Flex应用开发的主要内容,本章将介绍Flex应用性能优化相关的知识,比如如何减少SWF文件的大小和内存泄漏问题以及改善代码性能的技巧等。很多时候,影响应用性能的主要因素是设计。不好的设计是导致应用性能低下的主要原因,而针对不同特点的应用,采用何种设计方法往往与设计者本身的经验和素质相关。在排除了设计的因素之后在Flex应用开发中还有很多具体细节和技巧可以提高Flex应用的性能,本章将介绍RSL技术以减小SWF文件的体积,和Flex垃圾回收原理,以及预防内存泄露的一些基本技巧。此外还介绍了Flex应用中进行打印机打印的常见方法。


核心技能部分

Flex应用的性能优化除了设计的因素外,主要集中在两个方面

Ø 如何解决SWF文件过大的问题。

Ø 如何解决Flex内存泄漏的问题

本章会从这两个方面着手 讲解如何使用RSL技术降低SWF文件的体积以及Flex内存泄漏的原因、如何避免Flex应用内存泄漏、如何确定是否有内存泄漏、几个Flash提供的能移检查内存使用情况和Flash Player自身相关信息的系统类,以及其他提高性能的技巧。

1.1 RSL技术

Flex1.0的时候,MXML文件和它的资源文件全部编译到一个SWF文件中,导致SWF文件非常大。SWF文件中包含了基础的application模型(MODEL)组件(如Button CheckBox

Panel等组件)、图片资源、嵌人数据和自定义组件。

SWF文件导致的直接后果就是下载时间较长(虽然在没有修改的情况下只下载一次)。在多数情况下,一个Flex客户端包含多个应用,这些应用包含了很多相同的资源。但是由于每个应用都被编译成SWF文件,相同的资源被编译进不同的SWF,在下载不同应用的同时也下载了重复的资源。

Flex1.5出现了运行期共享库(Runtime Shared Libraries,RSL)的概念,通过RSL将共享资源提取成独立的文件,可以有效地减小应用SWF文件的大小。这些RSL文件可以分开下载并且在客户端缓存,它们能够被大量地应用在运行期共享使用,但是只需要传输到客户端一次

如果客户端有多个应用,并且这些应用共享一个图片文件、组件和其他资源的核心资源包,用户只需要下载一次包含这些资源的RSL,这明显减小了主应用文件的尺寸。如果某个RSL内的资源发生了变化,Flex可以重编译这个RSL,由客户端重新单独下载,这个过程不需要重编译那些引用资源的应用和其他的资源RSL

理解Flex的链接形式有利于我们理解RSL,如何工作。Flex的编译器支持两种连接方式,静态链接和动态链接.所谓静态链接就是将应用所需要的代码以及这些代码所依赖的代码都编译进应用的SWF文件,这样获得的SWF就会非常大,下载很慢,但运行很快,因为所有的代码都己经在swF里了。而动态链接是指Flex应用中引用的一些类放到了应用SWF文件之外的其他文件中,并且在运行器加载,使用RSL就是动态链接的一种形式。

下面我们首先通过配置编译环境,来看看使用系统RSL的效果。

1.1.1 使用系统RSL---Framework

首先我们新建一个空白的Application,文件名为Blank.mxml

示例9.1

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

 xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

</s:Application>

打开工程属性窗口,查看编译选项,其中的Library path有一个Framework linkage选项,如

9.1.1所示。

 

9.1.1 Application的链接属性设置

默认的选项为Merged into code(合并到代码中 即静态链接),将系统Frame框架库文件内容编译进应用文件代码中。保存设置,系统完成文件编译,查看得知blank.swf文件的大小为362KB。注意:这是一个空白的应用,仅有一个Application组件。造成文件较大的根本原因是文件中很大一部分是Flex基础代码,这部分代码几乎每个Application都会加载使用,如果它们不能够被共享使用,那么空间的浪费是相当巨大的。

下面我们把Framework linkage的选择设置为另外一个选项。Runtime Shared Libraries(RSL)重新构建整个工程,再查看得知blank.swf坟件的大小为142KB ,约减少到原来的1/3,这说明,使用RSL能够有效减少Flex应用SWF文件的大小,当然,减少的部分不是消失了,而是以RSL的形式在运行期动态加载了。

1.1.2 其他的系统RSL

使用Framework RSL使得应用的大小有了明显减少,但是还剩下的一百多KB是些什么东西

?让我们再次打开工程属性窗口,查看编译选项。仔细查看Library Path页,如图9.1.2所示。

 

                              9.1.2 工程引用库文件列表

除了Framework之外,工程中还引用了其他的库文件。对于一个空白的应用文件来说rpc.swc文件也可以设为运行期加载。展开rpc.swc属性,选择Link Type项,点击窗口右侧的Edit按钮,弹出库文件修改窗口如图9.1.3所示。

 

9.1.3 选择库文件是否采用摘要

选择Digests(摘要 摘要将在下一小节进行详细介绍)选项,点击【添加】按钮,在【部署路径】会增加一条记录,名字默认为rpc_3.0.0.477.swz,这个文件将被生成到发布目录,供客户端下载。文件名中的3.0.0.477是当前编辑环境所使用的RPC库的版本号。点击【OK】后系统将重新构建工程,编译后查看blank.swf文件的大小减到了80KB,这基本是在Flex环境下一个空白应用文件能够达到的最小尺寸了。

对于使用chart等组件通过图形方式展示数据的应用,将datavisualization库设置为动态加载后,SWF文件会明显减小。

我们知道了可以通过修改库文件的编译方式来实现RSL的使用。在配置中,我们使用了Digests(摘要)选项,它到底是做什么的呢?下面将详细介绍Digests(摘要)。

1.1.3 RSL摘要

Flash Player缓存是Flash Player 9.0.115.0版本提供的新功能,允许有Adobe签名的文件由Flash Player进行缓存,这些文件以swz为后缀。

与浏览器缓存相比,Flash Player缓存有几个好处。首先,由于缓存的swz文件是Adobe

名的,因此可以在多个域中共享,而不需要考虑这个文件是从哪个域下载的。或者说,只要文件的版本相同,经过前面的库文件可以为整个客户端的所有应用共享使用。

其次,由于,swz文件存储在Flash Player的缓存中,不会因为浏览器清理缓存内容被清理掉。Flash player会自行管理旧的swz文件,当缓存容量达到限制时,不使用的swz文件会被回收清理。

另外,签名的swz文件可以跨浏览器使用,这意味若如果在IE中已经下载了某个swz文件,

那么在Firefox中也可以共享该文件,而不需要另外下载一份。

如何使用具有摘要的RSL?Flex的编译环境中可以很方便地配置它。我们在Library path属性页中,从SDK库中找到framework.swc,如图9.1.4所示。

 

图图9.1.4 库文件配置

选择Link Type项,点击【Edit】编辑按钮,进入属性编辑界面,如图9.1.5所示。

 

9.1.5 库文件的发布形式

 

在属性编辑界面中,选择验证的方式为Digests(摘要),可以看到Deployment pahts(发布路径)有两个文件。一个是framework_3.0.0.477.swz,这个文件就是当前编译环境所使用的Frameworkswz文件,它将会被放到发布路径中供客户端下载;而第二个文件是在swz文件下载出现异常(Digests验证失败等)的情况下作为swz的替代文件进行下载,从而保证客户端能够正常运行。

属性设置中的policy File可以指向cross-domain.xml文件,不指定表示该文件可以任意下载。

Flex3是从Flash Player缓存获得好处的第一个版本,前面我们已经讲到了如何在Flex3中设

置编译环境,从而使用Framework RSL。但是,我们注意到,在编译时包含的库文件都是SWC

格式的,这是为什么呢?

在编译期需要SWC格式的库文件有两个原因:第一个就是要从SWC中读取库文件的摘要,这个摘要将被缓冲的framework进行校验,Flex3中使用的摘要是使用SHA-256加密算法创建的。

一个RSL的摘要是从SWC文件中提取出来并且编译到应用中,当运行时应用从网络或者本

地读取RSL时,会校验RSL的摘要与应用中包含的摘要是否一致,如果两个摘要不匹配,系统将显示一个错误,并放弃RSL的加载。

开发者可以使用未签名的RSL,这个RSL以普通的SWF形式缓存在浏览器中,会因为浏览

器缓存的清理而被清空,并且由于没有认证,也不能实现跨域共享。

1.2 Flex的内存垃圾回收机理

内存问题一直是程序员比较关心的问题之一,每个程序员都希望自己开发的程序足够健壮,在运行过程中不会因内存泄漏而导致程序运行变慢甚至崩溃。

现在,面向对象语言(比如Java)增强了内存管理机制,能够自动回收不被使用的内存,或者说能够回收垃圾内存,这种内存管理机制通常被称为“garbage collection(垃圾回收)“,简称GC

Flex开发中所使用的ActionScript语言(简称AS)也是一种支持GC的语言。经过编译后的AS代码运行在AS虚拟机(简称AVM)中,由AVM自动完成垃圾内存回收的工作。Flash Player就是一个AVM,所以有时候大家会将二者混为一谈。

既然AVM能够自动完成垃圾回收的功能,那么是不是Flex程序员就可以认为自己所开发的Flex应用不存在内存泄漏问题呢?答案是否定的。在某些情况下,处理不妥当的代码仍然会导致内存泄漏.如何才能避免内存泄漏?应该说,只有在 AS程序员清楚地了解了Flash Player的垃圾回收的基本原理,并且高度重视内存泄漏这个问题后,才能有效避免内存泄漏情况的发生。

Flash Player垃圾回收工作是由垃圾回收器(garbage collection)完成的。垃圾回收器是运行在后台的一个进程,它释放那些不再被应用所使用的对象所占用的内存。不再被应用所使用的对象是指那些不会再被活动着(正在工作的)的对象所引用的对象。在AS中,对于非基本类型(BooleanString, Number,Unit,Int)的对象,在对象之间传递的都是对象引用,而不是对象本身。删除一个变量只是删除对象的引用,而不是删除对象本身。一个对象可以被多处引用,通过这些不同的引用所操作的都是同一个对象。

通过示例9.2和示例9.3我们可以了解基本类型和非基本类型对象的差异。

示例9.2 基本类型的值传递

    private function testPrimitiverTypes():void

{

var s1:String="abcd";

var s2:String = s1;

s2+="efg";

trace("s1:",s1);

trace("s2:",s2);

var n1:Number=100;

var n2:Number=n1;

n2=n2+100;

trace("n1:",n1);

trace("n1:",n1);

}

 

示例9.3 非基本类型对象的引用传递

private function testNonPrimitiverTypes():void

{

var a:Object={foo:'bar'};

var b:Object=a;

delete(a);

trace(b.foo);

对于非基本类型对象,AS采用两种方法来判定一个对象是否还有活动的引用,从而决定是否可以将其回收。一种方法是引用计数法,另一种方法是标记清除法。

1.2.1 引用计数法

引用计数法是判定对象是否有活动引用的最简单方法,并且从AS1.0就开始在Flash中使用。

当创建一个对对象的引用后,对象的引用计数就加一,当删除一个引用时,对象的引用计数就减一。如果对象的引用计数为0,那么它被标记为可被GC删除,如示例9.4所示。

示例9.4 对象的引用计数示例

var a:Object={foo:'bar'};

//现在对象的引用计数为1(a)

var b:Object=a;

//现在对象的引用计数为2(ab)

delete(a);

//对象的引用计数又回到了1(b)

delete(b);

//对象的引用计数变成0,现在这个对象可以被GC释放

 

引用计数法很简单,并且不会增加CPU的开销,可惜的是,当出现对象之间的循环引用时,它就不起作用了。所谓循环引用就是指对象之间直接或间接地彼此引用,尽管应用已经不使用这些对象,但是它们的引用计数仍然大于0,因此,这些对象就不会从内存中移除,如示例9.5所示。

示例9.5 对象的循环引用

    //创建第一个对象

      var aObject={};

    //创建第二个对象来引用第一个对象

     var bObject={foo:a};

    //使第一个对象也引用第二个对象

   a.foo= b;

      //删除两个活动引用

      delete(a);

      delete(b):

在上面的例子中,两个活动引用都已被删除,因此在应用程序中再也无法访问这两个对象。

但是它们的引用计数都是1,因为它们彼此相互引用。这种对象间的相互引用可能会更加复杂(a引用bb引用aC又引用a,诸如此类),并且难以在代码中通过删除引用来使得引用计数变为0Flash Player6Flash Player7中就因为XML对象中的循环引用而痛苦,每个XML节点既引用了该节点的子节点,又引用了该节点的父节点,因此这些XML对象永远不会释放内存。好在Flash Player8之后增加了一种新的GC技术—标记清除。

1.2.2 标记清除法

AS3使用的第二种查找不活动对象的GC策略就是标记清除。Flash Player从应用的根(root )节点开始,遍历所有其上的引用,标记它所发现的每个对象,然后迭代遍历每个被标记的对象,标记它们的子对象。这个过程递归进行,直到Flash Player遍历了应用的整个对象树,并标记了它所发现的每个对象,在这个过程结束的时候,可以认为内存中那些没有被打标记的对象没有任何活动引用,因此可以被安全地释放,通过图9.1.6可以很直观地了解这种机制。

在图9.1.6中,深色(有底纹)的引用在标记清除过程中被遍历,深色对象被打上了标记,白色(无底纹)对象将被释放内存。

 

9.1.6 Flex内存对象树

标记清除机制非常准确。但是这种方法需要遍历整个对象结构,从而增加CPU占用率。因此Flash Player9为了减少这种开销,只是在需要的时候偶尔执行标记清除活动。

1.2.3 垃圾回收的时机

Flash Player在运行时请求内存的速度受限于浏览器。因此,Flash Player采取小量请求大块内存,而不是大量请求小块内存的内存请求策略。同样,Flasb Player在运行时释放内存的速度也相对较慢,所以Flasb Player会减少释放内存的次数, 只有在必要的时候才释放内存。也就是说,Flasb Player的垃圾回收只有在必要的时候才会执行。

Flasb Player9和之后的版本中,Flasb Player垃圾回收的时机是在Flasb Player需要请求新的内存之前。这样,Flasb Player可以重新利用垃圾对象所占用的内存资源,并且可以重新评估需要另外请求的内存数量,这也会节省时间。

程序的实际运行中并不是每次应用申请内存时都会导致垃圾回收的执行,只有当Flash占用的内存达到一定程度时,才会执行真止的垃圾回收。如果应用中内存开销增长是匀速的,那么计算机物理内存越大,则垃圾回收触发周期越长。如果计算机有2G的物理内存,直到打开Flash应用的浏览器占用700M物理内存之后才会导致Flash Player回收垃圾内存。

来自Adobe公司的Alex Harui总结了两点:

Ø 何时真正执行垃圾回收不可预知。

Ø 垃圾回收总是在请求内存时触发,而不是在对象删除时发生。

最后,有关Flash Player中垃圾回收的一件非常重要的事情就是:垃圾回收不是立即进行的,而是延迟的。当没有任何对象引用某个对象后,该对象也不会立即从内存中清除,相反地,它们会在将来某个不确定的时候被移出(从开发者的角度来看)。但这些对象在没有被垃圾回收之前,它们仍然在工作(占用内存和CPU }。尽管这不是我们所希望的。

虽然我们不能让Flash Player立即回收内存,但在程序确定不再引用一个正在工作的对象之前,应终止其工作。比如,停止已经启动的Timer ,停止正在播放的视频或声音,以防止其继续占用CPU

1.2.4 强制执行垃圾回收的技巧

很多程序员都想能够在程序中指定计算机进行垃圾回收。目前,Adobe官方没有公布能够强制执行垃圾回收操作的相关API。不过,可以利用Flash Playerbug来实现强制回收内存,主要是通过人为抛出某种特别的异常,从而让Flash Player回收内存,如示例9.6所示。

示例9.6 强制回收内存的代码

try{

var lc1:LocalConnection = new LocalConnection();

var lc2: LocalConnection = new LocalConnection();

lc1.connect(‘gcConnection’);

lc2.connect(‘gcConnection’);

}

catch(e:Error){

 

}

这种强制回收内存的方法并不是根据官方API而是利用系统的某些漏洞。因此,在开发

应用时,不要依赖于这种方法来回收内存,只能将其视为辅助方法。

1.3 导致内存泄漏的常见情况

通过上面的讨论我们可以知道,只要对象被其他活动对象(仍在运行的)所引用,那么这个对象就不会被垃圾回收,从而可能造成内存泄漏。

在开发中,如下的一些情形会导致内存泄漏:

(1) 不再使用被全局对象所引用的对象时,开发者忘记从全局对象上清除对它们的引用,这

时就会产生内存泄漏。常见的全局对象有Stage,Application ,类的静态成员变量以及采用Singleton模式创建的实例等。如果使用第三方框架,比如PureMVC,Cairongorm等,要注意这些框架的实现原理,尤其要注意框架里面采用Singleton模式创建的ControlerModel

无限次触发的Timer会导致内存泄漏。不管无限次触发的Timer是否为全局对象,它本身

以及注册在Timer中的监听器对象都不会被垃圾回收,如示例9.7所示。

示例9.7 含有无限次触发Timer的内存泄漏测试组件

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

<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"

 xmlns:s="library://ns.adobe.com/flex/spark"

 xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300"

 title="测试组件"

 creationComplete="panel1_creationCompleteHandler(event)"

 >

<s:layout>

<s:BasicLayout/>

</s:layout>

<fx:Script>

<![CDATA[

import mx.events.FlexEvent;

[bindable]

private var timeer:Timer = new  Timer(1000);

private var memoryBlocks:Array = new Array();

protected function panel1_creationCompleteHandler(event:FlexEvent):void

{

var mBlock:Array = this.allocateMemory();

memoryBlocks.push(mBlock);

this.timeer.addEventListener(TimerEvent.TIMER,onTime);

this.timeer.start();

}

protected function onTime(event:TimerEvent):void

{

trace(this.toString());

}

protected function allocateMemory():Array

{

  var memoryBlock:Array = new Array(25600000);

for(var i:uint=1;i<=25600000;i++){

memoryBlock[i-1]=i;

}

trace('allcate 100M memory!');

return memoryBlock;

}

 

]]>

</fx:Script>

<fx:Declarations>

<!-- 将非可视元素(例如服务、值对象)放在此处 -->

</fx:Declarations>

<s:Label x="119" y="50" text="内存测试组件" width="139" color="#c14717" fontSize="19" fontWeight="bold"/>

</s:Panel>

上面的代码自定义了一个测试内存泄漏的Canvas组件,这个组件在初始化时开辟了100M内存(为了方便查看内存的回收情况),同时创建了一个每隔1秒钟无限次数触发的Timer,并且启动了这个Timer

针对上面的组件,下面我们给出一个测试应用,其界面如图9.1.7所示。

 

9.1.7 Flex验证内存泄漏组件的应用程序界面

该测试应用上有三个按钮,分别是“强制回收内存’、“创建内存消耗组件”和“移出内存消耗组件”。点击“创建内存消耗组件”按钮就会执行创建一个用于内存泄漏测试的Canvs对象,并将其作为container的子对象显示到界面上,点击“移出内存消耗组件”按钮则会将“创建内存消耗组件”按钮所创建的Canvs对象从containe的子对象列表中删除,并且永远不再使用。

应用运行后,先点击“创建内存消耗组件”按钮,然后再点击“移出内存消耗组件”按钮,重复这样的操作,我们发现,由于Canvs对象上的无限次触发的Timer对象已经启动,导致Canvs对象所占用的内存无法被回收,内存会一直增加,最终导致浏览器崩溃。如果我们将Canvs初始化代码中启动Timer的语句注释掉,重复上述的测试操作,内存会在某个时候减少,这说明占用内存的Canvs对象己经被垃圾回收。

通过这个简单的侧试程序测试了Timer的情况,当然,将其稍加改造也可以用来测试其他情况,在本教材中所列举的内存泄漏的情况都是经过测试程序得到的结论。

上述程序代码如示例9.8所示。

示例9.8 验证内存泄润组件的应用程序

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

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">

<s:layout>

<s:BasicLayout/>

</s:layout>

<fx:Declarations>

<!-- 将非可视元素(例如服务、值对象)放在此处 -->

</fx:Declarations>

<fx:Script>

<![CDATA[

private var memoryTester:TimerTest=null;

//添加测试组件

private function addTest():void{

memoryTester = new TimerTest();

this.container.addChild(memoryTester);

}

//删除测试组件

private function removeTest():void{

this.container.removeChild(this.memoryTester);

this.memoryTester=null;

}

//回收内存

private function gc():void{

try{

var lc1:LocalConnection  = new LocalConnection();

var lc2:LocalConnection  = new LocalConnection();

lc1.connect('gcConnection');

lc2.connect('gcConnection');

}

catch(e :Error){

}

}

]]>

</fx:Script>

<mx:VBox id="container" x="0" y="0" width="100%" height="100%">

<s:Button label="强制收回内存" fontSize="12" width="120" click="this.gc();"/>

<s:Button label="创建内存消耗组件" fontSize="12" width="120" click="this.addTest();"/>

<s:Button label="删除内存消耗组件" fontSize="12" width="120" click="this.removeTest();"/>

 

</mx:VBox>

</s:Application>

 

(2)通过隐式方式建立的对象之间的引用关系更容易被程序员所忽略,从而导致内存泄漏。最常见的以隐式方式建立对象之间的引用就是“绑定”和“为对象添加事件监听器”。通过测试我们发现,“绑定”不会造成内存泄漏,对象可以放心地绑定全局对象,而调用addEventListener()方法“为对象添加事件监听器”则可能产生内存泄漏,大多数内存泄漏都因此而发生。

下面的代码:

   a.add.EventListener(Event.EVENT_TYPE,b.listenerFunction);

使得a对象引用了b对象,如果a对象是一个全局对象(全局对象在应用运行期间始终存在),则b对象永远不会被垃圾回收,可能会造成内存泄漏。比如下面的代码就有造成内存泄漏的可能:

this.stage.addEventListener(Event.RESIZE,onResize);

上面代码中的stageUIComponentstage属性,表示当前Flex应用运行的“舞台“。不过,通过以下三种方式使用addEventListener方法不会造成内存泄漏。

Ø 用弱引用方式注册监听器。就是调用时将addEventListener的第5个参数设置为true,例如:

SomeObject.addEventlistener(MouserClick.CLICK,otherObject.handlerFunction,false,0,true);

Ø 自引用的方式。即为对象添加的监听处理函数是对象本身的方法。例如:

this.addEventListener(MouseClick,CLICK,this.handlerFunction);

Ø 子对象引用。即为子对象添加的监听处理函数是父对象的方法。例如:

private var childObject:UIComponent=new UIComponent;

addChild(childObject);

childObject.addEventListener(MouseEvent.CLICK,this.clickHandler);

1.4 Flash Builder的内存泄漏分析工具

Flash Builder带有一个“剖析(Profiler)”工具,可以用来帮助我们识别内存泄漏。在Flash Builder中选择要被“剖析(Profiler)”的应用后,点击鼠标右键,在右键菜单中选择“Profile As”就可以运行“剖析(Profiler)“工具来分析所选择的应用,如图9.1.8所示。

 

                            9.1.8打开Flex内存泄漏分析工具的菜单

“剖析(Profiler)”工具运行后的界面如图9.1.9所示。

 

9.1.9  Flex内存泄漏分析工具界面

 

在这个工具中,有一个Force Garbage Collection(强制垃圾回收)”按钮,当应用被剖析时或者以剖析方式运行时,点击这个按钮然后观察“活动对象”列表,可以帮助我们分析内存泄漏。如果确定已经完全移除和清除对对象的引用,那么“活动对象”列表中的“类’就会减少。

通过“累计实例数”栏可以看到有多少个对象曾被创建,而通过“实例数”栏可以看到当前存在的对象实例有多少。如果在创建和移除对象之后运行“Force GC”,“果计实例数”的数量和“实例数”的数量相同,则可能存在内存泄漏。

“内存使用情况”图提供了另一种确定内存泄漏的方法,但只适合小应用。灰线代表最大的内存使用,而黑线则代表当前的内存使用。如果黑线和灰线从不分离,则说明有内存泄漏,

1.5 用于性能查看的Flash的系统API

Flash中提供了一些系统级的类,可以帮助开发者获取Flash Player的一些信息,并且可以提供一定的控制能力,这就是flash.system包。

该包中的flash.system.System类提供控制内存剪切板(Clipboard ) ,摄像头、麦克风、共享对象的控制,通过该类的totalMemory属性则可以查看当前Flash Player所占用的内存。

该包中的flash.system.Capabilities类则提供了很多属性用于说明Flash Player的版本、操作系统版本和所具备的能力,比如hasPrinting可以告诉开发者当前系统是否支持打印功能。

在本章并不准备详细介绍这些API,学员可以在应用系统里面可以开发一个应用界面,用来显示这些系统的信息,通过特定的快捷键(比如Alt+Ctrl+M组合键)来调出这个应用界面,为维护者提供一些有用的参考信息。

1.6 其他有关内存泄漏的问题

知道Flash内存管理机制和内存泄漏的原因之后还要注意以下两类问题。

(1) Flex SDK本身的Bug导致的内存泄漏。这通常由于程序员在开发程序的时候会有各种疏忽或者代码不够严谨导致,Adobe的程序员也不例外,因此,Flex SDK中有些组件或类也存在一些内存泄漏问题。

常见的如下:

Ø 如果对组件应用了效果(Effect),则删除该组件时,需要把应用在该组件及其子组件上的效果停止,然后再把效果的targt属性设置为null,如果不停止效果而直接将其target属性设置为null将不能正常移除对象。

Ø Flex SDK 3.2中,从ListBase下派生出来的ListGri d组件都存在内存泄漏。因为Adobe程序员在ListBase组件的mouseDownHandler()方法中以“强引用”的方式向SystemManager添加了一个Mouse_up事件监听器,代码如下:

SystemManager.getSandboxRoot().addEventlistener(MouseEvent.MOUSE_UP,mouseUPHandler,true,0,true);

Adobe公司在Flex SDK 3.3中已解决了这个问题,因此读者发现内存泄漏后应该检查一下自己的Flex SDK版本,看看是否需要更新Flex SDk,可能在更高版本中会有所改进。

(2)和其他语言一样,尽管Flash Player有内存回收机制,但这不代表所有的资源都可以回收。由程序员使用的外部资源或者系统类,必须由程序员自己释放或清理。比如:

Ø 使用完BindingUtils.bindSetter(),ChangeWatcher.watch()函数后需要调用ChangeWatcher.unwatch()函数来清除引用。

Ø 使用ModuleLoader1oadModule()方法加载一个模块,在关闭这个模块后应当使用unLoadModule()方法将其卸载,并将该模块对象的引用置为null

Ø 在开发图形应用时,如果使用Graphics画图,要将不再显示的图使用clear()方法清除。

Ø Image对象使用完毕后要把source设置为null

Ø 当不需要一个音乐或视频时需要停止音乐、删除对象,将引用设置为null

1.7 有关提高F lex应用性能的技巧

http://www.insideria.com/2009/04/51-actionscript-30-and-flex-op.html中列出了一些可以改进Flex程序性能的技巧。感兴趣的学员可以查看原文及其相关讨论。

(1)不要使用new操作符创建数组,应使用:

var a =[];

而不是:

var a = new Array();

(2)创建数组的性能开销很大,请谨慎进行如下操作:

Var vanityCollection01 : Array = new Array();

Var vanityCollection02 : Array = new Array();

Var vanityCollection03 : Array = new Array();

Var vanityCollection04 : Array = new Array();

(3)最快的数组复制方法:

Var copy:Array = sourceArray.concat();

(4)为数组中的元素赋值都是较慢的操作,比如:

employees.push(employee);

employees.[2]=employee;

(5)从数组中读取元素的速度是为元素赋值速度的2倍。

var employee:Employee=employees[2];

(6)使用静态方法不需要实例化对象,可以提高性能(某些工具函数可以这样用全部都用静态方法则违反了面向对象的方法论)

(7)将应用生命周期内都不会改变的属性声明为常量。

public const APPLICATION_PUBLISHER:String = “Company,Inc.”;

(8)当确定一个类不会派生子类时,应使用final修饰符。

public final class StringUtils;

9)ActionScript3中,方法和变量的名称长度不会对性能造成影响(在其他语言中也一样)

someCreazyLongMethodNameDoesntReallyImpactPerformanceTooMuch();

(10)在一行代码中进行多个变量赋值不会提高性能(在其他语言中也一样)

var i=0;j=10;k=200;

(11)使用if语句和switch语句无内存开销的差异。

使用if语句:

 If(condition){

//handle condition

}

使用switch语句

switch(condition){

case ‘A’:

//logic to handle case A

Break;

case ‘B’:

//logic to handle case B

Break;

 

}

(12)使用if语句时,尽可能地按照最有可能发生的情况的顺序进行判断和处理。

(13) AVM在循环体内部进行计算时,将整型(int)数据提升为浮点型Number进行处理(从版本9到版本10,ActionScript虚拟机已经有所改变,int ,unit, number之间的转换速度不再像之前那么慢了)

(14)要解决类型转换,就要先解决未知和不正确的对象类型。

(15)谨慎使用unit,它可能会较慢(从版本9到版本10 ActionScript虚拟机已经有所改变int,   uint,  number之间的转换不再像之前那么慢了)

Var footerHex : unit=oxooccff;

(16)应在for循环中使用int:

for(var i: int =0;i<n;i++)

而不是Number:

for(var i: Number =0;i<n;i++)

(17)不要用int类型来表示小数,应使用:

var decimal:Number = 14.65;

而不是:

var decimal:int = 14.65;

(18)乘法性能高于除法:不要用5000/1000而要用5000*0.001

(19)如果一个值是通过for或者while语句循环多次计算出来的(或者其他耗费较高性能才能得到的值),而且这个值需要在局部多次使用,那么应当在本地将该值存储。而不是每次访问该值时都重新计算。

for(..){a*180/Math.PI;}

声明:toRadians = a*180/Math.PI; 在循环体外

(20)尽量避免在循环体判断条件中进行计算或者方法调用,应当使用

var len:int = myArray.length;

for(var i=0;i<len;i++){}

而不是:

vor(var i=0;i<myArray.length;i++){}

(21)使用正则表达式RegEx进行校验,使用字符串的方法进行查找。

(22)尽量重用对象来保持“内存平稳’,这些对象包括DisplayObjects,URLLoader等。

(23)遵循Flex组件模式(失效机制)

createChildren();

commitProperties();

updateDisplayList();

(24)把使用DataGrids组件作为最后的显示手段(如果确信真的没有办法使用常规的手段来实现想要的功能,才使用Datagrids

(25)避免将Repeaters用于能够滚动的数据.

(26)避免使用setStyle()方法(Flex框架中最消耗性能的方法之一)

(27)使用过多的容器将会严重降低系统性能。

<mx:Panel>

        <mx:VBox>

                <mx:HBox>

                    <mx:Label  text=Label 1/>

                          <mx:VBox>

                             <mx:Label  text=Label2/>

                          </mx:Vbox>

                               <mx:HBox>

                            <mx:Label  text=Label3/>

<mx:VBox>

   <mx:Label  text=Label4/>

</mx:Vbox>

</mx:HBox>

</mx:HBox>

</mx:Vbox>

</mx:Panel>

(28)定义组件时不一定总要使用一个容器来作为该组件的顶级标签,也不一定需要顶级容器标签。

(29)清除不必要的容器包装以减少容器的嵌套。

(30)避免在标签内嵌套VB ox容器(消除冗余)

(31)mx :A pplication标签内部尽量避免使用VBox标签(消除冗余)

(32)设置RepeaterrecycleChildren属性为true可以提高Repeater对象的性能(重新利用已创建的子组件而不是再创建一个新的子组件)

(33)应用的frameRate应设为60fps以内。

(34)避免在一帧中进行过多的显示操作。

(35)对于重复的操作,应使用Enter_frame事件替代Timer事件:

(36)在多帧中延迟对象的创建,使用:

<mx:Container creationPolicy=”queued”/>

(37)使组件不可见时使用Alpha=0visible=false不是一回事(对象被标记为不可见将不会被渲染与布局处理),应当使用:

loginButton.visible=false;

而不是:

loginButton.alpha=0;

 

通过本节的内容。应该可以了解到以一下内容:

Ø Flash  Player的内存垃圾回收机理。

Ø 如何在开发中规避内存泄漏。

Ø 如何通过工具来发现内存泄漏?

Ø 哪些系统级的API可以帮助我们在运行期间杳看系统内存的使用情况?

Ø 提高程序性能的编程方式和技巧。

1.8 Flex打印

Web打印的方案有很多,根据它们实现方式的不同,大致可以分为以下三类:

Ø 原生打印(Native Print),利用嵌入在浏览器中的AppletFlash Player等插件提供的打印接口进行打印。由子打印的全部过程都在插件中完成,所以称之为原生打印。

Ø 宿主打印(Host Print),使用浏览器提供的打印接口进行打印。我们把使用浏览器插件增强打印功能的打印方式也归为此类,因为这些插件仅仅用于弥补浏览器打印接口的不足之处。

Ø 外部打印(External Print) ,先根据打印格式生成目标文档,然后使用此文档的宿主程序打开并打印。

1.8.1 原生打印

这类打印方案的特点是直接使用SDK提供的API,具有响应迅速,跨平台跨浏览器,不依赖外部工具的优点。常见的有如下几种。

Ø JavaApplet:利用JDK提供的打印API,可以向打印机输出各种文字、图形图像和swing组件。

Ø Flex:利用Flex Framework提供的打印API,可以将FlexFlash可视化组件输出至打印机。

1.8.2 宿主打印

由于Web页面必须通过浏览器来呈现,而常见的基于图形的浏览器都提供打印功能,因此可以通过浏览器实现打印。常见的有如下几种。

HTML:利用浏览器提供的打印功能,直接将其呈现的HTML输出至打印机。可以使用层叠样式表来控制HTML元素在打印纸上的呈现方式。

ActiveX+HTML:使用JavaScript调用专用的ActiveX控件来打印HTML元素或页面。这个ActiveX控件可以利用Win32打印API和浏览器的开发接口将HTML输出至打印机。

1.8.3 外部打印

严格来说,这种方案不能叫做打印,它实际上是一种数据导出方案。它将应用的数据导出到外部工具,然后利用此工具来进行打印。常见的有如下几种。

Ø PDF打印:PDF格式是行业标准,通过PDF开发包(如基于JavaiText ,基于FlexAliverPDF)可以将应用的数据形成PDF文档,然后由Adobe Reader或其他工具负责打印。

Ø office打印:Microsoft  Office套件中的WordExcel使用广泛,而且许多其他Office软件都能兼容,因此可以将应用的数据导出为WordExcel文档,然后再打印。

外部打印又可以按照文档形成的时机分为服务器端打印和客户端打印。前者的打印过程一般是由客户端发出打印命令、服务器根据打印的数据生成目标文档,然后传送至客户端。客户端再使用宿主程序打开此文档并打印。与前者不同。后者的文档直接在客户端生成。上述方案各具优缺点,在进行选择时需要综合考虑项目、团队和用户等方面的因素。表8-1-1对上述方案进行了对比,可以作为选型时的参考。

8-1-1  Web打印常见方案及其比较

类别

方案

有点

缺点

原生打印

JavaApplet

能够通过程序选择打印机,设置纸张、页边距,打印方向和任务名称等打印任务参数;能够打印图片、文字Swing组件等、延迟生成打印内容,多页打印效率高      

依赖于JRE,因此可能需要客户端下载

FLex

能够打印Flex中所有的可视化组件; 延迟生成打印内容,多页打印效率高

不能够通过程序选择打印机。也不能设置纸张、页边距、打印方向等打印任务参数         

宿主打印

HTML

不需任何插件、能够打印HTML页面,可以使用CSS控制页面在打印时的呈现方式  

需要事先生成好所有打印内容,多页打印时可能使页面过大.共他缺点同上

ActiveX+HTML

一般能够通过程序选择打印机,能够设置纸张,页边距、打印方向等打印任务参数,能够打印HTML页面或HTML元素,可以使用CSS控制页面在打印时的呈现方式    

需要下载额外的插件;需要事先生成好所有的打印内容,多页打印时可能使HTML页面过大  

外部打印

PDF打印

Office打印

打印内容丰富,布局灵活,数据的导出非常容易    

需要安装相应的工具软件;同时也具有HTML方案的缺点  

1.8.4 为什么使用Flex来打印

尽管Flex无法通过程序选择打印机和设置打印参数,但是,如果选择了Flex技术作为系统的前端展现,那么PrintJob天生就能与系统的其他功能模块无缝集成。没有进程调用,没有数据转换,能够快速响应打印请求,无需引入额外技术或语言,实现单一技术的易维护性,并且也具有Flex跨主流平台和浏览器的优势。

由于Flex打印的是可视化组件,所以无论多么复杂,只要界面上能展示,那么Flex就能打印出来。依托于Flex布局容器的自动布局功能,可以实现像屏幕显示那样动态地调整元素的布局。

因此,如果不想在项目中引入额外的技术或工具,并且又能说服客户接受Flex打印的缺点,那么可以考虑使用这种“便宜”而且实用的方案。

1.8.5 使用PrintJob打印

如果你开发过Win32打印程序,你一定会对打印心有余悸。不过别担心,现在我们有了PrintJob。相信你第一眼看到它一定会对它的简洁感到吃惊。

首先,让我们通过一段简单的代码来看看PrintJob是如何工作的,如示例9.9所示。

示例9.9典型的PrintJob打印过程

var job = new PrintJob();//创建一个PrintJob的实例

if(job.start()){//启动PrintJob

job.addPage(Application.application);//将整个application作为一页添加到打印内容

job.send();//发送数据

下面,让我们来分析整个打印过程。

Ø Start():调用这个方法后,Flash Player将会为我们查找系统的默认打印机,并且使用此打印机的默认设置显示打印对话框。如果用户选择取消,那么方法返false,此时如果继续调用addPagesend方法,将会引发F1ash Player异常。如果用户选择打印,那么方法返回true,并且用户通过打印对话框进行的设置将会被保存在PrintJob的这个实例中。

PrintJob5个属性,分别记录打印方向、纸张大小和可打印区域,其中纸张大小和可打印区域的单位都已映射为像素,这是由Flash Player根据打印机的默认分辨率完成的。

Ø addPage():这个方法告诉PrintJob开始新的打印页,并且在此页上绘制指定的Sprite实例。在一个PrintJob中可以通过多次调用addPage实现多页打印。这个方法是打印过程中最重要和最复杂的,稍后将详细介绍。

Ø send():将数据发送至操作系统的打印任务池并结束这个PrintJob

除了打印内容的生成外,这几乎是打印功能所需要的所有代码了,当然,你还可以添加异常处理代码以增强程序的健壮性。

1.8.6 深入了解PrintJob

从上一节的分析可以看出,打印前的谁备工作很繁琐,幸运的是,F1ash Playr提供的PrintJob. Start()方法为我们完成了这部分工作。

既然知道了用户选择的纸张和打印方向,那么就可以使用PrintJob.addPage()把打印纸映射成屏幕区域,然后以一种我们熟悉的、与屏幕显示类似的方式向打印机输出内容了。

PrintJob.addPage()4个参数,下面是它的方法签名:

addPage(sprite:Sprite,printArea:Rectangle=null,options:PrintJobOptions=null,frameNum:int=0):void

只有第1个参数是必需的,其他参数可以忽略。

(1) sprite    

这里必须是一个flash.display.Sprite的实例,它是要打印的根组件。我们知道,Flex中的所有显示组件都是从sprite派生而来,因此这些组件都可以安全地传递给addPage()。但是,要想正确地打印,这个sprite须位于显示列表中。与屏幕显示不同的是,打印时会忽略这个sprit。的visible属性,也就是说,打印内容不必在屏幕上可见。这个例外仅局限于打印的根组件,如果这个sprite包含一个mx.controls.Label且其visible=false,那么打印时也是不可见的,这是Flex考虑得比较周到的地方。示例9.10展示的小例子说明了这个特性,请注意组件的visibile属性。

示例9.10 组件在打印时的可见性演示

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

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

layout="vertical" applicationComplete="doPrint();" >

<mx:Script>

<![CDATA[

import com.flexbook.blazeds.DataRow;

private function doPrint():void{

var job:PrintJob=new PrintJob();

if(job.start()){

job.addPage(printContent);

job.send();

}

}

]]>

</mx:Script>

<!--设置背景色和边框以证明VBox确实输出至打印纸了-->

<mx:VBox id="printContent" visible="false" borderStyle="outset" backgroundColor="0xA1CE78" >

<mx:Label text="第一个Label可见"/>

<mx:Label text="第二个Label可见" visible="false"/><!--不会被打印-->

<mx:Label text="第三个Label可见"/>

</mx:VBox>

</mx:Application>

编译并运行示例9.10,程序将在应用启动后打印出如图9.10所示的画面。由此可见printContent作为打印的根组件,它的visible属性不影响打印。但printContent的内部的三个三个Lablevisible属性对可见性的影响仍然遵循“与屏幕显示一致“的规则:visibletrue时可见,否则不可见。

9.10是使用虚拟打印机(pdfFactory)打印出来的结果,因此可能会与物理打印机打印出的实际效果有所不同。

 

9-10 printContent的打印结果

 

(2) printArea

打印范围,它与前述PrintJob保存的打印机的可打印区域不同,它用于指定第一个参数sprite的需要输出的区域范围,而后者表示的是打印机受到物理或机械限制在特定纸张中的打印能力。sprite中不在此区域范围内的内容不会被输出。此区域的左上角坐标被映射成打印机可打印区域的左上角坐标,如果此区域表示的打印的范围超过了打印机的可打印区域,右下方的内容将会被裁剪掉。假设我们想将左图窗口中灰色区域部分打印到A4纸上,那么打印的根对象就是整个左图窗口,灰色区域表示printArea指定的打印范围,右图的外部实线框表示纸张,内部实线框表示打印机的可打印区域,最终我们指定的打印区域会被打印到右图纸张的灰色区域部位。掌握了这一点,我们就可以精确地在纸张的任意位置上打印内容了。

(3)options

指示打印输出位图还是矢量图,如果打印内容不含位图图片,那么使用矢量图可以获得更

高的打印质量。

(4)frameNum

用于指定要打印的帧的序号。一般的Flex应用只有两帧,所以不用指定此参数,默认是打

印当前帧。

小结

Flex打印中 应该注意一下事项:

Ø PrintJobstart方法是阻塞的,也就是说,在它返回之前,Flash Player会阻塞ActionScript代码的执行。

Ø start,addPagesend三个方法调用间隔不能大于1秒,否则会出现脚本超时异常,这在Adobe的文档中有详细说明。

Ø PrintJob. pageHeightPrintJob. pageWidth记录的是打印机的可打印区域,并未刨除用户设置的页边距,这样我们就可以在程序中控制输出的页边距。

Ø 在将从UIComponent继承而来的显示组件传递给addPage前,必须保证组件已经在显示列表中并被有效化(validated)。这可以通过调用UIomponent.validateNowFlex立即更新组件,这在动态打印中非常重要。

Ø mx.printing.FlexPrintJob也是Flex提供的打印API ,它实际上封装PrintJob并加入了对UIComponent的支持,在打印前后会分别调用UIComponentprepareToPrintfinishPrint方法,同时也加入了简单的分页功能。

Ø Flex提供了mx.printing.PrntDataGrid,可用于打印简单的表格。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

任务实训部分 

实训任务1:使用RSI技术减少SWF文件体积

训练技能点

RSI的应用。

需求说明

使用RSI技术减少SWF文件体积

实现思路

(1)创建MXML应用程序。

(2)分别设置工程的框架链接类型为 静态 和动态 并观察生成的SWF文件体积的变化。

设置rpc.swc  datavisualization.swc库的摘要,并观察SWF文件体积的变化。

实训任务2:检测内存泄露

训练技能点

Ø 内存泄露分析工具的使用。

Ø 优化程序的内存使用。

实现思路:

(1)导入第六章上机任务3

(2)启动Flash内存泄露检测工具,分析系统的内存使用情况 找出存在的问题。

(3)结合理论内容8.6  8.7小节的内容 修改程序代码,优化内存使用,防止内存泄露。

 

 

 

 

 

 

 

 

 

 

 

 

巩固练习

选择题

1.  关于减少SWF文件的体积 ,错误的说法是 ()

A.  可以使用动态链接降低SWF体积。

B.  可以使用静态链接降低SWF体积。

C.  可以使用RSL技术降低SWF体积。

D.  通过垃圾回收可以降低SWF体积。

2.  以下关于Flex程序垃圾回收的说法,正确的是 ()

A.  ActionScript语言是一种支持GC的语言。经过编译后的AS代码运行在AS虚拟机(简称AVM)中,由AVM自动完成垃圾内存回收的工作。不会发生内存泄露。

B.  删除变量就意味着释放掉对象占用的内存。

C.  AS根据引用计数法就可以精确的判断对象是否需要释放。

D.  垃圾回收总是在请求内存时触发,而不是在对象删除时发生。

3.  以下哪些操作可能引发内存泄露()

A.  使用对象绑定。

B.  使用addEventListener()方法“为对象添加事件监听器”。

C.  用弱引用方式注册监听器。

D.  自引用的方式注册监听器。

4.  Flex打印分为多种方式 分别是()

A.  原生打印。

B.  本地打印。

C.  宿主打印。

D.  外部打印。


 

第2章 复习指导

2.1 知识总结

2.1.1  阶段核心技能目标

本阶段课程要掌握如下技能和知识:

Ø 理解RIAFlex的的概念和特点

Ø 熟练掌握AS基本语法

Ø 会熟练使用Flex常用控件和视图状态构建程序界面

Ø 会熟练使用FLex技术与外部进行数据通信,并整合后台各种框架

Ø 会熟练使用客户端MVC框架

Ø 会熟练使用AIR技术开发应用

Ø 熟练掌握美化Flex界面以及性能优化的基本原理和技巧

 

2.1.2  知识体系总结

 

图10.1.1 Flex知识结构图

 

2.2 综合实训

2.2.1 任务描述

本次任务实现一个Flv格式的视频播放器,能够播放flv格式的视频文件,并具有最大化 最小化 开始 暂停 快捷键和系统托盘等功能,使用AIR结合cairngorm框架进行开发。

2.2.2 系统功能

视频播放器的设计应该说是非常直观的。视频播放功能的实现其实并不复杂,前面我们已经学习过视频播放必须具备的某些核心功能(如Flexflv播放器),现在马上就可以开始播放器的开发工作了,其实也就是将Flexflv播放器集成到我们的程序界面中,在添加一些常用功能就可以了,但如果想充分运用AIR的功能来创建视频播放程序,那么还需要投入更多的精力进行规划设计。

我们的视频播放器具有AIR Video player的所有核心功能,这些功能可以大致分为两类,一类是视频播放,一类是程序控制。这些功能如下所示。

Ø 播放控制:所有的视频播放器都至少具有一个play(播放)按钮和一个停止按钮。Play按钮可以在暂停和播放两个状态之间切换,并相应的改变按钮的外观。除了Play Stop控件外,还应该具有一个能让你跳到视频任何部分的时间轴滑轨,一个只是播放进度的时间显示控件和音量控制器。

Ø 易于调整的布局:AIR视频视频播放器的一大特征就是可以实现视频的全屏播放。为了实现该特征,我们需要创建一个能随着视频尺寸变化而调整的布局。例如,如果视频的尺寸由300*250变为640*480或者全屏,那么播放控件要能根据调整后的尺寸显示视频。

Ø 网络摄像头抓图功能:这一功能并不是必须的,但给视频播放器添加这一功能是简单和有趣的事情。这部分工作将会留给学员扩展完成。

在这个视频播放器的主要控件完成后,需要在主容器(<mx:WindowedApplication>)中提供以下这些功能:

Ø 全屏支持:Flash播放器支持全屏播放模式。这一特色对于像AIR视频播放器这样的应用程序来说非常有用,当让也很容易做到。该功能将使用户的电脑变成一个虚拟的影院。

Ø 个性化的主窗口,这也是AIR程序的一大特色,可以随意定制程序主窗口的形状,达到更好的效果,以及窗口的拖拽功能。

Ø 支持快捷键播放 暂停。

Ø 主窗口可以最小化到系统托盘。

Ø 在网络环境下,还可以在程序窗口中打开迅雷看看高清电影的主页,以实现在线播放的功能。

2.2.3 实现步骤

1)构建目录结构

建立新的AIR工程并导入Cairngorm框架,按照MVC模式在工程中建立目录,目录结构如图10.1.1所示。

 

10.1.1 目录结构

目录建好之后将程序所需要的各种资源(图片)放入assets/images目录下备用。下面就可以开始进入视频播放器的开发环节了。

2)建立Cairngorm框架组件

本任务中并没有严格的按照Cairngorm的模式来实现功能,只是在播放功能中部分使用了Cairngorm的模式。学员们可以在完成主要功能后再严格按照Cairngorm结构重构系统。

 

OpenVideoEvent.as 该事件将会在选择要播放的视频文件后被派发。

package com.xi.flvPlayer.event

{

import com.adobe.cairngorm.control.*;

import com.xi.flvPlayer.control.*;

public class OpenVideoEvent extends CairngormEvent

{

public function OpenVideoEvent()

{

super(FlvControl.EVENT_OPEN_VIDEO);

}

}

}

 

OpenVideoCommand.as  用来处理OpenVideoEvent事件的命令。

package com.xi.flvPlayer.commands

{

import com.adobe.cairngorm.control.*;

import com.xi.flvPlayer.business.*;

import com.xi.flvPlayer.model.FlvModel;

import com.adobe.cairngorm.commands.*;

import com.adobe.cairngorm.view.*;

import com.xi.flvPlayer.control.FlvControl;

import com.xi.flvPlayer.view.*;

    import flash.events.*;

    import flash.net.*;

    import com.xi.flvPlayer.event.*;

public class OpenVideoCommand implements Command

{

public function execute(event:CairngormEvent):void

{

//定义FlvPlayerViewHelper实例

var fpViewHelper:FlvPlayerViewHelper=FlvPlayerViewHelper(ViewLocator.getInstance().getViewHelper("flvPlayerViewHelper"));

         //不相同的视频或从未点击过的视频,需要加载

         if(FlvModel.currVideo==""||FlvModel.currVideo!=FlvModel.videoSource)

         {   

           fpViewHelper.loadMediaPlay(FlvModel.videoSource);

           FlvModel.currVideo=FlvModel.videoSource;

            }

            else//相同的视频则重新开始播放

            {

             fpViewHelper.restartVideo();

            }

}

}

}

 

FlvModel.as 数据模型。

package com.xi.flvPlayer.model

{

import com.adobe.cairngorm.model.ModelLocator;

//import com.asiatom.englishr.vo.PlayListVO;

    import mx.controls.ToolTip;

public class FlvModel implements ModelLocator

{

private static var flvModel : FlvModel = new FlvModel();

public static function getInstance() : FlvModel {

return flvModel;

}

 

public function initialise() : void

{

}

// 当前播放视频地址

public static var currVideo : String="";

//将要加载的视频地址

public static var videoSource:String="";

//是否为全屏状态

public static var isFullSrceen:Boolean=false;

        //---------- 资源定义 ---------------------------------------------------------------

[Bindable]

[Embed(source="/assets/images/btn_moveover_break.png")]

     public var btn_moveover_break:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_moveover_fullscreen.png")]

     public var btn_moveover_fullscreen:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_moveover_play.png")]

     public var btn_moveover_play:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_moveover_stop.png")]

     public var btn_moveover_stop:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_break.png")]

     public var btn_normal_break:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_fullscreen.png")]

     public var btn_normal_fullscreen:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_play.png")]

     public var btn_normal_play:Class;

    

     [Bindable]

     [Embed(source="/assets/images/btn_normal_stop.png")]

     public var btn_normal_stop:Class;

    

     [Bindable]

     [Embed(source="/assets/images/ico_break.png")]

     public var ico_break:Class;

}

}

 

FlvPlayerViewHelper.as 辅助操作视图类。使用ViewHelper类可在AS文件中修改其他MXML文件中的视图。

package com.xi.flvPlayer.view

{

import com.adobe.cairngorm.view.ViewHelper;

import mx.core.Application;

 

public class FlvPlayerViewHelper extends ViewHelper

{

public function FlvPlayerViewHelper()//构造函数

{

super();

}

//加载视频处理函数

public function loadMediaPlay(src:String):void

{

view.mainDisplay.source=src;

view.mainDisplay.load();

view.mainDisplay.play();

}

//重新播放视频处理函数

public function restartVideo():void

{

view.mainDisplay.playheadTime=0;

}

}

}

 

FlvControl.as 前端控制器

package com.xi.flvPlayer.control{

import com.adobe.cairngorm.control.FrontController;

import com.xi.flvPlayer.commands.*;

  

public class FlvControl extends FrontController {

public function FlvControl() : void

{

initialiseCommands();

}

public function initialiseCommands() : void

{

   addCommand( FlvControl.EVENT_OPEN_VIDEO, OpenVideoCommand );  

}

    //------------------- 打开视频 ---------------------------------//

    public static var EVENT_OPEN_VIDEO:String="openVideo";

}

}

 

Utils.as 工具类 主要用来格式化时间和字符串。

package com.xi.flvPlayer.utils

{

//import com.adobe.cairngorm.view.ViewLocator;

//import com.xi.flvPlayer.control.FlvControl;

//import com.xi.flvPlayer.model.FlvModel;

//import com.adobe.cairngorm.control.CairngormEventDispatcher;

public  class Utils

{

public static function formatVideoTime(t : Number) : String//格式化时间处理函数

{

var s : Number = Math.floor(t);//取整数

var hour : Number = int(s/3600);//取小时

var minute : Number = int((s-hour*3600)/60);//取分钟

var second : Number = s-hour*3600-minute*60;//取秒

var p : Number = Math.round( ( t - s ) * 1000 );//取毫秒

//转特定格式“hh:mm:ss xxx

return padStr(hour.toString(), 2) + ":" + padStr(minute.toString(), 2) + ":" + padStr(second.toString(), 2) + " " + padStr(p.toString(), 3);

}

public static function padStr(src : String, len : int ) : String//添加前导零

{

if ( src.length > len )//若字符串长度超过指定长度,则截断字符串

return src.substr(0, len);

var s : String = src;

for ( var i : int = s.length;  s.length < len; )//添加前导零

{

s = "0" + s;

}

return s;

}

}

}

 

(3)开发主界面

首先建立主程序Player.MXML,然后修改Player-app.xml,在第50行 添加以下代码。

 <systemChrome>none</systemChrome>

 

 <transparent>false</transparent>

用来取消主窗口的标题栏和边框。

主程序代码如下。

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

<mx:WindowedApplication

xmlns:mx="http://www.adobe.com/2006/mxml"

xmlns:view="com.xi.flvPlayer.view.*"

xmlns:control="com.xi.flvPlayer.control.*"

layout="absolute"

backgroundColor="#ccffff"

minWidth="587"

minHeight="439"

showFlexChrome="false"

width="587" height="439" xmlns:s="library://ns.adobe.com/flex/spark">

<mx:Script>

<![CDATA[

import com.xi.flvPlayer.model.FlvModel;

import com.xi.flvPlayer.utils.Utils;

//拖动条显示提示处理函数

private function SliderToolTipFormatter(val : Number) : String

{

return Utils.formatVideoTime(val);//格式化为时间格式“00:00:000 000

}

]]>

</mx:Script>

 

<!--添加FlvPayer.mxml的视图定位器“FlvPlayerViewHelper-->

<view:FlvPlayerViewHelper id="flvPlayerViewHelper"/>

<!--添加前台控制器“FlvControl-->

<control:FlvControl id="flvControl"/>

<mx:VBox  verticalGap="0" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<!--添加菜单组件-->

<mx:ApplicationControlBar  id="me" height="25" width="586">

<mx:Button id="closebut"  width="13"  height="13"    toolTip="关闭"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="openbut"  width="13"  height="13"    toolTip="打开影片"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="movebut"   width="13"  height="13"    toolTip="片库"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="minbut" width="13"  height="13"    toolTip="最小化"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

</mx:ApplicationControlBar>

<mx:Canvas  id="canvas1">

<!--添加“VideoDisplay”组件播放FLV-->

<mx:VideoDisplay id="mainDisplay"

 autoRewind="false"

 volume=" {videoVolume.value / 100 } "

 playheadUpdateInterval="100"

 doubleClickEnabled="true"

 width="586"

 height="330"/>

<mx:Image id="img_break" source="{FlvModel.getInstance().ico_break}"

  width="{WindowedApplication(mx.core.Application.application).width*(60/624)}"

  height="{img_break.width}"

  alpha="0.7"

  x="{(mainDisplay.width-img_break.width)/2}"

  y="{(mainDisplay.height-img_break.height)/2}"

  visible="false"/>

</mx:Canvas>

<mx:VBox id="controlPanel" width="586" height="81">

<mx:HBox width="582">

<mx:Label id="videoState" text="Ready"/>

<mx:Spacer width="100%"/>

<mx:Label id="videoCurrTime" text="00:00:00 000"/>

<mx:Label text="/" width="10"/>

<mx:Label id="videoTotalTime" text="00:00:00 000"/>

</mx:HBox>

<!--添加视频拖动条-->

<mx:HSlider id="videoSlider"

liveDragging="true" showDataTip="true"

dataTipFormatFunction="SliderToolTipFormatter"

width="100%"

allowTrackClick="true"

value="{mainDisplay.playheadTime}"

enabled="false"/>

<!--添加视频控件按钮-->

<mx:HBox width="100%" paddingLeft="3" paddingRight="3" verticalAlign="middle" paddingBottom="1" paddingTop="1">

<mx:Spacer width="5"/>

<mx:Button id="play"

   width="35" height="35"

   icon="{FlvModel.getInstance().btn_normal_play}"  cornerRadius="25" enabled="false" toolTip="播放/暂停" />

<mx:Button id="btnStop"  cornerRadius="25"

   width="19" height="19" icon="{FlvModel.getInstance().btn_normal_stop}" overIcon="{FlvModel.getInstance().btn_moveover_stop}"  enabled="false" toolTip="停止" />

<mx:Spacer width="100%"/>

<mx:HBox horizontalGap="0" verticalAlign="middle" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<mx:Button id="btnQueit" width="13"  height="13"  paddingLeft="0" paddingRight="0" enabled="false" toolTip="静音" cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:HSlider id="videoVolume" width="70" height="16"

liveDragging="true" value="50"

enabled="false"

maximum="100" tickInterval="50" showDataTip="false"/>

<mx:Button id="btnFullScreen"

   icon="{FlvModel.getInstance().btn_normal_fullscreen}"

   overIcon="{FlvModel.getInstance().btn_moveover_fullscreen}"

   width="35" height="35"

   cornerRadius="25"

   />

</mx:HBox>

</mx:HBox>

</mx:VBox>

</mx:VBox>

</mx:WindowedApplication>

运行程序,效果如图10.1.2所示。

 

图10.1.2 播放器界面

 

(4)完成基本播放功能

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

<mx:WindowedApplication

xmlns:mx="http://www.adobe.com/2006/mxml"

xmlns:view="com.xi.flvPlayer.view.*"

xmlns:control="com.xi.flvPlayer.control.*"

layout="absolute"

backgroundColor="#ccffff"

minWidth="587"

minHeight="439"

showFlexChrome="false"

applicationComplete="initApp()"

width="587" height="439" xmlns:s="library://ns.adobe.com/flex/spark">

<mx:Script>

<![CDATA[

import com.adobe.cairngorm.control.*;

import com.xi.flvPlayer.commands.*;

import com.xi.flvPlayer.control.*;

import com.xi.flvPlayer.event.*;

import com.xi.flvPlayer.model.FlvModel;

import com.xi.flvPlayer.utils.*;

import flash.filesystem.File;

import mx.controls.Alert;

import mx.core.Application;

import mx.events.CloseEvent;

import mx.events.MenuEvent;

import mx.events.SliderEvent;

//定义FileFilter,只允许.flv格式

private var displayTypes:FileFilter = new FileFilter("播放格式(*.flv)", "*.flv");

private var allTypes:FileFilter=new FileFilter("全部(*.*)","*.*");

private var fileFilter:Array = new Array(displayTypes, allTypes);

//定义File实例,用以存储打开的文件

private var choosedFile:File = new File();

//应用程序初始化处理函数

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

}

/**        

 

 * 用户决定窗口是隐藏还是关闭

 

 * @Author: S.Radovanovic

 

 */

private function closingApplication(evt:Event):void {

// 以下语句防止默认的关闭动作发生

evt.preventDefault();

// 弹出窗口,让用户判断是否要关闭窗口

//Alert.buttonWidth = 110;

Alert.yesLabel = "关闭";

Alert.noLabel = "最小化";

Alert.show("关闭还是最小化?", "关闭?", 3, this, alertCloseHandler);

}

// 响应弹出窗口中用户的选择

private function alertCloseHandler(event:CloseEvent):void {

closeApp(event);

}

/**

 

 * 关闭程序

 

 */

private function closeApp(evt:Event):void {

stage.nativeWindow.close();

}

protected function closebut_clickHandler(event:MouseEvent):void

{

WindowedApplication(mx.core.Application.application).close();//退出应用程序

}

protected function openbut_clickHandler(event:MouseEvent):void

{

choosedFile.browse(fileFilter);//打开选择对话框

choosedFile.addEventListener(Event.SELECT,selectHandle);//添加选择文件后的监听

}

//选择文件后的处理函数

private function selectHandle(e:Event):void

{

FlvModel.videoSource="file:///"+choosedFile.nativePath;//获取视视频路径

//加载视频

var evt:OpenVideoEvent=new OpenVideoEvent();

CairngormEventDispatcher.getInstance().dispatchEvent(evt);

}

//拖动条放开事件处理函数

private function thumbReleased(event:SliderEvent):void

{

mainDisplay.pause();//暂停播放

mainDisplay.playheadTime=videoSlider.value;//设置时间点

mainDisplay.play();//继续播放

}

//开始/暂停事件处理函数

public function toggle() : void

{

if (mainDisplay.playing)//当前状态正在播放时,暂停视频

{

play.setStyle("icon",FlvModel.getInstance().btn_normal_break);//设置“play”按钮的icon样式

play.setStyle("overIcon",FlvModel.getInstance().btn_moveover_break);//设置“play”按钮的overIcon样式

img_break.visible=true;//显示“暂停”图标

mainDisplay.pause();//暂停视频    

}

else//当前状态是暂停,播放视频

{

play.setStyle("icon",FlvModel.getInstance().btn_normal_play);//设置“play”按钮的icon样式

play.setStyle("overIcon",FlvModel.getInstance().btn_moveover_play);//设置“play”按钮的overIcon样式

img_break.visible=false;//隐藏“暂停”图标

mainDisplay.play();//播放视频

}

}

//视频停止播放事件处理函数

public function stopPlay() : void

{

mainDisplay.playheadTime=0;//设置时间点为0

mainDisplay.stop();//停止播放    

}

//“静音”处理函数

private function LowerVolume():void

{

videoVolume.value = 0;//设置音量最小

}

//拖动条显示提示处理函数

private function SliderToolTipFormatter(val : Number) : String

{

return Utils.formatVideoTime(val);//格式化为时间格式“00:00:000 000

}

//拖动条按下事件处理函数

private function thumbPressed(event:SliderEvent):void

{

if ( mainDisplay.playing )//正在播放,则暂停播放

{

mainDisplay.pause();

}

}

//playheadTime定时更新事件 处理函数

private function videoPlayheadTimeChanged() : void

{

if(Math.abs(mainDisplay.playheadTime-videoSlider.maximum)<0.1)//拖曳滑动条到结束位置时,重置时间

{

mainDisplay.playheadTime=0;//设置时间点为0

mainDisplay.pause();//暂停播放

}

else

{  

videoCurrTime.text = Utils.formatVideoTime(mainDisplay.playheadTime);//修改当前播放时间

}

}

//视频状态改变事件处理函数

private function videoStateChange() : void

{

videoState.text = mainDisplay.state;//显示当前视频状态

}

//视频准备完毕事件处理函数

private function videoReady() : void

{

WindowedApplication(mx.core.Application.application).width=mainDisplay.videoWidth;//设置应用程序宽度为实际视频宽度

WindowedApplication(mx.core.Application.application).height=mainDisplay.videoHeight*(458/352);//设置应用程序高度

videoTotalTime.text = Utils.formatVideoTime(mainDisplay.totalTime);//显示总时间

videoSlider.maximum =mainDisplay.totalTime;//设置拖动条数值长度

enableAllCtrl(true);//设置播放组件状态为可用

}

//播放控件enable设置处理函数

public function enableAllCtrl(b : Boolean) : void

{

videoSlider.enabled = b;

play.enabled = b;

videoVolume.enabled = b;

btnStop.enabled=b;

btnQueit.enabled=b;

}

]]>

</mx:Script>

<!--添加FlvPayer.mxml的视图定位器“FlvPlayerViewHelper-->

<view:FlvPlayerViewHelper id="flvPlayerViewHelper"/>

<!--添加前台控制器“FlvControl-->

<control:FlvControl id="flvControl"/>

<mx:VBox  verticalGap="0" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<!--添加菜单组件-->

<mx:ApplicationControlBar  id="me" height="25" width="586">

<mx:Button id="closebut" click="closebut_clickHandler(event)" width="13"  height="13"    toolTip="关闭"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="openbut" click="openbut_clickHandler(event)" width="13"  height="13"    toolTip="打开影片"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="movebut"   width="13"  height="13"    toolTip="片库"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:Button id="minbut" width="13"  height="13"    toolTip="最小化"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

</mx:ApplicationControlBar>

<mx:Canvas  id="canvas1">

<!--添加“VideoDisplay”组件播放FLV-->

<mx:VideoDisplay id="mainDisplay"

 stateChange="videoStateChange()"

 autoRewind="false"

 ready="videoReady()"

 volume=" {videoVolume.value / 100 } "

 playheadUpdate="videoPlayheadTimeChanged()"

 playheadUpdateInterval="100"

 doubleClickEnabled="true"

 width="586"

 height="330"/>

<mx:Image id="img_break" source="{FlvModel.getInstance().ico_break}"

  width="{WindowedApplication(mx.core.Application.application).width*(60/624)}"

  height="{img_break.width}"

  alpha="0.7"

  x="{(mainDisplay.width-img_break.width)/2}"

  y="{(mainDisplay.height-img_break.height)/2}"

  visible="false"/>

</mx:Canvas>

<mx:VBox id="controlPanel" width="586" height="81">

<mx:HBox width="582">

<mx:Label id="videoState" text="Ready"/>

<mx:Spacer width="100%"/>

<mx:Label id="videoCurrTime" text="00:00:00 000"/>

<mx:Label text="/" width="10"/>

<mx:Label id="videoTotalTime" text="00:00:00 000"/>

</mx:HBox>

<!--添加视频拖动条-->

<mx:HSlider id="videoSlider"

liveDragging="true" showDataTip="true"

thumbRelease="thumbReleased(event);"

thumbPress="thumbPressed(event);"

dataTipFormatFunction="SliderToolTipFormatter"

width="100%"

allowTrackClick="true"

value="{mainDisplay.playheadTime}"

enabled="false"/>

<!--添加视频控件按钮-->

<mx:HBox width="100%" paddingLeft="3" paddingRight="3" verticalAlign="middle" paddingBottom="1" paddingTop="1">

<mx:Spacer width="5"/>

<mx:Button id="play"

   width="35" height="35"

   icon="{FlvModel.getInstance().btn_normal_play}" click="toggle();" cornerRadius="25" enabled="false" toolTip="播放/暂停" />

<mx:Button id="btnStop"  cornerRadius="25"

   width="19" height="19" icon="{FlvModel.getInstance().btn_normal_stop}" overIcon="{FlvModel.getInstance().btn_moveover_stop}" click="stopPlay();" enabled="false" toolTip="停止" />

<mx:Spacer width="100%"/>

<mx:HBox horizontalGap="0" verticalAlign="middle" verticalScrollPolicy="off" horizontalScrollPolicy="off">

<mx:Button id="btnQueit" width="13" click="LowerVolume();" height="13"  paddingLeft="0" paddingRight="0" enabled="false" toolTip="静音" cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

<mx:HSlider id="videoVolume" width="70" height="16"

liveDragging="true" value="50"

enabled="false"

maximum="100" tickInterval="50" showDataTip="false"/>

<mx:Button id="btnFullScreen"  

   icon="{FlvModel.getInstance().btn_normal_fullscreen}"

   overIcon="{FlvModel.getInstance().btn_moveover_fullscreen}"

   width="35" height="35"

   cornerRadius="25"

   />

</mx:HBox>

</mx:HBox>

</mx:VBox>

</mx:VBox>

</mx:WindowedApplication>

运行程序 效果如图10.1.3所示。

 

10.1.3 播放器基本功能

 

(5)添加窗口移动功能

由于主窗口取消了边框和标题栏,所以窗口现在无法使用鼠标拖动,现在我们需要在程序的<mx:ApplicationControlBar>上添加move事件,在事件处理方法中添加窗口移动代码即可。

 

protected function me_mouseDownHandler(event:MouseEvent):void

{

this.nativeWindow.startMove();

}

//省略部分代码

 

<mx:ApplicationControlBar mouseDown="me_mouseDownHandler(event)"  id="me" height="25" width="586">

 

 

 

(6)添加全屏和退出全屏功能

通过设置舞台(stage)的displayState属性设置为 StageDisplayState.NORMAL(非全屏) StageDisplayState.FULL_SCREEN(全屏)即可将Flash播放器改为全屏播放模式或者退出全屏。

在主程序中添加如下代码。

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

//为舞台添加监听器 ,在全屏和退出全屏的时候调用fullscreenresize方法 来调整控件大小,这主要是为了解决在全屏状态下按下ESC退出全屏带来的布局混乱问题。

stage.addEventListener(FullScreenEvent.FULL_SCREEN,fullScreenreSize);

}

protected function fullScreenHandler():void

{

if(FlvModel.currVideo!="")//不在播放视频时不能全屏

{

if(stage.displayState==StageDisplayState.NORMAL)//当前是非全屏状态,则进入全屏状态

{

stage.displayState=StageDisplayState.FULL_SCREEN;//进入应用程序全屏

}

else //当前是全屏状态,则非全屏

{

stage.displayState=StageDisplayState.NORMAL;//应用程序非全屏

}

}

}

//当发生全屏或者退出全屏的事件 则调整控件大小以适应舞台

public function fullScreenreSize(event:Event):void

{

if( stage.displayState==StageDisplayState.NORMAL)//如果退出应用程序全屏则恢复 控件的原始大小

{ this.mainDisplay.width=586;

this.mainDisplay.height=330;

this.me.width=586;

this.controlPanel.width=586;

}

else {//如果进入全屏 则放大控件

this.mainDisplay.width=this.width;

this.me.width=this.width;

this.controlPanel.width=this.width;

this.mainDisplay.height=this.height-this.controlPanel.height-this.me.height;

}

}

//省略部分代码

 

<!--添加“VideoDisplay”组件播放FLV-->

<mx:VideoDisplay id="mainDisplay"

 stateChange="videoStateChange()"

 autoRewind="false"

 ready="videoReady()"

 volume=" {videoVolume.value / 100 } "

 playheadUpdate="videoPlayheadTimeChanged()"

 playheadUpdateInterval="100"

 doubleClickEnabled="true"

doubleClick="fullScreenHandler()"

 width="586"

 height="330"/>

//省略部分代码

 

<mx:Button id="btnFullScreen"  click="fullScreenHandler()"

   icon="{FlvModel.getInstance().btn_normal_fullscreen}"

   overIcon="{FlvModel.getInstance().btn_moveover_fullscreen}"

   width="35" height="35"

   cornerRadius="25"

   />

 

 

(7)添加快捷键

所谓快捷键 ,其实就是在主窗口监听器keydown事件,当特定键按下的时候,执行指定操作即可。

在主程序中添加以下代码。

//应用程序初始化处理函数

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

//为舞台添加监听器 ,在全屏和退出全屏的时候调用fullscreenresize方法 来调整控件大小,这主要是为了解决在全屏状态下按下ESC退出全屏带来的布局混乱问题。

stage.addEventListener(FullScreenEvent.FULL_SCREEN,fullScreenreSize);

//添加快捷键的监听

WindowedApplication(mx.core.Application.application).addEventListener(KeyboardEvent.KEY_DOWN,KeyboardClickHandle);

//添加定时器

var timer:Timer=new Timer(100,0);

timer.addEventListener(TimerEvent.TIMER,checkFocusHandle);

timer.start();    

}

//定时设置focus

public function checkFocusHandle(e:TimerEvent):void

{

WindowedApplication(mx.core.Application.application).setFocus();

}

//快捷键处理函数        

public function KeyboardClickHandle(e:KeyboardEvent):void

{

switch(e.keyCode)

{

case 32:  //空格键按下,则暂停or播放

toggle();

break;

case 83://按下“S”时停止播放

stopPlay();

break;

case 113://按下“s”时停止播放

stopPlay();

break;

}          

}

定时调用checkFocusHandle方法主要是为了解决文件选择框的焦点问题。

 

8)最小化到系统托盘

所谓最小化到系统托盘,其实质就是将主窗口隐藏,并在系统托盘中添加一个图标而已。

//定义变量 作为系统图标

private var dockImage:BitmapData;

//应用程序初始化处理函数

private function initApp():void

{

// 捕获关闭窗口事件,让用户决定窗口是隐藏还是关闭

this.addEventListener(Event.CLOSING, closingApplication);

//为舞台添加监听器 ,在全屏和退出全屏的时候调用fullscreenresize方法 来调整控件大小,这主要是为了解决在全屏状态下按下ESC退出全屏带来的布局混乱问题。

stage.addEventListener(FullScreenEvent.FULL_SCREEN,fullScreenreSize);

//添加快捷键的监听

WindowedApplication(mx.core.Application.application).addEventListener(KeyboardEvent.KEY_DOWN,KeyboardClickHandle);

//添加定时器

var timer:Timer=new Timer(100,0);

timer.addEventListener(TimerEvent.TIMER,checkFocusHandle);

timer.start();  

//进行系统托盘的设置

//loader对象载入一张图像作为系统托盘图标

var loader:Loader = new Loader();

loader.contentLoaderInfo.addEventListener(Event.COMPLETE, prepareForSystray);

loader.load(new URLRequest("btn_normal_quiet.png"));

}

// 响应弹出窗口中用户的选择

private function alertCloseHandler(event:CloseEvent):void {

if (event.detail==Alert.YES) {

closeApp(event);

} else {

//用户单击关闭按钮的时候,如果选择了最小化 则调用dock方法。最小化到系统托盘

dock();

}

}

/**

 

 * 设置基本属性。

 

 

 */

public function prepareForSystray(event:Event):void {

// 把图像作为系统图标

dockImage = event.target.content.bitmapData;

// windows 支持系统托盘图标, 苹果系统同样也支持,

if (NativeApplication.supportsSystemTrayIcon){

setSystemTrayProperties();

// 设置系统托盘菜单           

SystemTrayIcon(NativeApplication.nativeApplication .icon).menu = createSystrayRootMenu();

}

}

/**

 

 * 创建系统托盘菜单

 

 

 

 */

private function createSystrayRootMenu():NativeMenu{

// 添加菜单元件,每个元件响应相应的方法

var menu:NativeMenu = new NativeMenu();

var openNativeMenuItem:NativeMenuItem = new NativeMenuItem("Open");

var exitNativeMenuItem:NativeMenuItem = new NativeMenuItem("Exit");

// 当用户双击元件时发生的事件

openNativeMenuItem.addEventListener(Event.SELECT, undock);

exitNativeMenuItem.addEventListener(Event.SELECT, closeApp);

// 把菜单元件添加到菜单中

menu.addItem(openNativeMenuItem);

menu.addItem(new NativeMenuItem("",true));

menu.addItem(exitNativeMenuItem);

return menu;

}

/**

 

 * 设置隐藏和激活的一些事件侦听

 

 */

private function setSystemTrayProperties():void{

// 当鼠标悬停在系统托盘图标上时显示文字。       

SystemTrayIcon(NativeApplication.nativeApplication .icon).tooltip = "Systray test application";

// 双击系统托盘图标打开程序

SystemTrayIcon(NativeApplication.nativeApplication .icon).addEventListener(MouseEvent.CLICK, undock);

// 侦听窗口显示状态的改变,这样就可以捕获最小化事件     

stage.nativeWindow.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, nwMinimized); //捕获最小化事件

}

/**

 

 * 窗口的状态改变时执行合适的动作。

 

 * 当用户点击最小化按钮,隐藏程序到系统托盘

 

 *

 

 

 

 */

private function nwMinimized(displayStateEvent:NativeWindowDisplayStateEvent):void {

if(displayStateEvent.afterDisplayState == NativeWindowDisplayState.MINIMIZED) {

displayStateEvent.preventDefault(); // 当按下最小化按钮是,要阻止默认的最小化发生

dock();

}

}

/**

 

 * 在系统托盘中显示程序图标

 

 

 

 */

public function dock():void {

// 隐藏当前窗口

stage.nativeWindow.visible = false;

// 设置 bitmaps 数组,在系统托盘中显示程序图标。

NativeApplication.nativeApplication .icon.bitmaps = [dockImage];

}

/**

 

 * 重新显示程序窗口,从系统托盘中移除图标

 

 

 

 */

public function undock(evt:Event):void {

// 把窗口设置为可见, 并确保程序在最前端。

stage.nativeWindow.visible = true;

stage.nativeWindow.orderToFront();

// 清空 bitmaps 数组,同时清空了系统托盘中的程序图标。

NativeApplication.nativeApplication .icon.bitmaps = [];

}

运行程序效果如图10.1.4所示 ,程序最小化到了系统托盘 通过菜单可以将程序恢复。

 

图10.1.4 系统托盘

9)在播放器中添加片库

所谓添加片库,就是使用一个<mx:HTML>控件,在程序界面中加载迅雷看看的首页,进行在线搜索和播放。在程序中添加以下代码。

protected function moviebut_clickHandler(event:MouseEvent):void

{

if(this.currentState=="movie"){

this.currentState="default";

}

else{

this.currentState="movie";

}

}

]]>

</mx:Script>

<mx:states>

<mx:State name="default"/>

<mx:State name="movie">

<mx:RemoveChild target="{mainDisplay}"/>

<mx:RemoveChild target="{canvas1}"/>

<mx:RemoveChild target="{controlPanel}"/>

<mx:AddChild position="lastChild">

<mx:HTML x="10" y="33" width="565" height="394" location="http://www.xunlei.com/"/>

</mx:AddChild>

</mx:State>

</mx:states>

//省略部分代码...

<mx:Button id="movebut" click="moviebut_clickHandler(event)"  width="13"  height="13"    toolTip="片库"  cornerRadius="25">

<mx:icon>@Embed('assets/images/btn_normal_quiet.png')</mx:icon>

</mx:Button>

 

运行程序 效果如图10.1.5所示。

 

10.1.5 片库

 

 

 

 

 

 

 

 

 

 

 

第3章 项目案例:图书进销存系统

3.1 训练技能点

Ø 使用Flex控件实现视图界面

Ø 使用Spring+Hibernate完成后台

Ø 使用BlazeDS与后台交互

3.2 需求说明

3.2.1 需求概述

在现实生活中,从事商业活动的企业需要经常进行进货、销售等业务,进货即采购,就是企业购入了一些商品,并需要记录采购商品的名字、价格、数量等信息,同时企业中的原商品库存量相应的增加;销售就是企业把采购的商品以高于采购价的价格售出,并需要记录售出商品的名字、价格、数量等信息,同时企业中的原商品库存量相应的减少。

在激烈的市场竞争环境里,企业需要及时地了解进货、销售等所有进销存环节上的实际情况或具体数据,并以此做出商业决策。随着Internet的发展和计算机的普及,人们发现通过计算机系统进行商品进销存管理的优势十分明显,所以进销存工作也进入了信息时代,众多的进销存管理系统纷纷出现。

进销存管理系统是一个典型的数据库应用程序,根据企业的需求,能有效解决企业账目混乱,库存不准,信息反馈不及时等问题,集采购、销售、库存管理为一体,能帮助企业处理日常的进销存业务,同时提供丰富的实时查询统计功能。

本此项目为书店开发一个图书进销存管理系统,涵盖了基本的进货、销售、查询等功能。

3.2.2 系统用例图

1. 图书进销存管理系统的权限分成两类:管理员和操作员。管理员主要对整个系统进行管理,包括用户管理、出版社管理、类别管理和各种查询,如图11.1.1所示。

图11.1.1 管理员业务

Ø 用户管理:管理员可以对用户进行添加和删除,也可以修改用户的权限。

Ø 出版社管理:管理员可以对出版社进行增删改查操作。

Ø 图书类别管理:管理员可以对图书类别进行增删改查操作。

Ø 图书进货查询:管理员可以根据条件查询图书的进货记录。

Ø 图书销售查询:管理员可以根据条件查询图书的销售记录。

Ø 图书库存查询:管理员可以根据条件查询图书的当前库存数量。

2. 操作员没有管理权限,只能进行日常的进货和销售操作,如图11.1.2所示。

图11.1.2 操作员业务

Ø 图书进货:当书店新采购一批图书时,操作员需要通过本系统记录图书的采购数据

Ø 图书销售:当书店销售图书时,操作员需要通过本系统记录图书的销售数据

3. 无论是管理员还是操作员,都有一些相同的业务,如图11.1.3所示。

11.1.3 相同业务

Ø 登录:要使用本系统首先要能正常进行登录,在登录时系统会自动判断用户的权限,如果用户是管理员就进入管理员后台;如果用户是操作员就进入操作员后台。

Ø 修改密码:方便用户修改自己的登录密码。

Ø 退出:不使用本系统时可以退出并回到登录页面。

 

3.2.3 开发环境

Ø JDK 1.5以上

Ø MyEclipse8.5以上

Ø Oracle 10g以上

Ø Tomcat 6以上

3.3 设计与思路分析

3.3.1 功能分析

图11.1.4 功能模块

图11.1.4详细列出了本项目中的功能模块,下面我们对这些功能模块牵涉到的数据、业务和界面进行详细介绍。

Ø 用户登录:用户只有输入正确的登录名称和登录密码才能进入系统,如果登录失败就进行错误提示,如图11.1.5所示。管理员登录成功后进入如图11.1.6所示的后台,操作员登录成功后进入如图11.1.7所示的后台。

 

图11.1.5 用户登录

 

11.1.6 管理员后台

 

图12.7 操作员后台

Ø 用户管理:单击管理员后台的【用户管理】菜单打开如图11.1.8所示的页面,该菜单有两个选项,添加用户和编辑用户。在这里可以对用户进行集中管理。

 

11.1..8 用户管理

单击【添加用户】超链接打开如图11..9所示的页面,填写用户名和密码并选择权限。

 

图11.1.9 添加用户

Ø出版社管理和类别管理都是简单的增删改查操作,这里不再多述。

Ø 图书库存查询:管理员可以按照图书ISBN、图书名称、出版社、图书类别进行图书当前库存数量的查询,图书进货/销售查询与次类似,只是多了一个时间段条件,如图11.1.10所示。

 

图11.1.10图书库存查询

单击【提交】按钮后以表格的形式显示查询到的数据信息。

Ø 图书进货:单击操作员后台的【图书进货】菜单打开如图11.1.11所示的页面。填写表单并单击【提交】按钮即可完成图书进货操作。注意:出版社和图书类别下拉列表框中的选项在页面打开时就需要填充,图书销售功能与此类似。

 

图11.1.11 图书进货

Ø 其他功能由于比较简单,这里不再多述。

Ø 上述功能中凡是涉及到用户输入的都有进行输入验证。

3.3.2 数据库设计

根据上述需求描述和功能分析设计出以下六个表。

 

图11.1.12 出版社表

 

图11.1.13 图书类别表

 

图11.1.14 用户表

 

图11.1.15 图书库存表

 

图11.1.16 图书进货记录表

 

图11.1.17 图书销售记录表

 

图11.1.18 权限表

3.4 实现步骤

(1) 通过Oracle建库建表

(2) 在MyEclipse中创建Flex工程,指定服务器技术为J2EE,并使用BlazeDS

(3) 为项目导入JDBC驱动并添加Spring,Hibernate支持

(4) 使用逆向工程生成映射文件和实体类等,并创建业务类

(5) SpringFlex进行整合,并配置业务类为远程调用

(6) 开发Flex界面,并与后台通信完成功能

3.5 关键代码

图书进销存项目中,后台业务部分并没有什么难度,基本上就是增删改查而已,主要的难点就集中在构建前台界面和各种效果的实现,以及与后台进行通信这部分。下面就通过初始界面部分的代码来展示其中的一些细节。在初始界面阶段主要是显示界面的基本元素,并与后台通信获取到基本权限,并在界面中据此生成基本功能的桌面菜单,包括登陆,退出等。

(1) 后台部分

Right.java

package com.zzzy.flexjxc.entity;

 

 

 

/**

 * Right entity. @author MyEclipse Persistence Tools

 */

 

public class Right  implements java.io.Serializable {

 

 

    // Fields    

 

     private String id;

     private String parent;

     private String righttext;

     private String righturl;

     private String source;

     private Integer type;

 

 

    // Constructors

 

    /** default constructor */

    public Right() {

    }

 

/** minimal constructor */

    public Right(String parent, String righttext, Integer type) {

        this.parent = parent;

        this.righttext = righttext;

        this.type = type;

    }

    

    /** full constructor */

    public Right(String parent, String righttext, String righturl, String source, Integer type) {

        this.parent = parent;

        this.righttext = righttext;

        this.righturl = righturl;

        this.source = source;

        this.type = type;

    }

 

   

    // Property accessors

 

    public String getId() {

        return this.id;

    }

    

    public void setId(String id) {

        this.id = id;

    }

 

    public String getParent() {

        return this.parent;

    }

    

    public void setParent(String parent) {

        this.parent = parent;

    }

 

    public String getRighttext() {

        return this.righttext;

    }

    

    public void setRighttext(String righttext) {

        this.righttext = righttext;

    }

 

    public String getRighturl() {

        return this.righturl;

    }

    

    public void setRighturl(String righturl) {

        this.righturl = righturl;

    }

 

    public String getSource() {

        return this.source;

    }

    

    public void setSource(String source) {

        this.source = source;

    }

 

    public Integer getType() {

        return this.type;

    }

    

    public void setType(Integer type) {

        this.type = type;

    }

   }

 

RightBizImpl.java

package com.zzzy.flexjxc.biz;

 

import java.util.List;

 

import com.zzzy.flexjxc.dao.RightDAO;

 

public class RightBizImpl {

private RightDAO rightdao;

public List getBaseRight(){

return rightdao.findByType(2);

}

public RightDAO getRightdao() {

return rightdao;

}

 

public void setRightdao(RightDAO rightdao) {

this.rightdao = rightdao;

}

}

 

(2) 界面部分

自定义时间控件 deskTop.DIYTimer.mxml

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

<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" backgroundAlpha="0.0" layout="absolute" width="230" height="210" showCloseButton="true" headerHeight="12"

  cornerRadius="35">

<mx:Script>

<![CDATA[

import mx.controls.Alert;

import mx.managers.PopUpManager;

]]>

</mx:Script>

<mx:DateChooser width="100%" height="100%" x="0" y="0" cornerRadius="0"/>

<mx:close>

<![CDATA[

PopUpManager.removePopUp(this);

jxc.b_timer = false;

]]>

</mx:close>

</mx:TitleWindow>

 

主界面 jxc.mxml

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

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"

   xmlns:new="components.*"

   xmlns:flexlib="http://code.google.com/p/flexlib/"

   creationComplete="init()"

   xmlns:local="*">

<fx:Style>

global

{

font-size: 12;

}

</fx:Style>

<!--引入css-->

<fx:Style source="Main.css"/>

<fx:Declarations>

<!--定义RemoteObject 远程获取基本权限-->

<mx:RemoteObject id="initRight" destination="rightbiz" endpoint="http://localhost:8400/jxc4/messagebroker/amf" showBusyCursor="true" fault="init_faultHandler(event)" result="init_resultHandler(event)"/>

<!--定义Glow效果 用来实现鼠标进入 移出的效果-->

<mx:Glow id="imgInGlow"

 alphaTo="0.8"

 blurXTo="80.0"

 blurYTo="80.0"

 color="#FFFFFF">

</mx:Glow>

<!-- color="#68b3e6"  #91d314-->

<mx:Glow id="imgOutGlow"

 alphaFrom="0.8"

 alphaTo="0"

 blurXFrom="80.0"

 blurXTo="0.0"

 blurYFrom="80.0"

 blurYTo="0.0"

 color="#FFFFFF">

</mx:Glow>

</fx:Declarations>

<fx:Script>

<![CDATA[

import deskTop.DIYTimer;

import deskTop.MyToolTip;

import mx.collections.ArrayCollection;

import mx.controls.Alert;

import mx.events.ToolTipEvent;

import mx.managers.PopUpManager;

import mx.rpc.events.FaultEvent;

import mx.rpc.events.ResultEvent;

public static var theWidth:int=996;

public static var theHight:int=600;

[Bindable]

public var thisYears:String="获取失败!";

public var timers:Timer;

[Bindable]

public var currentTime:String="正在获取系统时间!";

public var thisTime:Date=new Date();

//所有权限的集合

public var ops:ArrayCollection;

//拼凑当前时间

private function getTime(event:TimerEvent):void

{   

thisTime=new Date();

currentTime=""+thisTime.getHours()+":"+thisTime.getMinutes()+":"+thisTime.getSeconds();

}

//隐藏开始菜单

private function hideStart(evt:MouseEvent):void

{

if(evt.target==this)

{

if(start.visible==true)

start.setVisible(false);

}

}

public function init():void

{

this.initRight.getBaseRight();

start.setVisible(false);

theWidth= this.width;

theHight= this.height;

thisTime=new Date();

thisYears=thisTime.getFullYear()+""+(thisTime.getMonth()+1)+""+thisTime.getDate()+"";

timers=new Timer(1000);

timers.addEventListener(TimerEvent.TIMER,getTime);

timers.start();

this.addEventListener(MouseEvent.CLICK,hideStart,false);

}

protected function init_faultHandler(event:FaultEvent):void

{

Alert.show(event.fault.toString());

}

 

private function showLogin(event:Event):void{

}

private function exit(event:Event):void{

}

protected function init_resultHandler(event:ResultEvent):void

{

ops=event.result as ArrayCollection;

var imgnum:Number = 1;

for each(var v in ops){

var opimg:Image = new Image();

opimg.source=v.source;

opimg.width=64;

opimg.height=64;

opimg.id=v.id;

opimg.toolTip=v.tip;

opimg.setStyle("rollOverEffect",this.imgInGlow);

opimg.setStyle("rollOutEffect",this.imgOutGlow);

if(v.righturl!=''){

opimg.addEventListener(MouseEvent.CLICK,this[v.righturl]);

 

}

opimg.toolTip=v.righttext;

/* opimg.toolTip=" ";

opimg.addEventListener(ToolTipEvent.TOOL_TIP_CREATE,createToolTip);

如果要注册自定义提示方法 一定记得opimg.toolTip=" ";

*/

if(imgnum<6){

this.sg1.addElement(opimg);

}

else{

this.sg2.addElement(opimg);

}

imgnum++;

}

}

 

private var songFlag:Boolean = true;

private function nextSong():void

{

if(songFlag)

{

song.source='assets/images/nosong_logo.png'

songFlag=false;

}

else

{

song.source='assets/images/song_logo.gif'

songFlag=true;

}

}

public static var b_timer:Boolean =false;

private var mdiTimer:DIYTimer;

private function showTimer():void

{

if(b_timer==false)

{

mdiTimer= new DIYTimer();

PopUpManager.addPopUp(mdiTimer,this,false);

mdiTimer.x = this.width - mdiTimer.width -5;

mdiTimer.y = this.height-mdiTimer.height-10;

b_timer=true;

}

}

]]>

</fx:Script>

<s:BorderContainer backgroundImage="@Embed(source='assets/bg3.jpg')" width="100%" height="100%">

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/6.swf" autoLoad="true" id="rainFlash" />

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/27.swf" autoLoad="true" id="globuleFlash" />  

<mx:VBox width="100%" height="100%" top="0" left="0">

<flexlib:MDICanvas id="mdiCanvas" horizontalScrollPolicy="off" verticalScrollPolicy="off"

   width="100%"

   snapDistance="10" tilePadding="10"

   effectsLib="flexlib.mdi.effects.effectsLib.MDILinearEffects" height="100%">

<s:Group id="sg1" width="155" height="511" y="25" x="32">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<s:Group id="sg2" width="155" height="521" y="25" x="199">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<mx:Panel width="200" height="350"  color="#020202" layout="absolute" id="start" title="印水" bottom="0" left="0"

  showEffect="wipeUp"

  hideEffect="wipeDown" titleIcon="@Embed(source='assets/images/logo_32.png')" status="进销存管理系统" backgroundAlpha="0.5" >

<mx:Accordion width="160" height="300" horizontalCenter="0"  verticalCenter="0" fontSize="12" id="startCan" backgroundAlpha="0.5" >

</mx:Accordion>

</mx:Panel>

</flexlib:MDICanvas>

<mx:ApplicationControlBar bottom="0" width="100%" left="0" fillAlphas="[0.75, 0.0]" fillColors="[#FDFFFB, #85CB21]" color="#020202">

<mx:VRule height="25" y="0" left="0"/>    

<mx:Canvas width="100%" height="100%" >

<mx:LinkButton visible="false" label="开   始" id="startButton"

   rollOverEffect="{imgInGlow}"

   rollOutEffect="{imgOutGlow}"

   toolTip="单击这里开始" fontWeight="bold"  left="0" verticalCenter="0">

</mx:LinkButton>

<mx:VRule height="100%" y="0" left="58"/>

<mx:Tile id="tile" label="Tile" borderColor="#41DA18" direction="horizontal" bottom="0" left="65" width="707" height="100%">

</mx:Tile>

<mx:VRule height="100%" y="0" right="150" width="15"/>

<mx:Image source="assets/images/song_logo.gif" click="nextSong()"

  rollOverEffect="{imgInGlow}"

  rollOutEffect="{imgOutGlow}" right="85" top="5" width="20" height="16"

  id="song" />

<mx:Label text="{currentTime}" id="time" toolTip="{thisYears}" right="10" color="#050505" top="3" click="showTimer()"/>

</mx:Canvas>

</mx:ApplicationControlBar>

</mx:VBox>

</s:BorderContainer>

</s:Application>

 

(3) 添加登录退出功能

登录和退出功能本身比较简单,主要是我们要实现登录框最小化到任务栏,和缩略图的功能,这部分代码比较复杂 首先我们要定义一个自定义控件,用来在任务栏上添加最小化窗口的图标。

ButtonTitle.Mxml

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

<mx:Button xmlns:mx="http://www.adobe.com/2006/mxml"

 label="{labelTW}" click="mw.setVisible(true)"

 toolTip="{labelTW}"

 toolTipCreate="createToolTip(event)"

 toolTipShow="positionToolTip(event)"

 rollOverEffect="{imgInGlow}" rollOutEffect="{imgOutGlow}">

  <mx:Glow id="imgInGlow"

alphaTo="0.8"

blurXTo="100.0"

blurYTo="100.0"

color="#FFFFFF">

</mx:Glow>

<mx:Glow id="imgOutGlow"

alphaFrom="0.8"

alphaTo="0"

blurXFrom="100.0"

blurXTo="0.0"

blurYFrom="100.0"

blurYTo="0.0"

color="#FFFFFF">

</mx:Glow>

<mx:Script>

<![CDATA[

import mx.core.IUIComponent;

import mx.graphics.ImageSnapshot;

private function takeSnapshot(source:IBitmapDrawable,st:ShowTW):void {

var imageSnap:ImageSnapshot = ImageSnapshot.captureImage(this.mw);

var imageByteArray:ByteArray = imageSnap.data as ByteArray;

st.load(imageByteArray);

st.width=240;

st.height=200;

}

import flexlib.mdi.containers.MDIWindow;

import mx.events.ToolTipEvent;

[Bindable]

public var labelTW:String="获取失败!";

public var mw:MDIWindow;

public function show(mw:MDIWindow):void

{

this.mw=mw;

labelTW=mw.title;

}

private function createToolTip(e:ToolTipEvent):void

   {

    var st:ShowTW = new ShowTW();

    e.toolTip = st;

    takeSnapshot(mw,st);

   }

   

   //ToolTip定位在距离该组件上方(55)的位置

   private function positionToolTip(e:ToolTipEvent):void

   {

    var pt:Point = new Point();

    pt.x = 0;

    pt.y = 0;

    

    pt = this.localToGlobal(pt);

    e.toolTip.x = pt.x;

    e.toolTip.y = pt.y-203;

   }

]]>

</mx:Script>

</mx:Button>

 

定义一个所有弹出框的基类,程序中所有弹出例如登录框,添加出版社对话框等都会继承该类。

MyWindow.mxml

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

<code:MDIWindow xmlns:fx="http://ns.adobe.com/mxml/2009"

xmlns:s="library://ns.adobe.com/flex/spark"

xmlns:mx="library://ns.adobe.com/flex/mx"

xmlns:code="http://code.google.com/p/flexlib/"

layout="absolute" width="400" height="300"

fontSize="12" cornerRadius="8" backgroundColor="#FFFFFF"

titleIcon="@Embed(source='../../../assets/images/dis/Sketches0.png')"

backgroundAlpha="0.8"

alpha="1.0"  borderAlpha="0.2" fontWeight="normal" headerHeight="25"

color="#297AD6" hideEffect="{irisOut}" showEffect="{irisIn}"

>

<fx:Declarations>

<mx:Iris id="irisOut" duration="1000" showTarget="true"/>

<mx:Iris id="irisIn" duration="1000" showTarget="false"/>

</fx:Declarations>

<fx:Script>

<![CDATA[

import deskTop.ButtonTile;

import flexlib.mdi.containers.MDICanvas;

import flexlib.mdi.events.MDIWindowEvent;

import mx.containers.Tile;

import mx.controls.Alert;

import mx.events.CloseEvent;

import mx.events.FlexEvent;

public var button:ButtonTile;

public var mainframe:Object;

public function set Mainframe(m:main):void

{

this.mainframe=m;

}

private function initlize(event:MouseEvent):void

{

this.unMinimize();

}

public function minlize(event:MDIWindowEvent):void

{

event.preventDefault();

this.setVisible(false);

}

public function removeBT(flag:int):void

{

mainframe.mdiCanvas.windowManager.remove(this);

mainframe.tile.removeChild(button);

switch(flag){

case 1:{

main.b_login=true;

this.mainframe.mdilogin=null;

break;

}

case 2:{

main.b_addUser=true;

this.mainframe.adduser=null;

break;

}

case 3:{

main.b_editUser=true;

this.mainframe.edit=null;

break;

}

case 4:{

main.b_updateUser=true;

this.mainframe.updateUser=null;

break;

}

case 5:{

main.b_BookIn=true;

this.mainframe.mdiBookIn=null;

break;

}

case 6:{

main.b_stock=true;

this.mainframe.mdiStock=null;

break;

}

case 7:{

main.b_stocklist=true;

this.mainframe.mdiStockList=null;

break;

}

}

}

 

protected function init():void

{

this.button=new ButtonTile();

this.addEventListener(MDIWindowEvent.MINIMIZE,minlize);

button.addEventListener(MouseEvent.CLICK,initlize);

button.show(this);

mainframe.tile.addChild(button);

this.mainframe.start.setVisible(false);

 

}

 

 

 

]]>

</fx:Script>

</code:MDIWindow>

 

定义VO Userinfo.as 与后台实体类对应。

package com.oa.vo

{

[RemoteClass(alias="com.zzzy.flexjxc.entity.User")]

public class Userinfo

{

public function Userinfo()

{

}

public var userid:int;

public var roleid:int;

public var loginpwd:String;

public var loginname:String;

}

}

定义登录框 LoginWindow.mxml

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

<view:MyWindow xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx"

   xmlns:view="com.oa.view.*"

   layout="absolute" width="400" height="300"

   title="登录"

 initialize="mywindow1_initializeHandler(event)"

 

   >

<fx:Style>

global

{

font-size: 12;

}

</fx:Style>

<fx:Style source="../../../Main.css"/>

 

<fx:Script>

<![CDATA[

import com.oa.vo.Userinfo;

import flexlib.mdi.events.MDIWindowEvent;

import mx.collections.ArrayCollection;

import mx.containers.Accordion;

import mx.containers.HBox;

import mx.containers.VBox;

import mx.controls.Alert;

import mx.controls.Image;

import mx.events.FlexEvent;

import mx.rpc.events.FaultEvent;

import mx.rpc.events.ResultEvent;

import mx.validators.Validator;

protected function sub_clickHandler(event:MouseEvent):void

{

var check:Array = Validator.validateAll([v1,v2]);

if(check.length!=0){

return;

}

var user:Userinfo = new Userinfo();

user.loginname=this.uname.text;

user.loginpwd=this.pwd.text;

this.mainframe.userremoting.login(user);

}

protected function button1_clickHandler(event:MouseEvent):void

{

this.pwd.text="";

this.uname.text="";

}

 

public  function alert(event:MDIWindowEvent):void

{

event.preventDefault();

removeBT(1);

}

protected function mywindow1_initializeHandler(event:FlexEvent):void

{

super.init();

main.b_login=false;

this.addEventListener(MDIWindowEvent.CLOSE,alert);

}

 

 

 

]]>

</fx:Script>

<fx:Declarations>

<mx:StringValidator id="v1" source="{uname}" property="text" maxLength="12" minLength="2" trigger="{sub}" triggerEvent="click"/>

<mx:StringValidator id="v2" source="{pwd}" property="text" maxLength="8" minLength="3" trigger="{sub}" triggerEvent="click"/>

</fx:Declarations>

<mx:Form horizontalCenter="0" verticalCenter="-9" height="156">

<mx:FormItem required="true" label="用户名">

<s:TextInput id="uname"/>

</mx:FormItem>

<mx:FormItem required="true" label="密码">

<s:TextInput id="pwd" displayAsPassword="true"/>

</mx:FormItem>

<s:HGroup  horizontalAlign="center" gap="20" textAlign="right" width="184">

<mx:Button label="登录" id="sub" click="sub_clickHandler(event)"/><mx:Button label="重置" click="button1_clickHandler(event)"/>

</s:HGroup>

</mx:Form>

</view:MyWindow>

 

在主界面jxc.mxml添加远程调用对象的定义和showLogin exit等方法修改后的代码如下。

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

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"

   xmlns:new="components.*"

   xmlns:flexlib="http://code.google.com/p/flexlib/"

   creationComplete="init()"

   xmlns:local="*">

<fx:Style>

global

{

font-size: 12;

}

</fx:Style>

<!--引入css-->

<fx:Style source="Main.css"/>

<fx:Declarations>

<!--定义RemoteObject 远程获取基本权限-->

<mx:RemoteObject id="initRight" destination="rightbiz" endpoint="http://localhost:8400/jxc4/messagebroker/amf" showBusyCursor="true" fault="init_faultHandler(event)" result="init_resultHandler(event)"/>

<mx:RemoteObject id="userremoting" destination="userbiz" endpoint="http://localhost:8400/jxc4/messagebroker/amf" showBusyCursor="true" >

<mx:method

name="login"

result="loginremoting_resultHandler(event)"

fault="loginremoting_faultHandler(event)"

/>

</mx:RemoteObject>

<!--定义Glow效果 用来实现鼠标进入 移出的效果-->

<mx:Glow id="imgInGlow"

 alphaTo="0.8"

 blurXTo="80.0"

 blurYTo="80.0"

 color="#FFFFFF">

</mx:Glow>

<!-- color="#68b3e6"  #91d314-->

<mx:Glow id="imgOutGlow"

 alphaFrom="0.8"

 alphaTo="0"

 blurXFrom="80.0"

 blurXTo="0.0"

 blurYFrom="80.0"

 blurYTo="0.0"

 color="#FFFFFF">

</mx:Glow>

</fx:Declarations>

<fx:Script>

<![CDATA[

import com.oa.view.LoginWindow;

import deskTop.DIYTimer;

import deskTop.MyToolTip;

import mx.collections.ArrayCollection;

import mx.containers.HBox;

import mx.controls.Alert;

import mx.controls.Button;

import mx.events.ToolTipEvent;

import mx.managers.PopUpManager;

import mx.rpc.events.FaultEvent;

import mx.rpc.events.ResultEvent;

public static var theWidth:int=996;

public static var theHight:int=600;

[Bindable]

public var thisYears:String="获取失败!";

public var timers:Timer;

[Bindable]

public var currentTime:String="正在获取系统时间!";

public var thisTime:Date=new Date();

//所有权限的集合

public var ops:ArrayCollection;

//建立tooltip组件

private function createToolTip(e:ToolTipEvent):void

{

var tip:MyToolTip = new MyToolTip();

e.toolTip = tip;

}

//拼凑当前时间

private function getTime(event:TimerEvent):void

{   

thisTime=new Date();

currentTime=""+thisTime.getHours()+":"+thisTime.getMinutes()+":"+thisTime.getSeconds();

}

//隐藏开始菜单

private function hideStart(evt:MouseEvent):void

{

if(evt.target==this)

{

if(start.visible==true)

start.setVisible(false);

}

}

public function init():void

{

this.initRight.getBaseRight();

start.setVisible(false);

theWidth= this.width;

theHight= this.height;

thisTime=new Date();

thisYears=thisTime.getFullYear()+""+(thisTime.getMonth()+1)+""+thisTime.getDate()+"";

timers=new Timer(1000);

timers.addEventListener(TimerEvent.TIMER,getTime);

timers.start();

this.addEventListener(MouseEvent.CLICK,hideStart,false);

}

protected function init_faultHandler(event:FaultEvent):void

{

Alert.show(event.fault.toString());

}

 

protected function init_resultHandler(event:ResultEvent):void

{

ops=event.result as ArrayCollection;

var imgnum:Number = 1;

for each(var v in ops){

var opimg:Image = new Image();

opimg.source=v.source;

opimg.width=64;

opimg.height=64;

opimg.id=v.id;

opimg.toolTip=v.tip;

opimg.setStyle("rollOverEffect",this.imgInGlow);

opimg.setStyle("rollOutEffect",this.imgOutGlow);

if(v.righturl!=''){

opimg.addEventListener(MouseEvent.CLICK,this[v.righturl]);

 

}

opimg.toolTip=v.righttext;

/* opimg.toolTip=" ";

opimg.addEventListener(ToolTipEvent.TOOL_TIP_CREATE,createToolTip);

如果要注册自定义提示方法 一定记得opimg.toolTip=" ";

*/

if(imgnum<6){

this.sg1.addElement(opimg);

}

else{

this.sg2.addElement(opimg);

}

imgnum++;

}

}

 

private var songFlag:Boolean = true;

private function nextSong():void

{

if(songFlag)

{

song.source='assets/images/nosong_logo.png'

songFlag=false;

}

else

{

song.source='assets/images/song_logo.gif'

songFlag=true;

}

}

public static var b_timer:Boolean =false;

private var mdiTimer:DIYTimer;

private function showTimer():void

{

if(b_timer==false)

{

mdiTimer= new DIYTimer();

PopUpManager.addPopUp(mdiTimer,this,false);

mdiTimer.x = this.width - mdiTimer.width -5;

mdiTimer.y = this.height-mdiTimer.height-10;

b_timer=true;

}

}

public var loginstate:Boolean = false;

public static var username:String;

public var mdilogin:LoginWindow;

public static var b_login:Boolean=true;

private function showLogin(event:Event):void{

if(!this.loginstate){

if(b_login)

{

mdilogin= new LoginWindow();

mdilogin.mainframe=this;

mdiCanvas.windowManager.add(mdilogin);

mdilogin.x = mdiCanvas.width/2-mdilogin.width/2;

mdilogin.y = mdiCanvas.height /2-mdilogin.height/2;

}

else

{

mdilogin.setVisible(true);

}

}

}

private function exit(event:Event):void{

if(this.loginstate){

Alert.show("确定退出?","进销存",Alert.YES|Alert.NO,null,function(evt){

if(evt.detail==Alert.YES){

loginstate=false;

startCan.removeAllChildren();

startButton.visible=false;

}

});

}

}

protected function loginremoting_resultHandler(event:ResultEvent):void

{

if(event.result!=null){

loginstate=true;

initMenu(event);

username = mdilogin.uname.text;

this.mdilogin.removeBT(1);

}

else{

Alert.show('用户名密码错误');

}

}

public  function initMenu(event:ResultEvent):void{

var sys_rights:ArrayCollection = event.result as ArrayCollection;

for each(var v in sys_rights){

if(v.parent=="root_menu"){

var vb1 :VBox= new VBox;

vb1.label=v.righttext;

vb1.id=v.id;

for each( var sr in sys_rights){

if(sr.parent==v.id){

var hb:HBox = new HBox();

var img:Image = new Image();

var but:Button = new Button();

img.width=32;

img.height=32;

img.source=sr.source;

but.label=sr.righttext;

but.addEventListener(MouseEvent.CLICK,this[sr.righturl]);

hb.addChild(img);

hb.addChild(but);

vb1.addChild(hb);

}

}

startCan.addChild(vb1);

startButton.visible=true;

}

}

}

protected function loginremoting_faultHandler(event:FaultEvent):void

{

Alert.show(event.fault.toString());

}

private function showStart():void

{ if(start.visible==true){

start.setVisible(false);

}else{

start.setVisible(true);

}

}

public function addUser(event:Event):void{}

public function editUser(event:Event):void{}

public function addPublisher(event:Event):void{}

public function editPublisher(event:Event):void{}

public function addCategroy(event:Event):void{}

public function editCategory(event:Event):void{}

public function bookInFind(event:Event):void{}

public function bookOutFind(event:Event):void{}

public function bookStoreFind(event:Event):void{}

]]>

</fx:Script>

<s:BorderContainer backgroundImage="@Embed(source='assets/bg3.jpg')" width="100%" height="100%">

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/6.swf" autoLoad="true" id="rainFlash" />

<mx:SWFLoader width="100%" height="100%" left="0" top="0" source="assets/swf/27.swf" autoLoad="true" id="globuleFlash" />  

<mx:VBox width="100%" height="100%" top="0" left="0">

<flexlib:MDICanvas id="mdiCanvas" horizontalScrollPolicy="off" verticalScrollPolicy="off"

   width="100%"

   snapDistance="10" tilePadding="10"

   effectsLib="flexlib.mdi.effects.effectsLib.MDILinearEffects" height="100%">

<s:Group id="sg1" width="155" height="511" y="25" x="32">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<s:Group id="sg2" width="155" height="521" y="25" x="199">

<s:layout>

<s:VerticalLayout paddingLeft="50"  gap="50"/>

</s:layout>

</s:Group>

<mx:Panel width="200" height="350"  color="#020202" layout="absolute" id="start" title="印水" bottom="0" left="0"

  showEffect="wipeUp"

  hideEffect="wipeDown" titleIcon="@Embed(source='assets/images/logo_32.png')" status="进销存管理系统" backgroundAlpha="0.5" >

<mx:Accordion width="160" height="300" horizontalCenter="0"  verticalCenter="0" fontSize="12" id="startCan" backgroundAlpha="0.5" >

</mx:Accordion>

</mx:Panel>

</flexlib:MDICanvas>

<mx:ApplicationControlBar bottom="0" width="100%" left="0" fillAlphas="[0.75, 0.0]" fillColors="[#FDFFFB, #85CB21]" color="#020202">

<mx:VRule height="25" y="0" left="0"/>    

<mx:Canvas width="100%" height="100%" >

<mx:LinkButton visible="false" label="开   始" id="startButton" click="showStart()"

   rollOverEffect="{imgInGlow}"

   rollOutEffect="{imgOutGlow}"

   toolTip="单击这里开始" fontWeight="bold"  left="0" verticalCenter="0">

</mx:LinkButton>

<mx:VRule height="100%" y="0" left="58"/>

<mx:Tile id="tile" label="Tile" borderColor="#41DA18" direction="horizontal" bottom="0" left="65" width="707" height="100%">

</mx:Tile>

<mx:VRule height="100%" y="0" right="150" width="15"/>

<mx:Image source="assets/images/song_logo.gif" click="nextSong()"

  rollOverEffect="{imgInGlow}"

  rollOutEffect="{imgOutGlow}" right="85" top="5" width="20" height="16"

  id="song" />

<mx:Label text="{currentTime}" id="time" toolTip="{thisYears}" right="10" color="#050505" top="3" click="showTimer()"/>

</mx:Canvas>

</mx:ApplicationControlBar>

</mx:VBox>

</s:BorderContainer>

</s:Application>

运行程序效果如图11.1.19  11.1.20 所示

 

11.1.19 登录界面

 

11.1.20 登录后生成功能菜单

其他的功能则与此大致相同,在这里就不再详述了,另外,在进行图书进货时,首先要判断新采购的图书在数据库中是否存在,如果存在则只需更新一下库存量即可,然后需要向图书进货表中插入一条进货记录;如果不存在则需要向库存表中插入一条新的图书信息并同时向图书进货表中插入一条进货记录。

在进行图书销售时,除了要向图书销售表中插入一条销售记录外,还需要更新库存表中的库存量。图书销售和图书进货的数据库操作相对复杂,建议通过存储过程并结合事务来实现。

 

3.6 时间分配

阶段一:分析和讲解需求(50分钟)

Ø 教员在教师机上运行参考项目并讲解需求

Ø 学员分析并理解需求

阶段二:学员实现数据库(30分钟)

阶段三:学员实现业务功能(300分钟)

阶段四:运行并测试(20分钟)

作者:zhangchen124 发表于2017/3/18 21:18:47 原文链接
阅读:62 评论:0 查看评论

CSDN日报20170318——《一个程序员的五年工作总结》

$
0
0

【程序人生】一个程序员的五年工作总结
作者:Programmer_Zhou

三月的重庆到处弥漫着浓浓的春味,在这个春暖花开的季节里,人们在闲暇的时间都忙着踏青和春游。“一年之计在于春”,春天是赏花旅游的季节,春天更是为未来作打算、定计划的时候。在2017年的春天,我做出了人生中的一个重要决定:离开工作近五年的ZTE(中兴通讯),去开启新的人生征程。最近我在罗辑思维的得到APP上订阅了薛兆丰老师的“北大经济学课”专栏,我认为他说的一句话特别有道理,这句话的大意是:“我们如何选择是否离开当前的公司呢?可以拿公司里面比自己资深的人作为参考,如果十几二十年之后自己也到了他们的位置上,自己会不会对那种生活状态感到满意呢?如果满意,那么我们应该继续在原来的公司继续工作;如果不满意,那么就要考虑换一个岗位或换一家公司了。”这也部分道出了我离开ZTE的原因。


【Web 前端】Web 业务性能优化技术总结
作者:Horky

Web业务的性能优化是一个系统工程,既有深度,又有广度。以下所简称性能均特指Web业务性能。

技术的广度上,主要从大背景下考虑到其各个相关方,基于共同的数据指标发掘和评估方案。

技术的深度上是一个渐进和迭代的过程。可以从性能的本质展到目前各端的主要优化方向。


【Android 开发】深入理解 Android 渲染机制
作者:code_xzh

读懂 Android 的渲染机制对于优化,特别是在写布局的时候是很有帮助的。减少布局层级,减少 GPU 的渲染这对我们提供 app 的质量是很有帮助的。


【游戏开发】游戏中水的渲染技术系列一
作者:姜雪伟

水的渲染一直是图形学需要解决的问题,网上也有很多关于这方面的技术实现,本博客的系列文章也是给读者做一个总结,本篇博客主要介绍用傅里叶变换算法实现的水反射,也是一种假反射效果,目的是优化效率。


【系统运维】Linux 4.10中两个新特性与我的一段故事
作者:赵亚

在研发以及部署期间,其实最头疼的不是网关端,而是接入设备端,也就是那些Android客户端。

典型的需求是,为一组App构建一个沙盒,沙盒里的App走VPN通道到网关,而沙盒外的App不受任何影响。比方说,交警使用的移动警务App的数据显然要进行保护,而交警在轮班期间无聊时看看新浪微博则不应该受到影响,且移动警务App和新浪微博不能互访。


【大数据】Spark MLlib 算法调用展示平台及其实现过程
作者:fansy1990

  1. Spark算法调用工程还有很多页面没有完成,这个是类似重复性工作,并没有难点需要克服;

  2. Spark算法调用工程中针对每个算法,本来是想在其算法调用界面加上其数据描述、算法描述、参数描述的,不过暂时还没有添加,but这些信息在Scala算法封装工程里面都有;

  3. 关于使用SPARK ON YARN的方式调用Spark算法,并使用YARN来管理任务的流程基本在Spark算法调用工程中体现淋漓尽致了,再多也玩不出花儿了,所以如果有想学习研究这块内容的,则工程是一个很好的参考;


【云计算】图片流量节省大杀器:基于 CDN 的 sharpP 自适应图片技术实践
作者:腾讯云技术社区

目前移动端运营素材大部分依赖图片,基于对图片流量更少,渲染速度更快的诉求,我们推动 CDN, X5 内核,即通产品部共同推出了一套业务透明,无痛接入的 CDN 图片优化方案:基于 CDN 的 sharpP 自适应图片无痛接入方案。据统计效果可在原图基础上节省 60% - 75% 的流量,比之前 webP 无痛接入方案效果提升 40% - 50% ,减少流量的同时提高页面渲染速度,提升用户体验。


【编程语言】Python爬虫爬取知乎小结
作者:Java转Python–晴明

最近学习了一点网络爬虫,并实现了使用Python来爬取知乎的一些功能,这里做一个小的总结。网络爬虫是指通过一定的规则自动的从网上抓取一些信息的程序或脚本。我们知道机器学习和数据挖掘等都是从大量的数据出发,找到一些有价值有规律的东西,而爬虫则可以帮助我们解决获取数据难的问题,因此网络爬虫是我们应该掌握的一个技巧。


关注专栏【CSDN 日报】,获取最新及往期内容。
作者:blogdevteam 发表于2017/3/18 21:23:13 原文链接
阅读:735 评论:0 查看评论

第9章(1) 接口和抽象类

$
0
0

接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。

 

抽象方法:仅有声明而没有方法体。(相当于C++中的纯虚函数)

abstract void X();

抽象类:包含了一个或多个抽象方法的类叫作“抽象类”。

抽象类,是普通的类与接口之间的一种中庸之道。

如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中的所有抽象方法提供方法定义。

 

下面是抽象类的一个例子:

 

接口interface这个关键字产生一个完全抽象的类,它没有提供任何的具体实现。它允许创建者确定方法名参数列表返回类型,但是没有任何方法体。

一个接口表示:“所有实现了该特定接口的类看起来都像这样”。

要让一个类实现特定接口,需要使用implements关键字。

接口中的方法自动都是public的。

 

下面是接口的例子:

 

 

Java中的多重继承

Java通过组合多个接口实现多重继承。

 

对于创建抽象类或是接口的选择:

如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。

通过继承扩展接口:

可以通过继承在新的接口中扩展数个接口。(与类不同,可以继承多个)

同时应该避免继承的接口中同名方法带来的冲突。

接口中的域:

接口中任何域都自动是staticfinal的,所以接口可以成为用来创建常量组的工具。

其初始化发生在类第一次被加载的时候,或是任何域首次被访问的时候。

//: Months.java
// Using interfaces to create groups of constants
package c07;
public interface Months {
int
JANUARY = 1, FEBRUARY = 2, MARCH = 3,
APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12;
} ///: 


作者:qq_18738333 发表于2017/3/18 21:41:16 原文链接
阅读:52 评论:0 查看评论

第9章(2) 策略设计模式

$
0
0

策略设计模式

参考:http://www.cnblogs.com/chenssy/p/3295643.html

所谓策略模式就是定义了算法族,分别封装起来,让他们之前可以互相转换,此模式然该算法的变化独立于使用算法的客户。

例如:创建一个能够根据所传递的参数对象不同而具有不同行为的方法。策略是指传递进去的参数对象,它包含要执行的代码。

这使得你的方法更加灵活、通用,并更具可复用性。

使用场景

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

2、一个系统需要动态地在几种算法中选择一种。

3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

模式结构:

 

我们用排序算法来做例子:

 

 

public interface Sort{
    public abstract int[] sort(int arr[]);
}
public class BubbleSort implements Sort{
    public int[] sort(int arr[]){
       int len=arr.length;
       for(int i=0;i<len;i++){
           for(int j=i+1;j<len;j++){
              int temp;
              if(arr[i]>arr[j]){
                  temp=arr[j];
                  arr[j]=arr[i];
                  arr[i]=temp;
              }             
           }
        }
        System.out.println("冒泡排序");
        return arr;
    }
}
public class InsertionSort implements Sort {
    public int[] sort(int arr[]) {
        int len = arr.length;
        for (int i = 1; i < len; i++) {
            int j;
            int temp = arr[i];
            for (j = i; j > 0; j--) {
                if (arr[j - 1] > temp) {
                    arr[j] = arr[j - 1];
 
                } else
                    break;
            }
            arr[j] = temp;
        }
        System.out.println("插入排序");
        return arr;
    }
}
public class SelectionSort implements Sort {
    public int[] sort(int arr[]) {
        int len = arr.length;
        int temp;
        for (int i = 0; i < len; i++) {
            temp = arr[i];
            int j;
            int samllestLocation = i;
            for (j = i + 1; j < len; j++) {
                if (arr[j] < temp) {
                    temp = arr[j];
                    samllestLocation = j;
                }
            }
            arr[samllestLocation] = arr[i];
            arr[i] = temp;
        }
        System.out.println("选择排序");
        return arr;
    }
}
public class ArrayHandler
{
    private Sort sortObj;
    
    public int[] sort(int arr[])
    {
        sortObj.sort(arr);
        return arr;
    }
 
    public void setSortObj(Sort sortObj) {
        this.sortObj = sortObj;
    }
}
public class Client
{
    public static void main(String args[])
    {
       int arr[]={1,4,6,2,5,3,7,10,9};
       int result[];
       ArrayHandler ah=new ArrayHandler();
       
       Sort sort = new SelectionSort();    //使用选择排序
       
       ah.setSortObj(sort); //设置具体策略
       result=ah.sort(arr);
       
       for(int i=0;i<result.length;i++)
       {
               System.out.print(result[i] + ",");
       }
    }
}


 运行结果

      选择排序 
      1,2,3,4,5,6,7,9,10,

 

作者:qq_18738333 发表于2017/3/18 21:47:04 原文链接
阅读:55 评论:0 查看评论

第9章(3) 适配器设计模式

$
0
0

适配器设计模式:

参考:http://blog.csdn.net/ymeng_bupt/article/details/6833480

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

使用场景:

1、已经存在的类的接口不符合我们的需求;

2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作;

3、在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类。

模式中的角色:

1 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。

2 需要适配的类(Adaptee):需要适配的类或适配者类。

3 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。 

 

其实现方式主要有两种:

     1.类的适配器模式(采用继承实现)

     2.对象适配器(采用对象组合方式实现)

 

类适配器:

 

// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class Adaptee {
	public void specificRequest() {
		System.out.println("被适配类具有 特殊功能...");
	}
}


// 目标接口,或称为标准接口
interface Target {
	public void request();
}

// 具体目标类,只提供普通功能
class ConcreteTarget implements Target {
	public void request() {
		System.out.println("普通类 具有 普通功能...");
	}
} 


// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target{
	public void request() {
		super.specificRequest();
	}
} 


// 测试类
public class Client {
	public static void main(String[] args) {
		// 使用普通功能类
		Target concreteTarget = new ConcreteTarget();
		concreteTarget.request();
		
		// 使用特殊功能类,即适配类
		Target adapter = new Adapter();
		adapter.request();
	}
}



对象适配器:

 

// 适配器类,直接关联被适配类,同时实现标准接口
class Adapter implements Target{
	// 直接关联被适配类
	private Adaptee adaptee;
	
	// 可以通过构造函数传入具体需要适配的被适配类对象
	public Adapter (Adaptee adaptee) {
		this.adaptee = adaptee;
	}
	
	public void request() {
		// 这里是使用委托的方式完成特殊功能
		this.adaptee.specificRequest();
	}
}


// 测试类
public class Client {
	public static void main(String[] args) {
		// 使用普通功能类
		Target concreteTarget = new ConcreteTarget();
		concreteTarget.request();
		
		// 使用特殊功能类,即适配类,
		// 需要先创建一个被适配类的对象作为参数
		Target adapter = new Adapter(new Adaptee());
		adapter.request();
	}
}


作者:qq_18738333 发表于2017/3/18 21:54:15 原文链接
阅读:54 评论:0 查看评论

第9章(4) 工厂方法设计模式

$
0
0

工厂方法设计模式

参考:http://www.cnblogs.com/jingmoxukong/p/4016173.html#_label0

参考:http://blog.csdn.net/hguisu/article/details/7505909

工厂方法模式(Factory Method)定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其他子类。

工厂模式是一种类创建型模式。

通过工厂方法模式,我们的代码将完全与接口实现分离。

结构图:

 

具体代码:

abstract class Product {
    public abstract void Use();
}
class ConcreteProduct extends Product {
    public ConcreteProduct() {
        System.out.println("创建 ConcreteProduct 产品");
    }

    @Override
    public void Use() {
        System.out.println("使用 ConcreteProduct 产品");
    }
}
interface Creator {
    public Product FactoryMethod();
}
class ConcreteCreator implements Creator {
    @Override
    public Product FactoryMethod() {
        return new ConcreteProduct();
    }
}
public class FactoryMethodPattern {
    public static void main(String[] args) {
        Creator factory = new ConcreteCreator();
        Product product = factory.FactoryMethod();
        product.Use();//即使有更多不同的具体产品,依旧可以这样用
    }
}


使用场景

 1、当客户程序不需要知道要使用对象的创建过程。 
 2、客户程序使用的对象存在变动的可能,或者根本就不知道使用哪一个具体的对象。

注意:

1、增加一种产品类,就要增加一个工厂类。(每个工厂类只能创建一种产品的实例)

2、遵循开放-封闭原则。(新增一种产品并不需要修改原有类)

作者:qq_18738333 发表于2017/3/18 21:58:45 原文链接
阅读:63 评论:0 查看评论

公众平台测试帐号开发全流程第8篇-素材管理

$
0
0

这篇文章主要讲述微信公众平台开发的素材管理模块接口的开发

现在版本的素材接口有如下几个接口

  • 新增临时素材
  • 获取临时素材
  • 新增永久素材
  • 获取永久素材
  • 获取永久素材
  • 删除永久素材
  • 修改永久图文素材
  • 获取素材总数
  • 获取素材列表

本篇文章只对其中的新增其他类型永久素材(图片、音频和视频)进行介绍和分析,其他的跟以前的类似,就不在赘述。

看到上面那句话的时候,可能会产生一个疑问,为什么新增永久素材变成了新增其他类型永久素材?这里解释一下,永久素材总共分为两类,一个是图文素材另一个统称为其他类型素材(主要有视频和语音)。

下图是开发者调用接口获取access_token的序列图,一般来说系统都会设置个定时器,定时获取access_token(凭证有效时间,单位:7200秒)。当调用其他接口时,只需得到缓存中的access_token即可,这样可以避免多次访问微信后台。

Created with Raphaël 2.1.0开发者开发者微信公众平台微信公众平台AppID&AppSecret验证开发者发送的数据{"access_token":"ACCESS_TOKEN","expires_in":7200} or {"errcode":40013,"errmsg":"invalid appid"}

下图是开发者调用新增其他类型永久素材的序列图,开发者发送access_token、媒体文件类型和文件的其他信息(其中access_token和type跟在url后面即可,但是文件信息需要自己构造表单提交)。

Created with Raphaël 2.1.0开发者开发者微信公众平台微信公众平台access_token(调用接口凭证)&type(媒体文件类型)&media(form-data中媒体文件标识,有filename、filelength、content-type等信息)验证开发者发送的数据{"media_id":MEDIA_ID,"url":URL} or {"errcode":40007,"errmsg":"invalid media_id"}

接下来就是对各个接口进行介绍


新增其他类型永久素材

->接口调用请求说明

通过POST表单来调用接口,表单id为media,包含需要上传的素材内容,有filename、filelength、content-type等信息。请注意:图片素材将进入公众平台官网素材管理模块中的默认分组。

http请求方式: POST,需使用https
https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN
调用示例(使用curl命令,用FORM表单方式新增一个其他类型的永久素材,curl命令的使用请自行查阅资料)

->参数说明

参数 是否必须 说明
access_token 调用接口凭证
type 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
media form-data中媒体文件标识,有filename、filelength、content-type等信息

新增永久视频素材需特别注意

在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON,格式如下:
{
“title”:VIDEO_TITLE,
“introduction”:INTRODUCTION
}

下面就是核心代码,第一个参数传入接口拼装的url,第二个是file(我这边使用springmvc的,可以改成其他的),第三个是视频特有的表单字段(也就是{“title”:VIDEO_TITLE,”introduction”:INTRODUCTION})。返回的InputStream也就是微信公众平台返回的信息,进行转换后按照资格需求进行处理。

/**
     * Jul 28, 2016
     * 7:43:16 PM
     * @param urlStr
     * @param file
     * @param VideoDescriptionForm
     * @return
     * @throws IOException 
     * @Description: 上传文件
     */
    public static InputStream connectHttpAndUpload(String urlStr,MultipartFile file,String videoDescriptionForm) throws IOException{
        URL url = new URL(urlStr);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setRequestMethod("POST"); 
        connection.setDoOutput(true);  
        connection.setDoInput(true);
        connection.setUseCaches(false);

        //设置请求头信息
        connection.setRequestProperty("Connection", "Keep-Alive");
        connection.setRequestProperty("Charset", "UTF-8");
        String boundary = "-----------------------------"+System.currentTimeMillis();
        connection.setRequestProperty("Content-Type", "multipart/form-data; boundary="+boundary);

        // 请求正文信息
        // 第一部分:
        StringBuilder sb = new StringBuilder();
        sb.append("--"); // 必须多两道线
        sb.append(boundary);
        sb.append("\r\n");
        sb.append("Content-Disposition: form-data;name=\"media\";filelength=\""+file.getSize()+"\";filename=\""+ file.getOriginalFilename() + "\" \r\n");
        sb.append("Content-Type:application/octet-stream\r\n\r\n");
        byte[] head = sb.toString().getBytes("utf-8");

        // 获得输出流
        OutputStream out = new DataOutputStream(connection.getOutputStream());
        // 输出表头
        out.write(head);

        // 文件正文部分
        // 把文件已流文件的方式 推入到url中
        DataInputStream in = new DataInputStream(file.getInputStream());
        int bytes = 0;
        byte[] bufferOut = new byte[1024];
        while ((bytes = in.read(bufferOut)) != -1) {
                out.write(bufferOut, 0, bytes);
        }

        // 结尾部分
        byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
        out.write(foot);

        if (videoDescriptionForm != null) {
             StringBuilder sb1 = new StringBuilder();
             sb1.append("--"); // 必须多两道线
             sb1.append(boundary);
             sb1.append("\r\n");
             sb1.append("Content-Disposition: form-data;name=\"description\";");
             sb1.append("Content-Type:application/octet-stream\r\n\r\n");
             out.write(sb1.toString().getBytes());
             out.write(videoDescriptionForm.getBytes());
             out.write(("\r\n--" + boundary + "--\r\n").getBytes("utf-8"));// 定义最后数据分隔线
        }


        out.flush();
        out.close();
        in.close();


        return connection.getInputStream();
    }
作者:u013239111 发表于2017/3/18 22:13:23 原文链接
阅读:49 评论:0 查看评论

GIS讲堂第四课-大量POI点的展示

$
0
0

概述:

今天,阳光灿烂;今天,晴转多云。咦,怎么就突然间晴转多云了呢?算了,不管了,这不是我等小辈该思考的问题,还是就第四节课程的内容“大量POI点的展示”给大家做一个汇报吧。


课程详情:

1、课程主题

大量POI点的展示


2、问题提出

a、数据量大
成千上万,甚至百万级的数据量

b、数据传输慢
由于量比较大,导致数据传输慢

c、前台渲染受限制
不论是OL2(Arcgis3)还是OL3(Arcgis 4),前台的渲染都收到很大的挑战


3、主要内容

a、聚类


b、根据四至实时获取


c、点抽稀


d、后台生成图片


e、优劣对比



文章内容和相关代码大家可移步百度网盘一观究竟

http://pan.baidu.com/s/1slMypPV

---------------------------------------------------------------------------------------------------------------

技术博客

CSDN:http://blog.csdn.NET/gisshixisheng

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

在线教程

http://edu.csdn.Net/course/detail/799

Github

https://github.com/lzugis/

联系方式

q       q:1004740957

e-mail:niujp08@qq.com

公众号:lzugis15

Q Q 群:452117357(webgis)

             337469080(Android)

作者:GISShiXiSheng 发表于2017/3/18 22:17:09 原文链接
阅读:125 评论:0 查看评论

谷哥的小弟学后台(40)——MyBatis输出映射resultType以及resultMap

$
0
0

探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南


自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理


版权声明


我们知道:MyBatis通过resultType对sql的输出参数进行定义,参数的类型可以是:基本类型、HashMap、pojo。在此分别介绍为resultType传入三种类型的不同处理方式。

基本类型

在此,请看一个小例子:统计学生的女同学

先看mapper.xml

<select id="countStudent" parameterType="String" resultType='int'>
        SELECT count(*) from student where gender=#{value}
</select>

此处,resultType的类型是int

再来看mapper.java

public int countStudent(String string);

最后来看一下测试代码:

@Test
    public void countStudent() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        int count = studentMapper.countStudent("female");
        System.out.println("count="+count);
        sqlSession.commit();
        sqlSession.close();
    }

HashMap

把resultType的类型指定为hashmap时在执行完sql之后MyBatis将输出的字段名称作为map的key,value为字段值。现在,我们在上个例子的基础上稍加改造。

先看mapper.xml

<select id="countStudentByHashMap" parameterType="String" resultType='hashmap'>
        SELECT count(*) as total from student where gender=#{value}
</select>

此处,resultType的类型是hashmap。我们将查询的结果放在total列中

再来看mapper.java

public HashMap<String, Object> countStudentByHashMap(String string);

嗯哼,返回的类型是一个HashMap

最后来看一下测试代码:

@Test
    public void countStudentByHashMap() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        HashMap<String, Object> hashMap = studentMapper.countStudentByHashMap("female");
        System.out.println("total="+hashMap.get("total"));
        sqlSession.commit();
        sqlSession.close();
    }

我们从查询的结果hashmap中取出key为total对应的值

pojo

我们可将resultType指定为pojo,从而查询出对应的结果。比如,我们可以将resultType的类型指定为Student,从而查询出单个Student或者一个List,在此以查询单个Student为例

先来看mapper.xml

<select id="selectStudentByID" parameterType="int" resultType="cn.com.Student">
        SELECT * from student where id=#{value}
</select>

在此指定resultType的类型是Student

再来看mapper.java

public Student selectStudentByID(int id);

最后请看测试

@Test
    public void selectStudentByID() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        Student student = studentMapper.selectStudentByID(13);
        System.out.println(student);
        sqlSession.commit();
        sqlSession.close();
    }

在此请注意:

使用resultType进行输出映射时务必将查询的列名与pojo中的属性名保持一致!

看到这里,或许有人会问了:如果如果查询出来的列名和pojo的属性名不一致又怎么办呢?嗯哼,此时最好就不要再用resultType了,可以考虑使用resultMap,请继续往下看

resultMap

为了便于说明,我们来创建一个新的类_Student

/**
 * 本文作者:谷哥的小弟
 * 博客地址:http://blog.csdn.net/lfdfhl
 */
package cn.com;

import java.util.Date;

public class _Student {
    private int _id;
    private String _name;
    private String _gender;
    private Date _birthday;
    public int get_id() {
        return _id;
    }
    public void set_id(int _id) {
        this._id = _id;
    }
    public String get_name() {
        return _name;
    }
    public void set_name(String _name) {
        this._name = _name;
    }
    public String get_gender() {
        return _gender;
    }
    public void set_gender(String _gender) {
        this._gender = _gender;
    }
    public Date get_birthday() {
        return _birthday;
    }
    public void set_birthday(Date _birthday) {
        this._birthday = _birthday;
    }
    @Override
    public String toString() {
        return "_Student [_id=" + _id + ", _name=" + _name + ", _gender="
                + _gender + ", _birthday=" + _birthday + "]";
    }

}

首先在mapper.xml中定义一个resultMap

<resultMap type="cn.com._Student" id="studentResultMap">
    <id column="id" property="_id" />
    <result column="name" property="_name" />
    <result column="gender" property="_gender" />
    <result column="birthday" property="_birthday" />
</resultMap>
  • <resultMap>标签中的type属性表示pojo,请参见代码第1行
  • <resultMap>标签中的id属性表示resultMap的名字,请参见代码第1行
  • <id>标签表示数据库表中的主键与pojo的属性的映射关系;比如,此处,将表中的id字段映射为pojo中的_id,请参见代码第2行
  • <restult>标签表示数据库表中除了主键以外的其他字段与pojo的属性的映射关系,请参见代码第3-5行

然后请看mapper.xml中的SQL语句

<select id="_selectStudentByID" parameterType="int" resultMap="studentResultMap">
       SELECT * from student where id=#{value}
</select>

此处利用resultMap指定了返回的类型为我们刚才定义的studentResultMap

再来看mapper.java

public Student _selectStudentByID(int id);

最后来瞅瞅测试代码

    @Test
    public void _selectStudentByID() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        _Student _student = studentMapper._selectStudentByID(13);
        System.out.println(_student);
        sqlSession.commit();
        sqlSession.close();
    }

输出结果:

_Student [_id=13, _name=大泽玛利亚, _gender=female, _birthday=Thu Mar 16 00:00:00 CST 2017]

作者:lfdfhl 发表于2017/3/18 22:29:38 原文链接
阅读:133 评论:0 查看评论

谷哥的小弟学后台(41)——MyBatis的动态SQL

$
0
0

公众平台测试帐号开发全流程第9篇-模板消息

$
0
0

本片文章将讲述微信公众平台消息管理中的模板消息

接下来对模板消息进行一番介绍(也就是公众开发者文档考过来的^_^,当然自己的使用过程也会穿插进去)

模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息。具体模板消息运营规则请读模板消息运营规范
关于使用规则,请注意:

1、所有服务号都可以在功能->添加功能插件处看到申请模板消息功能的入口,但只有认证后的服务号才可以申请模板消息的使用权限并获得该权限;
2、需要选择公众账号服务所处的2个行业,每月可更改1次所选行业;
3、在所选择行业的模板库中选用已有的模板进行调用;
4、每个账号可以同时使用25个模板。
5、当前每个账号的模板消息的日调用上限为10万次,单个模板没有特殊限制。【2014年11月18日将接口调用频率从默认的日1万次提升为日10万次,可在MP登录后的开发者中心查看】。当账号粉丝数超过10W/100W/1000W时,模板消息的日调用上限会相应提升,以公众号MP后台开发者中心页面中标明的数字为准。

关于接口文档,请注意:

1、模板消息调用时主要需要模板ID和模板中各参数的赋值内容;
2、模板中参数内容必须以”.DATA”结尾,否则视为保留字;
3、模板保留符号”{{ }}”。

关于模板消息内容的设置

下面这张图就是我设置的一个模板消息
这里写图片描述
下面这张是测试账号里面新增弹出的框题,其中内容的换行直接敲回车就可以了。当时测试的时候又是加/n和//n的,发现自己好傻。。。
然后参数就是{{xx.DATA}}xx不可以重复,因为调用时需要为他们设置值,就跟实体Bean一样。
这里写图片描述

内容设置完了,后面就是代码调用了


下面是序列图

Created with Raphaël 2.1.0开发者开发者微信公众平台微信公众平台设置模板内容填写的参数access_token&template_id&其他一些信息验证开发者发送的参数成功消息or失败消息

下面这块代码是获取模板消息json,TemplateMsg 是封装的一个实体Bean,包括发送人和其他一些信息。

private static String getTemplateJson(TemplateMsg templateMsg) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("touser", templateMsg.getTouser());
        jsonObject.put("template_id", TEMPLATEID);
        jsonObject.put("url", templateMsg.getUrl());

        JSONObject data = new JSONObject();

        JSONObject result = new JSONObject();
        result.put("value", templateMsg.getResult());
        result.put("color", "#173177");

        JSONObject bookTitle = new JSONObject();
        bookTitle.put("value", templateMsg.getBookTitle());
        bookTitle.put("color", "#173177");

        JSONObject timeName = new JSONObject();
        timeName.put("value", templateMsg.getTimeName());

        JSONObject time = new JSONObject();
        Date currentTime = templateMsg.getTime();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd HH:mm:ss");
        String dateString = formatter.format(currentTime);
        time.put("value", dateString);
        time.put("color", "#173177");

        JSONObject msg = new JSONObject();
        msg.put("value", templateMsg.getMsg());
        msg.put("color", "#173177");


        data.put("result", result);
        data.put("bookName", bookTitle);
        data.put("timeName", timeName);
        data.put("time", time);
        data.put("msg", msg);

        jsonObject.put("data", data);

        return jsonObject.toString();
    }

从上面那个代码块获取到json后,调用下方代码块获得InputStream 解析即可。调用方式:connectHttp(url, "POST", getTemplateJson(templateMsg).getBytes("UTF-8"));

public static InputStream connectHttp(String urlStr,String method,byte[] bs) throws IOException{
        URL url = new URL(urlStr);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setRequestMethod(method);
        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.connect();

        if (bs != null) {
            OutputStream outputStream = connection.getOutputStream();
            outputStream.write(bs);
            outputStream.flush();
            outputStream.close();
        }

        return connection.getInputStream();
    }
作者:u013239111 发表于2017/3/18 22:44:45 原文链接
阅读:47 评论:0 查看评论

Priority-Queue模式

$
0
0

Priority Queue模式

优先级队列模式:将请求根据优先级进行分流,令高优先级的请求能够比低优先级的请求更快的接收和处理。优先级队列模式对于针对需要根据不同的客户端来实现不同的服务级别的情况下十分实用。

问题

应用可能会将某些指定的任务代理到一些其他的服务或应用上面。比如,会存在需要执行一些后台处理,或者与其他应用或者服务集成的情况。在云环境中,通常使用消息队列来代理任务和后台的处理。在很多时候,服务所接收的请求的顺序并不是十分重要的。然而,在一些特殊的场景下,是需要对特殊的一些请求进行优先处理的。这些请求需要比其它请求能够更优先的来处理。

解决方案

队列通常来都是属于FIFO的结构,消费者的消费顺序,通常来说和收到信息的顺序也是一样的。然而,有些消息队列是支持优先级处理的。应用在发送消息的时候,会将消息附加一个优先级,然后再添加到消息队列中,这样高优先级的消息会比低优先级的信息优先处理。图1说明了优先级消息队列的处理方式。
这里写图片描述

图1
使用支持消息优先级的队列机制

很多的消息队列实现都是支持多个消费者的(详见Competing-Consumers模式),并且消费者可以根据需求向上或者向下扩展的。

在那些不支持优先级消息队列的系统中,一种可选择的方案就是在为不同的优先级额外维护一个单独的队列。每个队列拥有各自的消费者。高优先级的队列拥有更多的消费者,并且比低优先级的队列享有更好的硬件。图2展示了该种实现。

这里写图片描述

图2
为每一种优先级单独维护一条优先级队列

该策略的另一个变种是仅有一个消费者的集合,但是消费者会优先检查高优先级的队列,当高优先级的消息队列存在消息的时候,优先消费高优先级的消息,然后再去消费低优先级的消息。使用一个消费者集合消费多个队列和每一个消费者单独消费一个队列在语义上还是有所不同的。

在仅仅使用一个消费者集合的方法中,高优先级的消息会总是比低优先级的消息优先接收和处理的。理论上来说,很低优先级的消息可能会一直被推迟无法执行的。但是在多个消费者集合的方法中,低优先级的消息仍然是可以消费掉的。只是处理的速度要比高优先级队列的速度要慢(具体的速度区别,要取决于消费池的大小和可用资源的差别)。

使用优先级队列技术可以带来以下好处:

  • 优先级队列可以令应用根据业务上的需求来根据可用性和性能来定制优先级。比如需要领服务为指定的客户提供不同的服务级别的情况下。
  • 优先级队列可以有效减少操作性消耗。在单一队列方法中,开发者在必要的情况下,可以削减消费者的数量。高优先级的消息将仍然优先处理(尽管处理的更慢),而低优先级的消息可能推迟更久。如果开发者实现了多个消费者队列的方法并每个队列使用单独的消费者,开发者可以减少低优先级队列的消费者数量,甚至暂时挂起很低优先级的队列,为高优先级队列让出更多的资源。
  • 多消息队列的方法可以帮助最大化应用的性能,而且可以较为容易的通过分区进行横向扩展。举例来说,一些关键的任务,可以配置高优先级,由接受者立刻处理,而不那么重要的任务,可以由接受者在不太繁忙的情况下运行。

实现优先级队列需要考虑的一些问题

在考虑实现优先级队列的时候需要考虑如下问题:

  • 为解决方案定义不同的优先权。举个例子,高优先级可能意味着消息应该在10秒内处理。根据需求来处理高不同先级的消息,并为不同优先级的情况分配合适的资源。
  • 实现优先级队列需要决定是否所有高优先级的请求必须在低优先级的请求之前执行。如果消息都是由一个消费池处理的话,可能需要实现一些机制来抢占执行,以便在高优先级消息可用的情况下,并延迟当前对低优先级任务的处理,优先处理高优先级的任务。
  • 在使用多个队列的方法时,如果没有针对每个队列使用单独的消费池,而是使用的是一个消费池来监听所有的消息队列的话,那么消费者必须实现算法来保证消费者总是优先处理高优先级的队列,而不是低优先级的队列。
  • 实现优先级队列的时候还需要监控低优先级和高优先级队列的处理消息的速度,来保证队列中消息的处理达到开发者预期的速度。
  • 如果开发者需要保证低优先级的任务也需要执行的话,可以考虑使用多个消息队列,每个消息队列都有自己的消费池的方法。当然,如果一个队列支持消息的优化,能够根据消息存在的时间动态的调整消息的优先级的话,也是可以的。但是这种情况下,更多的依赖的是消息队列本身的特性了。
  • 当在系统已经定义了少量的优先级的情况下,为每个优先级,使用单独的队列较为合适。
  • 消息的优先级可能是由系统的逻辑来决定的。举个例子,相对于明确的高优先级和低优先级的消息,很多系统可能会根据业务的情况将消息定义为“付费客户”或者“非付费客户”。开发者的系统可以根据情况来为付费用户分配更多的资源,而为非付费用户提供较少的资源。
  • 查询和处理队列中的消息可能会带来一些额外的费用(有些商业消息系统会在每次发送和接收消息的时候收取少量的费用)。如果使用多个多个队列的话,这些代价也会随之增加。
  • 实现优先级队列模式的时候,可以考虑根据队列的长度来动态调整消费池的大小。想了解更多的信息,可以参考Autoscaling Guidance.

何时使用优先级队列模式

优先级队列模式十分适合以下场景:

  • 当系统必须针对不同的任务使用不同优先级来处理的时候,可以考虑使用优先级模式。
  • 当系统的不同的用户需要提供不同级别的服务的时候,可以考虑使用优先级模式。

使用举例

Windows Azure并不提供那种能够通过排序自动调整消息优先级的队列机制。但是,Windows Azure是提供Windows Azure Service Bus主题和订阅的,Windows Azure的服务总线提供队列服务,同时还支持对消息进行过滤等很多灵活的能力,可以很好的支持优先级队列的实现。

Windows Azure的解决方案可以将实现一个服务总线,指定一个主题,来支持应用以队列的方式发送消息。消息可以包含应用所自定义的格式的元数据。服务总线的订阅可以根据其主题相关联,订阅可以根据他们自己的一些属性对消息进行过滤。当应用将消息发送到某个主题时,消息会根据消费者的订阅进行转发到指定的消费者。消费者通过订阅来获取消息,就如同从消息队列中获取消息是一样。

图3展示了一个使用了Windows Azure Service Bus主题和订阅的解决方案

这里写图片描述

图3
基于Windows Azure Service Bus的主题和订阅的优先级队列实现

在图3中,应用创建了一些消息,并为每个消息指定了一个优先级属性,为High或者Low。应用将这些消息发送给某个主题。该主题包含两个相关的订阅,通过检查消息的优先级属性来过滤消息。一个消息订阅用来接收优先级属性为High的消息,而另一个消息订阅用来接收优先级属性为Low的消息。消费池会读取每一个订阅中的消息。高优先级的订阅配置了更大的消费池,这些消费者可以运行在性能更加,使用了更多资源的集群上,而低优先级的消费者运行在性能稍差的集群上。
需要注意的是,在该例子中,我们并没有为高优先级或者低优先级做特别的设计。它们仅仅是以标签存在于每个消息中的属性之中,并且只是用来将消息转发到对应的订阅源的。如果需要增加额外的需求,开发者也可以相对轻松的定制其他的订阅和消费池。

本文也包含了一个可用的PriorityQueue解决方案。该方案中包含了2个优先级角色,分别是PriorityQueue.HighPriorityQueue.Low。他们都继承于类PriorityWorkerRole,其中OnStart()方法中包含了一些函数用来连接到特定的订阅源。优先级为PriorityQueue.HighPriorityQueue.Low的消费者连接的是不同的订阅源。管理员可以为每个优先级的消费池进行自定义配置。一般情况下,在PriorityQueue.High的消费池中的消费者要更多些。

PriorityWorkerRole类中的Run()方法将ProcessMessage(BrokeredMessage message)作为回调,消费者(PriorityWorkerRole)收到了消息的时候,进行回调。下面的代码展示了Run()方法和ProcessMessage(BrokeredMessage message)QueueManager类,则定义在PriorityQueue.Shared项目中,为使用Windows Azure Service Bus队列提供一些辅助方法。

public class PriorityWorkerRole : RoleEntryPoint
{
    private QueueManager queueManager;
    ...
    public override void Run()
    {
        // Start listening for messages on the subscription.
        var subscriptionName = CloudConfigurationManager.GetSetting("SubscriptionName");
        this.queueManager.ReceiveMessages(subscriptionName, this.ProcessMessage);
        ...;
    }
    ...
    protected virtual async Task ProcessMessage(BrokeredMessage message)
    {
        // Simulating processing.
        await Task.Delay(TimeSpan.FromSeconds(2));
    }
}

优先级为PriorityQueue.HighPriorityQueue.Low消费者都覆盖了PriorityWorkerRole中的ProcessMessage(BrokeredMessage message)方法。下面的代码是PriorityQueue.High消费者的一个例子。

protected override async Task ProcessMessage(BrokeredMessage message)
{
    // Simulate message processing for High priority messages.
    await base.ProcessMessage(message);
    Trace.TraceInformation("High priority message processed by " +
        RoleEnvironment.CurrentRoleInstance.Id + " MessageId: " + message.MessageId);
}

当应用将消息发送到指定的主题的时候,PriorityQueue.HighPriorityQueue.Low都会订阅对应主题的消息,应用可以通过指定消息的属性来配置对应的优先级,消费者然后根据优先级来进行过滤获取对应的消息。QueueManager中定义的SendBatchAsync()方法来分批的将消息发送给配置的主题。

// Send a low priority batch.
var lowMessages = new List<BrokeredMessage>();
for (int i = 0; i < 10; i++)
{
    var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
    message.Properties["Priority"] = Priority.Low;
    lowMessages.Add(message);
}
this.queueManager.SendBatchAsync(lowMessages).Wait();
...
// Send a high priority batch.
var highMessages = new List<BrokeredMessage>();
for (int i = 0; i < 10; i++)
{
    var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
    message.Properties["Priority"] = Priority.High;
    highMessages.Add(message);
}
this.queueManager.SendBatchAsync(highMessages).Wait();

相关的其他模式

在考虑实现优先级队列模式的时候,也可以同时参考如下文章:

  • Asynchronous Messaging Primer。消费者服务处理请求需要将依赖信息发送给产生消息的应用实例的。Asynchronous Messaging Primer中描述了更多的关于实现基于request/response消息策略的信息。
  • Competing Consumers模式。为了增加队列的吞吐,是很有可能有多个消费者监听同一个队列,并且并行的执行任务的。这些消费者会处理消息,但是每个消费者只能处理一个消息。Competing-Consumers模式中描述了关于实现多个消费者的利弊之间的取舍的信息。
  • Throttling模式。开发者可以通过使用队列来实现节流。优先级消息可以用来确保那些来自于关键应用的请求,能够比那些没有那么重要的应用优先处理。
  • Autoscaling Guidance。令消费池的大小随着队列的长度进行自动弹性扩展来处理较多的请求。该策略可以帮助增强性能,尤其是当队列过长时,及时增加高优先级的消费池大小,可以极大的提高吞吐。
作者:EthanWhite 发表于2017/3/19 0:46:02 原文链接
阅读:31 评论:0 查看评论

索引

$
0
0

知识点的梳理:

  1. 可以使用自动跟踪功能,来判断当前查询语句是否使用的全表扫描。如果使用索引来查询,也可以通过这种方式来分辨当前是索引查询,还是全表扫描;
  2. 索引是提升数据库查询性能的一种手段,但是频繁更新数据表时,索引反而会造成性能的降低;


这是啥?

索引是专门用来提升数据库查询操作性能的;在Oracle中,为了维护这种查询性能,需要对某一个类数据进行指定结构的排列;

B树索引

该索引是Oracle默认建立的索引;
该索引在检索“高基数数列”(该列上的重复内容较少或没有)的时候可以提高性能;
示例:如果当前表中存在10W行数据,而2W行记录之后已经不存在符合查询要求的数据,如果此时继续向下采用逐行扫描的方式明显是浪费资源,这个时候就可以采用树形排序方式;
假设该表中的工资数据为:1300,2850,1100,1600,2450,2975,5000,3000,1250,950,800,则按以下原则进行树结构的绘制:

取第一个数据作为根节点;

比根节点小的数据放在左子树,比根节点大的数据放在右子树;

在进行数据排序的时候,除了使用sal字段的内容之外,每一个操作节点中还保存了一个ROWID的信息,而利用此ROWID的信息就可以找到对应的完整记录;


B树索引结构分析

叶子节点(Leaf Node):包含直接指向表中的数据行(即:索引项);该节点保存的就是索引项,而索引项由3个部分组成:

索引项头:存储了行数和所得信息;

索引列长度和值:两者需要同时出现,定义了列的长度,在长度之后保存的就是列的内容;

ROWID:指向表中数据行的ROWID,通过此ROWID找到完整记录;

分支节点(Branch Node):包含指向索引里其他的分支节点或叶子节点;

根节点(Root Node):一个B树索引只有一个根节点,是位于最顶端的分支节点;


创建B*Tree索引方法:

方法1:当某一个列上设置了主键约束或唯一约束,则会自动创建约束;

方法2:利用命令直接创建索引;


语法:

create index [用户名.]索引名 on[用户名.]表名称 (列名称 [ASC|DESC],....);

create index emp_sal_ind on emp(sal);
--在emp表的sal字段上创建了一个索引,这会自动地在内存中将相关的数据形成一颗索引树,以提升查询性能;


通过user_indexes数据字典,查看哪张表存在索引:

SELECT index_name , index_type , table_owner , table_name , uniqueness , status FROM user_indexes ;


通过emp_sal_ind数据字典,查看表中的哪列存在索引:

SELECT * FROM user_ind_columns WHERE index_name='EMP_SAL_IND' ;


如何利用B树索引提升查询性能?

最方便的方法是准备两张表,A表进行数据的更新,B表在每天数据库空闲时间,将A表更新后的数据保存在B表,同时B表上设置索引,用户检索利用B表,更新使用A表,就可以提升性能;

但是这样做会牺牲速度;

位图索引

当某一个列的数据都属于低基数列的时候,就可以利用位图索引来提升性能;
例如,表示雇员的数据表上会存在部门编号的数据列,而在部门编号列上只有3种取值,分别是10,20,30,此时就可以使用位图索引;如果雇员的数据表包含30万条数据,那么按照位图索引,可以将这些数据,按照部门编号分成3组进行查询,在效率上是一定快过直接进行全表扫描的;


语法:create bitmap index [用户名.]索引名称 on[用户名.]表名称 (列名称 [asc|desc],...);

示例:CREATE BITMAP INDEX emp_deptno_ind ON emp(deptno) ;

通过“user_indexes”数据字典查看索引
SELECT index_name , index_type , table_owner , table_name , uniqueness , status FROM user_indexes ;



删除索引

索引自身需要进行数据结构的维护,会占用较大的磁盘空间,随着表的增长,索引所占的空间也会增大;索引被删除后,其所占空间也会被一同释放;
删除语法:drop index索引名称;
示例:DROPINDEX emp_sal_ind ;



作者:qq_33301113 发表于2017/3/19 11:29:59 原文链接
阅读:161 评论:0 查看评论

移动端真机调试方法介绍

$
0
0

移动端调试困难

很多时候,我们在进行移动端开发时,都是先在PC端使用手机模拟器进行调试,没有问题后,我们才会在手机端的浏览器进行测试,这个时候,如果没有出现问题,皆大欢喜。但是一旦出现问题,我们就很难解决,因为缺乏可视化的界面。不似在PC端,我们能直观的去改变样式,或者是进行断点调试。有时,在移动端我们不得不借助于alert来调试,但是这样的调试方法效率极其低下,很多时候,都是靠经验,或者是靠排除法。甚至,我们不得不归结为是浏览器的实现问题。

解决策略

主要介绍三种调试方法,皆可行,至于选择哪一种方式,就看自己的喜欢了。

  1. chrome真机调试
  2. weinre调试
  3. spy-debugger调试
    先说一下每一种方式的优缺点。
    第一种,chrome真机调试,有一个很大的局限性就是,只能调试手机端的chrome浏览器,对于UC,QQ这些浏览器均不适用,因此在调试兼容问题时,帮助不大,但是最大的优点是: 简单快捷。
    第二种,weinre调试方式,安装和适用不复杂,适用于全平台的调试,即任何手机的任何浏览器皆可以调试,不过需要手机和电脑在同一个网段下。
    第三种,spy-debugger,安装时,稍微复杂一点,spy-debugger集成了weinre,不过还增加了抓包工具。使用最为方便。

下面逐一介绍这三种方法:

chrome真机调试

手机端下载好chrome浏览器,使用USB连接到PC,打开手机的USB调试模式。
然后在PC端打开chrome浏览器,在地址栏输入: chrome://inspect. 勾选”discovery usb device”。然后在手机端浏览网页,就可以看到如下的页面,点击inspect,进行调试。(鉴于我的工作电脑是加了域的,因为并不能使用这个方式,如果有和我一样情况得童鞋,可以考虑使用另外两种调试方式)

这里写图片描述

weinre

Weinre(WebInspector Remote)是一款基于Web Inspector(Webkit)的远程调试工具,借助于网络,可以在PC上直接调试运行在移动设备上的远程页面。

这里写图片描述
本地服务器,可以使用http-server、tomcat等,也可以使用编译器集成的服务

weinre安装

全局安装: npm install –g weinre
局部安装: npm install weinre
启动: weinre –httpPort 8090 –boundHost -all-
如果是局部安装的话,需要在前面加上 node_modules/.bin/
相信前端的童鞋都会用npm包管理工具,对于这个工具,我就不展开了,如果没有安装npm的,自行安装。

weinew启动参数说明:
–httpPort: 设置Wninre使用的端口号,默认是8080
–boundHost: [hostname | Ip | -all-]: 默认是 ‘localhost’.
–debug [true | false] : 这个选项与–verbose类似, 会输出更多的信息。默认为false。
–readTimeout [seconds] : Server发送信息到Target/Client的超时时间, 默认为5s。
–deathTimeout [seconds] : 默认为3倍的readTimeout, 如果页面超过这个时间都没有任何响应, 那么就会断开连接。

8080端口使用情况较多,所以我选择了指定8090端口。
启动了weinre之后,我们在浏览器中输入localhost:8090.显示如下界面,表示已经启动成功。
这里写图片描述

点击debug client user interface,进入调试页面。
这里写图片描述

当前的targets中内容为空。

现在,我们需要做另外一点操作,在我们要调试的页面中,增加一个脚本。

<script src="http://localhost:8090/target/target-script-min.js#anonymous"></script>

记住将localhost换成你的IP地址.

然后,我们在本地启动一个服务器,可以是IDE继承了服务器,或者是http-server,我使用的是http-server.启动之后,我们在手机端访问要调试的网页。然后就会发现targets下面增加了记录。

这是,点击Elements进行调试。
这里写图片描述

修改样式时,会在手机端即时生效,并且也可以查看控制台信息,唯一一点就是,不能进行断点调试。
还有就是,在调试结束之后,别忘记删除嵌入的script。

除了这种方法之后,还介绍了在手机端保存一段Js代码,在需要调试某个页面时,点击执行JS,但是现在浏览器为了安全起见,已经不再支持此方法。默认的方法是搜索,而非执行,所以不可取。

spy-debugger

最后,再介绍一下spy-debugger方法。用这个方法,我们不再需要自己增加和删除脚本。

Spy-debugger内部集成了weinre,通过代理的方式拦截所有html自动注入weinre所需的js代码。简化了weinre需要给每个调试的页面添加js代码。spy-debugger原理是拦截所有html页面请求注入weinre所需要的js代码。让页面调试更加方便。

特性:
1、页面调试+抓包
2、操作简单
3、支持HTTPS。
4、spy-debugger内部集成了weinre、node-mitmproxy、AnyProxy。
5、自动忽略原生App发起的https请求,只拦截webview发起的https请求。对使用了SSL pinning技术的原生App不造成任何影响。
6、可以配合其它代理工具一起使用(默认使用AnyProxy)

Spydebugger安装与使用
1.安装: 全局安装 npm install –g spy-debugger
局部安装 npm install spy-debugger

2.启动: spy-debugger,如果是局部安装,需要加 node_modules/.bin

3.设置手机的HTTP代理
代理的地址为 PC的IP地址 ,代理的端口为spy-debugger的启动端口(默认端口为:9888)
默认端口是 9888。
如果要指定端口: spy-debugger –p 8888
Android设置步骤:设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动
iOS设置代理步骤:设置 - 无线局域网 - 选中网络 - HTTP代理手动
4.手机安装证书(node-mitmproxy CA根证书)
扫一扫安装:
这里写图片描述

Spy-debugger启动界面,同样,在手机端刷新页面之后,targets中会有记录
这里写图片描述

总结:
1.chrome inspect应用场景有限
2.weinre安装简单,使用过程中需要增加和删除script,如果调试页面很多的情况下,不适用。
3.spy-debugger安装略复杂,但是使用过程非常愉快。

作者:liuyan19891230 发表于2017/3/19 11:56:23 原文链接
阅读:157 评论:0 查看评论

react/reactNative预热篇

$
0
0

关于react,还是会以ES6的写法为主,前端在发展,ES7已经来了,我们总不能一直停留在ES5时代,当然啦,如果你能好好理解ES5的对象,继承,以及this。那自然是再好不过的一件事。

学习ES6,书籍可以不用准备,但是阮一峰ES6的教程还是需要看一下的。
http://es6.ruanyifeng.com/

infoQ上的深入浅出ES6系列,也很是不错。 http://www.infoq.com/cn/articles/es6-in-depth-arrow-functions

当然啦,如果仅仅是react,我们大可不必将这些知识逐一学习,毕竟这也是不小的时间成本,我建议学习的章节是: let,const命令,变量的解构赋值,函数的扩展,Class和Module.至于其它内容,可以在需要的时候再看。

另外,就是webpack啦,现在webpack已经是2.2版本了,和第一版相比还是要不少差异,建议还学习一下,当然我觉得会用即可,是否需要一步步深究,就要看自己的兴趣了。我们在配置过一两个项目之后,后期可能就会使用已有的配置修修改改了。所以,我们可以配置一个脚手架工程,方便我们日后的使用。
https://webpack.js.org/guides/
如果你的英文实在是差强人意,并且你还非常不喜欢看英文文档,那么请移步webpack2中文文档: http://www.css88.com/doc/webpack2/guides/development/

当然啦,学习react的后期,我们可能免不了还有学习react-router,redux等等的知识,不过那些都是在我们学习了基础之后的事情了。
react中文 http://reactjs.cn/react/index.html

至于RN呢,恩,相信很多前端同学都略有耳闻,或者已经进行了RN的开发工作.学习RN,如果你有Mac,当然是一件再好不过的事情,如果没有,我们同样可以在windows下跑RN,也可以在windows中安装Mac虚拟机,或者装双系统,总之,我们都是有各种各样的方法的。RN只是搭建环境复杂一点。在编写RN的时候,如果你有react的基础,剩下的无非就是熟悉RN中的组件和API,并不是什么难事。
reactNative中文 http://reactnative.cn/

作者:liuyan19891230 发表于2017/3/19 12:25:16 原文链接
阅读:241 评论:0 查看评论

Weex基于Vue2.0开发框架模板搭建

$
0
0

Weex基于Vue2.0开发框架搭建

前言

最近有一些人反馈说在面试过程中常常被问到weex相关的知识,也侧面反映的weex的发展还是很可观的,可是目前weex的开发者大多数是中小型公司或者个人,大公司屈指可数,揪其原因可能是基于weex的开发正确的姿势大家并没有找到,而且市面上的好多轮子还是.we后缀的,众所周知,weex和vue一直在努力的进行生态互通,而且weex实现web标准化是早晚的问题,今天和大家分享一下weex基于vue2.0的开发框架模板~

工作原理

先简单熟悉一下weex的工作原理,这里引用一下weex官网上的一直图片,详细信息见官网

Weex工作原理

开发环境搭建

weex 开发环境搭建

关于weex开发环境搭建问题见官方文档

android 、iOS 开发环境

关于native开发环境搭建问题见官方文档

框架说明

基于vue2.0搭建

像前面说的那样weex和vue一直在努力的进行生态互通,而且weex实现web标准化是早晚的问题,所以也建议开发者不要在用.we做后缀来开发了

多页模式(抛弃vue-router)

单页形态对于原生可能体验不够好,目前在 native App 里单页模式不太合适

集成三端(android、ios、h5平台)

关于android、ios、h5平台的集成与打包问题,在项目中都以解决~

集成eslint代码检查

代码检查是必要的操作,为了能够拥有vue开发的体验,将eslint集成进来~

注:

由于weexpack暂不支持vue问题,打包相关后续会集成进来~

框架介绍

package.json依赖

"dependencies": {
    "vue": "^2.1.8",
    "vue-router": "^2.1.1",
    "vuex": "^2.1.1",
    "vuex-router-sync": "^4.0.1",
    "weex-vue-render": "^0.1.4"
  },
  "devDependencies": {
    "babel-core": "^6.20.0",
    "babel-eslint": "^7.1.1",
    "babel-loader": "^6.2.9",
    "babel-preset-es2015": "^6.18.0",
    "css-loader": "^0.26.1",
    "eslint": "^3.15.0",
    "eslint-config-standard": "^6.2.1",
    "eslint-loader": "^1.6.1",
    "eslint-plugin-html": "^2.0.1",
    "eslint-plugin-promise": "^3.4.2",
    "eslint-plugin-standard": "^2.0.1",
    "postcss-cssnext": "^2.9.0",
    "serve": "^1.4.0",
    "vue-loader": "^10.0.2",
    "vue-template-compiler": "^2.1.8",
    "webpack": "^1.14.0",
    "weex-devtool": "^0.2.64",
    "weex-loader": "^0.4.1",
    "weex-vue-loader": "^0.2.5"
  }

打包配置

1、 遍历.vue文件后缀,生成相应的entry.js文件

function getEntryFileContent (entryPath, vueFilePath) {
  const relativePath = path.relative(path.join(entryPath, '../'), vueFilePath);
  return 'var App = require(\'' + relativePath + '\')\n'
    + 'App.el = \'#root\'\n'
    + 'new Vue(App)\n'
}

function walk (dir) {
  dir = dir || '.'
  let directory = path.join(__dirname, './src', dir)
  let entryDirectory = path.join(__dirname, './src/entry');
  fs.readdirSync(directory)
    .forEach(file => {
      let fullpath = path.join(directory, file)
      let stat = fs.statSync(fullpath)
      let extname = path.extname(fullpath)
      if (stat.isFile() && extname === '.vue') {
        let entryFile = path.join(entryDirectory, dir, path.basename(file, extname) + '.js')
        fs.outputFileSync(entryFile, getEntryFileContent(entryFile, fullpath))
        let name = path.join(dir, path.basename(file, extname))
        entry[name] = entryFile + '?entry=true'
      } else if (stat.isDirectory()) {
        let subdir = path.join(dir, file)
        walk(subdir)
      }
    })
}

walk()

2、通过weex-loader打包生成native jsbundle
3、 通过weex-vue-loader打包生成web jsbundle

function getBaseConfig () {
  return {
    entry: entry,
    output: {
      path: 'dist'
    },
    resolve: {
      extensions: ['', '.js', '.vue'],
      fallback: [path.join(__dirname, './node_modules')],
      alias: {
        'assets': path.resolve(__dirname, './src/assets/'),
        'components': path.resolve(__dirname, './src/components/'),
        'constants': path.resolve(__dirname, './src/constants/'),
        'api': path.resolve(__dirname, './src/api/'),
        'router': path.resolve(__dirname, './src/router/'),
        'store': path.resolve(__dirname, './src/store/'),
        'views': path.resolve(__dirname, './src/views/'),
        'config': path.resolve(__dirname, './config'),
        'utils': path.resolve(__dirname, './src/utils/')
      }
    },
    module: {
      preLoaders: [
        {
          test: /\.vue$/,
          loader: 'eslint',
          exclude: /node_modules/
        },
        {
          test: /\.js$/,
          loader: 'eslint',
          exclude: /node_modules/
        }
      ],
      loaders: [
        {
          test: /\.js$/,
          loader: 'babel',
          exclude: /node_modules/
        }, {
          test: /\.vue(\?[^?]+)?$/,
          loaders: []
        }
      ]
    },
    vue: {
      postcss: [cssnext({
        features: {
          autoprefixer: false
        }
      })]
    },
    plugins: [bannerPlugin]
  }
}

const webConfig = getBaseConfig()
webConfig.output.filename = 'web/[name].js'
webConfig.module.loaders[1].loaders.push('vue')

const weexConfig = getBaseConfig()
weexConfig.output.filename = 'weex/[name].js'
weexConfig.module.loaders[1].loaders.push('weex')

项目结构

weex-frame
├── android (android项目)
│       
├── ios (ios项目代码)
│
├── src (weex模块)
│      ├── api (api模块)
│      ├── components(组件模块) 
│      ├── constants(常量配置)   
│      ├── utils (工具模块)   
│      └── views(视图模块)  
│
└── dist (build输出模块)
       ├── weex (native使用jsbundle)
       └── web(web使用jsbundle) 

项目启动

  1. git clone git@github.com:osmartian/weex-frame.git
  2. cd weex-frame
  3. npm install
  4. 执行 ./start

android 启动

  1. 打开andorid studio
  2. File -> New -> Import Project -> weex-frame/android -> 启动

iOS 启动

  1. cd ios
  2. pod install (未安装pod,请先安装)
  3. open WeexFrame.xcworkspace

h5 启动方式

打开 http://localhost:12580/weex.html

项目示例

h5 端示例

h5我的页面

h5发起页面

android 端示例

android首页

android我的页面

android发起页面

iOS 端示例

iOS首页

iOS我的页面

iOS发起页面

结语

能看的出来上方的三端示例表现还是很一致的,本篇博文也是想给大家提供一个轮子,也欢迎大家多多提意见,共同促进weex生态成熟~

框架项目地址:

https://github.com/osmartian/weex-frame

作者:walid1992 发表于2017/3/19 14:33:42 原文链接
阅读:270 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live