[转载]Node.js之绝对选择 - 玄歌 - 博客园

mikel阅读(1115)

[转载]Node.js之绝对选择 – 玄歌 – 博客园.

几年前,完全放弃ASP.NET,彻底脱离微软方向。Web开发,在公司团队中,一概使用Node.js、Mongodb、Git,替换ASP.NET mvc、SQL server和Tfs。当时来看,这是高风险的决定。所有人都习惯了Asp.net,知识和技术积累也集中在这个方向。

表面看来,仅仅是我个人对多年跟从微软的厌烦,导致整个技术路线嘎然而止,从技术角度而言,团队由此南辕北辙。几年过去,各种辛苦和折腾,间或的彼此抱怨 之后,我们终于天经地义的,习惯了新的方向,没有人再有回到Asp.net的意思,恍若隔世,但…一定要比较,今天显然更为轻松。

当然,最初并非一切顺畅,每个选择,每一方都是王婆婆,她的瓜绝对是举世无双滴。面对诸多王婆的时候,我们也很难得到客观的比较,选择往往需要自己来做。 经过两个项目,才真正让一切顺畅起来。其中所涉的编程方式、各类细节甚至由此引发的不同设计思维,很明显经历了多处的反复。这个没有办法,node.js 相对较新,大规模在一些公司应用的情形并不多,这类文字当然也稀少,我们很难找到其他人归纳的常规的团队开发模式。

我简单的描述一下,对于以Node.js为主的公司,嗯,仅仅局限于中小型公司…或许有一定的帮助,少走些弯路。我们最终的选择是

1、IDE:Webstorm,没有其他。

2、版本管理系统:Git,独一无二。

3、单元测试:jsamine,前后端共用。

4、前端框架:Angular.js,让ember.js和几个老牌的框架性感的躺在床上吧。

5、服务端:纯静态页面+极少使用Jade+REST

6、socket.io+独立小模块:当然,这几乎是唯一可选的与客户端双向通信的方式。但一定要注意,多数情形下,我们只有很少的机会需要服务端推送,将这部分内容作为独立的小应用,是非常省事的做法。

7、异步流程控制:Promise是唯一选择,而且从一开始就要强制使用,绝不可忽略,这关系到设计思维的巨大差异,甚至关系到我们是否真正能够在 node.js方向坚持下来。我们用Q.js,和前端Angular.js使用的微缩版Q.js保持一致,减少学习周期。

8、前后端共用代码:只要前端有可能用到的代码,必须以符合规范的方式,做到前后端共用。

 

我知道多数的多数的,还是多数的技术文章,说话都是不极端的、中庸的,在肯定一到两个选择的时候,也会认同其他的选择,嗯,这固然是很成熟的文风,很厚道的人格。我呢,只会极端的就每个选择给出唯一的答案。

不是因为我性情不成熟,嗯,好歹我也算是超级资深的架构师、高级程序员、过程管理大半个专家…啊,还有没有其他的光环和帽子可戴?

既然我们在集中选择中左右碰撞后,得出了结论,我们的选择就是唯一的…多数情况下,您看到这篇随手的文字,就不再需要将这个痛苦的过程,重复一遍。我 觉的这是真正的厚道…那么多客气干嘛?噢,这可能有点”你们不用思考,元首都帮你们思考过了”的意思,这点不好…我在下面将几种主要选择的理由, 列出来…以避免从天而降的、聚合成团队的板砖。

一、IDE的选择:WebStorm

我们最初遇到的问题,是语言,C#转到js。 当然,这个事实上不是太大的问题,Asp.net方向的团队,基本上每个人都有相关语法知识。尤其开心的 是,node.js,在后端开发,不需要如前端开发一般,考虑浏览器差异。最初大家的共识是很简单的思维:js+node系统库+诸多可选的 包=C#+.net framework。额,当然,整个node.js占据的空间大约只有十来兆,IIS和.net framework加起来是多少?几十倍的体积差别,即使两者旗鼓相当,是不是该鄙视下,那过于吝啬硬盘的一方?

当然,要开始,第一个问题是IDE。最初,我一个人左右折腾,使用Visual studio+iisnode+Tfs,各种不便。1周后转到Webmetrix,3天后灰溜溜的放弃。然后各种ide象流水般的试试…WebStorm最初也被排斥。

在一个月黑风高的深夜,一双迷茫的眼睛,盯着天花板。

我重新捡起WebStorm,更精细的一步步尝试它所宣称的各类特性。语法自动感知、node.js的调试、Git集成、单元测试框架的集成….

嗯,一切之外的选择变成了浮云。

 

二、版本管理系统:Git

选择Git,最初是因为Tfs不再可用,我们已经告别了伟大的Visual Studio 20XX。那么…风评最好的,显然是Git,GitHub已经成长到让其他的开源社区瞠目结舌的地步,甚至Tfs也不得不支持Git。这不免太庸俗: 人人一边倒的说一个东西好,这东西就是好。

但是,但是那个但是…这东西用起来比Tfs麻烦许多噢,我们都不算是喜欢一个个敲命令的人吧?我本人率先使用,然后培训其他人,先行的过程虽然持续三天…但是:

1、分布式确实是最人性的做法:不再需要家中和公司服务器同步来同步去,不在同一城市也不再是问题。

2、分支成为主要的思维…前提是合并、冲突惊人的简易。

3、最为欣喜的是,结合WebStorm,几乎达到了VS中使用Tfs的效果,我们已经有几年不再手工输入命令了。

那么,你还需要考虑别的?

 

三、单元测试:Jasmine

这个选择,纯粹是由于我天生的懒惰。 前端Angular.js,单元测试用karma…jasmine,我开始尝试在服务端使用jsmine,到解决了与ide集成、Promise测试之后,我们还需要用不同的方式来做单元测试吗?

 

四、异步流程控制:Promise,Q.js

茴香豆的茴,有N种写法。所以,专家告诉我们,处理异步流程,除了回调函数方式外,还有事件方式、订阅发布模式、Promise。我的答案只有一 个,Promise,in Q.js。 在我们第一个项目中,我们避免使用太多第三方库,所有异步流程的处理,均老老实实的用嵌套得晕死的回调函数处理。这个,虽然折磨了大家,但很 明显是每个人可以快速的理解、快速做到的。不过,第一个项目中,遇到有十几个步骤的复杂计算的时候,层层嵌套几乎令我们团队出现一位精神失常者…为了 避免家长打上门来,老将出马…我用了一个通宵,在async.js(一个几乎居垄断地位的异步流程库)、Q.js以及其他一些方式中徜徉。

很惭愧的说,Promise的理解看似轻松,但在两个小时的时间里,我发觉自己很难真正的理解,这是很少见的事情,我照着镜子,看着那一向自以为性能不错的人类脑袋,沮丧的叹息。在项目进度的压力下,我选择了async.js…

之后的空闲时间,我终于经过两次波折,彻底的理解Promise的概念、使用的细节。在第二个项目开始之前,我用2天的时间在团队传播,并订下了非常不近 人情的编程规范:本项目不允许出现回调函数方式、不允许使用async、只准使用Q.js Promise。所有不符合此规范的代码将被退回,所有于此有关的问题我会随时解答。

原因是什么?如果没有Promise,node.js的编程将是一个异常枯燥、乏味、不可靠、江湖风格的苦差。我上升到这个高度,估计会面对有一千个番茄 加两千个鸡蛋,但,信者得永生。扁平的流程处理,统一的编程模式,链式的编程风格,无与伦比的异常处理。async.js实现的异步流程,所有的代码是相 关的。Promise则可以做到各步骤全不相关,嗯,想到了最基本的”封装”了吧?这就是所有的理由,之所以选择Q,也是降低学习的难度—我们在 angular.,js前端,已经模糊的使用微缩版的q.js了。

五、前端框架:Angular.js

前端框架,近年如同地下世界的老鼠,数量十分庞大。

Angular.js、微软的Knockout、某人推崇为第一的ember.js….等等等等。

我测试过多种之后,选择angular.js,这是入门简易,但学习曲线陡峭的框架。理由:我比较后,发现angular.js的代码量比多数的框架,精 简许多,在理想和现实中折中得相当平衡。结合REST,我们几乎可以方便的制作全静态的应用,当然,每个地方都是无刷新的。

 

没有草稿,随手而写,好象也没有什么修改。其他的几个选择,相对而言更好理解一些,不罗嗦。使用mongodb或许是一个错误…不支持事务,是个很要 命的问题。但也坚持很久了…我们的团队,始终是不能回避noSQL的,而noSQL的第一选择,目前仍然是mongodb,至少从思维方面,大家目前 已经非常熟悉和习惯。node.js真正的在企业中作为主力方向,国内估计并不太多,很多资料匮乏。我建了119874409群,欢迎诸位同好交流。

node.js的年龄,已经十岁,似乎并不容易成为真正的主流之一,但我本人,仍然很看好今后的发展,考虑到其轻松跨平台、独特的异步模式、与C++天然的亲和,甚至在桌面开发、安卓平台上的一些尝试,node.js极可能成为重要的技术方向之一。

[转载]Android 使用Fragment,ViewPagerIndicator 制作csdn app主要框架 - Hongyang - 博客频道 - CSDN.NET

mikel阅读(821)

[转载]Android 使用Fragment,ViewPagerIndicator 制作csdn app主要框架 – Hongyang – 博客频道 – CSDN.NET.

转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/23513993

本来准备下载个CSDN的客户端放手机上,没事可以浏览浏览资讯,下载了官方的之后,发现并不能很好的使用。恰好搜到一个大神自己写的csdn的app,下载安装了一下,感觉很不错,也很流畅,基本满足了我们 日常浏览的需求。

app效果图:




我会在博客中完整的介绍这个项目的制作,第一篇当然是整个项目的整体结构了。

效果图:


1、头部的布局文件,这个很简单:

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

&nbsp;

&nbsp;

&nbsp;

就显示一个图标和标题。
2、主布局文件:


&nbsp;

&nbsp;

&nbsp;

一个TabPageIndicator和一个ViewPager。
3、主Activity

package com.zhy.csdndemo;

import com.viewpagerindicator.TabPageIndicator;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;

public class MainActivity extends FragmentActivity
{
private TabPageIndicator mIndicator ;
private ViewPager mViewPager ;
private FragmentPagerAdapter mAdapter ;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mIndicator = (TabPageIndicator) findViewById(R.id.id_indicator);
mViewPager = (ViewPager) findViewById(R.id.id_pager);
mAdapter = new TabAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);
mIndicator.setViewPager(mViewPager, 0);

}

}

TabAdapter.java

package com.zhy.csdndemo;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class TabAdapter extends FragmentPagerAdapter
{

public static final String[] TITLES = new String[] { "业界", "移动", "研发", "程序员杂志", "云计算" };

public TabAdapter(FragmentManager fm)
{
super(fm);
}

@Override
public Fragment getItem(int arg0)
{
MainFragment fragment = new MainFragment(arg0);
return fragment;
}

@Override
public CharSequence getPageTitle(int position)
{
return TITLES[position % TITLES.length];
}

@Override
public int getCount()
{
return TITLES.length;
}

}

MainFragment.java

package com.zhy.csdndemo;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

@SuppressLint("ValidFragment")
public class MainFragment extends Fragment
{

private int newsType = 0;

public MainFragment(int newsType)
{
this.newsType = newsType;
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.tab_item_fragment_main, null);
TextView tip = (TextView) view.findViewById(R.id.id_tip);
tip.setText(TabAdapter.TITLES[newsType]);
return view;
}

}

4、在styles.xml中自定义Theme


AndroidManifest中注册使用:

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

&nbsp;

&nbsp;

&nbsp;

总体还是很简单的,但是效果很不错啊,现在不是流行Fragment么~好了 ,如果这篇文章对你有帮助,赞一个~

源码点击此处下载

[转载]node.js 初体验 - 聂微东 - 博客园

mikel阅读(894)

[转载]node.js 初体验 – 聂微东 – 博客园.

PS: ~ 此篇文章的进阶内容在为《Nodejs初阶之express

~ 2014/09/24 更新《Express 4.X 启航指南

欢迎阅读和评论:)

 

最近写的文章收到许多朋友的反馈,感谢大家的支持和建议,让我对坚持写博客充满热情,一个月一篇文章确实有点少,所以以后尽力多做分享,做好的分享,希望能对朋友们有用。

  到新公司的这段时间学到了很多新东西,有好多东西需要去总结去探索,不过事情得一件一件来,今天咱们先从Node开始。注:以后出现的Node即node.js。

先搞点前戏热热场 – 为什么写这篇文章:

1.前段时间单位有新项目启动,服务端要做的工作不多也不算麻烦,就是处理一些中间层的服务,而且我们团队里面个个都会JavaScript, 领导就决定试试服务器端的JavaScript,结果本人有幸被派去研究了几天Node,怀着鸡冻的心情开始了node.js的篇章,这篇文章也就是为这 几天研究的总结。

2.一个JavaScript工程师如果没听过node.js那么我想你是不是错过了什么,每个优秀的前端工程师都有必要去了解后台处理流程,那么如果又能从JavaScript出发,岂不是一件很美妙的事么。

3.互联网的火热使得JavaScript风光无限,且服务端的JavaScript也并不是什么新技术了,相关的框架也有不少,只是node.js的成功让他爆发式的出现在我们的视线中,让很多前端工程师看到了从前端写到后端的另一种实现希望。注:node.js 是一个允许开发人员使用 JavaScript 语言编写服务器端代码的框架。

4.今年8月曾在某大公司最后一轮(第五轮)的面试被问到Node.js的问题,相对应的回答那是相当之糟糕,结果怎样你们懂的,感觉这个问题 是导致没有通过的关键点之一…那家公司是我在读大学的时候就无比向往的公司,现在回想起那次经历和过程,谈不上惋惜,毕竟我真的尽力了 – 其实这篇文章更多的也是为了完成自己一个小小的心结…好吧,又扯远了。

5.欢迎各种转载,不过请注明出处,谢谢。

 

Node是个啥?

写个东西还是尽量面面俱到吧,所以有关基本概念的东西我也从网上选择性的拿了些下来,有些地方针对自己的理解有所改动,对这些概念性的东西有过了解的可选择跳过这段。

1.Node 是一个服务器端 JavaScript 解释器,可是真的以为JavaScript不错的同学学习Node就能轻松拿下,那么你就错了,总结:水深不深我还不知道,不过确实不浅。

2.Node 的目标是帮助程序员构建高度可伸缩的应用程序,编写能够处理数万条同时连接到一个物理机的连接代码。处理高并发和异步I/O是Node受到开发人员的关注的原因之一。

3.Node 本身运行Google V8 JavaScript引擎,所以速度和性能非常好,看chrome就知道,而且Node对其封装的同时还改进了其处理二进制数据的能力。因此,Node不仅仅简单的使用了V8,还对其进行了优化,使其在各种环境下更加给力。(什么是V8 JavaScript 引擎?请“百度知道”)

4.第三方的扩展和模块在Node的使用中起到重要的作用。下面也会介绍下载npm,npm就是模块的管理工具,用它安装各种 Node 的软件包(如express,redis等)并发布自己为Node写的软件包 。

 

安装Node

  在这简单说说在window7和linux两种环境下安装Node。安装的时候一定要注意Python的版本,多次因为Python版本的问题安装失败,建议2.6+的版本,低版本会出现Node安装错误,查询Python版本可在终端中输入:pyhton -v 

  1.先介绍linux下的安装吧,Node在Linux环境下的安装和使用都非常方便,建议在Linux下运行Node,^_^…我使用的是Ubuntu11.04

a.安装依赖包:50-100kb/s大概每个包一分钟就能下载安装完成

  sudo apt-get install g++ curl libssl-dev apache2-utils 
  sudo apt-get install git-core

b.在终端一步步运行一下命令:

  git clone git://github.com/joyent/node.git   
  cd node
  ./configure
  make   
  sudo make install

安装顺利的话到这一步Node就算安装成功了,2M的网络用了共计12分钟。

注:如果不用git下载也可以直接下载源码,不过这样下载安装需要注意Node版本问题。使用git下载安装是最方便的,所以推荐之。

 

2.在Windows下使用Cygwin安装Node,这个方式不太推荐,因为真的需要较长时间和较好的人品。我的系统是 win7旗舰版

Cygwin是一个在windows平台上运行的unix模拟环境,下载地址:http://cygwin.com/setup.exe

下载好Cygwin后开始安装,步骤:

a.选择下载的来源 – Install from Internet

b.选择下载安装的根目录

c.选择下载文件所存放的目录

d.选择连接的方式

e.选择下载的网站 – http://mirrors.163.com/cygwin

f.麻烦就麻烦在这步,考验人品的时候到了。需要的下载安装时间不确定,反正需要比较长的时间(超过20分钟),偶尔会出现安装失败的情况。 单击一下各个程序包前面的旋转箭头图标选择你想要的版本,选中时会出现了”x”号表示已经选中了该程序包。选择需要下载的程序包:

复制代码
  Devel包:
    gcc-g++: C++ compiler
    gcc-mingw-g++: Mingw32 support headers and libraries for GCC C++
    gcc4-g++: G++ subpackage
    git: Fast Version Control System – core files
    make: The GNU version of the ‘make’ utility
    openssl-devel: The OpenSSL development environment
    pkg-config: A utility used to retrieve information about installed libraries
    zlib-devel: The zlib compression/decompression library (development)
  Editor包:vim: Vi IMproved – enhanced vi editor
  Python包:把Default切换成install状态即可
  Web包:
    wget: Utility to retrieve files from the WWW via HTTP and FTP
    curl: Multi-protocol file transfer command-line tool
复制代码

上个截图,以下载zlib-devel为例:

cygwin例子

其上几步走完才算把环境搭建完成,可是现在还没有到安装Node,还需要在Cywgin的ASH模式下执行rebaseall,步骤如下:

a. cmd命令行

b. 进入cygwin安装目录下的bin子目录

c. 运行ash进入shell模式

d. ./rebaseall -v

e. 没有错误就关闭命令行窗口
好了,现在到下载安装Node了,启动Cygwin.exe后输入:

复制代码
  $ wget http://nodejs.org/dist/node-v0.4.12.tar.gz
  $ tar xf node-v0.4.12.tar.gz
  $ cd node-v0.4.12
  $ ./configure
  $ make
  $ make install
复制代码

 

  3.直接下载node.exe文件

  nodejs.org下载较慢所以我在网盘上传了一个,下载地址:http://www.everbox.com/f/VhyL6EiGF5Lm3ZSRx85caFDIA5

  听说有不太稳定的问题,不过你假如只是想先在windows下了解Node,个人感觉这个方法比你装个Cygwin好很多。

 

  注:原本不太想写安装Node这段,可是为了这篇文章的全面性还是写了,没想到一写就是那么长一段了…茶几了

 

“Hello World” – 为什么每次见到这句心情都会小激动,不解…

首先,创建个hello.js的文件,在文件中copy如下代码:

复制代码
  var http = require('http');
  http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
  }).listen(1337, "127.0.0.1");
  console.log('Server running at http://127.0.0.1:1337/');
复制代码

代码逻辑:

a. 全局方法require()是用来导入模块的,一般直接把 require() 方法的返回值赋值给一个变量,在 JavaScript 代码中直接使用此变量即可 。require(“http”) 就是加载系统预置的 http 模块

b. http.createServer 是模块的方法,目的就是创建并返回一个新的web server对象,并且给服务绑定一个回调,用以处理请求。

c. 通过 http.listen() 方法就可以让该 HTTP 服务器在特定端口监听。

d. console.log就不用多说了,了解firebug的都应该知道,Node实现了这个方法。

    注: 想了解具体细节请查看文档 cnodejs.org/cman/all.html#http.createServer

接着运行Node服务器,执行hello.js代码,成功启动会看见console.log()中的文本。有图有真相:

 

npm的下载和使用

除Node本身提供的API外,现在有不少第三方模块可极大的提高开发效率,npm就是Node的软件包管理器,可以用它安装所需软件包并发布自己为nodejs写的软件包。官网地址:npmjs.org

安装只需要在终端写入一行代码:

  curl http://npmjs.org/install.sh | sh

npm安装node扩展包同样是一行代码:

  npm install <包名>    //例:npm install express

注:如果安装模块的过程中报域名错误的话,请清空缓存 >npm cache clean 或重启计算机即可。

  

理解Node的模块概念

在Node中,不同的功能组件被划分成不同的模块。应用可以根据自己的需要来选择使用合适的模块。每个模块都会暴露一些公共的方法或属性。模块 的使用者直接使用这些方法或属性即可,对于内部的实现细节就可以不用了解。除了Node本身提供的API外,开发人员也可以利用这个机制来将应用拆分成多 个模块,以提高代码的可复用性。

1.如何使用模块?

在Node中使用模块是非常方便的,在 JavaScript 代码中可以直接使用全局函数 require() 来加载一个模块。

在刚刚”Hello World”的例子中,require(“http”) 可以加载系统预置的 http 模块;模块名称以 “./” 开始的,如 require(“./myModule.js”) 用来加载与当前 JavaScript 文件同一目录下的 myModule.js 模块。

2.自己如何开发模块?

刚刚介绍使用require()导入模块的时候,模块名称以 “./” 开始的这种,就是自己开发的模块文件。需要注意的就是JS文件的系统路径。

代码中封装了模块的内部处理逻辑,一个模块一般都会暴露一些公开的方法或属性给其他的人使用。模块的内部代码需要把这些方法或属性给暴露出来。

3.来一套简单的例子。先创建一个模块文件如myModule.js,就一行代码

  console.log('Hi Darren.')

然后创建一个test.js文件,导入这个JS文件,执行node看到结果

现在Node社区中已有不少第三方的模块,希望能有更多人通过学习Node,加入到这个大家庭中,为Node社区来添砖加瓦。先谢谢之,咱们继续。

4.来一个深点的例子。这个例子中将会针对 私有和共有 进行介绍。先创建一个myModule.js,代码如下:

  var name = "Darren";
  this.location = "Beijing";
  this.showLog = function(){
      console.log('Hi Darren.')
  };

代码中出现了三种类型,分别是: 私用属性,共有属性和共有方法,再创建一个test.js,执行Node

结果高亮的地方很清楚的告诉我们,私有方法我们在模块以外是取不到的,所以是undefined。共有属性和共有方法的声明需要在前面加上 this 关键字。

 

Node能做什么和它的优势

Node核心思想:  1.非阻塞;  2.单线程;  3.事件驱动

在目前的web应用中,客户端和服务器端之间有些交互可以认为是基于事件的,那么AJAX就是页面及时响应的关键。每次发送一个请求时(不管请 求的数据多么小),都会在网络里走一个来回。服务器必须针对这个请求作出响应,通常是开辟一个新的进程。那么越多用户访问这个页面,所发起的请求个数就会 越来越多,就会出现内存溢出、逻辑交错带来的冲突、网络瘫痪、系统崩溃这些问题。

Node的目标是提供一种构建可伸缩的网络应用的方案,在hello world例子中,服务器可以同时处理很多客户端连接。

Node和操作系统有一种约定,如果创建了新的链接,操作系统就将通知Node,然后进入休眠。如果有人创建了新的链接,那么它(Node)执行一个回调,每一个链接只占用了非常小的(内存)堆栈开销。

举一个简单的异步调用的例子,把test.js和myMydule.js准备好了,^_^。把以下代码拷贝到test.js中并执行:

复制代码
  var fs = require('fs');
  fs.readFile('./myModule.js', function (err, data) {
    if (err) throw err;
    console.log('successfully');
  });
  console.log('async');
复制代码

所谓的异步,大家应该都能想得到运行时会先打先显示”async”,再显示”successfully”。

Node是无阻塞的,新请求到达服务器时,不需要为这个请求单独作什么事情。Node仅仅是在那里等待请求的发生,有请求就处理请求。

Node更擅长处理体积小的请求以及基于事件的I/O。

Node不仅仅是做一个Web服务的框架,它可以做更多,比如它可以做Socket服务,可以做比方说基于文件的,然后基于像一些比方说可以有 子进程,然后内部的,它是一个很完整的事件机制,包括一些异步非注射的解决方案,而不仅仅局限在网络一层。同时它可能,即使作为一个Web服务来说,它也 提供了更多可以深入这个服务内核、核心的一些功能,比方说Node使用的Http Agent,这块就是它可以更深入这个服务内核来去做一些功能。

 

Node事件流概念

因为Node 采用的是事件驱动的模式,其中的很多模块都会产生各种不同的事件,可由模块来添加事件处理方法,所有能够产生事件的对象都是事件模块中的 EventEmitter 类的实例。代码是全世界通用的语言,所以我们还是用代码说话:

复制代码
  var events = require("events"); 
  var emitter = new events.EventEmitter(); 
  emitter.on("myEvent", function(msg) { 
    console.log(msg); 
  }); 
  emitter.emit("myEvent", "Hello World.");
复制代码

简单的分析这段:

1. 使用require()方法添加了events模块并把返回值赋给了一个变量

2. new events.EventEmitter()这句创建了一个事件触发器,也就是所谓的事件模块中的 EventEmitter 类的实例

3. on(event, listener)用来为某个事件 event 添加事件处理方法监听器

4. emit(event, [arg1], [arg2], […]) 方法用来产生事件。以提供的参数作为监听器函数的参数,顺序执行监听器列表中的每个监听器函数。

 

EventEmitter 类中的方法都与事件的产生和处理相关:

1. addListener(event, listener) 和 on(event, listener) 这两个方法都是将一个监听器添加到指定事件的监听器数组的末尾

2. once(event, listener) 这个方法为事件为添加一次性的监听器。该监听器在事件第一次触发时执行,过后将被移除

3. removeListener(event, listener) 该方法用来将监听器从指定事件的监听器数组中移除出去

4. emit(event, [arg1], [arg2], […]) 刚刚提到过了。

在Node中,存在各式各样不同的数据流,Stream(流)是一个由不同对象实现的抽象接口。例如请求HTTP服务器的request是一个 流,类似于stdout(标准输出);包括文件系统、HTTP 请求和响应、以及 TCP/UDP 连接等。流可以是可读的,可写的,或者既可读又可写。所有流都是EventEmitter的实例,因此可以产生各种不同的事件。

可读流主要会产生以下事件:

  • data   当读取到流中的数据时,此事件被触发
  • end   当流中没有数据可读时,此事件被触发
  • error   当读取数据出现错误时,此事件被触发
  • close   当流被关闭时,,此事件被触发,可是并不是所有流都会触发这个事件。(例如,一个连接进入的HTTP request流就不会触发’close’事件。)

还有一种比较特殊的 fd 事件,当在流中接收到一个文件描述符时触发此事件。只有UNIX流支持这个功能,其他类型的流均不会触发此事件。

相关详细文档:http://cnodejs.org/cman/all.html#events_

 

强大的File System 文件系统模块

Node 中的 fs 模块用来对本地文件系统进行操作。文件的I/O是由标准POSIX函数封装而成。需要使用require(‘fs’)访问这个模块。所有的方法都提供了异步和同步两种方式。

fs 模块中提供的方法可以用来执行基本的文件操作,包括读、写、重命名、创建和删除目录以及获取文件元数据等。每个操作文件的方法都有同步和异步两个版本。

异步操作的版本都会使用一个回调方法作为最后一个参数。当操作完成的时候,该回调方法会被调用。而回调方法的第一个参数总是保留为操作时可能出现的异常。如果操作正确成功,则第一个参数的值是 null 或 undefined 。

同步操作的版本的方法名称则是在对应的异步方法之后加上一个 Sync 作为后缀。比如异步的 rename() 方法的同步版本是 renameSync() 。下面列出来了 fs 模块中的一些常用方法,都只介绍异步操作的版本。

test.js和myModule.js文件准备好了木?把下面这段代码copy到test.js中执行一次

  var fs = require('fs');
  fs.unlink('./myModule.js', function (err) {
    if (err) throw err;
    console.log('successfully deleted myModule.js');
  });

如果没有报error,那么myModule.js就被删除了,就是这么简单

这只是一个简单的例子,感兴趣的话自己去多多尝试,实践出真理。由于篇幅原因就不多举例了。^_^

 

学习Node的总结:

1.对于一个linux的命令和shell知识几乎为零的我来说,这段时间又学到了不少关于linux知识;vim真是一个强大的编辑器,不用 鼠标的感觉真的很好;而且有一点对我来说很重要,在linux下编程很cool,尤其是在团队中都是使用windows的,装装更健康^_^。

2.理解了服务端JavaScript的一个成功框架-Node,以及它的一些优势和使用的方式,这篇文章就是最好的总结,当然,这只会是一个开始

3.对于没有进入那么梦想的公司其实是有那么点遗憾,不过生活就应该要这样,有波折有起伏,这正是我需要并且期待的…那么新的生活还是要继续,做自己的舵手,把握好自己的方向,过去的就让它过去吧。

 

一些想对大伙说的话:

1. 在这我得打击一部分人的积极性。假如你对后台技术不够了解或者没接触过服务端语言,不知道I/O这些知识,没有后台处理流程这种概念,那么……Node并不是一门适合入门的服务端技术。为什么这么说:

a.重点就是中文实例少,文章少,想系统的学习会比较麻烦,所以在使用过程中总有一种不成熟的感觉,当然主要还是因为我对它不熟悉所造成的。国内使用Node的公司确实不多,当然国外还是有不少了,从cnodejs.org截了一个图:

b.对没有经验的朋友来说node其实并不好上手,从最简单“Hello world”就可以看出来(各种运行环境和安装细节的了解都得费点功夫),不要以JQuery库为比较,所处理的事物不同,学习的成本也不同 – 所以不太建议作为新手入门的服务端技术,如果想学习一门服务端语言PHP和Python都是不错的选择,因为:书多 例子多 框架多 上手简单 容易理解 搭建方便…

c.以上都是我个人善意的建议,由于水平有限,请大家多多指教,希望嘴下留情。

2. 关于Node的书写规范和具体技巧本人就不献丑了,自己写Node的代码也不多,不过面向对象的编程思想在哪都是好使的。

3. 希望这篇文章能对大家学习Node有用,如果觉得这文章也算用心,请劳驾点右下角的推荐。

 

 推荐几个学习Node的网址:
  http://nodejs.org/

  http://cnodejs.org/   由淘宝人建立的社区,内有Node中文文档

  http://www.oschina.net/p/nodejs/

http://www.ibm.com/developerworks/cn/opensource/os-nodejs/index.html

 注:最终领导决定放弃Node而使用Python,可是这不会影响我对Node的喜爱。我说过,这篇文章只会是一个开始。

作者:聂微东
出处:http://www.cnblogs.com/Darren_code/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

DIY: Make Your Own Programming Language

mikel阅读(995)

.

This is part 1 in a series of blog posts that follow my work in creating a programming language.

Why?

  • It is fun
  • Someone might learn something (That someone is probably just me)
  • Street cred (Name of the street: The Information Superhighway)

Not why

  • It’s not going to be useful
  • It’s not going to be Faster than C1
  • I’m not expecting anyone to use it

How?

  • Pick a cool name for the language
  • Decide what kind of language you want to create
  • Iterate:
    • Pick some sweet features
    • Come up with some kind of syntax of the new language (AKA “concrete syntax”)
    • Decide what should happen when running the code (This is actually called “operational semantics”)

That is all that’s actually needed to create a programming language. However if you actually want a language to do something there must also exist an interpreter or a compiler for that language. To create a compiler we need to know some kind of lower level language that can be translated to machine code for x86, JVM, LLVM or similar. An interpreter is much easier to make. Let’s add “Implement features in an interpreter” to the iteration. Testing could also be useful.

How? 2nd try

  • Pick a cool name for the language
  • Decide what kind of language you want to create
  • Iterate:
    • Pick some sweet features
    • Come up with some kind of syntax of the new language (AKA “concrete syntax”)
    • Decide what should happen when running the code (This is actually called “operational semantics”)
    • Implement the features in an interpreter
    • Test with some source code

Let’s get started

Language name2: ion

Many of my ideas for ion are coming from Python and Haskell (which also happens to be my favorite languages).

  • Imperative
  • Explicit typing
  • Primitive types: integer, boolean, string
  • Classes as composite types
  • Instances of classes = objects
  • Whitespace dependent, very whitespace dependent. Python’s PEP 8 Style guide was to created to ‘solve’ some problems that the interpreter or compiler could do much better. Some things in van Rossums’ Python Regrets slides are interesting too.
  • No pointers
  • Higher order functions (functions that takes or returns functions)
  • Top level statements. This makes the language less verbose compared to C or Java where there are only definitions at the top level and there’s an agreement that the whole thing should start with a main function.

Iteration 1

The language features added to this iteration are not going to be massive as there’s plenty of backend ‘infrastructure’ that has to be implemented.

Types

One type is enough for now. Only one type implies that we don’t need a type checker since there’s no types to check. The type checker can be added in a later iteration. Integer is a good primitive type to start with as it gives us the possibility to create simple expressions using some of the very well defined arithmetic operators.

Literals

Literals are constant, ‘hard coded’ values of the language’s primitive types. For integers the literals are all the numbers like 0, 1, -2 or 9999 and so on.

Variables

Can be declared to hold values of a certain type.

Expressions

  • Literals: Nothing surprising here. Literals are expressions since they’re evaluated to a value.
  • Identifiers: Just like literals but the values depends on the environment.
  • Plus and minus: Okay, so the arithmetic operations (i.e. +, -, *, /, % (modulo)) are indeed expressions as mentioned before. There’s no need to add all operators that can be applied to integers now. If we’re only adding plus and minus there wont be too many problems with operator precedence.
  • Unary negation operator: The unary (one argument) operator that negates an integer is good to have as it can be used to represent negative integers. This means that the negative integers isn’t literals! They will be positive literals that this operator is applied to.

Statements

  • Declare a variable to have a certain type
  • Assign value of expression to variable. The assignments should not be expressions (as in C). It makes the code harder to maintain.

Syntax

Now it’s time to make up some syntax of the new language.

Variables must start with a lowercase letter, types should start with an uppercase letter. They should then be followed by letters of any case or numbers.

Statement syntax

I’ll take some inspiration from Haskell and its syntax for types. The explicit typing in Haskell is done on a separate line where the variable and type are separated by double colons. Id :: Type\n

a :: Int

Assignment is done with a single equals sign with the variable to the left and an expression to the right. Id = Expr\n

a = 5

Putting it all together

a :: Int
a = 5

You should be able to assign values more than once to a variable

a1 :: Int
a1 = 222
a1 = 333

Declaring the same type to a variable should be permitted, as long as it’s the same type. (Note that there’s no notion of scopes in ion yet!)

a2 :: Int
a2 :: Int

Expression syntax

The plus operator should be a + and be between the two expression operands Expr + Expr

b :: Int
b = a + 3

Same with minus

b1 :: Int
b1 = a - 3

Since the operator takes two expressions it’s implied that it can take two literals

c :: Int
c = 3 + 2

c1 :: Int
c1 = 3 - 2

or two variables

c2 :: Int
c2 = c + cc

and even another expression, that are built from other expressions and so on…

d :: Int
d = b + 3 - 2 - a

Don’t forget the negation operator. Note that there’s no space between the operator and the operand -Expr

e :: Int
e = -a

e1 :: Int
e1 = -99

Since that is also an expression it can be used like this

f :: Int
f = 1 + -12

And even like this. This could be a nice thing to add optimizations for.

f1 :: Int
f1 = --12

At last perhaps the most interesting thing we can do at the moment.

g :: Int
g = -5 + 5

When implementing this we need to be careful so that the right thing is happening here. Should g be 0 or -10?

Invalid syntax

I mentioned that ion should be whitespace dependent. Having more strict rules for this could also be useful.

The following program should fail because there’s a missing space before the type

a ::Int
a = 3

Same thing for expressions and assignment

a2 :: Int
a2 =5

a3 :: Int
a3=5

a4 :: Int
a4 = 5 +3

a5 :: Int
a5 = 5+ 3

a6 :: Int
a6 = 5+ 3

Types must start with a uppercase letter

a7 :: int

and variables with a lowercase

A :: Int
A = 5 + 3

Trailing spaces at the end of lines are not permitted

a8 :: Int \n

No arbitrary spaces at the start of lines!

a9 :: Int
 a9 = 5 + 3

Invalid expressions

a10 :: Int
a10 = - 3

a11 :: Int
a11 = 3 +

a12 :: Int
a12 = 3 + b

Semantics

The operational semantics are straight forward at this point. I will not be very strict about the semantics and sometimes I’ll come up with them after implementation so it will sometimes be ‘This is how it works’ rather than ‘This is how it should work’. That won’t be noticed very much in the blog posts though as they’re written at the end of or after the whole iteration is done.

  • Variable declaration adds a note of what type the variable should have. In C and Java the declaration usually initializes the variable to null or similar. I don’t want that.
  • Assignments assign the value of an expression to an identifier in the current environment. The environment is a table that the interpreter use to store and look up things in. Depending on what phase of the interpretation we are in it’s going to keep track of different things (e.g. Types of variables during type checking or values of variables during evaluation).
  • Literals evaluates to their literal value (1 has the value 1 and so on).
  • Identifiers evaluates to the value of that identifier.
  • + and - evaluates to the value of the first expression added to (or subtracted with) the value of the second one.
  • The negation operator evaluates to the negation of the value of the expression it’s applied to.

Interpreter

An interpreter is a program that takes a file with source code as input and runs the program it represents. How the interpreter achieves this depend on the approach of the person creating the interpreter and also on how the language works.

The process I’m going to use involves the following steps

  1. Preprocessing: Unnecessary things from the source are removed.
  2. Parsing: The processed file is parsed and an abstract syntax tree (AST) is created that represents the program. Sometimes there’s a step before parsing which is called lexing. That step creates tokens from the source file so that the parser just has to deal with smaller chunks instead of a massive string.
  3. Static analysis: Checking of types, control flows and such.
  4. Evaluation: Add definitions, execute statements, evaluate expressions. The program is being run.

For an compiler the last step would be exchanged with code generation of low level code. There could also be a step for code optimization.

Implementation

The set of programming languages that I know is quite big and I’ve got some experience creating interpreters and compilers in the past. For almost all of them3 I used Haskell which is a really good choice because of pattern matching, strong typing, and tools like BNFC. The State monad and lenses are pretty useful too.

This time I’m not going with Haskell, I want to try something new. Language candidates are:

Out of these I’ve decided to go with Rust which has been getting plenty of time in the spotlight lately. The main selling point of Rust is how it can be memory safe without using garbage collection. This is achieved by freeing memory when the variable that ‘owns’ that memory falls out of scope. This is not the reason why I picked Rust though. I agree it is interesting but at the moment I’m more interested in pattern matching and strong typing, two nice features of rust.

It’s time to write some code!

First we need the interpreter to read the file with source code. Providing the filename as an argument seem reasonable.

fn main() {
    let args = std::os::args();
    if args.len() < 2 {
        panic!("Please provide a file");
    }
    let path = Path::new(&args[1]);
    let s = File::open(&path).read_to_string().unwrap();
}

A simple preprocessor that splits up the file for all lines and throws away those who are empty. Don’t worry about the Line type yet, it will be described later. Let’s put this function next to main in a main.rs file.

fn preprocess<'a>(s: &'a String) -> Vec<Line>{
    let mut res: Vec<Line> = vec![];
    for line in s.as_slice().lines() {
        match line {
            "" => {} // Discard empty lines
            _ => res.push(Line(line))
        }
    }
    return res;
}

The abstract representation

Before we go further with the parser we need to come up with how the abstract syntax tree should be represented. Let’s create a new file abs.rs with the following enums and structs.

#[deriving(Show, Clone)]
pub struct Type(pub String);

#[deriving(Show, Clone)]
pub enum Stm {
    Vardef(Expr, Type),
    Assign(Expr, Expr),
}

#[deriving(Show, Clone)]
pub enum Expr {
    Id(String),
    LitInt(int),
    Neg(Box<Expr>),
    Plus(Box<Expr>, Box<Expr>),
    Minus(Box<Expr>, Box<Expr>)
}

Woah! Look at that. This will definitely make things easier as we can pattern match these things in a really clean way. Box is used for pointing as we can’t have recursive types. The file can be found here: abs.rs.

The parser

For the parser in parser.rs we’re going to need two structs.

struct ParseRule {
    name: String,
    regex: Regex,
}
pub struct Parser {
    rules: Vec<ParseRule>
}

A parse rule is a String with a name of what you get when matching the associated regular expression. The parser struct keeps track of all the rules. When a parser instance is created we set up all the rules.

impl Parser {

    pub fn new() -> Parser {
        let id = r"([:lower:][:alnum:]*)";
        let typ = r"([:upper:][:alnum:]*)";
        let litint = r"([:digit:]+)";
        let expr = r"(.*)";

        let parse_patterns = vec![
            ("Vardef", vec![id, r" :: ", typ]),
            ("Assign", vec![id, r" = ", expr]),

            ("Type", vec![typ]),

            ("Id", vec![id]),
            ("LitInt", vec![litint]),
            ("Plus", vec![expr, r" \+ ", expr]),
            ("Minus", vec![expr, r" - ", expr]),
            ("Neg", vec![r"-", expr]),
        ];

        let mut rules = vec![];
        for pp in parse_patterns.iter() {
            let (name, ref pattern_parts) = *pp;
            let mut regex_string = String::new();
            regex_string.push_str("^");
            for part in pattern_parts.iter() {
                regex_string.push_str(*part);
            }
            regex_string.push_str("$");
            let regex = Regex::new(regex_string.as_slice()).unwrap();
            rules.push(ParseRule {name: String::from_str(name), regex: regex});
        }
        return Parser {rules: rules};
    }
}

There are three interesting things happening here. First we got the basic building blocks for the patterns.

let id = r"([:lower:][:alnum:]*)";
let typ = r"([:upper:][:alnum:]*)";
let litint = r"([:digit:]+)";
let expr = r"(.*)";

Then we got the list of all things we should be able to parse at the moment. First the name of the match and then a list that the regular expression will be created from.Having the pattern as a list that uses the earlier defined building blocks saves us a few keystrokes and make it less likely for typos.

let parse_patterns = vec![
    ("Vardef", vec![id, r" :: ", typ]),
    ("Assign", vec![id, r" = ", expr]),

    ("Type", vec![typ]),

    ("Id", vec![id]),
    ("LitInt", vec![litint]),
    ("Plus", vec![expr, r" \+ ", expr]),
    ("Minus", vec![expr, r" - ", expr]),
    ("Neg", vec![r"-", expr]),
];

Finally the rules are created. First the pattern parts are concatenated to a string and then a regular expression object is created from that. Now we got everything we need to create our ParseRules to instantiate the Parser.

let mut rules = vec![];
for pp in parse_patterns.iter() {
    let (name, ref pattern_parts) = *pp;
    let mut regex_string = String::new();
    regex_string.push_str("^");
    for part in pattern_parts.iter() {
        regex_string.push_str(*part);
    }
    regex_string.push_str("$");
    let regex = Regex::new(regex_string.as_slice()).unwrap();
    rules.push(ParseRule {name: String::from_str(name), regex: regex});
}
return Parser {rules: rules};

Remember the Line type? It’s a structure that represents a line of source code.

#[deriving(Show)]
pub struct Line<'a>(pub &'a str);

It doesn’t do much now, but it could be useful in future iterations. The parser should of course have a parse function that parses these lines.

pub fn parse(&self, s: Vec<Line>) -> Vec<Stm> {
    let mut res: Vec<Stm> = vec![];
    for line in s.iter() {
        let Line(s) = *line;
        let l = self.parse_stm(s);
        res.push(l);
    }
    return res;
}

The source code is going be a bunch of statements, one for each line. To parse a statement we need a statement parser that returns an object of type Stm.

fn parse_stm(&self, s: &str) -> Stm {
    for rt in self.rules.iter() {
        let ref rule = *rt;
        if rule.regex.is_match(s) {
            let c = rule.regex.captures(s).expect("No captures");
            return match rule.name.as_slice() {
                "Vardef" => self.vardef(c),
                "Assign" => self.assign(c),
                _ => panic!("Bad match: {}", rule.name)
            };
        }
    }
    panic!("No match: {}", s);
}

Iterate over all the parse rules and see if something match either a Vardef or Assign. If there is a match we give the matched regular expression capture groups to functions that know how to create the right kind of Stm.

fn vardef(&self, cap: Captures) -> Stm {
    let e = self.parse_expr(cap.at(1).unwrap());
    let t = cap.at(2).and_then(from_str).unwrap();
    return Vardef(e, Type(t));
}

fn assign(&self, cap: Captures) -> Stm {
    let e1 = self.parse_expr(cap.at(1).unwrap());
    let e2 = self.parse_expr(cap.at(2).unwrap());
    return Assign(e1, e2);
}

These functions use a parse_expr function which works just like parse_stm but returns a matched Expr.

fn parse_expr(&self, s: &str) -> Expr {
    for rt in self.rules.iter() {
        let ref rule = *rt;
        if rule.regex.is_match(s) {
            let c = rule.regex.captures(s).expect("No captures");
            return match rule.name.as_slice() {
                "Id" => self.id(c),
                "LitInt" => self.litint(c),
                "Neg" => self.neg(c),
                "Plus" => self.plus(c),
                "Minus" => self.minus(c),
                _ => panic!("Bad match: {}", rule.name)
            };
        }
    }
    panic!("No match: {}", s);
}

fn id(&self, cap: Captures) -> Expr {
    let s = cap.at(1).and_then(from_str).unwrap();
    return Id(s);
}

fn litint(&self, cap: Captures) -> Expr {
    let i = cap.at(1).and_then(from_str).unwrap();
    return LitInt(i);
}

fn neg(&self, cap: Captures) -> Expr {
    let e = self.parse_expr(cap.at(1).unwrap());
    return Neg(box e);
}

fn plus(&self, cap: Captures) -> Expr {
    let e1 = self.parse_expr(cap.at(1).unwrap());
    let e2 = self.parse_expr(cap.at(2).unwrap());
    return Plus(box e1, box e2);
}

fn minus(&self, cap: Captures) -> Expr {
    let e1 = self.parse_expr(cap.at(1).unwrap());
    let e2 = self.parse_expr(cap.at(2).unwrap());
    return Minus(box e1, box e2);
}

Note that there’s some recursion going on here as expressions can contain expressions.

The parser is done and the source code can be found here: parser.rs.

Now we can use it in main.rs.

use parser::{Line, Parser};

mod abs;
mod parser;

fn main() {
    let args = std::os::args();
    if args.len() < 2 {
        panic!("Please provide a file");
    }
    let path = Path::new(&args[1]);
    let s = File::open(&path).read_to_string().unwrap();

    let lines = preprocess(&s);

    let p = Parser::new();
    let stms = p.parse(lines);
    println!("Parsed:\n{}\n", stms);
}

With a very simple file g02.ion:

a :: Int
a = 5

the output when passing it to the interpreter is:

$ ./ion g02.ion
Parsed:
[Vardef(Id(a), Type(Int)),
 Assign(Id(a), LitInt(5))]

Another example g03.ion:

a :: Int
a = 5

b :: Int
b = a + 3

yields:

$ ./ion g02.ion
Parsed:
[Vardef(Id(a), Type(Int)),
 Assign(Id(a), LitInt(5)),
 Vardef(Id(b), Type(Int)),
 Assign(Id(b), Plus(Id(a), LitInt(3)))]

A list of statements. If we visualize this with pictures4 it’s obvious why this is called an abstract syntax tree (they are not connected so it’s rather 4 trees). Vardef a Assign a Vardef b Assign b

Let’s take a look at a program that is more interesting. I mentioned it before and I’m sure you remember. Should g be 0 or -10 after evaluation?

g :: Int
g = -5 + 5

This should of course be parsed to:

[Vardef(Id(g), Type(Int)),
 Assign(Id(g), Plus(Neg(LitInt(5)), LitInt(5)))]

Vardef g Assign g

as this is how it’s defined in mathematics but what decides this in the parser? Why isn’t the assignment parsed as:

 Assign(Id(g), Neg(Plus(LitInt(5), LitInt(5))))

Assign g alt.

The answer is called operator precedence. And if we take a second look on how the matching is done we will find out how this is determined. The first lines of parse_expr:

for rt in self.rules.iter() {
    let ref rule = *rt;
    if rule.regex.is_match(s) {
        ...
    }
    ...
}

Ok, so this piece of code is going to try to match the patterns in the order they have in the list of rules. In the parse pattern list Plus is before Neg it is always going to be picked first => higher precedence. If Neg was put first we would end up with the alternative way of parsing these nested expressions.

let parse_patterns = vec![
    ("Vardef", vec![id, r" :: ", typ]),
    ("Assign", vec![id, r" = ", expr]),

    ("Type", vec![typ]),

    ("Id", vec![id]),
    ("LitInt", vec![litint]),
    ("Plus", vec![expr, r" \+ ", expr]),
    ("Minus", vec![expr, r" - ", expr]),
    ("Neg", vec![r"-", expr]),
];

No checking

At the moment there is not much we can test with static checks. Type checking is not necessary as we only got one type. However there is one thing that we could check and that is if a variable exist or not.

a :: Int
a = b

This will fail since b does not exist. Implementing a static check for this could be done but a type checker would cover this case too. To reduce the amount of unnecessary work I’ll ignore this opportunity now and wait for a type checker in a later iteration.

Execute statements, evaluate expressions

Now it’s time to start working on the evaluator. When a program is executed the interpreter keeps track of all variables and their values in the environment. This is definitely something we could use a data structure for.

#[deriving(Show)]
struct Env(HashMap<String, int>);

impl Env {

    fn new() -> Env {
        return Env(HashMap::new());
    }

    fn add(&mut self, id: String, value: int) {
        let ref mut m = self.0;
        m.insert(id, value);
    }

    fn lookup(&mut self, id:String) -> int {
        let ref mut m = self.0;
        return *m.get(&id).expect("Undefined variable");
    }
}

Creating the Env struct might seem unnecessary since it is essentially a HashMap. That is true but it’s to prepare for future functionality when scopes are added to the language.

The Eval struct keeps track of a environment. Statements modify the environment and expressions use the environment to a value. Since we’re only dealing with integers that’s what eval return.

pub struct Eval {
    env: Env,
}

impl Eval {

    pub fn new() -> Eval {
        Eval {env: Env::new()}
    }

    pub fn print_env(&self) {
        println!("Environment:\n{}", self.env);
    }

    pub fn exec_stm(&mut self, stm: Stm) {
        match stm {
            Vardef(Id(_), _) => {},
            Assign(Id(s), e) => {
                let x = self.eval(e);
                self.env.add(s, x)
            },
            _ => panic!("Unknown stm: {} in exec", stm)
        };
    }

    fn eval(&mut self, expr: Expr) -> int {
        match expr {
            Id(s) => self.env.lookup(s),
            LitInt(i) => i,
            Neg(box e) => - self.eval(e),
            Plus(box e1, box e2) => self.eval(e1) + self.eval(e2),
            Minus(box e1, box e2) => self.eval(e1) - self.eval(e2),
        }
    }
}

This code is even more simple than the parser. That’s because we’ve already done the messy parts. The fun things are happening here! Take a look at the complete file: eval.rs.

To tie things together we also need to add some stuff to main to execute all statemnts in the same environment.

use parser::{Line, Parser};
use eval::Eval;

mod abs;
mod parser;
mod eval;

fn main() {
    let args = std::os::args();
    if args.len() < 2 {
        panic!("Please provide a file");
    }
    let path = Path::new(&args[1]);
    let s = File::open(&path).read_to_string().unwrap();

    let lines = preprocess(&s);

    let p = Parser::new();
    let stms = p.parse(lines);
    println!("Parsed:\n{}\n", stms);

    let mut e = Eval::new();
    for stm in stms.iter() {
        e.exec_stm((*stm).clone());
    }
    e.print_env();
}

That’s it. Check out: main.rs. Now all implementation is done for this iteration. When running an example program the interpreter is going to output the environment containing the values of all variables after all statements has been executed.

g05.ion:

a :: Int
a = 5

a1 :: Int
a1 = 222
a1 = 333

a2 :: Int
a2 :: Int

a3 :: Int
a3 = 1 + 2 - 3 + 4

a4 :: Int
a4 = 1 + -12

a5 :: Int
a5 = -a + 5

Output:

$ ./ion g05.ion
Parsed:
[Vardef(Id(a), Type(Int)),
 Assign(Id(a), LitInt(5)),
 Vardef(Id(a1), Type(Int)),
 Assign(Id(a1), LitInt(222)),
 Assign(Id(a1), LitInt(333)),
 Vardef(Id(a2), Type(Int)),
 Vardef(Id(a2), Type(Int)),
 Vardef(Id(a3), Type(Int)),
 Assign(Id(a3), Plus(Plus(LitInt(1), Minus(LitInt(2), LitInt(3))), LitInt(4))),
 Vardef(Id(a4), Type(Int)),
 Assign(Id(a4), Plus(LitInt(1), Neg(LitInt(12)))),
 Vardef(Id(a5), Type(Int)),
 Assign(Id(a5), Plus(Neg(Id(a)), LitInt(5)))]

Environment:
Env({a3: 4, a4: -11, a1: 333, a5: 0, a: 5})

Testing

The amount of testing that can be done at the moment is somewhat limited. It is easy to test if there’s an error interpreting the file or not but that is almost only something that can be used for negative testing. The resulting environment that is being sent to stdout could be parsed but that is not a good solution in the long run. For now I think it’s enough to test with some good and some bad files to verify that they do and do not pass the interpretation. Test script and test files.

Source code

The source code of iteration 1 can be found here:

If you want the most recent source code you should look at the master branch. This code is going to be changed as the project continues. The link to the iterations is to a specific git tag (a specific commit) and will never be changed.

Future iterations

Some things that I consider adding to the language are:

  • Boolean and String primitive types
  • Type checker
  • Function definitions
  • Classes / Type definitions
  • Source code comments
  • Block statements / Scopes
  • More Integer operators
  • A print statement or maybe a print function
  • If-else statement
  • Some kind of loop (While/For) statement
  • Modules

But if, what and when these futures will be implemented is open for the future.

To be continued…

This is the end of the very first post and iteration of my project of creating a programming language from scratch. More posts is of course to come but until then please leave a comment if you got any questions or find any bugs/typos.


1There’s no such thing. Languages got no speed as they are just syntax and operational semantics. Some implementations can be faster than other implementations though.

2Inspiration on how to name a language can be found here.

3In Haskell: Interpreter and compiler (JVM) for a C-like language, compiler (LLVM) for another C-like language, interpreter for a functional language. Also Brainfuck interpreters in C, Python and Haskell.

4The pictures of the syntax trees are generated with mshang/syntree

[转载]Android开发-API指南-Fragment - 呆呆大虾 - 博客园

mikel阅读(989)

[转载]Android开发-API指南-Fragment – 呆呆大虾 – 博客园.

英文原文:http://developer.android.com/guide/components/fragments.html
采集日期:2014-12-31

Fragment 代表 Activity 当中的一项操作或一部分用户界面。 一个 Activity 中的多个 Fragment 可以组合在一起,形成一个多部分拼接而成的用户界面组件,并可在多个 Activity 中复用。一个 Fragment 可被视为 Activity 中一个模块化的部分, 它拥有自己的生命周期,并接收自己的输入事件,在 Activity 运行过程中可以随时添加或移除它 (有点类似“子 Activity”,可在不同的 Activity 中重用)。

Fragment 必须嵌入某个 Activity 中,其生命周期直接受到宿主 Activity 生命周期的影响。 例如,当 Activity 被暂停(Paused)时,其内部所有的 Fragment 也都会暂停。 而当 Activity 被销毁时,它的 Fragment 也都会被销毁。 不过,在 Activity 运行期间( 生命周期状态处于恢复(Resumed) 状态时),每一个 Fragment 都可以被独立地操作,比如添加或移除。 在执行这些操作事务时,还可以将它们加入该 Activity 的回退栈(Back Stack)中 — Activity 回退栈的每个入口就是一条操作过的 Fragment 事务记录。 回退堆栈使得用户可以通过按下 回退(Back) 键来回退 Fragment 事务(后退一步)。

当把 Fragment 加入 Activity 布局(Layout) 后,它位于 Activity View 层次架构(Hierarchy)的某个 ViewGroup 里,且拥有自己的 View 布局定义。 通过在 Activity 的 Layout 文件中声明 <fragment> 元素,可以在 Layout 中添加一个 Fragment。 也可以用程序代码在已有的 ViewGroup 中添加一个 Fragment。 不过, Fragment 并不一定非要是 Activity 布局的一部分,它也可以没有自己的界面,而是用作 Activity 的非可视化工作组件。

本文介绍了如何创建应用程序并使用 Fragment , 包括: Fragment 在加入 Activity 的回退栈后如何维护自身的状态、 如何与 Activity 及同一 Activity 中的其他 Fragment 共享事件、 如何构建 Activity 的 Action Bar,等等。

设计理念

Android 自 3.0 (API 级别 11)开始引入 Fragment,主要用来在平板电脑之类的大屏幕设备上提供更加动态、灵活的用户界面设计。 因为平板电脑的屏幕要比手机大得多,可以有更多的空间来组合和变换 UI 组件的位置。 有了 Fragment ,就可以在设计界面时不用去维护 View 层次架构的复杂变化。 通过把 Activity 的布局拆分到多个 Fragment 中去,可以在运行时修改 Activity 的外观,还可以把这些变动保存到 Activity 的回退栈中。

例如,某个新闻应用可以在左侧用一个 Fragment 显示文章列表,在右侧用另一个 Fragment 显示文章内容, 这两个 Fragment 并排显示在同一个 Activity 中,每个 Fragment 拥有各自的生命周期回调方法,并处理自己的用户输入事件。 这样,就不是用一个 Activity 选择文章、另一个 Activity 阅读文章了,用户可以在同一个 Activity 中完成这两个操作, 图1 给出了平板电脑上的布局示意。

每个 Fragment 都应被设计为模块化、可复用的 Activity 组件。 也就是说,由于每个 Fragment 都定义了自己的布局, 并用自己的生命周期回调方法定义了自己的运行方式, 一个 Fragment 可以被放入多个 Activity 中去, 所以它应该被设计为可复用的,并避免从一个 Fragment 中直接操作另一个 Fragment。 这一点非常重要,因为模块化的 Fragment 可以实现不同屏幕尺寸下对 Fragment 组合的修改。 在设计通用于平板电脑和手机的应用程序时,可以在各种不同的布局下复用 Fragment ,以便根据可用屏幕空间的大小优化用户的体验。 例如,在手机上,如果在同一个 Activity 中放不下多个 Fragment , 就可能有必要把多个 Fragment 分开,界面上只显示单个组件。

图 1. 两个由 Fragment 定义的 UI 模块示例,在平板电脑上可以并入一个 Activity 中显示,而在手机上则可以分开显示。

例如 — 继续以上面的新闻应用为例 — 如果在平板电脑尺寸的设备上运行,此应用可以把两个 Fragment 都嵌入 Activity A 中显示。 而在手机尺寸的屏幕上,空间不足以同时容纳两个 Fragment , 所以 Activity A 就仅包含一个文章列表 Fragment , 用户选中某篇文章后,就会打开 Activity B,里面放置了阅读文章用的第二个 Fragment 。 这样,通过以不同的组合方式复用 Fragment ,应用程序就能同时支持平板电脑和手机了,如图 1 所示。

关于针对不同屏幕参数设计不同的 Fragment 组合,详情请参阅指南 同时支持平板电脑和手机

创建 Fragment

图 2. Fragment 的生命周期(当其 Activity 正在运行时)

要创建 Fragment ,必须创建一个 Fragment 对象(或者它的已存在子类)的子类。 Fragment 类的代码与 Activity 非常相像。它包含了一些与 Activity 类似的回调方法,比如 onCreate()onStart()onPause()onStop()。 实际上,如果要把某个已有的 Android 应用转换成使用 Fragment 的应用,只要把 Activity 回调方法中的代码移入对应的 Fragment 方法中去即可。

通常,至少要实现以下生命周期方法:

onCreate()
系统将在创建 Fragment 时调用本方法。 在本方法的代码中,应该对那些在 Fragment 暂停或停止时需要保存状态的必要组件进行初始化。
onCreateView()
系统将在 Fragment 第一次绘制用户界面时调用本方法。 为了绘制 Fragment 的用户界面,必须让本方法返回一个 View ,用作 Fragment 布局的根元素。 如果 Fragment 没有用户界面,则可以返回 null 。
onPause()
只要用户有要离开 Fragment 的迹象(尽管这并不意味着一定会销毁 Fragment),系统将会首先调用本方法。 通常这时应该提交当前用户会话(Session)中需要保存的的所有改动(因为用户可能不会再回来了)。

大部分应用程序都至少应该为每个 Fragment 实现以上三个方法, 为了能够应对 Fragment 各个生命周期状态的变化,还应该实现更多其他的回调方法。 所有的生命周期回调方法将在处理 Fragment 的生命周期一节中进行详细讨论。

除了基类 Fragment 之外,还有其他一些子类可供扩展:

DialogFragment
显示一个浮动的对话框。 用这个类创建的对话框可以很好地替代 Activity 类的助手(Helper)方法创建的对话框, 因为 Fragment 对话框可以置入 Activity 的回退栈,这样用户就可以返回已经关闭的 Fragment 了。
ListFragment
显示一个列表框,其中的列表项由适配器(Adapter)提供(比如 SimpleCursorAdapter ,这类似于 ListActivity )。本 Fragment 提供了很多管理列表 View 的方法,比如用于处理点击事件的 onListItemClick() 回调方法。
PreferenceFragment
以列表的方式显示一个由 Preference 对象组成的层次结构,类似于 PreferenceActivity 。这在创建应用程序的“设置” Activity 时会很有用。

添加用户界面

Fragment 通常用作 Activity 用户界面的一部分,并且把自己的布局提供给 Activity 使用。

要为 Fragment 提供一个布局,必须实现 onCreateView() 回调方法,当 Fragment 需要绘制自己的布局时, Android 系统将会调用该方法。 该方法必须返回一个 View ,用作 Fragment 布局的根元素 。

注意: 如果 Fragment 是 ListFragment 的子类, onCreateView() 的默认代码将返回一个 ListView ,这时就不必再自行实现这个 View 了。

要让 onCreateView() 返回一个布局 ,可以从 Layout 资源 中读取 XML 定义并生成。 onCreateView() 里有一个 LayoutInflater 对象,可有助于完成建立布局的工作。

下面给出了一个 Fragment 子类的例子,它从 example_fragment.xml 文件中载入布局定义:

复制代码
1 public static class ExampleFragment extends Fragment {
2 @Override
3 public View onCreateView(LayoutInflater inflater, ViewGroup container,
4  Bundle savedInstanceState) {
5 // 载入 Fragment 的布局
6 return inflater.inflate(R.layout.example_fragment, container, false);
7 }
8 }
复制代码

传给 onCreateView()container 参数是当前 Fragment 布局的上一级 ViewGroup (来自 Activity 的布局),Fragment 的布局定义都将插入其中。 savedInstanceState 参数是一个 Bundle 对象,如果该 Fragment 是被恢复运行,这里面给出了前一次 Fragment 实例的有关数据(关于还原状态的更多信息,在 处理 Fragment 的生命周期一节中讨论)。

inflate() 方法有三个参数:

  • 用来生成布局的资源 ID。
  • 要生成的布局的上一级 ViewGroup 。传入 container 是非常重要的,系统用它作为新生成布局的根 View ,它将被设为新布局的父 View 。
  • 布尔值,指明了新生成的布局是否要与 ViewGroup (第二个参数)绑定。 (在此例中设为 False ,因为系统已经是要把新生成的布局插入到 container 中去了。 这里如果传入 True 将会在最终的布局中创建一个多余的 ViewGroup 。)

这就是创建一个自带布局的 Fragment 的过程。接下来,需要把 Fragment 加入到 Activity 中去。

把 Fragment 加入 Activity

通常,Fragment 向宿主 Activity 提供了一些用户界面,这些 UI 嵌入到 Activity 整体的 View 层次结构中,成为其中的组成部分。 把 Fragment 加入 Activity 布局的途径有两种:

  • 在 Activity 布局文件中声明 Fragment这种情况下,可以把 Fragemt 视为 View ,指定其 Layout 属性。 比如,下面是包含两个 Fragment 的 Activity 的布局文件:
    复制代码
    < ?xml version="1.0" encoding="utf-8"? > 
    < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent" > 
    < fragment android:name="com.example.news.ArticleListFragment"
    android:id="@+id/list"
    android:layout_weight="1"
    android:layout_width="0dp"
    android:layout_height="match_parent" / > 
    < fragment android:name="com.example.news.ArticleReaderFragment"
    android:id="@+id/viewer"
    android:layout_weight="2"
    android:layout_width="0dp"
    android:layout_height="match_parent" / > 
    < /LinearLayout >
    复制代码

    <fragment>android:name属性 指定了要在布局中实例化的 Fragment 类。

    在创建这个 Activity 布局时,系统会实例化布局中每一个 Fragment,并调用每个 Fragment 的 onCreateView() 方法以读取各自的布局定义。 系统会在 <fragment> 元素的位置直接插入 Fragment 返回的 View

    注意: 每个 Fragment 的标识符都必须唯一,当重启 Activity 时,系统可以用此标识符来恢复 Fragemt (还可以用此标识符来记录 Fragment 状态,以便执行移除之类的事务操作。 给 Fragment 赋予 ID 的方式有三种:

    • android:id 属性给定一个唯一的 ID。
    • android:tag 属性给定一个唯一的字符串。
    • 如果前两者都未指定,系统就采用父容器 View 的 ID。
  • 或者,还可以通过程序代码在已有的 ViewGroup 内添加 Fragment。 在 Activity 运行的任何时刻,都可以在 Activity 布局中添加 Fragment 。 只需要指定放置 Fragment 的 ViewGroup 即可。

    要想在 Activity 中执行 Fragment 事务(如添加、移除、替换 Fragment),必须使用 FragmentTransaction 提供的 API 来完成。 可以采用如下途径从 Activity 获得 FragmentTransaction 的一个实例:

    1 FragmentManager fragmentManager = 
    2 getFragmentManager()
    3 FragmentTransaction fragmentTransaction = fragmentManager.
    4 beginTransaction();

    然后,就可以用 add() 方法添加一个 Fragment ,指定要添加的 Fragment 及其所在的 View 即可。 例如:

    1 ExampleFragment fragment = new ExampleFragment();
    2 fragmentTransaction.add(R.id.fragment_container, fragment);
    3 fragmentTransaction.commit();

    传入 add() 的第一个参数是将要放置 Fragment 的 ViewGroup ,给出资源 ID 即可。 第二个参数是要添加的 Fragment。

    一旦用 FragmentTransaction 做出了改动,就必须调用 commit() 让改动生效。

添加不带用户界面的 Fragment

上述例子演示了如何在 Activity 中添加一个提供用户界面的 Fragment 。 然而,还可以用 Fragment 为 Activity 执行一个不带界面的后台操作。

要为 Activity 添加一个不带用户界面的 Fragment,请使用 add(Fragment, String) (这里要为 Fragment 指定唯一的字符串标签“tag”,而不是 View 的 ID )。 因为未与 Activity 布局中的任何 View 关联,所以此类 Fragment 不会接收到 onCreateView() 调用。这样就不需要实现此方法了。

为 Fragment 定义的字符串标签并不是只能用于无界面的 Fragment,带有用户界面的 Fragment 也可以给定字符串标签。 但是,如果 Fragment 没有用户界面,字符串标签就是标识它的唯一方式。 如果以后需要从 Activity 中获取该 Fragment ,就要用到 findFragmentByTag() 了。

关于使用不带界面的 Fragment 执行后台操作的 Activity 示例,请参阅例程 FragmentRetainInstance.java

管理 Fragment

要对 Activity 中的 Fragment 进行管理,需要用到 FragmentManager 。它可以通过调用 Activity 的 getFragmentManager() 方法来获取。

通过 FragmentManager 可以完成的操作包括:

  • findFragmentById() 方法获取 Activity 中已存在的 Fragment (适用于向 Activity 布局提供了用户界面的 Fragment ),或者用 findFragmentByTag() 方法获取(不论是否提供界面都可使用)。
  • popBackStack() 方法将 Fragment 从回退栈中弹出(模拟由用户发起的回退Back指令)。
  • addOnBackStackChangedListener() 注册一个用于监视回退栈变化的侦听器。

关于这些方法的更多详情,请参阅 FragmentManager 类的文档。

在上一节的示例中,可以用 FragmentManager 启动一个 FragmentTransaction ,这样就可以执行一些诸如添加和移除之类的事务了。

执行 Fragment 事务

在 Activity 中使用 Fragment 的一大好处,就是可以根据用户的需求对其进行添加、移除、替换及其他操作。 提交给 Activity 的一组操作被称作一个事务,可以通过 FragmentTransaction 提供的 API 来执行事务。 每一个事务都可被保存到 Activity 的回退栈中,用户可以回退这些 Fragment 操作(类似于回退多个 Activity 一样)。

以下演示了从 FragmentManager 中获取一个 FragmentTransaction 的实例:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每个事务由一系列需要一起完成的操作组成。 可以在一个事务中执行多个操作,诸如 add()remove()replace() 等。然后,必须调用 commit() 向 Activity 提交事务。

为了能把某个事务加入 Fragment 事务的回退栈中, 可以在 commit() 之前先调用 addToBackStack() 。这个回退栈是由 Activity 管理的,以便用户可以按下Back按钮返回之前的 Fragment 状态。

例如,下面演示了用一个 Fragment 替换另一个,并把前一个状态保存到回退栈中:

复制代码
 1 // 创建新的 Fragment 和事务
 2 Fragment newFragment = new ExampleFragment();
 3 FragmentTransaction transaction = getFragmentManager().beginTransaction();
 4 
 5 // 用该 Fragment 替换 fragment_container View 中的内容,
 6 // 并把事务加入回退栈
 7 transaction.replace(R.id.fragment_container, newFragment);
 8 transaction.addToBackStack(null);
 9 
10 // 提交回退栈
11 transaction.commit();
复制代码

在这个例子中,newFragment 替换了 ID 为 R.id.fragment_container 的布局容器中的当前 Fragment 。 通过调用 addToBackStack() ,这次替换操作作为事务被保存到回退栈中,这样用户就可以按下 Back 键回滚事务,回到前一个 Fragment 中。

如果在一个事务中加入了多步操作(比如更多的 add()remove() 操作),并且调用了 addToBackStack() ,那么在 commit() 之前的全部这些操作都会作为同一个事务进入回退栈,按下 Back 键后将会回滚全部操作。

同一个 FragmentTransaction 中的操作与加入顺序无关,除了:

  • commit() 必须最后调用;
  • 如果在同一个容器中添加了多个 Fragment ,那么添加的顺序决定了在 View 层次结构中的显示顺序。

如果在执行移除 Fragment 事务时没有调用 addToBackStack() ,那么事务提交后该 Fragment 将会被销毁,用户就无法再返回了。 反之,如果移除前调用了 addToBackStack() ,那么 Fragment 将会被停止,当用户返回时将会被恢复。

提示: 每个 Fragment 事务都可以应用一种过渡动画效果,这通过在提交事务前调用 setTransition() 来实现。

调用 commit() 后事务并不是立即被执行的,而是被排入 Activity UI 线程(主线程)的运行计划,一有空闲就会执行。 不过在必要时,也可以调用当前 UI 线程的 executePendingTransactions() ,以便立即执行已提交的事务。 通常没必要这么做,除非有其他线程中的任务在等待该事务的完成。

警告: 只有在 Activity 保存状态 之前才能利用 commit() 来提交 Fragment 事务。 如果在之后提交会抛出异常。这是因为在恢复 Activity 时,事务提交之后的状态可能会丢失。 如果丢失提交内容也没什么关系,请使用 commitAllowingStateLoss()

与 Activity 通讯


虽然 Fragment 是独立于 Activity 实现的对象,且一个 Fragment 可在多个 Activity 中使用,但对某个 Fragment 实例而言,是与其所在的 Activity 绑定在一起的。

具体来说, Fragment 可以通过 getActivity() 访问 Activity 实例,很容易就能实现查找 Activity 布局中的 View 之类的任务:

    View listView =getActivity().findViewById(R.id.list);

同理, Activity 也可以通过 FragmentManagerfindFragmentById()findFragmentByTag() 来获取一个 Fragment 的引用。 例如:

    ExampleFragment fragment =(ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

为 Activity 创建事件回调方法

某些情况下, Fragment 也许需要与 Activity 共享事件。 较好的处理方式是在 Fragment 中定义一个回调接口,并要求宿主 Activity 实现这个接口。 当 Activity 通过接口接收到某个回调方法时,可以根据需要与同一布局中的其他 Fragment 分享事件信息。

例如,某个新闻应用在一个 Activity 中包含了两个 Fragment —— 一个用于显示文章列表(Fragment A),另一个显示文章内容(Fragment B) —— 当某个列表项被选中时,Fragment A 必须把信息告诉 Activity,以便通知 Fragment B 显示文章内容。 这种情况下,就可在 Fragment A 中定义一个 OnArticleSelectedListener接口:

复制代码
1 public static class FragmentA extends ListFragment {
2     ...
3     // 容器 Activity 必须实现该接口
4     public interface OnArticleSelectedListener {
5         public void onArticleSelected(Uri articleUri);
6     }
7     ...
8 }
复制代码

然后,Fragment 的宿主 Activity 实现了 OnArticleSelectedListener 接口,并重写(Override) onArticleSelected() 方法,以便将 Fragment A 的事件发送给 Fragment B。 为了确保宿主 Activity 必须实现该接口,在 Fragment A 的 onAttach() 回调方法(系统会在把 Fragment 添加到 Activity 之后调用)中,通过对传入 onAttach()Activity 进行强制类型转换(Cast),尝试实例化OnArticleSelectedListener对象:

复制代码
 1 public static class FragmentA extends ListFragment {
 2     OnArticleSelectedListener mListener;
 3     ...
 4     @Override
 5     public void onAttach(Activity activity) {
 6         super.onAttach(activity);
 7         try {
 8             mListener = (OnArticleSelectedListener) activity;
 9         } catch (ClassCastException e) {
10             throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
11         }
12     }
13     ...
14 }
复制代码

如果 Activity 没有实现接口, Fragment 将会抛出异常 ClassCastException。 如果转换成功,成员变量mListener中就指向了 Activity 已实现的OnArticleSelectedListener, 这样 Fragment A 就可以通过调用OnArticleSelectedListener接口中定义的方法,与 Activity 分享事件了。 例如,假定 Fragment A 扩展自 ListFragment ,当用户每次点击列表项时,系统就会调用 Fragment 的 onListItemClick() 方法,然后由它调用 onArticleSelected() 与 Activity 分享事件。

复制代码
 1 public static class FragmentA extends ListFragment {
 2     OnArticleSelectedListener mListener;
 3     ...
 4     @Override
 5     public void onListItemClick(ListView l, View v, int position, long id) {
 6         // Append the clicked item's row ID with the content provider Uri
 7         Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
 8         // Send the event and Uri to the host activity
 9         mListener.onArticleSelected(noteUri);
10     }
11     ...
12 }
复制代码

传入onListItemClick()id参数是被点击项的原始 ID, Activity(或其他 Fragment)用它从应用程序的 ContentProvider 中读取文章内容。

关于更多使用 Content Provider 的信息,请参阅文档 Content Provider

向 Action Bar 添加菜单项

通过实现 onCreateOptionsMenu() ,Fragment 可以为 Activity 的 选项菜单(及 Action Bar)提供菜单项。 不过,要让该方法能接收到调用,必须在 onCreate() 方法中调用 setHasOptionsMenu() ,以便声明该 Fragment 将要在“选项”(Options)菜单中添加菜单项(否则,Fragment 将不会收到 onCreateOptionsMenu()) 调用。

所有由 Fragment 添加到“选项”菜单中去的菜单项,都将追加到已有菜单项的后面。 当选中某个菜单项时, Fragment 同时还会收到 onOptionsItemSelected() 调用。

通过调用 registerForContextMenu() ,还可以把 Fragment 布局中的某个 View 注册为上下文(Context)菜单。 当用户打开此菜单时, Fragment 将会接收到一次 onCreateContextMenu(). When the user selects an item, the fragment receives a call to onContextItemSelected() 调用。

注意: 当用户选中某个菜单项时,虽然 Fragment 是会收到“on-item-selected”的回调,但 Activity 将首先收到相应的回调。 如果 Activity 的“on-item-selected”回调方法的代码中没有对选中项进行处理,事件才会传给 Fragment 回调。 “选项”菜单和上下文菜单都是如此。

关于菜单的更多信息,请参阅开发指南中的 菜单Action Bar 章节。

处理 Fragment 的生命周期


图 3. Activity 生命周期对 Fragment 生命周期的影响

Fragment 生命周期的管理与 Activity 的非常相像。 与 Activity 类似, Fragment 可能处于三种状态:

恢复(Resumed)
Fragment 在当前 Activity 中可见。
暂停(Paused)
其他 Activity 处于前台并获得焦点,但 Fragment 所在的 Activity 仍然可见 (前台 Activity 是部分透明的或者未完全遮挡整个屏幕)。
停止(Stopped)
Fragment 不可见。也许是宿主 Activity 已被停止,也许 Fragment 已从 Activity 中移除且被加入回退栈中。 已被停止的 Fragment 仍然是存活的(所有的状态和成员信息都被系统保存着)。 不过,用户看不到此 Fragment 了,当 Activity 被杀死时 Fragment 也会被杀死。

与 Activity 类似,在 Activity 进程被杀死并被重新创建,且需要恢复 Fragment 的状态时, 可以利用 Bundle 取回 Fragment 的状态。 可以在 Fragment 的 onSaveInstanceState() 回调方法中保存 Fragment 的状态,并在 onCreate()onCreateView()onActivityCreated() 方法中恢复状态。 关于保存状态的更多信息,请参阅文档 Activity

Activity 与 Fragment 生命周期之间的最明显区别,就是在各自的回退栈中的保存方式。 当 Activity 被停止时,它默认会被存放在系统管理的 Activity 回退栈中(如 任务和回退栈 所述,用户可以用Back键回退回去)。 然而,仅当在移除 Fragment 的事务中调用 addToBackStack() 显式地请求保存实例, Fragment 才会被放入由宿主 Activity 管理的回退栈中。

除此之外, Fragment 生命周期的管理与 Activity 非常类似。 因此,管理 Activity 生命周期 一文中介绍的做法同样也适用于 Fragment 。 当然,还有必要了解 Activity 生命周期对 Fragment 生命周期的影响。

提醒: 如果需要在 Fragment 中使用 Context 对象,可以调用 getActivity()。 但是请注意,只能在 Fragment 附着于 Activity 之后,才能进行调用。 当 Fragment 还没有与 Activity 关联之前,或者在生命周期结束被解除关联之后, getActivity() 将会返回 null。

与 Activity 的生命周期合作

宿主 Activity 的生命周期直接影响着 Fragment 的生命周期, Activity 的所有生命周期回调方法都会触发其中每一个 Fragment 的对应回调方法。 比如,当 Activity 收到 onPause() 时,其中的每一个 Fragment 都会收到 onPause() 调用。

不过,Fragment 还拥有一些自己的生命周期回调方法,这些方法只与 Activity 有关,用于执行创建或销毁 Fragment 界面之类的操作。 这包括:

onAttach()
当 Fragment 与 Activity 建立关联时将会调用(这里会传入 Activity)。
onCreateView()
创建与 Fragment 关联的 View 层次架构。
onActivityCreated()
当 Activity 的 onCreate() 方法返回时将会调用。
onDestroyView()
当与 Fragment 关联的 View 层次架构被删除时将会调用。
onDetach()
当 Fragment 与 Activity 解除关联时将会调用。

图3中给出了 Fragment 生命周期的流程,及其与 宿主 Activity 之间的关系。 从图中可知, Activity 所处的各个状态决定了 Fragment 可能收到的回调方法。 比如,当 Activity 已收到过 onCreate() 调用之后,该 Activity 中的 Fragment 将不再会收到 onActivityCreated() 调用了。

只要 Activity 进入了恢复(Resumed)状态,就可以在其中自由添加或移除 Fragment 了。 也只有在 Activity 处于已恢复状态时,Fragment 的生命周期才可以脱开 Activity 而独自变化。

不过,一旦 Activity 离开了已恢复状态, Fragment 的生命周期就又被 Activity 所左右了。

示例


以下示例将把本文所述综合在一起展示,这里的 Activity 用到两个 Fragment 创建了双板块布局。 在下面的 Activity 中,一个 Fragment 显示了莎士比亚戏剧列表,另一个 Fragment 显示了列表选中剧目的简介。 并且,还演示了如何根据屏幕配置设置 Fragment 的参数。

注意: 本例的完整源码见 FragmentLayout.java

按惯例,主 Activity 在 onCreate() 中应用了一个布局:

复制代码
1 @Override
2 protectedvoid onCreate(Bundle savedInstanceState){
3     super.onCreate(savedInstanceState);
4 
5     setContentView(R.layout.fragment_layout);
6 }
复制代码

布局由fragment_layout.xml文件给出:

复制代码
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"android:layout_height="match_parent">

    <fragmentclass="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"android:layout_weight="1"
            android:layout_width="0px"android:layout_height="match_parent"/>

    <FrameLayoutandroid:id="@+id/details"android:layout_weight="1"
            android:layout_width="0px"android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground"/>
</LinearLayout>
复制代码

只要 Activity 载入该布局文件,系统就立刻实例化了TitlesFragment(显示剧目用的)。而 FrameLayout (用于显示戏剧简介的 Fragment)将会占据屏幕右侧的空间,但一开始是空的。 如下所示,只有当用户在列表中选择了一项,才会在 FrameLayout 中放入一个 Fragment 。

然而,不是所有的屏幕都能同时把剧目表和简介并排显示出来。 所以上述布局只适用于横向屏幕,即保存到res/layout-land/fragment_layout.xml

但是,当屏幕为纵向模式,系统将会使用保存在res/layout/fragment_layout.xml中的以下布局:

复制代码
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>
复制代码

该布局只包含TitlesFragment。 这就是说,当设备处于纵向模式下,将只显示剧目列表。 因此,如果这时用户点击列表项的话,应用程序将会打开一个新 Activity 用来显示简介,而不是载入第二个 Fragment 。

下面将展示如何在 Fragment 类中实现这种显示方式。 首先是显示莎士比亚戏剧列表的TitlesFragment。 这个 Fragment 扩展自 ListFragment 并靠它来完成大部分列表框(List View)的操作。

在查看这部分代码时请注意,当用户点击某个列表项时,可能要执行两种操作: 根据当前的布局不同,或是在同一个 Activity 中新建并显示一个展现戏剧简介的 Fragment (在 FrameLayout 中添加 Fragment),或是启动一个新的 Activity (用于显示 Fragment)。

复制代码
 1 public static class TitlesFragment extends ListFragment {
 2     boolean mDualPane;
 3     int mCurCheckPosition = 0;
 4 
 5     @Override
 6     public void onActivityCreated(Bundle savedInstanceState) {
 7         super.onActivityCreated(savedInstanceState);
 8 
 9         // 用存放剧目名称的静态数组填充列表
10         setListAdapter(new ArrayAdapter<String>(getActivity(),
11                 android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
12 
13         // 检查以下,当前界面容器中是否存在 Frame,
14         // details Fragment 将置入其中。
15         View detailsFrame = getActivity().findViewById(R.id.details);
16         mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;
17 
18         if (savedInstanceState != null) {
19             // 恢复上次选中的位置
20             mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
21         }
22 
23         if (mDualPane) {
24             // 在双板块同时显示的模式下,列表 View 高亮显示选中项。
25             getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
26             // 确认界面状态是否正确
27             showDetails(mCurCheckPosition);
28         }
29     }
30 
31     @Override
32     public void onSaveInstanceState(Bundle outState) {
33         super.onSaveInstanceState(outState);
34         outState.putInt("curChoice", mCurCheckPosition);
35     }
36 
37     @Override
38     public void onListItemClick(ListView l, View v, int position, long id) {
39         showDetails(position);
40     }
41 
42     /**
43      * 助手函数,用于显示选中项的内容,
44      * 或是在当前界面显示 Fragment,
45      * 或是新开一个 Activity 显示。
46      */
47     void showDetails(int index) {
48         mCurCheckPosition = index;
49 
50         if (mDualPane) {
51             // 当前可以用 Fragment 同时显示两块内容,
52             // 因此只要更新列表,把选中项高亮显示,并显示其详情。
53             getListView().setItemChecked(index, true);
54 
55             // 检查当前显示的是哪个 Fragment,必要的话进行替换。
56             DetailsFragment details = (DetailsFragment)
57                     getFragmentManager().findFragmentById(R.id.details);
58             if (details == null || details.getShownIndex() != index) {
59                 // 新建 Fragment 显示选中项
60                 details = DetailsFragment.newInstance(index);
61 
62                 // 提交事务,
63                 // 用 Frame 中的当前 Fragment 替换原有的。
64                 FragmentTransaction ft = getFragmentManager().beginTransaction();
65                 if (index == 0) {
66                     ft.replace(R.id.details, details);
67                 } else {
68                     ft.replace(R.id.a_item, details);
69                 }
70                 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
71                 ft.commit();
72             }
73 
74         } else {
75             // 否则,需要启动新的,
76             // 把包含所选项内容的对话框 Fragment 显示出来。
77             Intent intent = new Intent();
78             intent.setClass(getActivity(), DetailsActivity.class);
79             intent.putExtra("index", index);
80             startActivity(intent);
81         }
82     }
83 }
复制代码

第二个 Fragment 的代码,用于显示TitlesFragment内所选剧目的简介:

复制代码
 1 public static class DetailsFragment extends Fragment {
 2     /**
 3      * 创建 DetailsFragment 的新实例,
 4      * 初始时显示文字“index”。
 5      */
 6     public static DetailsFragment newInstance(int index) {
 7         DetailsFragment f = new DetailsFragment();
 8 
 9         // 将 index 作为输入参数
10         Bundle args = new Bundle();
11         args.putInt("index", index);
12         f.setArguments(args);
13 
14         return f;
15     }
16 
17     public int getShownIndex() {
18         return getArguments().getInt("index", 0);
19     }
20 
21     @Override
22     public View onCreateView(LayoutInflater inflater, ViewGroup container,
23             Bundle savedInstanceState) {
24         if (container == null) {
25             // 这里存在多种布局,
26             // 其中一个不存在包含本 Fragment 的容器 Frame。
27             // Fragment 可能是从已保存的状态中创建,
28             // 但因为不会再显示出来了,所以没有理由再为其创建 View 层次结构。
29             // 请注意,这段代码不是必需的——
30             // 可以直接运行后面的代码,创建并返回 View 层次结构;
31             // 只是它再也用不上罢了。
32             return null;
33         }
34 
35         ScrollView scroller = new ScrollView(getActivity());
36         TextView text = new TextView(getActivity());
37         int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
38                 4, getActivity().getResources().getDisplayMetrics());
39         text.setPadding(padding, padding, padding, padding);
40         scroller.addView(text);
41         text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
42         return scroller;
43     }
44 }
复制代码

请回忆一下TitlesFragment类,如果用户点击了某个列表项,且当前布局中包含R.id.details View (也就是DetailsFragment),那么应用程序将会启动DetailsActivity来显示列表项的有关内容。

下面是DetailsActivity类,当屏幕处于纵向模式时,这里只是简单地把DetailsFragment嵌进来而已,用于显示所选剧目的简介。

复制代码
 1 publicstaticclassDetailsActivityextendsActivity{
 2 
 3     @Override
 4     protectedvoid onCreate(Bundle savedInstanceState){
 5         super.onCreate(savedInstanceState);
 6 
 7         if(getResources().getConfiguration().orientation
 8                 ==Configuration.ORIENTATION_LANDSCAPE){
 9             // 如果当前屏幕处于横向模式,
10             // 对话框可以与列表同时显示,就不需要此 Activity了。
11             finish();
12             return;
13         }
14 
15         if(savedInstanceState ==null){
16             // 在初始化过程中,插入details Fragment。
17             DetailsFragment details =newDetailsFragment();
18             details.setArguments(getIntent().getExtras());
19             getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
20         }
21     }
22 }
复制代码

请注意,此 Activity 在屏幕横向放置时将会自动终止,以便让主 Activity 接替它把DetailsFragment显示在TitlesFragment旁边。 如果用户在打开DetailsActivity时是纵向模式,然后旋转到横向模式(这时会重新启动当前 Activity),就会发生这种情况。

关于使用 Fragment 的更多示例(以及本例的完整代码),请参阅 ApiDemos 中的 API 演示例程(可在 SDK 组件实例 )下载。

[转载]php图像处理函数大全(推荐收藏) - un123 - 博客园

mikel阅读(910)

[转载]php图像处理函数大全(推荐收藏) – un123 – 博客园.php图片处理代码分享,包括缩放、剪裁、缩放、翻转、旋转、透明、锐化等。

一、创建图片资源
imagecreatetruecolor(width,height);
imagecreatefromgif(图片名称);
imagecreatefrompng(图片名称);
imagecreatefromjpeg(图片名称);画出各种图像 imagegif(图片资源,保存路径);
imagepng()
imagejpeg();
二、获取图片属性
imagesx(res//宽度
imagesy(res//高度
getimagesize(文件路径)
返回一个具有四个单元的数组。索引 0 包含图像宽度的像素值,索引 1 包含图像高度的像素值。索引 2 是图像类型的标记:1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM。这些标记与 PHP 4.3.0 新加的 IMAGETYPE 常量对应。索引 3 是文本字符串,内容为“height=”yyy” width=”xxx””,可直接用于 IMG 标记。
销毁图像资源
imagedestroy(图片资源);
三、透明处理
PNG、jpeg透明色都正常,只有gif不正常
imagecolortransparent(resource image [,int color])//将某个颜色设置成透明色
imagecolorstotal()
imagecolorforindex();
四、图片的裁剪
imagecopyresized()
imagecopyresampled();
五、加水印(文字、图片)
字符串编码转换string iconv ( string $in_charset , string $out_charset , string $str )
六、图片旋转
imagerotate();//制定角度的图片翻转
七、图片的翻转
沿X轴 沿Y轴翻转
八、锐化
imagecolorsforindex()
imagecolorat()
在图片上画图形 $img=imagecreatefromgif(“./images/map.gif”);

/**
* 图片锐化处理
*/
$red= imagecolorallocate($img, 255, 0, 0);

imageline($img, 0, 0, 100, 100, $red);
imageellipse($img, 200, 100, 100, 100, $red);
imagegif($img, “./images/map2.gif”);
imagedestroy($img);
图片普通缩放
$filename=”./images/hee.jpg”;

$per=0.3;
list($width, $height)=getimagesize($filename);
$n_w=$width*$per;
$n_h=$width*$per;
$new=imagecreatetruecolor($n_w, $n_h);
$img=imagecreatefromjpeg($filename);
//拷贝部分图像并调整
imagecopyresized($new, $img,0, 0,0, 0,$n_w, $n_h, $width, $height);
//图像输出新图片、另存为
imagejpeg($new, “./images/hee2.jpg”);
imagedestroy($new);
imagedestroy($img);

图片等比例缩放、没处理透明色
function thumn($background, $width, $height, $newfile) {
list($s_w, $s_h)=getimagesize($background);//获取原图片高度、宽度
if ($width && ($s_w < $s_h)) {
$width = ($height / $s_h) * $s_w;
} else {
$height = ($width / $s_w) * $s_h;
}
$new=imagecreatetruecolor($width, $height);
$img=imagecreatefromjpeg($background);
imagecopyresampled($new, $img, 0, 0, 0, 0, $width, $height, $s_w, $s_h);
imagejpeg($new, $newfile);
imagedestroy($new);
imagedestroy($img);
}
thumn(“images/hee.jpg”, 200, 200, “./images/hee3.jpg”);

gif透明色处理
function thumn($background, $width, $height, $newfile) {
list($s_w, $s_h)=getimagesize($background);
if ($width && ($s_w < $s_h)) { $width = ($height / $s_h) * $s_w; } else { $height = ($width / $s_w) * $s_h; } $new=imagecreatetruecolor($width, $height); $img=imagecreatefromgif($background); $otsc=imagecolortransparent($img); if($otsc >=0 && $otst < imagecolorstotal($img)){//判断索引色
$tran=imagecolorsforindex($img, $otsc);//索引颜色值
$newt=imagecolorallocate($new, $tran[“red”], $tran[“green”], $tran[“blue”]);
imagefill($new, 0, 0, $newt);
imagecolortransparent($new, $newt);
}
imagecopyresized($new, $img, 0, 0, 0, 0, $width, $height, $s_w, $s_h);
imagegif($new, $newfile);
imagedestroy($new);
imagedestroy($img);
}
thumn(“images/map.gif”, 200, 200, “./images/map3.gif”);

图片裁剪

图片加水印 文字水印

/**
*
* 图片添加文字水印
*/

function mark_text($background, $text, $x, $y){
$back=imagecreatefromjpeg($background);
$color=imagecolorallocate($back, 0, 255, 0);
imagettftext($back, 20, 0, $x, $y, $color, “simkai.ttf”, $text);
imagejpeg($back, “./images/hee7.jpg”);
imagedestroy($back);
}
mark_text(“./images/hee.jpg”, “细说PHP”, 150, 250);
//图片水印
function mark_pic($background, $waterpic, $x, $y){
$back=imagecreatefromjpeg($background);
$water=imagecreatefromgif($waterpic);
$w_w=imagesx($water);
$w_h=imagesy($water);
imagecopy($back, $water, $x, $y, 0, 0, $w_w, $w_h);
imagejpeg($back,”./images/hee8.jpg”);
imagedestroy($back);
imagedestroy($water);
}
mark_pic(“./images/hee.jpg”, “./images/gaolf.gif”, 50, 200);

图片旋转

图片水平翻转垂直翻转
复制代码 代码如下:

/**
* 图片水平翻转 垂直翻转
*/
function turn_y($background, $newfile){
$back=imagecreatefromjpeg($background);
$width=imagesx($back);
$height=imagesy($back);
$new=imagecreatetruecolor($width, $height);
for($x=0; $x < $width; $x++){
imagecopy($new, $back, $width-$x-1, 0, $x, 0, 1, $height);
}
imagejpeg($new, $newfile);
imagedestroy($back);
imagedestroy($new);
}
function turn_x($background, $newfile){
$back=imagecreatefromjpeg($background);
$width=imagesx($back);
$height=imagesy($back);
$new=imagecreatetruecolor($width, $height);
for($y=0; $y < $height; $y++){ imagecopy($new, $back,0, $height-$y-1, 0, $y, $width, 1); } imagejpeg($new, $newfile); imagedestroy($back); imagedestroy($new); } turn_y(“./images/hee.jpg”, “./images/hee11.jpg”); turn_x(“./images/hee.jpg”, “./images/hee12.jpg”); ?>

[转载]PHP图形图像的典型应用 --简单图像的应用(水印) - clouds008 - 博客园

mikel阅读(845)

[转载]PHP图形图像的典型应用 –简单图像的应用(水印) – clouds008 – 博客园.

php使用图像要用到GD或GD2库。才行。如果要高级的图形形状,还可以下载JPgraph类库它是完全由php语言写的。当然也是基于GD/GD2库的

 

1、创建一个简单的图像

<?php
    /*在图像输出前,不能有html元素输出*/
    header("Content-type:image/jpeg"); //这里要设置一下头信息、告诉页面以什么方式呈现。否则直接输出图像会乱码
    /* ---------------例 12.1 创建一个简单的普通的图像 ---------------- */
    $im = imagecreate(200,60);  //创建一个画布
    $white = imagecolorallocate($im,255,66,159);
    imagegif($im);
?>

2、在照片上添加文字(文字水印)

<?php
    /* ----------------- 例 12.2 在照片上添加文字。可以是水印 -------------------*/
    header("Content-type:image/jpeg"); //这里要设置一下头信息、告诉页面以什么方式呈现。否则直接输
    $im = imagecreatefromjpeg("images/123.jpg");  //载入图片
    $textcolor = imagecolorallocate($im,56,73,136);  //设置字体颜色为蓝色,值为RGB颜色值
    $fnt = "c:/windows/fonts/simhei.ttf"; //定义字体
    
    /* 
        这里关于转不转的问题,原理我还没弄明白。但是有一个判断的方法:
        charset=utf-8, 如果html页面的指定显示编码为utf-8的话,那么中文字就不需要用iconv转。如果是那种
        gb2312的编码就必须转utf-8 。因为在php中GD2这个图形库对中文的支持只认识utf-8的编码.
    */
    
    //$motto = iconv("gb2312","utf-8","长白山天池"); //定义输出字体串
    $motto = "长白山天池";
    imagettftext($im,50,0,20,150,$textcolor,$fnt,$motto); //写ttf文字到图形中
    imagejpeg($im);  //建立jpeg图形
    imagedestroy($im); //结束图形,释放内存空间
?>

3、图片水印

<?php
    /* ---------------- 图片水印 ----------- */
    header("Content-type:image/jpeg"); //这里要设置一下头信息、告诉页面以什么方式呈现。否则直接输
    $im = imagecreatefromjpeg("images/123.jpg"); //载入背景图
    $iml = imagecreatefromjpeg("images/321.jpg"); //载入背景图
    $imarr = getimagesize("images/321.jpg");  //获取图片的大小类型等信息。具体返回请查阅php帮助手册
    imagecopy($im,$iml,20,150,0,0,$imarr[0],$imarr[1]); //将一张图片复制到一张图片上,具体请看php手册
    imagejpeg($im);
    imagedestroy($im); //结束图像,释放内存空间
?>

[转载]移动支付SDK2.0应用小结 - ponos - 博客园

mikel阅读(1304)

[转载]移动支付SDK2.0应用小结 – ponos – 博客园.

临时接受支付宝支付任务,最初研究旧版本,后来发现新版本更简单明了优化,使用最新版的,看见旧版的写出来的人多,新版的少,咱这最精炼的通过实践滴,与大家共同进步。

1.下载移动支付接口SDK2.0标准版,解压取出:

(1)从客户端alipay-sdk-common文件夹中取出alipaysdk.jar  、alipaysecsdk.jar 、alipayutdid.jar放入新建项目libs中,Android4.0之后只要放入免手动导入,低于这个版本的按旧方法手动导入。

(2)从客户端Demo中取出Base64.java 、Result.java、SignUtils.java放在src中,对应支持的。

2.权限开通:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />   
    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />

3.支付接口调用:

/**
  * 通过支付宝支付订单
  *void
  * @exception
  * @since  1.0.0
  */
 public void pay(final String orderInfo, final String sign){
  
  threadManager.startTaskThread(new Runnable() {
   
   @Override
   public void run() {
    // TODO Auto-generated method stub
    // 构造PayTask 对象
    PayTask alipay = new PayTask(activity);
    //拼接成完整支付信息(订单+签名)
    final String payInfo = orderInfo + "&sign=\"" + sign + "\"" + "&"
      + getSignType();
    // 调用支付接口
    String result = alipay.pay(payInfo);
    payResult = Base64.encode(result.getBytes());
    
    Result rtl = new Result(result); //解析支付结果
    //TextUtils.equals(resultStatus, "9000")
    //支付结果错误码:
    /*9000:订单支付成功
     *8000:正在处理中("支付结果确认中")  代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态)
     *4000:订单支付失败
     *6001:用户中途取消
     *6002:网络连接出错  */
    final String resultStatus = rtl.resultStatus; //支付错误码
    GoloLog.d(ALIPAY_PAY_KEY, "get alipay result status: "+ resultStatus);
    if (activity != null) {
     activity.runOnUiThread(new Runnable() {
      
      @Override
      public void run() {
       // TODO Auto-generated method stub
       if (TextUtils.equals(resultStatus, "9000")) { //9000:订单支付成功   
        Toast.makeText(activity, "支付成功",
          Toast.LENGTH_SHORT).show();
       }else {
        // 判断resultStatus 为非“9000”则代表可能支付失败
        // “8000” 代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态)
        if (TextUtils.equals(resultStatus, "8000")) {
         Toast.makeText(activity, "支付结果确认中",
           Toast.LENGTH_SHORT).show();

        } else {
         Toast.makeText(activity, "支付失败",
           Toast.LENGTH_SHORT).show();

        }
       }
       
      }
     });
    }    
   }
  });
 }

4.个人界面调用:

orderInfo 订单内容

strsign  签名

//支付宝支付
 private void toAlipay() {
  // TODO Auto-generated method stub

  String orderInfo = OrderBean.getOrderInfo();
  String strsign = OrderBean.getOrderSign();

  alipayPayHandler.pay(orderInfo, strsign);

 }

[转载]SQL Server调优系列进阶篇(深入剖析统计信息) - 指尖流淌 - 博客园

mikel阅读(1254)

[转载]SQL Server调优系列进阶篇(深入剖析统计信息) – 指尖流淌 – 博客园.

前言

经过前几篇的分析,其实大体已经初窥到SQL Server统计信息的重要性了,所以本篇就要祭出这个神器了。

该篇内容会很长,坐好板凳,瓜子零食之类…

不废话,进正题

技术准备

数据库版本为SQL Server2008R2,利用微软的以前的案例库(Northwind)进行分析,部分内容也会应用微软的另一个案例库AdventureWorks

相信了解SQL Server的朋友,对这两个库都不会太陌生。

概念理解

关于SQL Server中的统计信息,在联机丛书中是这样解释的

查询优化的统计信息是一些对象,这些对象包含与值在表或索引 视图的一列或多列中的分布有关的统计信息。查询优化器使用这些统计信息来估计查询结果中的基数或行数。通过这些基数估计,查询优化器可以创建高质量的查询 计划。例如,查询优化器可以使用基数估计选择索引查找运算符而不是耗费更多资源的索引扫描运算符,从而提高查询性能。

其实关于统计信息的作用通俗点将就是:SQL Server通过统计信息理解库中每张表的数据内容项分布,知道里面数据“长得啥德行,做到心中有数”,这样每次查询语句的时候就可以根据表中的数据分布,基本能定位到要查找数据的内容位置。

比如,我记得我以前有篇文章写过一个相同的查询语句,但是产生了完全不同的查询计划,这里回顾下,基本如下:

SELECT * FROM Person.Contact
WHERE FirstName LIKE 'K%'

SELECT * FROM Person.Contact
WHERE FirstName LIKE 'Y%'

完全相同的查询语句,只是查询条件不同,一个查找以K开头的顾客,一个查找以Y开头的顾客,却产生了完全不同的查询计划。

其实,这里的原因就是统计信息在作祟。

我们知道,在这张表的FirstName字段存在一个非聚集索引,目标就是为了提升如上面的查询语句的性能。

但是这张表里面FirstName字段中的数据内容以K开头的顾客存在1255行,也就是如果利用非聚集索引查找的方式,需要产生1225次IO操 作,这可能不是最糟的,糟的还在后面,因为我们获取的数据字段并不全部在FirstName字段中,而需要额外的书签查找来获取,而这个书签查找会产生的 大量的随机IO操作。记住:这里是随机IO。关于这里的查找方式在我们第一篇文章中就有介绍。

所以相比利用非聚集索引所带来的消耗相比,全部的所以索引扫描来的更划算,因为它依次扫描就可以获取想要的数据。

而以Y开头的就只有37行,37行数据完全通过非聚集索引获取,再加一部分的书签查找很显然是一个很划算的方式。因为它数据量少,产生的随机IO量相对也会少。

所以,这里的问题来了:

SQL Server是如何知道这张表里FirstName字段中以K开头的顾客会比较多,而以Y开头反而少呢?。

这里就是统计信息在作祟了,它不但知道FirstName字段中各行数据的内容“长啥样”,并且还是知道每行数据的分布情况。

其实,这就好比在图书库中,每个书架就是一张表,而每本书就是一行数据,索引就好像图书馆书籍列表,比如按类区分,而统计信息就好像是每类书籍的多少以及存放书架位置。所以你借一本书的时候,需要借助索引来查看,然后利用统计信息指导位置,这样才能获取书本。

希望这样解释,看官已经明白了统计信息的作用了。

 

这里多谈点,有很多童鞋没有深入了解索引和统计信息的作用前提下,在看过很多调优的文章之后,只深谙了一句话:调优嘛,创建索引就行了。

我不否认创建索引这种方式调优方式的作用性,但是很多时候关于建索引的技巧却不了解。更巧的是大部分情况下属于误打误撞创建完索引后,性能果真提升了,而有时候创建的索引却毫无用处,只会影响表的其它操作的性能(尤其是Insert),更有甚者会产生死锁情况。

而且,关于索引项的作用,其实很多的情况下,并不想你想象的那么美好,后续文章我们会分析那些索引失效的原因。

所以遇到问题,其实还要通过表象理解其本质,这样才能做到真正的有的放矢,有把握的解决问题。

 

解析统计信息

我们来详细分析一下统计信息中的内容项,我们知道在上面的语句中,在表Customers中ContactName列中存在一个非聚集索引项,所以在该列存在统计信息,我们可以通过如下脚本查看该表的统计信息列表

sp_helpstats Customers

然后通过以下命令来查看该统计信息的详细内容,代码如下

DBCC SHOW_STATISTICS(Customers,ContactName)

每一个统计信息的内容都包含以上三部分的内容。

我们依次来分析下,通过这三部分内容SQL Server如何了解该列数据的内容分布的。

a、统计信息的总体属性项

该部分包含以下几列:

  • Name:统计信息的名称。
  • Updated:统计信息的最近一次更新时间,这个时间信息很重要,根据它我们能知道该统计信息什么时候更新的,是不是最新的,是不是存在统计信息更新不及时造成统计的当前数据分布不准确等问题。
  • Rows:描述当前表中的总行数。
  • Rows Sampled:统计信息的抽样数据。当数据量比较多的时候,统计信息的获取是采用的抽样的方式统计的,如果数据量比较就会通过扫描全部获取比较精确的统计值。比如,上面的例子中抽样数据就为91行。
  • Steps:步长值。也就是SQL Server统计信息的根据数据行的分组的个数。这个步长值也是有SQL Server自己确定的,因为步长越小,描述的数据越详细,但是消耗也越多,所以SQL Server会自己平衡这个值。
  • Density:密度值,也就是列值前缀的大小。
  • Average Key length:所有列的平均长度。
  • String Index:表示统计值是否为字符串的统计信息。这里字符串的评估目的是为了支持LIKE关键字的搜索。
  • Filter Expression:过滤表达式,这个是SQL Server2008以后版本的新特性,支持添加过滤表达式,更加细粒度进行统计分析。
  • Unfiltered Rows:没有经过表达式过滤的行,也是新特性。

经过上面部分的数据,统计信息已经分析出该列数据的最近更新时间、数据量、数据长度、数据类型等信息值。

 

b、统计信息的覆盖索引项

All density:反映索引列的稠密度值。这是一个非常重要的值,SQL Server会根据这个评分项来决定该索引的有效程度。

该分值的计算公式为:density=1/表中非重复的行数。所以该稠密度值取值范围为:0-1。

该值越小说明该列的索引项选择性更强,也就说该索引更有效。理想的情况是全部为非重复值,也就是说都是唯一值,这样它的数最小。

举个例子:比如上面 的例子该列存在91行,假如顾客不存在重名的情况下,那么该密度值就为1/91=0.010989,该列为性别列,那么它只存在两个值:男、女,那么该列 的密度值就为0.5,所以相比而言SQL Server在索引选择的时候很显然就会选择ContactName(顾客名字)列。

简单点讲:就是当前索引的选择性高,它的稠密度值就小,那么它就重复值少,这样筛选的时候更容易找到重复值。相反,重复值多选择性就差,比如性别,一次过滤只能过滤掉一半的记录。

Average Length:索引的平均长度。

Columns:索引列的名称。这里因为我们是非聚集索引,所以会存在两行,一行为ContactName索引列,一行为ContactName索引列和聚集索引的列值CustomerID组合列。希望能明白这里,索引基础知识。

通过以上部分信息,SQL Server会知道该部分的数据获取方式那个更快,更有效。

 

c、统计信息的直方图信息

我们接着分析第三部分,该列直方图信息,通过这块SQL Server能直观“掌控”该列的数据分布内容,我们来看

  • RANGE_HI_KEY:直方图中每一组数据的最大值。这个好理解,如果数据量大的话,经过分组,这个值就是当前组的最大值。上面例子的统计信息总共分了90组,总共才91行,也就是说,SQL Server为了准确的描述该列的值,大部分每个组只取了一个值,只有一个组取了俩值。
  • RANGE_ROWS:直方图的没组数据的区间行数(不包括最大值)。这里我们说了总共就91行,它分了90组,所以有一组会存在两个值,我们找到它:
  • EQ_ROWS:这里表示和上面最大值相等的行数目。因为我们不包含一样的,所以这里值都为 1
  • DISTINCT_RANGE_ROWS:直方图每组数据区间的非重复值的数目。上限值除外。
  • AVG_RANGE_ROWS:每个直方图平均的行数。

经过最后一部分的描述,SQL Server已经完全掌控了该表中该字段的数据内容分布了。想获取那些数据根据它就可以从容获取到。

所以当我们每次写的T-SQL语句,它都能根据统计信息评估出要获取的数据量多少,并且找到最合适的执行计划来执行。

我也相信经过上面三部分的分析,关于文章开篇我们提到的那个关于‘K’和‘Y’的问题会找到答案了,这里不解释了。

当然,如果数据量特别大,统计信息的维护也会有小小的失误,而这时候就需要我们来站出来及时的弥补。

 

创建统计信息

通过上面的介绍,其实我们已经看到了统计信息的强大作用了,所以对于数据库来说它的重要性就不言而喻了,因此,SQL Server会自动的创建统计信息,适时的更新统计信息,当然我们可以关闭掉,但是我非常不建议这么做,原因很简单:No Do  No Die…

 

这两项功能默认是开启的,也就是说SQL Server会自己维护统计信息的准确性。

在日常维护中,我们大可不必要去更改这两项,当然也有比较极端的情况,因为我们知道更新统计信息也是一个消耗,在非常的大的并发的系统中需要关掉自动更新功能,这种情况非常的少之又少,所以基本采用默认值就可以。

在以下情况下,SQL Server会自动的创建统计信息:

1、在索引创建时,SQL Server会自动的在索引列上创建统计信息。

2、当SQL Server想要使用某些列上的统计信息,发现没有的时候,这时候会自动创建统计信息。

3、当然,我们也可以手动创建。

比如,自动创建的例子

select * into CustomersStats from Customers
sp_helpstats CustomersStats

来添加一个查询语句,然后再查看统计信息

select * from CustomersStats
where ContactName='Hanna Moos'
go
sp_helpstats CustomersStats
go

当然,我们也可以根据自己的情况来手动创建,创建脚本如下

USE [Northwind]
GO
CREATE STATISTICS [CoustomersOne] ON [dbo].[CustomersStats]([CompanyName])
GO

SQL Server也提供了GUI的图像化操作窗口,方便操作

 

在以下情况下,SQL Server会自动的更新统计信息:

1、如果统计信息是定义在普通的表格上,那么当发生以下任一种的变化后,统计信息就会被触发更新动作。

  • 表格从没有数据变成大于等于1条数据。
  • 对于数据量小于500行的表格,当统计信息的第一个字段数据累计变化大于500以后。
  • 对于数据量大于500行的表格,当统计信息的第一个字段数据累计变化大于500+(20%*表格总的数据量)以后。所以对于较大的表,只有1/5以上的数据发生变化后,SQL Server才会重新计算统计信息。

2、临时表上也可以有统计信息。这也是很多情况下采用临时表优化的原因之一。其维护策略基本和普通表格一样,但是表变量不能创建统计信息。

 

文章写的有点糙….但篇幅已经稍长了….先到此吧…后续我再补充一部分关于统计信息的内容。

关于调优内容太广泛,我们放在以后的篇幅中介绍,有兴趣的可以提前关注。

 

参考文献

  • 参照书籍《Microsoft SQL Server企业级平台管理实践》
  • 参照书籍《SQL.Server.2005.技术内幕》系列

有问题可以留言或者私信,随时恭候有兴趣的童鞋加入SQL SERVER的深入研究。共同学习,一起进步。

 

文章最后给出前面几篇的连接,以下内容基本涵盖我们日常中所写的查询运算的分解,看来有必要整理一篇目录了…..

SQL Server调优系列基础篇

SQL Server调优系列基础篇(常用运算符总结)

SQL Server调优系列基础篇(联合运算符总结)

SQL Server调优系列基础篇(并行运算总结)

SQL Server调优系列基础篇(并行运算总结篇二)

SQL Server调优系列基础篇(索引运算总结)

SQL Server调优系列基础篇(子查询运算总结)

—————–以下进阶篇——————-

SQL Server调优系列进阶篇(查询优化器的运行方式)

SQL Server调优系列进阶篇(查询语句运行几个指标值监测)

 

如果您看了本篇博客,觉得对您有所收获,请不要吝啬您的“推荐”。

[转载]php给图片加文字 - nd - 博客园

mikel阅读(1026)

转载php给图片加文字 – nd – 博客园.

在图片上加文字是论坛,博客,新闻网站上最喜欢用的功能,防止盗图。这里看看代码是如何实现的。

首先还是upload_image.php这个文件,注意这里的caption文本框中输入的内容最终会写到图片上面

<!--?php  //修改图片效果 $db = mysql_connect('localhost','root','Ctrip07185419') or die('can not connect to database'); mysql_select_db('moviesite',$db) or die(mysql_error($db)); //上传文件的路径 $dir = 'D:\Serious\phpdev\test\images'; //设置环境变量 //putenv('GDFONTPATH='.'C:\Windows\Fonts'); $font = "C:\Windows\Fonts\arial.ttf"; //upload_image.php页面传递过来的参数,如果是上传图片 if($_POST['submit'] == 'Upload') {     if($_FILES['uploadfile']['error'] != UPLOAD_ERR_OK)     {         switch($_FILES['uploadfile']['error'])         {             case UPLOAD_ERR_INI_SIZE:                 die('The uploaded file exceeds the upload_max_filesize directive');             break;             case UPLOAD_ERR_FORM_SIZE:                 die('The upload file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form');             break;             case UPLOAD_ERR_PARTIAL:                 die('The uploaded file was only partially uploaded');             break;             case UPLOAD_ERR_NO_FILE:                 die('No file was uploaded');             break;             case UPLOAD_ERR_NO_TMP_DIR:                 die('The server is missing a temporary folder');             break;                 case UPLOAD_ERR_CANT_WRITE:                 die('The server fail to write the uploaded file to the disk');             break;                     case UPLOAD_ERR_EXTENSION:                 die('The upload stopped by extension');             break;                         }     }     $image_caption = $_POST['caption'];     $image_username = $_POST['username'];     $image_date = date('Y-m-d');     list($width,$height,$type,$attr) = getimagesize($_FILES['uploadfile']['tmp_name']);     $error = 'The file you upload is not a supported filetype';     switch($type)     {         case IMAGETYPE_GIF:             $image = imagecreatefromgif($_FILES['uploadfile']['tmp_name']) or die($error);         break;         case IMAGETYPE_JPEG:             $image = imagecreatefromjpeg($_FILES['uploadfile']['tmp_name']) or die($error);         break;         case IMAGETYPE_PNG:             $image = imagecreatefrompng($_FILES['uploadfile']['tmp_name']) or die($error);         break;         default:         break;     }     $query = 'insert into images(image_caption,image_username,image_date) values("'.$image_caption.'" , "'.$image_username.'","'.$image_date.'")';     $result = mysql_query($query,$db) or die(mysql_error($db));     $last_id = mysql_insert_id();          // $imagename = $last_id.'.jpg';     // imagejpeg($image,$dir.'/'.$imagename);     // imagedestroy($image);          $image_id = $last_id;     imagejpeg($image , $dir.'/'.$image_id.'.jpg');     imagedestroy($image); } else  //如果图片已经上传,则从数据库中取图片名字 {     $query = 'select image_id,image_caption,image_username,image_date from images where image_id='.$_POST['id'];     $result = mysql_query($query,$db) or die(mysql_error($db));     extract(mysql_fetch_assoc($result));     list($width,$height,$type,$attr) = getimagesize($dir.'/'.$image_id.'.jpg'); } //如果是保存图片 if($_POST['submit'] == 'Save') {     if(isset($_POST['id']) && ctype_digit($_POST['id']) && file_exists($dir.'/'.$_POST['id'].'.jpg'))     {         $image = imagecreatefromjpeg($dir.'/'.$_POST['id'].'.jpg');     }     else     {         die('invalid image specified');     }     $effect = (isset($_POST['effect'])) ? $_POST['effect'] : -1;     switch($effect)     {         case IMG_FILTER_NEGATE:             imagefilter($image , IMG_FILTER_NEGATE);     //将图像中所有颜色反转         break;         case IMG_FILTER_GRAYSCALE:             imagefilter($image , IMG_FILTER_GRAYSCALE);  //将图像转换为灰度的         break;         case IMG_FILTER_EMBOSS:             imagefilter($image , IMG_FILTER_EMBOSS);     //使图像浮雕化         break;         case IMG_FILTER_GAUSSIAN_BLUR:             imagefilter($image , IMG_FILTER_GAUSSIAN_BLUR); //用高斯算法模糊图像         break;         }          if(isset($_POST['emb_caption']))     {         imagettftext($image , 12 , 0 , 20 , 20 , 0 , $font , $image_caption);     }          imagejpeg($image , $dir.'/'.$_POST['id'].'.jpg' , 100);     ?-->

Here is your pic!
<h1>Your image has been saved!</h1>
<img src="images/&lt;?php echo $_POST['id'];?&gt;.jpg" alt="" />


<!--?php  } else { ?-->

Here is your pic!
<h1>So how does it feel to be famous?</h1>
Here is the picture you just uploaded to your servers:

<!--<img src="images/<?php echo $imagename;?>" alt="" style="float:left;" />-->


<!--?php         if($_POST['submit'] == 'Upload')         {             $imagename = 'images/'.$image_id.'.jpg';         }         else         {             $imagename = 'image_effect.php?id='.$image_id.'&e='.$_POST['effect'];             if(isset($_POST['emb_caption']))             {                 $imagename .= '&capt='.urlencode($image_caption);             }         }     ?-->
<img style="float: left;" src="&lt;?php echo $imagename;?&gt;" alt="" />
<table>
<tbody>
<tr>
<td>Image save as:</td>
<td></td>
</tr>
<tr>
<td>Height:</td>
<td></td>
</tr>
<tr>
<td>Widht:</td>
<td></td>
</tr>
<tr>
<td>Upload date:</td>
<td></td>
</tr>
</tbody>
</table>
You may apply a special effect to your image from the list of option below.
Note:saving an image with any of the filters applied <em>can be undone</em>

<form action="&lt;?php echo $_SERVER['PHP_SELF'];?&gt;&lt;p&gt;" method="post">
<div><input name="id" type="hidden" value="&quot;&lt;?php" />"/&gt;
Filter:<select name="effect">
<option value="-1">None</option><!--?php <br ?--> echo '
<option selected="selected" value="'.IMG_FILTER_GRAYSCALE.'">Black and white</option>';
</select>echo 'Blur';

echo 'Emboss';

echo 'Negative';
?&gt;

<!--?php <br ?--> echo '<input checked="checked" name="emb_caption" type="checkbox" />Embed caption in image?';
?&gt;
<input name="submit" type="submit" value="Preview" /><input name="submit" type="submit" value="Save" />

</div>
</form><!--?php  } ?-->

注意这里有个问题,选择字体的时要加上绝对路径并且带上后缀.tff,否则的话是看不到图片的,下面的代码是image_effect.php

<!--?php  //修改图片效果 $db = mysql_connect('localhost','root','Ctrip07185419') or die('can not connect to database'); mysql_select_db('moviesite',$db) or die(mysql_error($db)); //上传文件的路径 $dir = 'D:\Serious\phpdev\test\images'; //设置环境变量 //putenv('GDFONTPATH='.'C:\Windows\Fonts'); $font = "C:\Windows\Fonts\arial.ttf"; //upload_image.php页面传递过来的参数,如果是上传图片 if($_POST['submit'] == 'Upload') {     if($_FILES['uploadfile']['error'] != UPLOAD_ERR_OK)     {         switch($_FILES['uploadfile']['error'])         {             case UPLOAD_ERR_INI_SIZE:                 die('The uploaded file exceeds the upload_max_filesize directive');             break;             case UPLOAD_ERR_FORM_SIZE:                 die('The upload file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form');             break;             case UPLOAD_ERR_PARTIAL:                 die('The uploaded file was only partially uploaded');             break;             case UPLOAD_ERR_NO_FILE:                 die('No file was uploaded');             break;             case UPLOAD_ERR_NO_TMP_DIR:                 die('The server is missing a temporary folder');             break;                 case UPLOAD_ERR_CANT_WRITE:                 die('The server fail to write the uploaded file to the disk');             break;                     case UPLOAD_ERR_EXTENSION:                 die('The upload stopped by extension');             break;                         }     }     $image_caption = $_POST['caption'];     $image_username = $_POST['username'];     $image_date = date('Y-m-d');     list($width,$height,$type,$attr) = getimagesize($_FILES['uploadfile']['tmp_name']);     $error = 'The file you upload is not a supported filetype';     switch($type)     {         case IMAGETYPE_GIF:             $image = imagecreatefromgif($_FILES['uploadfile']['tmp_name']) or die($error);         break;         case IMAGETYPE_JPEG:             $image = imagecreatefromjpeg($_FILES['uploadfile']['tmp_name']) or die($error);         break;         case IMAGETYPE_PNG:             $image = imagecreatefrompng($_FILES['uploadfile']['tmp_name']) or die($error);         break;         default:         break;     }     $query = 'insert into images(image_caption,image_username,image_date) values("'.$image_caption.'" , "'.$image_username.'","'.$image_date.'")';     $result = mysql_query($query,$db) or die(mysql_error($db));     $last_id = mysql_insert_id();          // $imagename = $last_id.'.jpg';     // imagejpeg($image,$dir.'/'.$imagename);     // imagedestroy($image);          $image_id = $last_id;     imagejpeg($image , $dir.'/'.$image_id.'.jpg');     imagedestroy($image); } else  //如果图片已经上传,则从数据库中取图片名字 {     $query = 'select image_id,image_caption,image_username,image_date from images where image_id='.$_POST['id'];     $result = mysql_query($query,$db) or die(mysql_error($db));     extract(mysql_fetch_assoc($result));     list($width,$height,$type,$attr) = getimagesize($dir.'/'.$image_id.'.jpg'); } //如果是保存图片 if($_POST['submit'] == 'Save') {     if(isset($_POST['id']) && ctype_digit($_POST['id']) && file_exists($dir.'/'.$_POST['id'].'.jpg'))     {         $image = imagecreatefromjpeg($dir.'/'.$_POST['id'].'.jpg');     }     else     {         die('invalid image specified');     }     $effect = (isset($_POST['effect'])) ? $_POST['effect'] : -1;     switch($effect)     {         case IMG_FILTER_NEGATE:             imagefilter($image , IMG_FILTER_NEGATE);     //将图像中所有颜色反转         break;         case IMG_FILTER_GRAYSCALE:             imagefilter($image , IMG_FILTER_GRAYSCALE);  //将图像转换为灰度的         break;         case IMG_FILTER_EMBOSS:             imagefilter($image , IMG_FILTER_EMBOSS);     //使图像浮雕化         break;         case IMG_FILTER_GAUSSIAN_BLUR:             imagefilter($image , IMG_FILTER_GAUSSIAN_BLUR); //用高斯算法模糊图像         break;         }          if(isset($_POST['emb_caption']))     {         imagettftext($image , 12 , 0 , 20 , 20 , 0 , $font , $image_caption);     }          imagejpeg($image , $dir.'/'.$_POST['id'].'.jpg' , 100);     ?-->

Here is your pic!
<h1>Your image has been saved!</h1>
<img src="images/&lt;?php echo $_POST['id'];?&gt;.jpg" alt="" />


<!--?php  } else { ?-->

Here is your pic!
<h1>So how does it feel to be famous?</h1>
Here is the picture you just uploaded to your servers:

<!--<img src="images/<?php echo $imagename;?>" alt="" style="float:left;" />-->


<!--?php         if($_POST['submit'] == 'Upload')         {             $imagename = 'images/'.$image_id.'.jpg';         }         else         {             $imagename = 'image_effect.php?id='.$image_id.'&e='.$_POST['effect'];             if(isset($_POST['emb_caption']))             {                 $imagename .= '&capt='.urlencode($image_caption);             }         }     ?-->
<img style="float: left;" src="&lt;?php echo $imagename;?&gt;" alt="" />
<table>
<tbody>
<tr>
<td>Image save as:</td>
<td></td>
</tr>
<tr>
<td>Height:</td>
<td></td>
</tr>
<tr>
<td>Widht:</td>
<td></td>
</tr>
<tr>
<td>Upload date:</td>
<td></td>
</tr>
</tbody>
</table>
You may apply a special effect to your image from the list of option below.
Note:saving an image with any of the filters applied <em>can be undone</em>

<form action="&lt;?php echo $_SERVER['PHP_SELF'];?&gt;&lt;p&gt;" method="post">
<div><input name="id" type="hidden" value="&quot;&lt;?php" />"/&gt;
Filter:<select name="effect">
<option value="-1">None</option><!--?php <br ?--> echo '
<option selected="selected" value="'.IMG_FILTER_GRAYSCALE.'">Black and white</option>';
</select>echo 'Blur';

echo 'Emboss';

echo 'Negative';
?&gt;

<!--?php <br ?--> echo '<input checked="checked" name="emb_caption" type="checkbox" />Embed caption in image?';
?&gt;
<input name="submit" type="submit" value="Preview" /><input name="submit" type="submit" value="Save" />

</div>
</form><!--?php  } ?-->

这里如果我们写成下面这样

putenv('GDFONTPATH='.'C:\Windows\Fonts');
$font = "arial";

是看不到效果的,这里顺便提一下,火狐是最佳的开发工具,怎么讲呢,看看下面的对比吧。

火狐的提示是这样的:

图片没有截完整提示是:The image “http://localhost:81/test/image_effect.php?id=31&e=-1&capt=you+are+big+bitch” cannot be displayed because it contains errors.

谷歌浏览器的显示如下:

一个未能正确显示的图片,什么信息都没有。

再来看看大IE的,如下:

也能看到错误信息,但是一大堆乱码啊!

 

所以说开发人员好帮手还是火狐。