posts - 33, comments - 0, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2011年9月1日

      在第一篇时就说过框架要在URL上作文章,是的,本文就框架怎样充分利用url上作尽可能详细的说明。

      做web开发的不可能对url陌生,早在web1.0时代,url作为统一资源定位符,在对web中资源的如何获得上起到巨大作用。不论用户请求的时静态页面或者是各种图片、脚本文件,通过url总能从web网站获取要访问的资源。Web2.0更是常常使用url作为get请求时参数的传递,如http://xxx.xxx.xxx/xxx.jsp?user=admin。以及近几年很火restful web service 摒弃soap而使用url传递请求参数 都说明合理利用url的可行和流行。

      当然不止是使用了url就算好的实践,而是能够做到优雅的使用,保证层次分明和整体的简洁,这才算是好的方式,这也正是本框架对使用url 所追求的目标。

      首先来看几个例子:

http://www.cnblogs.com/p2
http://www.xxx.com/index.do?page=2

http://www.xxx.com/product/
http://www.xxx.com/channel.do?channel=product

http://www.xxx.com/product/mobile
http://www.xxx.com/channel.do?channel=product&&subChannel=mobile

      相信各位看官不用我说也能明白,这几组的实现肯定第一种实现的方式更佳。抛开它能屏蔽服务器端使用的技术这一特性不说,它还能够更好地说明动态网站的层次结构,让用户在访问时能明确知道在网站的什么位置,而不会觉得是陷入了一个迷宫。

      当然上面列举的例子是网站前端所使用的url表现方式,因为表现方式可以多种多样,个人喜好不同,本框架在设计时没有给指定前端url的表现方式,而是定义接口,把这个权利留给使用的用户。框架将考虑更多 通用性的东西而不是 个性 自由的东西。

 

      下面对框架里默认使用的url Router AMPPathRouter做详细的介绍,包括设计的思想和实现的方式。首先AMPPathRouter的用途定位为后台使用。为了理解快速的理解它的工作原理,先来和struts做一下对比。

      Struts关于请求的配置:

 

<action name="login" class="com.lscmjx.action.LoginAction" method="login">
<result name="success">
main.jsp
</result>
<result name="failure">
login.jsp
</result>
</action>

    它提交的url会是http://xxx.xxx.xxx/login,访问web服务器时会把此url传递到struts框架交给它处理,之后struts会在struts.xml中寻找login的相关的配置,像上面例子,struts会找到LoginAction的类,并且调用其login的方法。

      写到这里,我请问这是最好的方式吗?当然不是,至少我在使用struts时就认为这是相当撇脚的设计。上面例子只是列举一个login方法,假如一个系统中要对后台调用的方法是100个,那岂不是就需要在struts.xml中写100个与之类似的配置。想想都头大,这样繁琐的工作,应该是由框架自己去处理,而不是人工给配置。

 

      再来看实现相同功能的Unicorn web框架的配置。

 

<action class="com.mh.action.UserAction"></action>

      当然提交的url肯定需要包含多一些的信息,来保证能通过url正确调用框架Action里的方法。这里提交的url方式:http://xxx.xxx.xxx/UserAction/login/

      通过在url里附加调用的Action类的信息,可以省略为不同的方法都在xml里配置的麻烦。假如UserAction里有100个方法,框架也只需这一行的配置。

      有了大体的认识之后,来看框架的核心部分AMPPathRouter的具体实现。

 

  /**
* 检查url是否是此Router类要处理的,/Action/Method/Param 格式的将会被检查合格,返回true
*
@param relativeUri
*
@param actionMap
*
@return
*/
public boolean checkUrl(String relativeUri, Map<String, ActionSupport> actionMap) {
Pattern pattern = Pattern.compile("^/\\w+/\\w+/\\S*");
Matcher matcher = pattern.matcher(relativeUri);
if(matcher.matches()) {
String actionName = relativeUri.split("/")[1];
ActionSupport actionSupport = actionMap.get(actionName);
if(null != actionSupport) {
String actionMethodName = relativeUri.split("/")[2];
Class<?> actionClass = actionSupport.getClass();
Method[] methods = actionClass.getMethods();
for(int i = 0; i < methods.length; i++) {
Method method = methods[i];
String methodName = method.getName();
if(methodName.equals(actionMethodName)) {
return true;
}
}
} else {
return false;
}
}
return false;
}
/**
* 匹配规则为:
* 1、符合/Action/method/param格式,
* 2、并且Action在actionMap中的确存在
* 3、method在此Action中存在
*/
@Override
public boolean route(String relativeUri, UrlFilter urlFilter) {
Map<String, ActionSupport> actionMap = urlFilter.getActionMap();
if(!this.checkUrl(relativeUri, actionMap)) {
return false;
}
// 拦截Action/Method/Param方式的请求,并构建ActionSupport类的属性
String[] params = relativeUri.split("/");
try {
ActionSupport actionSupport = actionMap.get(params[1]);
Class<?> action = actionSupport.getClass();
Method method = action.getMethod(params[2], new Class[] {});
if(params.length > 3) {
this.boxingRequest(urlFilter.getRequest(), params[3]);
}
// 只要找到ActionSupport的子类,则初始化其所具有的属性
Object newInstance = action.newInstance();
this.initActionSupport(newInstance, urlFilter);
String result = (String) method.invoke(newInstance, new Object[] {});
if (null == result || ActionSupport.AJAX.equals(result) || ActionSupport.FORWARD.equals(result) || ActionSupport.WEB_SERVICE.equals(result)) {
return true;
}
if(ActionSupport.REDIRECT.equals(result)) {
urlFilter.getResponse().sendRedirect(result);
return true;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 把url中得param加入到request的attribute里
*
@param request
*
@param parameter
*/
private void boxingRequest(HttpServletRequest request, String parameter) {
String[] parameters = parameter.split("&");
for (int i = 0; i < parameters.length; i++) {
String param = parameters[i];
String[] key_value = param.split("=");
if(key_value.length == 2) {
request.setAttribute(key_value[0], key_value[1]);
}
}
}
/**
* 初始化ActionSupport类中所需的request、response、session、application等对象
*
@param obj
*
@param urlFilter
*/
private void initActionSupport(Object obj, UrlFilter urlFilter) {
ActionSupport action = (ActionSupport) obj;
action.setRequest(urlFilter.getRequest());
action.setResponse(urlFilter.getResponse());
action.setSession(urlFilter.getSession());
action.setApplication(urlFilter.getApplication());
}

 

      这便是AMPPathRouter的全部内容,其中在把请求分发到ActionSupport的子类 并调用相关方法时 是通过反射实现,其他地方地方都是相当容易理解的。

      空说无凭,把框架应用到实战中才是硬道理:

 

      好了,下一篇介绍Action 和 json。

posted @ 2011-11-25 18:18 马航 阅读(245) | 评论 (0)编辑 收藏

      上篇说过,所有提交到web程序的url都被此UrlFilter拦截。拦截到请求后,UrlFilter则召集它的好多个得力干将Router 们, 询问他们:“谁能处理此URL啊 ?”

这时一位叫做AMPRouter 首当其冲 说:“这个url交给我了”。这时filter就会把此url全权交给AMPRouter来办,至于如何去处理,filter也不再过问,它觉得:“我把任务都交给你了,怎么解决是你的事”。

      根据单一职责的原则,UrlFilter就负责上面情景中的分发urlRouter中的差事,url如何分发交给Router处理。并且Router实际是一个接口,使用框架的用户完全可以自己实现Router,这样用户可以自主定义的url分发的策略。另外呢,框架初始化的一些操作它也是 推脱不掉的,像根据unicorn-config.xml初始化系统中的RouterAction'。下面是具体的代码:

 

@Override
public void init(FilterConfig config) throws ServletException {
	application = config.getServletContext();
	String loadPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
	String classPath = loadPath.substring(1, loadPath.length());
	ArrayList<String> actions = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "actions");
	this.initActions(actions);
	ArrayList<String> routers = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "routers");
	this.initRouters(routers);
}

 

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) servletRequest;
	String path = request.getContextPath();
	String uri = request.getRequestURI();
	String relativeUri = uri.substring(path.length(), uri.length());
	this.request = request;
	this.session = request.getSession();
	this.response = (HttpServletResponse) servletResponse;
	// 用户自定义的Router优先级最高,url先通过用户定义的
	Iterator<IPathRouter> iterator = routerList.iterator();
	while(iterator.hasNext()) {
		IPathRouter router = iterator.next();
		if(router.route(relativeUri, this)) {
			return ;
		}
	}
	// 拦截不到的继续访问
	filterChain.doFilter(servletRequest, servletResponse);
}

        其中Router类的初始化,Action类的初始化于这个类似:

private void initRouters(ArrayList<String> routers) {
	routerList = new ArrayList<IPathRouter>();
	for (int i = 0; i < routers.size(); i++) {
		String routerName = routers.get(i);
		try {
			Class<?> clz = Class.forName(routers.get(i));
			// 单例模式通过方法获取对象实例
			IPathRouter router = (IPathRouter) clz.newInstance();
			routerList.add(router);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
	}
	// 最后把框架默认的Router加入进来
	routerList.add(new AMPPathRouter());
}

        其中unicorn-config.xml文件的编写,拿其中我一个项目里的这个文件来举例:

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<routers>
<router class="com.mh.router.MySessionCheckRouter"></router>
</routers>
<actions>
<action class="com.mh.action.UserAction"></action>
<action class="com.mh.action.InformationAction"></action>
<action class="com.mh.action.UploadInformationIconAction"></action>
<action class="com.mh.action.TempPicAction"></action>
<action class="com.mh.action.MobileAction"></action>
</actions>
</config>

      这里即定义了Action,也定义了自己的Router,并且从名称上可以看出,这个SessionCheckRouter是要判断所有提交到服务器的指定url的请求 是否已经登录过,没有登录,可能会把此请求遣送会登录页。以及初始化所有的Action,在Router处理完请求,分发给action时,可以从filter里面去取。

 


posted @ 2011-11-25 12:35 马航 阅读(249) | 评论 (0)编辑 收藏

      承接上篇的简单介绍,下面详细介绍整个框架的大致结构。

      先来看一下整个框架包的结构:

 

      可以看出框架包含的包很少,包的结构也超简单。这里 涉及FilterActionSupportRouter等三个概念,他们之间的关系,通过下图来表示:

 

      图也不规范,说不上来是哪个UML图,不过通过它也能看出一个请求到达时,框架基本的处理流程。首先由Filter拦截到所有请求,然后把请求交给所有注册的Router类,如果请求的Url正好是一个Router要拦截的,则把此请求交给这个Router,框架不再把请求向下传递。Router得到请求后,分析Url,通过Url里的信息把请求交给对应的ActionSupport的子类来处理。

      这里拦截采用Filter来处理,这跟多数的web框架一样,使用FilterServlet有更多的能力进行请求的分发。首先在一个web工程的web.xml文件中配置框架的UrlFilter类来拦截所有的请求。需要注意的一点是dispatcher 要设置为request,如果设置了forward的话,由框架内部进行的forward又会被框架拦截,从而造成无限的循环。Url-pattern设置为/*,表示所有的请求都会拦截,从而把对url分发的权利交由框架本身,而不是采用jsp规范里的url分发策略。框架在处理所有请求的url 时,依次交给各个Router类来处理,如果Router类判断是符合自己的url格式,则分发给 action 处理。如果不能处理再交给下一级的Router,最后url经由所有Router处理完,剩下的资源文件的url,如http://xxx.xxx.xxx.jpg,则框架调用filterdoChain()方法,通过filter的过滤去访问web里的资源。

<filter>

      <filter-name>unicornWeb</filter-name>

      <filter-class>com.mh.mvc.filter.UrlFilter</filter-class>

</filter>

<filter-mapping>

    <filter-name>unicornWeb</filter-name>

    <url-pattern>/*</url-pattern>

    <dispatcher>REQUEST</dispatcher>

</filter-mapping>

      大致的原理就是这样,在下篇介绍框架的详细实现。

posted @ 2011-11-25 11:43 马航 阅读(324) | 评论 (0)编辑 收藏

      我承认有点标题党了,不过题目中所说的几项技术确实有其相似之处,欲知事情原委,且听我详细道来。

      项目一开始只是不满 struts 庞大的体积,于是想自己根据其原理实现一个tiny 版。后来的开发中觉得,完全可以把上述的ajax、Restuful web service的一些思想加入进来。经过几周的努力,便开发出了一个基本成型的web 框架,暂且起名为unicorn(独角兽,吼吼)。下文开始便对这个自编写的框架做一些列的介绍,并且初步打算是将其开源,希望能一起交流和完善它。

      首先,为了能快速了解它是什么,先来看一下配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<routers>
<router class="com.mh.router.MySessionCheckRouter"></router>
</routers>
<actions>
<action class="com.mh.action.UserAction"></action>
<action class="com.mh.action.InformationAction"></action>
<action class="com.mh.action.MobileAction"></action>
</actions>
</config>

      上面就是整个工程的配置文件,可以看出需要配置的东西非常少,只需要制定action类 和 router类有哪些就Ok。框架奉行约定大于配置的思想,至于请求如何分发,这个不需要人工配置,框架自动解决。这里要介绍两个概念Action 和 Router,熟悉Struts的肯定都知道Action,Action替代Servlet、JSP时代的Servlet,所有提交的请求由struts分发给不同的Action来处理。这里道理也是一样的,Action就是经过框架处理后的请求接受者。再来说一下Router,字面意思路由器,学过计算机网络的都知道,ip数据包在网络上之所以能够顺利到达,就是因为路由器根据路由表来来确定出来传输的途径。这里Router也是这个作用,根据访问服务器的URL来制定分发策略。Router是完全可以自定义的,用户可以定义自己的Router来制定URL分发的策略,并且用户自定义的Router比系统默认的Router有更高的优先权。

二、URL上做文章

/UserAction/login/username=admin&&password=admin

      先来介绍系统MethodRouter的处理方式。上面的url根据"/"分为三个部分,第一部分是请求的Action类,第二部分是类中的方法Method,第三部分是提交的参数Param。这一点受上篇文章优酷的架构里URL设计的启发。

      经过这样的设计,就明白在上述配置文件中为何可以如此简单了。

      当然也可以不以这样的方式,框架提供自定义Router的支持。比如你想这样处理URL:/前台页面/子栏目/子栏目

      想实现上面的方式,就可以自己定义Router,在Router里面获取上述的URL,然后做处理、forward到相应的jsp页面。

三、使用Json传输数据

      Ajax请求很容易处理json数据,ajax可以与系统轻松交互。

      当初Web Service使用SOAP的xml格式传输数据,如今也有人指责这是大费周折。Restful方式提倡遵循HTTP语义,完全使用URL结合GET、POST、PUT、DELETE来传输请求,结果在roil阵营里广泛使用,认为是web service更优雅的方式。所以本框架也吸取他们的优点,也完全可以通过url传输请求的数据,如上述URL中的Param部分。不过没有遵循Restful强调的Http语义,全部使用Get和POST的请求方式,当然也可以制定为其他,这完全看你的心情,因为这对功能实现无关紧要。而且我觉得统一使用一种,更避免了需要指定请求方式的麻烦。

      数据的返回使用json格式,比SOAP更为轻量简洁和优雅,而且有更多的平台直接支持。如在android平台,本身就支持json格式的处理, 如果使用web service 的SOAP,你可能还要导入KSOAP的第三方库。

      在非浏览器的客户端,可以借助编写的工具类,来完成web service方式的操作,

public interface IWebService {
	public List<LiteInformationDTO> getInformationsOfOwnerApp(String ownerApp, int start, int limit) throws SocketTimeoutException;
}

      经过这样的封装,已经与使用web service毫无差别,而且还会更加高效,因为处理json总比处理SOAP的xml要容易。

 

      先简单写这么多,之后的续篇详细介绍。

posted @ 2011-11-22 16:22 马航 阅读(267) | 评论 (0)编辑 收藏

记得以前给大家介绍过视频网站龙头老大YouTube的技术架构,相信大家看了都会有不少的感触,互联网就是这么一个神奇的东西。今天我突然想到,优酷网在国内也算是视频网站的老大了,不知道他的架构相对于YouTube是怎么样的,于是带着这个好奇心去网上找了优酷网架构的各方面资料,虽然谈得没有YouTube那么详细,但多少还是挖掘了一点,现在总结一下,希望对喜欢架构的朋友有所帮助。

一、网站基本数据概览

  • 据2010年统计,优酷网日均独立访问人数(uv)达到了8900万,日均访问量(pv)更是达到了17亿,优酷凭借这一数据成为google榜单中国内视频网站排名最高的厂商。
  • 硬件方面,优酷网引进的戴尔服务器主要以 PowerEdge 1950与PowerEdge 860为主,存储阵列以戴尔MD1000为主,2007的数据表明,优酷网已有1000多台服务器遍布在全国各大省市,现在应该更多了吧。

二、网站前端框架

从一开始,优酷网就自建了一套CMS来解决前端的页面显示,各个模块之间分离得比较恰当,前端可扩展性很好,UI的分离,让开发与维护变得十分简单和灵活,下图是优酷前端的模块调用关系:

这样,就根据module、method及params来确定调用相对独立的模块,显得非常简洁。下面附一张优酷的前端局部架构图:

 

三、数据库架构

应该说优酷的数据库架构也是经历了许多波折,从一开始的单台MySQL服务器(Just Running)到简单的MySQL主从复制、SSD优化、垂直分库、水平sharding分库,这一系列过程只有经历过才会有更深的体会吧,就像MySpace的架构经历一样,架构也是一步步慢慢成长和成熟的。

1、简单的MySQL主从复制:

MySQL的主从复制解决了数据库的读写分离,并很好的提升了读的性能,其原来图如下:

其主从复制的过程如下图所示:

但是,主从复制也带来其他一系列性能瓶颈问题:

  1. 写入无法扩展
  2. 写入无法缓存
  3. 复制延时
  4. 锁表率上升
  5. 表变大,缓存率下降

那问题产生总得解决的,这就产生下面的优化方案,一起来看看。

 

2、MySQL垂直分区

如果把业务切割得足够独立,那把不同业务的数据放到不同的数据库服务器将是一个不错的方案,而且万一其中一个业务崩溃了也不会影响其他业务的正常进行,并且也起到了负载分流的作用,大大提升了数据库的吞吐能力。经过垂直分区后的数据库架构图如下:

然而,尽管业务之间已经足够独立了,但是有些业务之间或多或少总会有点联系,如用户,基本上都会和每个业务相关联,况且这种分区方式,也不能解决单张表数据量暴涨的问题,因此为何不试试水平sharding呢?

 

3、MySQL水平分片(Sharding)

这是一个非常好的思路,将用户按一定规则(按id哈希)分组,并把该组用户的数据存储到一个数据库分片中,即一个sharding,这样随着用户数量的增加,只要简单地配置一台服务器即可,原理图如下:

如何来确定某个用户所在的shard呢,可以建一张用户和shard对应的数据表,每次请求先从这张表找用户的shard id,再从对应shard中查询相关数据,如下图所示:

但是,优酷是如何解决跨shard的查询呢,这个是个难点,据介绍优酷是尽量不跨shard查询,实在不行通过多维分片索引、分布式搜索引擎,下策是分布式数据库查询(这个非常麻烦而且耗性能)

 

四、缓存策略

貌似大的系统都对“缓存”情有独钟,从http缓存到memcached内存数据缓存,但优酷表示没有用内存缓存,理由如下:

  1. 避免内存拷贝,避免内存锁
  2. 如接到老大哥通知要把某个视频撤下来,如果在缓存里是比较麻烦的

而且Squid 的 write() 用户进程空间有消耗,Lighttpd 1.5 的 AIO(异步I/O) 读取文件到用户内存导致效率也比较低下。

但为何我们访问优酷会如此流畅,与土豆相比优酷的视频加载速度略胜一筹?这个要归功于优酷建立的比较完善的内容分发网络(CDN),它通过多种方式保证分布在全国各地的用户进行就近访问——用户点击视频请求后,优酷网将根据用户所处地区位置,将离用户最近、服务状况最好的视频服务器地址传送给用户,从而保证用户可以得到快速的视频体验。这就是CDN带来的优势,就近访问,有关CDN的更多内容,请大家Google一下。

好了,就总结这么多了,有兴趣的同学接着补充,虽然很多资料图片都来自网络,但整理也不容易,欢迎转载,转载留个出处:青藤屋 原文链接

posted @ 2011-11-02 11:23 马航 阅读(265) | 评论 (0)编辑 收藏

windows系统使我们经常使用的操作系统怎么才能使用我们现在经常使用的操作系统不变的情况下继续我们的SVN之旅,我们在综合了好动种方法的同时感觉这些内容非常贴近我们SVN在Windows种的应用与配置.

1.下载文件,

下载最新版本subversion,我这里选择svn-1.4.5-setup.exe

下载 "Subversion Windows Service" 软件包

下载 TortoiseSVN shell integration utility

2.安装Subversion 服务器

由于我下载的是setup.exe版本,安装程序安装后会自动设置系统变量.如果你下载的是zip版就需要手动设置系统变量.

setup.exe版直接安装就可以了.安装到D:/Program Files/Subversion

首先创建SVN储存库(repository)

svnadmin create F:/svn/

repository创建完毕后会在目录下生成若干个文件和文件夹,dav目录是提供给Apache与mod_dav_svn使用的目录,让它们存储内部数据;db目录就是所有版本控制的数据文件;hooks目录放置hook脚本文件的目录;locks用来放置Subversion文件库锁定数据的目录,用来追踪存取文件库的客户端;format文件是一个文本文件,里面只放了一个整数,表示当前文件库配置的版本号;

3.配置SVN服务器

(这个位置就是在你建储存库的地方F:/svn)

打开/conf/目录,打开svnserve.conf找到一下两句:

# [general]

# password-db = passwd

# anon-access = none

# auth-access = write

去之每行开头的#,其中第二行是指定身份验证的文件名,即passwd文件.anon-access = none 是匿名用户不能访问,必须要有用户名和密码。(注意:问题就出在这,一定要注意格式去掉注释后要顶格不能有空)

同样打开passwd文件,将

# [users]

# harry = harryssecret

# sally = sallyssecret

       格式为“用户名 = 密码”,如可插入一行:admin = admin888,即为系统添加一个用户名为admin,密码为admin888的用户

4.运行SVN服务器

运行SVN服务

在命令行执行

svnserve --daemon --root F:/svn

服务启动,--daemon可简写为-d,--root可简写为-r,可以建立一个批处理文件并放在windows启动组中便于开机就运行SVN服务(注意:这是临时打开的服务,命令执行后不能关闭窗口)

也可以制定subversion工作的端口:svnserve -d -r f:/svn --listen-port 9999

用后台服务的方式可以设置开机自动执行。

D:/Program Files/Subversion/bin>sc create svnservice binpath= "C:/Program Files/Subversion/bin/svnserve.exe --service -r f:/svn  --listen-port 9999"

就可以用net svnservice stop 或者start来启动服务了 也可以在Sevices.msc来启动了。

5、用客户端访问

格式:svn://服务器IP

 

---------------------------------------------------------------------------------------------------------

 

基于svnserve的服务器,权限文件authz配置的常见问题及解答  
 最近在我用Subversion论坛(http://www.iUseSVN.com/bbs)经常有人提到这样的问题: 
为什么我的客户端没有写权限? 
为什么我的权限没有起作用?

总结他们的配置,发现 
都是用svnserve作为服务器, 
都在svnserve.conf中使用了authz-db选项

原因可能如下:

1,配置authz时,没有注意svnserve启动参数-r所指定的目录。 
这里有两种情况: 
A:-r直接指定到版本库(称之为单库svnserve方式) 
比如,有一个库project1,位于D:/svn/project1 
使用以下命令启动svnserve

[Copy to clipboard] [ - ]CODE: 
svnserve -d -r D:/svn/project1 
在这种情况下,一个svnserve只能为一个版本库工作 
authz文件如果配置成下面这样就是错的,

[Copy to clipboard] [ - ]CODE: 
[groups] 
admin=user1 
dev=user2 
[project1:/] 
@admin=rw 
@doc=r 
应该配置成

[Copy to clipboard] [ - ]CODE: 
[groups] 
admin=user1 
dev=user2 
[/] 
@admin=rw 
@doc=r 
因为[project1:/]表示库project1的根目录,而按上面的启动参数,是没有库的概念的。 
使用类似这样的URL:svn://192.168.0.1/ 即可访问project1

B:-r指定到版本库的上级目录(称之为多库svnserve方式) 
同样,有一个库project1,位于D:/svn/project1 
如果使用以下命令启动svnserve

[Copy to clipboard] [ - ]CODE: 
svnserve -d -r D:/svn 
这种情况,一个svnserve可以为多个版本库工作, 
这时如果想限制指定库的指定目录,就应该指定具体的库,像这样

[Copy to clipboard] [ - ]CODE: 
[groups] 
admin=user1 
dev=user2 
[project1:/] 
@admin=rw 
@doc=r 
如果此时你还用[/],则表示所有库的根目录,同理,[/src]表示所有库的根目录下的src目录 
使用类似这样的URL:svn://192.168.0.1/project1 即可访问project1 
这样的URL:svn://192.168.0.1/project2 即可访问project2

2,对中文目录进行权限控制时,没有将权限文件authz改为utf-8格式。

svn对于非英文文件名和目录名使用utf-8格式编码处理,要对中文目录进行正确控制, 
应该使用无BOM的utf-8格式,如何将默认的文件转为utf-8, 
我使用的是UltraEdit的菜单"ASCII to UTF-8 (Unicode Editing)"。在UltraEdit的配置中,可以设置有无BOM  

posted @ 2011-10-12 17:26 马航 阅读(398) | 评论 (0)编辑 收藏

SIP协议

 

SIP协议过程概念及分析

 

SIP入门开发之路(含SIP开发需要学习的资源及网址)

 

SIP揭密(中文版)

 

使用Java的SIP Servlet进行SIP开发

 

 

Asterisk:

 

Asterisk安装及测试

 

Asterisk十问十答

 

Asterisk入门教程

 

Asterisk介绍-Asterisk RealTime SIP

asterisk配置文件列表及常用指令

 

asterisk 官方文档

 

asterisk目录及配置说明

 

Asterisk功能整理

 

Asterisk使用ODBC实现语音信箱

 

使用Asterisk实现可视的语音交换

 

 

OpenSIPS

 

开源SIP服务器OpenSIPS应用介绍

 

Opensips 安装

 

Opensips 配置文件

 

Mediaproxy的安装及其在OpenSIPS中的配置

 

Opensips文档之MediaProxy模块

 

使用OpenSIPS构建电话通信系统-8媒体服务整合

 

使用OpenSIPS构建电话通信系统-4脚本及路由基础

 

Opensips文档之TM模块

 

Opensips文档之RR模块

 

Opensips文档之TEXTOPS 模块

 

Opensips文档之AVPOPS模块

 

 

NAT穿透(即SIP打洞)

 

使用OpenSIPS构建电话通信系统-SIP穿透NAT

 

NAT穿透问题探讨

 

完美的NAT穿透技术ICE介绍

 

ICE-SIP穿透NAT问题的终极解决方案

 

NAT穿透技术ICE基础教程

posted @ 2011-10-07 20:31 马航 阅读(432) | 评论 (0)编辑 收藏

     摘要: Android中有一控件是ExpandableListView,比ListView更高级,ExpandableListView的效果很实用,比如因为需要查看一堆文件的目录结构或者开发像QQ好友那样的界面,就应该使用Expandablelistview。 本文最终效果如下: 首先是Activity代码,实际开发中数据(包括父item,子item及图片,Expandablelistview...  阅读全文

posted @ 2011-10-06 09:37 马航 阅读(5447) | 评论 (0)编辑 收藏

前言:做完了手机全能播放器的项目, 又要告别几个月来并肩作战,即将去北京发展的Manager zhu。准备把做过的3GP/FLV/AVI格式整理一遍, 算是对几个月辛苦成果的总结, 也为后来者提供一些参考。

1. 概述

流行的文件格式背后都有大公司的支持。FLV得益于ADOBE公司推动的网络视频分享风潮,而AVI则是MICROSOFT首创的RIFF即视频和音频交织在一起同步播放。 3GP/MP4是APPLE提出并得到ISO标准支持作为NOKIA等手机的默认视频格式。3GP是MP4格式在手机上的简化版。MP4的codec组合一般是mpeg4 + AAC, 3GP则按版本演进分为3gpp r5(h.263/mpeg4 + AMR-NB/AMR WB), 3gpp r6(增加h.264视频和aacPlus音频支持)。

有人会把MP4和MPEG4搞混, 前者是文件容器(container),后者是视频编码格式, 容器的作用是把压缩编码后的视频和音频数据尽可能紧凑的排布,就好像阿甘的巧克力盒子,你并不知道盒子里有什么, 但你可以按照既定的线索解开文件,取出你需要的数据。

文件格式一般包括以下三要素:

header: 标记文件类型,音视频码流的基本属性信息
index: 索引表,每个frame有对应的offset,size,timestamp.
stream: 真正的音视频流数据。
任何文件格式都应该有以上3要素。 当然AVI视频没有索引也能播放,但不能拖放seek,需要自己重建索引。解析器(demuxer)根据frame_id找到其在文件中的offset和size,然后读取出来解码并播放。

2. 文件格式分析

下面来分析一下3GP/MP4文件格式。APPLE的格式有2个特点,1. 排布紧凑几乎没有冗余数据(AVI则有很多junk数据),2.音视频码流数据可随意存放而不需按时间顺序排布。

3gp文件由一系列的box(atom)组成。每个box的结构都是4字节的size,4字节的type, 还有一些data数据。用mp4info查看3gp文件的数据排布如下图:

如上图, ftyp是表示文件的版本信息, mdat存放文字,音视频等数据。你可能要问,这些音视频数据怎么找到呢? 是通过moov box里的子box trak,里面存放着音视频的属性描述以及每个sample的索引。

3. 关于sample atoms

   video和audio的码流属性(如视频width/height,codec id, 音频采样率声道数等)存放在stsd box里; 下面着重介绍MP4高效压缩的精华:stts,stss,stsc,stsz,stco五个box。对比AVI的索引表是每个sample都有对应的id,flag,offset,size,3GP的高效索引方式可以把AVI转码成同码率的MP4后,文件size减小成原来的20-30%!

1. stts atom(time to sample atoms,见quicktime format 文档图2-28 标准文档点击下载): 存储了sample的时间信息。stts能让很方便的根据timestamp找到对应的sample,或者获取某个sample对应的timestamp. sttstable记录着有相同duration的sample的数量count和时长dutation。

2. stss atom(sync sample atom,见文档图2-31): 存储了每个关键帧的sample id。 stss能让你很方便的找到当前帧最近的关键帧。

3. stsc atom(sample to chunk atom): sample存放在chunk里为了允许优化的数据读取。比如音频sample size都很小(amr-nb sample size为32字节), 每次读取一个sample开销太大, 可一次性读所在chunk里一堆sample。

4. stsz atom(sample size atom): stsz可以描述每个sample的size.

5. stco atom(chunk offset atoms): stco描述了每个chunk在文件中的绝对偏移位置。该offset可以是32位的

也可以是64位的,后者用来支持处理超大文件。

4 .使用sample atoms来处理播放流程

· 查找sample         

1.确定时间,相对于媒体时间坐标系统

2.检查time-to-sample atom来确定给定时间的sample序号。

3.检查sample-to-chunk atom来发现对应该sample的chunk。

4.从chunk offset atom中提取该trunk的偏移量。

5.利用sample size atom找到sample在trunk内的偏移量和sample的大小。

例如,如果要找第1秒的视频数据,过程如下:

1. 第1秒的视频数据相对于此电影的时间为600

2. 检查time-to-sample atom,得出每个sample的duration是40,从而得出需要寻找第600/40 = 15 + 1 = 16个sample

3. 检查sample-to-chunk atom,得到该sample属于第5个chunk的第一个sample,该chunk共有4个sample

4. 检查chunk offset atom找到第5个trunk的偏移量是20472

5. 由于第16个sample是第5个trunk的第一个sample,所以不用检查sample size atom,trunk的偏移量即是该sample的偏移量20472。如果是这个trunk的第二个sample,则从sample size atom中找到该trunk的前一个sample的大小,然后加上偏移量即可得到实际位置。

6. 得到位置后,即可取出相应数据进行解码,播放

·       查找关键帧      

查找过程与查找sample的过程非常类似,只是需要利用sync sample atom来确定key frame的sample序号

确定给定时间的sample序号 
检查sync sample atom来发现这个sample序号之后的key frame 
检查sample-to-chunk atom来发现对应该sample的chunk 
从chunk offset atom中提取该trunk的偏移量 
利用sample size atom找到sample在trunk内的偏移量和sample的大小


5 .3GP/MP4相关资源

     quicktime file format specification: 最权威的格式文档 点击下载
     开源的3GP/MP4解析器: ffmpeg, GPAC, helix, google opencore等 

posted @ 2011-10-03 10:54 马航 阅读(671) | 评论 (0)编辑 收藏

Android开发又将带来新一轮热潮,很多开发者都投入到这个浪潮中去了,创造了许许多多相当优秀的应用。其中也有许许多多的开发者提供了应用开 源项目,贡献出他们的智慧和创造力。学习开源代码是掌握技术的一个最佳方式。下面推荐几个应用开源项目,这些项目不仅提供了优秀的创意,也可以直接掌握 Android内核的接口使用:

1、Android团队提供的示例项目

如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式等。
地址:http://code.google.com/p/apps-for-android/

2、 Remote Droid

RemoteDroid是一个Android应用,能够让用户使用自己的无线网络使用无线键盘、触摸屏操作手机。这个项目为开发者提供了如网络连接、触摸屏手指运动等很好的样例。
地址:http://code.google.com/p/remotedroid/

3、 TorProxy和Shadow

TorProxy应用实现了Android手机无线电电传通讯(TOR),和Shadow应用一起使用,可以使用手机匿名上网。从该项目源代码中,可以掌握socket连接、管理cookie等方法。
地址:http://www.cl.cam.ac.uk/research/dtg/code/svn/android-tor/

4、 Android SMSPopup

SMSPopup可以截获短信内容显示在一个泡泡形状的窗口中。从这个项目中可以掌握到如何使用内置的短信SMS接口。
地址:http://code.google.com/p/android-smspopup/

5、 Standup Timer

Standup Timer应用用于控制站立会议时间,类似秒表倒计时,可以提醒每个人的讲话时间已到,从而保证每个与会者使用时间一样。从该项目的代码中,可以学会如何使用时间函数。另外,这个项目的代码是采用视图view、模型model严格分离的设计思路。
地址:http://github.com/jwood/standup-timer

6、 Foursquare

是Foursquare.com的一个客户端应用,该应用主要分为两个模块:API(com.joelapenna.foursquare)和界面前端 (com.joelapenna.foursquared)两部分。从该项目代码中,可以学会如何同步、多线程、HTTP连接等技术。
地址:http://code.google.com/p/foursquared/

7、 Pedometer

Pedometer应用用于记录你每天走路步数的。尽管记录不一定精准,但是从这个项目中,可以学习几个不同的技术:加速器交互、语音更新、后台运行服务等。
地址:http://code.google.com/p/pedometer/

8、 OpenSudoku-android

OpenSudoku是一个简单的九宫格数独游戏。从代码中可以学习到如何在视图中显示表格数据,以及如何和一个网站交互等技术。
地址:http://code.google.com/p/opensudoku-android/

9、 ConnectBot

ConnectBot是Android平台的一个客户端安全壳应用。从该项目代码中,可以学习到很多Android安全方面的内容,这些是你在开发应用时经常需要考虑的安全问题。
地址:http://code.google.com/p/connectbot/

10、 WordPress的Android应用

当然在最后不能不提WordPress的Android应用了,这是WordPress官方开发团队提供的一个项目。从代码中可以学习到XMLRPC调用(当然还有更多的优秀内容)。
地址:http://android.svn.wordpress.org/trunk/

posted @ 2011-10-03 09:47 马航 阅读(454) | 评论 (0)编辑 收藏

导读:对于Android开发者来说,成系列的技术文章对他们的技术成长帮助最大。如下是我们向您强烈推荐的主题为Android开发的第一个系列文章。

文章皆来自CSDN网友maxleng的专栏,maxleng是名Android爱好者,长期从事嵌入式系统及手机软件系统研究,自2010年4月起,在CSDN上先后发表28篇《Android核心分析》系列博文,收到网友们的极高评价。《Android核心分析》整理如下:

1. 方法论探讨之设计意图

2. 方法论探讨之概念空间篇

3. 手机之硬件形态

4. 手机的软件形态

5. Android基本空间划分

6. IPC框架分析(Binder,Service,Service manager)

7. Service详解

8. Android启动过程详解

9. Zygote Service详解

10.Android GWES基本原理篇

11.Android GWES消息系统篇

12.Android核心分析之Android GEWS窗口管理基本架构篇

13.Android GWES窗口管理详解

14.Android GWES输入系统篇

15.Android GWES输入系统之输入路径详解

16.Android电话系统-概述篇

17.Android电话系统之Rild服务详解

18.Android电话系统之GSMCallTracker

19.Android电话系统之RIL-Java

20.Android应用程序框架之无边界设计意图

21.Android应用框架之AndroidApplication

22.Android应用框架之Activity

22.Andoird GDI之基本原理及其总体框架

23.Android GDI之显示缓冲管理

24.Android GDI之共享缓冲区机制

25.Android GDI之共享缓冲区机制

26.Android GDI之SurfaceFlinger

27.Android GDI之SurfaceFlinger之动态结构示意图

28.Android GDI之Surface&Canvas

原文地址:http://mobile.csdn.net/a/20110209/291511.html

posted @ 2011-09-23 15:34 马航 阅读(96) | 评论 (0)编辑 收藏

新手学堂:嵌入式Linux操作系统学习规划
ARM+LINUX路线,主攻嵌入式Linux操作系统及其上应用软件开发目标:
(1) 掌握主流嵌入式微处理器的结构与原理(初步定为arm9)
(2) 必须掌握一个嵌入式操作系统 (初步定为uclinux或linux,版本待定)
(3) 必须熟悉嵌入式软件开发流程并至少做一个嵌入式软件项目。
从事嵌入式软件开发的好处是:
(1)目前国内外这方面的人都很稀缺。这一领域入门门槛较高,所以非专业IT人员很难切入这一领域;另一方面,是因为这一领域较新,目前发展太快,大多数人无条件接触。
(2)与企业计算等应用软件不同,嵌入式领域人才的工作强度通常低一些(但收入不低)。
(3)哪天若想创业,搞自已的产品,嵌入式不像应用软件那样容易被盗版。硬件设计一般都是请其它公司给订做(这叫“贴牌”:OEM),都是通用的硬件,我们只管设计软件就变成自己的产品了。
(4)兴趣所在,这是最主要的。
从事嵌入式软件开发的缺点是:
(1)入门起点较高,所用到的技术往往都有一定难度,若软硬件基础不好,特别是操作系统级软件功底不深,则可能不适于此行。
(2)这方面的企业数量要远少于企业计算类企业。
(3)有少数公司经常要硕士以上的人搞嵌入式,主要是基于嵌入式的难度。但大多数公司也并无此要求,只要有经验即可。
(4)平台依托强,换平台比较辛苦。
兴趣的由来:
1、成功观念不同,不虚度此生,就是我的成功。
2、喜欢思考,挑战逻辑思维。
3、喜欢C
C是一种能发挥思维极限的语言。关于C的精神的一些方面可以被概述成短句如下:
相信程序员。
不要阻止程序员做那些需要去做的。
保持语言短小精干。
一种方法做一个操作。
使得它运行的够快,尽管它并不能保证将是可移植的。
4、喜欢底层开发,讨厌vb类开发工具(并不是说vb不好)。
5、发展前景好,适合创业,不想自己要死了的时候还是一个工程师。
方法步骤:
1、基础知识:
目的:能看懂硬件工作原理,但重点在嵌入式软件,特别是操作系统级软件,那将是我的优势。
科目:数字电路、计算机组成原理、嵌入式微处理器结构。
汇编语言、C/C++、编译原理、离散数学。
数据结构和算法、操作系统、软件工程、网络、数据库。
方法:虽科目众多,但都是较简单的基础,且大部分已掌握。不一定全学,可根据需要选修。
主攻书籍:the c++ programming language(一直没时间读)、数据结构-C2。

2、学习linux:
目的:深入掌握linux系统。
方法:使用linux—〉linxu系统编程开发—〉驱动开发和分析linux内核。先看深,那主讲原理。看几遍后,看情景分析,对照深看,两本交叉,深是纲,情是目。剖析则是0.11版,适合学习。最后深入代码。
主攻书籍:linux内核完全剖析、unix环境高级编程、深入理解linux内核、情景分析和源代。
3、学习嵌入式linux:
目的:掌握嵌入式处理器其及系统。
方法:(1)嵌入式微处理器结构与应用:直接arm原理及汇编即可,不要重复x86。
(2)嵌入式操作系统类:ucOS/II简单,开源,可供入门。而后深入研究uClinux。
(3)必须有块开发板(arm9以上),有条件可参加培训(进步快,能认识些朋友)。
主攻书籍:毛德操的《嵌入式系统》及其他arm9手册与arm汇编指令等。

4、深入学习:
A、数字图像压缩技术:主要是应掌握MPEG、mp3等编解码算法和技术。
B、通信协议及编程技术:TCP/IP协议、802.11,Bluetooth,GPRS、GSM、CDMA等。
C、网络与信息安全技术:如加密技术,数字证书CA等。
D、DSP技术:Digital Signal Process,DSP处理器通过硬件实现数字信号处理算法。
说明:太多细节未说明,可根据实际情况调整。重点在于1、3,不必完全按照顺序作。对于学习c++,理由是c++不只是一种语言,一种工具,她还是一种艺 术,一种文化,一种哲学理念、但不是拿来炫耀得东西。对于linux内核,学习编程,读一些优秀代码也是有必要的。
注意: 要学会举一反多,有强大的基础,很多东西简单看看就能会。想成为合格的程序员,前提是必须熟练至少一种编程语言,并具有良好的逻辑思维。一定要理论结合实践。
不要一味钻研技术,虽然挤出时间是很难做到的,但还是要留点余地去完善其他的爱好,比如宇宙,素描、机械、管理,心理学、游戏、科幻电影。还有一些不愿意做但必须要做的!
技术是通过编程编程在编程编出来的。永远不要梦想一步登天,不要做浮躁的人,不要觉得路途漫上。而是要编程编程在编程,完了在编程,在编程!等机会来了在创业(不要相信有奇迹发生,盲目创业很难成功,即便成功了发展空间也不一定很大)。
嵌入式书籍推荐

Linux基础
1、《Linux与Unix Shell 编程指南》
C语言基础
1、《C Primer Plus,5th Edition》【美】Stephen Prata着
2、《The C Programming Language, 2nd Edition》【美】Brian W. Kernighan David M. Rithie(K & R)着
3、《Advanced Programming in the UNIX Environment,2nd Edition》(APUE)
4、《嵌入式Linux应用程序开发详解》
Linux内核
1、《深入理解Linux内核》(第三版)
2、《Linux内核源代码情景分析》毛德操 胡希明著
研发方向
1、《UNIX Network Programming》(UNP)
2、《TCP/IP详解》
3、《Linux内核编程》
4、《Linux设备驱动开发》(LDD)

5、《Linux高级程序设计》 杨宗德著
硬件基础
1、《ARM体系结构与编程》杜春雷着
2、S3C2410 Datasheet
英语基础
1、《计算机与通信专业英语》
系统教程
1、《嵌入式系统――体系结构、编程与设计》
2、《嵌入式系统――采用公开源代码和StrongARM/Xscale处理器》毛德操 胡希明着
3、《Building Embedded Linux Systems》

4、《嵌入式ARM系统原理与实例开发》 杨宗德著
理论基础
1、《算法导论》
2、《数据结构(C语言版)》
3、《计算机组织与体系结构?性能分析》
4、《深入理解计算机系统》【美】Randal E. Bryant David O’Hallaron着
5、《操作系统:精髓与设计原理》
6、《编译原理》
7、《数据通信与计算机网络》
8、《数据压缩原理与应用》

C语言书籍推荐

1. The C programming language 《C程序设计语言》
2. Pointers on C 《C和指针》
3. C traps and pitfalls 《C陷阱与缺陷》
4. Expert C Lanuage 《专家C编程》
5. Writing Clean Code —–Microsoft Techiniques for Developing Bug-free C Programs
《编程精粹–Microsoft 编写优质无错C程序秘诀》
6. Programming Embedded Systems in C and C++ 《嵌入式系统编程》
7.《C语言嵌入式系统编程修炼》
8.《高质量C++/C编程指南》林锐

尽可能多的编码,要学好C,不能只注重C本身。算法,架构方式等都很重要。

posted @ 2011-09-22 14:25 马航 阅读(97) | 评论 (0)编辑 收藏

网上搜了N多解决方法,但是很多将log级别的,用法的,更多的是如何在logcat中设置filter进行log的过滤与查看,但是我遇到的问题是,模拟器怎么着都OK,但真机、手机进行开发调试的时候却看不到log信息,这是很恼人的事情(毕竟模拟器跑起来太慢了)。

刚开始没有查到好的方法,就用try catch把exception打到一个alertdialog中,但是这样只能看个大概,绕这个圈子没用,最后还是在eoe的论坛上看到了解决办法,恐怕原因是rom本身没有打开log的开关

问题表现:连接手机与电脑后,驱动安装正确,USB调试模式打开,在DDMS中可以看到device及其进程的信息,但是logcat中就是没有信息输出
问题原因:一些rom默认关闭logcat
问题说明:ddms中设备名字显示为问号不影响,即adb get-serialno显示为问号不影响.
解决方法:
1.需要root权限(部分rom不需要)
2.打开logcat,并设置level,执行命令如下(android 升级之后 adb 在 platform-tools中,不在tools中)
adb shell
echo 1 > /sys/kernel/logger/log_main/enable
说明:将1写入日志开关文件,1为开,0为关
echo 2 >/sys/kernel/logger/log_main/priority
说明:将代表level的2写入优先级文件
3.重启adb,如果使用eclipse,先关闭eclipse,再重启adb,再启动eclipse
adb kill-server
adb start-server
4.此时logcat应该可以工作了,如果仍旧不工作,则更新adb
android update adb
5.重复第三步,此时logcat应该可以工作了,如果仍旧不工作,找到个人主目录下的android目录,如C:\Documents and Settings\Administrator\.android
找到这个目录下的adb_usb.ini文件,其内容默认只有三行,全为注释,在后面添加一行,内容为0x12d1
6.重复第三步,此时logcat应该可以工作了

转自:http://www.gobbin.cn/2011/02/16/android-phone-logcat/

posted @ 2011-09-06 16:10 马航 阅读(11180) | 评论 (0)编辑 收藏

1.          app2sd是什么
app2sd
就是把应用程序放在SD卡上。有些android手机的用户数据分区(userdata)比较小(比如G1只有76M),dalvikcache和用户数据就占了大半,使得安装了几个软件后就没有空间了。为了安装更多软件,在SD卡上划出部分空间用于存在新软件和数据,使我们的手机可以使用更多软件。

2.          原理
一般情况下都SD卡都默认分成一个windows可识别的分区(FAT)。因为有linux系统的权限问题,为了让它可以存放软件,需要把SD卡的一部分划分成Linux的使用的ext2文件系统,然后在开机时把此分区挂载到某处,并通过链接的方法,让系统从SD卡中读取软件

3.          实现

1)         SD卡分区

a)          使用Linux系统中的工具fdisk,它是命令行工具,很快很简单

b)         Windows下的图形化工具
具体步骤见http://www.3haoweb.cn/a/digital/mobile/2010/0609/2273.html

2)         修改boot.img使得新分区在启动时被自动挂载

a)          说明:

                                       i.              也可以从网上下载带app2sd功能的update.zip包,升级整个系统,但是那样的话还要备份设置、数据、软件太麻烦,所以我选择修改我手机中自带的boot.img,以最小的修改来实现功能

                                      ii.              修改boot.img中的initrc(系统启动时运行的脚本,自动挂载SD卡的ext2分区)

b)         boot.img是什么
boot.img
包括了2K的文件头,后面紧跟着是用gzip压缩过的内核,再后面是一个ramdisk内存盘(系统基本目录结构的镜像档),然后紧跟着第二阶段的载入器程序(这个载入器程序是可选的,在某些映像中或许没有这部分)

c)          修改本机的boot.img

                                       i.              使用nandroid备份数据
任何对系统的修改都要先备份系统数据

                                      ii.              从机器中取出当前的boot.img
$ export PATH=$PATH:$ANDROID_DIR/out/host/linux-x86/bin/
$ adb shell
# cat /proc/mtd/
查看boot对应的mtdx,一般是mtd2
# cat /dev/mtd/mtd2 > /sdcard/boot.img
假设boot对应mtd2
# mkdir /system/sd1
建立目录以挂载分区
# exit
$ adb pull /sdcard/boot.img ./                 
复制到PC

                                    iii.              解包
下载工具split_boot.img.pl
http://cid-f8aecd2a067a6b17.office.live.com/self.aspx/.Public/android/reference/split^_bootimg.zip
$ ./split_boot.img.pl boot.img                  
解包,解出内核和ramdisk包两部分
$ mkdir ramdisk; cd ramdisk
$ gzip -dc ../boot.img-ramdisk.gz |cpio -i

                                    iv.              修改启动脚本
$ vi init.rc 
如果是乱码,请使用reset命令恢复一下
mount 最后加入
mount ext2 /dev/block/mmcblk0p2 /system/sd1 rw

                                      v.              重新打包
$ cd ../
$ mkbootfs ramdisk |gzip > ramdisk-new.gz
$ mkbooting --cmdline ‘no_console_suspend=1 console=null’ --kernel boot.img-kernel --ramdisk ramdisk-new.gz -o boot_new.img
(mkbootfs
mkbootimg可以android源码包中取得,和adb在一个目录)

                                    vi.              烧写新包到手机
$ adb push boot_new.img /sdcard
$ adb shell
# cat /dev/zero > /dev/mtd/mtd2 (
可能找错没空间,没关系)
# flash_image boot /sdcard/boot_new.im

                                   vii.              验证是否成功
然后重启手机即可,重启后用以下命令看一下是否分区是否被挂载
$ adb shell
$ df 
如果看到/system/sd1项就成功了

3)         做链接,使系统从SD卡读取软件
建立只对软件安装目录做修改(/data/app),这样拨出SD后除了后来安装的软件不能使用之外,不影响手机基本功能的使用
$ adb shell
# mkdir /system/sd1/data/
# cd /system/sd1/data/

# busybox cp -a /data/app ./  
建议做
# busybox cp -a /data/app-private ./        
不建议做
# busybox cp -a /data/dalvik-cache ./
不建议做
# busybox cp -a /data/data ./ 
不建议做
# rm -r /data/app
# ln -s /system/sd1/data/app /data/app

……
其它目录以此类推
然后重启手机即可

4)         注意
由于launcher数据库的关系,可能桌面上看不到原来的那些应用了,不过主菜单里是有的,再建一遍快捷方式即可

4.          参考
http://kb.cnblogs.com/a/1743704/

posted @ 2011-09-04 11:15 马航 阅读(1014) | 评论 (0)编辑 收藏

  一、 说明

1.        下载编译最基本的android源码,只能在模拟器上使用,无法在真机上使用(不能生成boot.img)。这是因为没有编译相关机型的内核和硬件驱动。以下介绍的是用android源码编译出对应HTC G1的版本,和烧写的过程。编译生成的版本除相机以外,其它功能均正常,稳定性不错,也很顺畅。

2.        以下步骤都经过验证(只验证G1手机),实验系统ubuntu8.04,实验日期2010712

3.        关键字: android 2.2 froyo g1 源码编译

二、 编译

1.        建立android源码编译目录
$ export ANDROID=/exports/android/android_2.2/
$ mkdir -p $ANDROID
$ cd $ANDROID

2.        源码下载
$ repo init -u git://android.git.kernel.org/platform/manifest.git -b android-2.2_r1
$ vi .repo/local_manifest.xml  #
新建下载配置文件,用以下载内核,编辑内容如下

注意:其中msm是高通芯片组,path指明下载到源码目录中的位置,name指明git上的项目名
$ repo sync           # 开始下载代码,此时需要等待较长时间

3.        编译内核及无线网络驱动
$ cd $ANDROID/kernel
$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- msm_defconfig          #
设定默认的msm配置

$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- #
编译内核
$ cd $ANDROID/system/wlan/ti/sta_dk_4_0_4_32
$ make ARCH=arm CROSS_COMPILE=$ANDROID/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- KERNEL_DIR=$ANDROID/kerne l       
#编译无线网络驱动
cp $ANDROID/kernel/arch/arm/boot/zImage $ANDROID/device/htc/dream/kernel
cp $ANDROID/system/wlan/ti/sta_dk_4_0_4_32/wlan.ko $ANDROID/device/htc/dream/wlan.ko

4.        配置编译选项
vi device/htc/dream/AndroidBoard.mk     #若kernel存在,则不重新编译kernel
ifeq ($(TARGET_PREBUILT_KERNEL),)        
TARGET_PREBUILT_KERNEL := $(LOCAL_PATH)/kernel
endif

file := $(INSTALLED_KERNEL_TARGET)
ALL_PREBUILT += $(file)
$(file): $(TARGET_PREBUILT_KERNEL) | $(ACP)
        $(transform-prebuilt-to-target)

5.        编译android源码
$ cd $ANDROID
$ vi buildspec.mk #
加入如下内容,以支持中文

CUSTOM_LOCALES:=zh_CN
$ source build/envsetup.sh
$ lunch full_dream-userdebug        
#指定编译机型
$ make -j2

6.        以打补丁的方式加入不提供源码的库
此时的系统可以被烧写,但电话音乐等基本功能均不正常,需要从系统或其它升级包中提取出源码中不包含的库,以支持相应功能。
HTC网站http://developer.htc.com/adp.html
下载名为signed-dream_devphone_userdebug-ota-14721.zip的包(一个普通的update包),并把它放在$ANDROID目录下,并将其改名为dreaem_update.zip
$ mv signed-dream_devphone_userdebug-ota-14721.zip dream_update.zip
$ cd device/htc/dream
$ ./unzip-files.sh  
此时会提示有几个库找不后,后面有对应解决办法

$ cd $ANDROID
$ vi vendor/htc/dream/device_dream-vendor-blobs.mk
删除包含以下内容的行,这是由于在update.zip中找不到相应库,为编译通过,选去掉它们
libGLES_qcom.so
liblvmxipc.so
liboemcamera.so
libstagefrighthw.so
$ make
$ cp device/htc/dream/wlan.ko out/target/product/dream/system/lib/modules/wlan.ko
#网卡驱动
$ make snod        
重新生成system.img

三、 把编译好的软件烧写到手机

usb线连接手机到电脑,按home+power键将手机启动到工程模式,按back键准备烧写
$ export PATH=$PATH:$ANDROID/out/host/linux-x86/bin        #
把烧写工具所在目录加上路径
$ cd out/target/product/dream/
$ fastboot flash system system.img
$ fastboot flash boot boot.img
$ fastboot reboot

烧写系统后第一次启动手机需要几分钟,请耐心等

四、 修改

1.        安装中文字体(可以在烧写前加入,加在此处用以说明在启动后修改系统的方法)
$ adb shell
# su            
取得root权限

# mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system    
使system分区可写
# chmod 777 /system/fonts     
使某个目录有写权限
# exit
# exit
$ adb push frameworks/base/data/fonts/DroidSansFallback.ttf /system/fonts/         
加中文字体
$ adb reboot

2.        支持GPRS上网
添加APN即可上网和发彩信,详见http://www.andbeta.com/Basics/678.html

3.        设置帐户
添加帐户时,服务器填写m.google.com

五、 参考

1.        刷写部分未详细描述,具体请参考文档
http://xy0811.spaces.live.com/blog/cns!F8AECD2A067A6B17!1452.entry

2.        源码编译部分未详细描述,具体请参考文档
http://xy0811.spaces.live.com/blog/cns!F8AECD2A067A6B17!1364.entry


转自:http://blog.csdn.net/xieyan0811/article/details/5931573

posted @ 2011-09-04 11:03 马航 阅读(401) | 评论 (0)编辑 收藏

在android的设计中,谷歌设计了一套专门为嵌入式设备使用的bionic C库,以替换原有的GUN Libc,这个精简的bionic库据说只有200多K,所以如果只想使用这个精简的C库像在linux下一样 开发C程序,基本是不可能的。当然如果只想让其在shell中运行还是可以做到的。

因为编译完的目标程序是在android下运行,就要使用交叉编译的工具,在下面地址下载:

http://www.codesourcery.com/gnu_toolchains/arm/download.html

下载完之后,bin目录下的arm-none-linux-gnueabi-gcc就是交叉编译器了

#include <stdio.h>
int main() {
	printf("nihao a\n");
	printf("你好 啊\n");
	return 1;
}

输入一下命令:

./arm-none-linux-gnueabi-gcc hello.c -o hello -static

-static选项在这里是必须的,否则会出现”not found”的错误。

然后就可以把编译好的hello传到手机上运行了。不过这里有个前提条件,要求android机器必须是root过的,好像简单的z4root还不行,必须使用更彻底的root方法,关于如何root,这里就不再赘述了,可以参考相关root的帖子。

adb push hello /dev/sample/

这里要上传的目录必须是root用户所有的。

然后就是运行程序,可以在adb shell里测试

adb shell

cd /dev/sample/

chmod 777 hello

./hello

或者在手机上安装超级终端,在终端里运行

./hello

posted @ 2011-09-02 09:43 马航 阅读(224) | 评论 (0)编辑 收藏

http://bbs.hiapk.com/thread-553691-1-1.html

在ubuntu下adb找不到设备的解决方法:

需要在Windows下锁定端口就好了

在adb shell中输入如下命令:

echo 22>/sys/devices/platform/msm_hsusb_periphera/fixusb 

posted @ 2011-09-01 17:20 马航 阅读(118) | 评论 (0)编辑 收藏

一、使用git下载android内核部分源码

首先新建要保存android内核源码的目录

mkdir android_kernel

cd android_kernel

android kernel的网站http://android.git.kernel.org/

git clone git://android.git.kernel.org/kernel/common.git

下载android内核源码,完成之后会看到common目录,内核的源码就算下载完成了

如果想下载某一内核的版本,可以使用下面几个命令:

git branch -a // 显示所有的分支

git branch -r // 显示romote端的分支

git checkout // 检出某一分支

二、设置交叉编译环境

交叉编译的环境在android源码已经存在,源码的下载可以参考《下载编译android源码》。在源码目录的android_source/prebuilt/linux-x86/toolchain/,可以看到多个交叉编译的工具

mac@mac-desktop:~/works/android_dev/prebuilt/linux-x86/toolchain$ ls -all

总用量 44

drwxr-xr-x 9 mac mac 4096 2011-08-28 15:16 arm-eabi-4.2.1

drwxr-xr-x 9 mac mac 4096 2011-08-28 15:16 arm-eabi-4.3.1

drwxr-xr-x 10 mac mac 4096 2011-08-28 15:16 arm-eabi-4.4.0

drwxr-xr-x 10 mac mac 4096 2011-08-28 15:16 arm-eabi-4.4.3

drwxr-xr-x 10 mac mac 4096 2011-08-28 15:16 arm-linux-androideabi-4.4.x

drwxr-xr-x 6 mac mac 4096 2011-08-28 15:16 i686-android-linux-4.4.3

drwxr-xr-x 8 mac mac 4096 2011-08-28 15:17 i686-linux-glibc2.7-4.4.3

drwxr-xr-x 10 mac mac 4096 2011-08-28 15:17 i686-unknown-linux-gnu-4.2.1

drwxr-xr-x 6 mac mac 4096 2011-08-28 15:17 sh-4.3.3

一般使用最新版本。

三、配置编译时的config文件

因为编译的镜像是要刷到模拟器运行的,模拟器的处理器架构是基于goldfish,所以需要下载有关goldfishconfig文件。下载方法:

mac@mac-desktop:~/works/kernel-2.6.29$ git branch -a

* (no branch)

android-2.6.29

android-2.6.36

remotes/origin/HEAD -> origin/android-2.6.36

remotes/origin/android-2.6.35

remotes/origin/android-2.6.36

remotes/origin/android-2.6.37

remotes/origin/android-2.6.38

remotes/origin/android-2.6.39

remotes/origin/android-3.0

remotes/origin/archive/android-2.6.25

remotes/origin/archive/android-2.6.27

remotes/origin/archive/android-2.6.29

remotes/origin/archive/android-2.6.32

remotes/origin/archive/android-gldfish-2.6.29

remotes/origin/archive/android-goldfish-2.6.27

remotes/origin/linux-bcm43xx-2.6.39

remotes/origin/linux-wl12xx-2.6.39

下载remotes/origin/archive/android-gldfish-2.6.29 版本:

git checkout origin/archive/android-gldfish-2.6.29

然后就可以到arch/arm/configs下看到goldfish_defconfig这个文件了。

goldfish_defconfig文件拷贝到android_kernel目录,并重命名为.config

四、make编译

首先设置环境:

export PATH=$PATH:~/android_source/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin

然后修改Makefile

ARCH = arm #体系结构为arm

CROSS_COMPILE = arm-eabi- #交叉编译工具链前缀,参考/toolchain/arm-eabi-4.4.3/bin

然后就可以make编译了,编译过程中会连续提示yes/not,能看懂的多注意几眼,看不懂的一路狂按Enter,最后如果前面的设置正确,编译完整后会看到下面的提示:

OBJCOPY arch/arm/boot/zImage

Kernel: arch/arm/boot/zImage is ready

五、编译得到zImage,用新内核启动模拟器

./emulator -avd android2.1 -kernel ~/kernel-2.6.29/arch/arm/boot/zImage

在模拟器上查看系统信息:

posted @ 2011-09-01 14:19 马航 阅读(1335) | 评论 (0)编辑 收藏

现在使用linux的朋友越来越多了,在linux下做开发首先就是需要配置环境变量,下面以配置java环境变量为例介绍三种配置环境变量的方法。

 

1.修改/etc/profile文件

如果你的计算机仅仅作为开发使用时推荐使用这种方法,因为所有用户的shell都有权使用这些环境变量,可能会给系统带来安全性问题。

 

(1)用文本编辑器打开/etc/profile

 

(2)在profile文件末尾加入:

JAVA_HOME=/usr/share/jdk1.5.0_05

PATH=$JAVA_HOME/bin:$PATH

CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

export JAVA_HOME

export PATH

export CLASSPATH

 

(3)重新登录

 

注解:

a. 你要将 /usr/share/jdk1.5.0_05jdk 改为你的jdk安装目录

 

b. linux下用冒号“:”来分隔路径

 

c. $PATH / $CLASSPATH / $JAVA_HOME 是用来引用原来的环境变量的值,在设置环境变量时特别要注意不能把原来的值给覆盖掉了,这是一种常见的错误。

 

d. CLASSPATH中当前目录“.”不能丢,把当前目录丢掉也是常见的错误。

 

e. export是把这三个变量导出为全局变量。

 

f. 大小写必须严格区分。

 

2. 修改.bashrc文件  

这种方法更为安全,它可以把使用这些环境变量的权限控制到用户级别,如果你需要给某个用户权限使用这些环境变量,你只需要修改其个人用户主目录下的.bashrc文件就可以了。

 

(1)用文本编辑器打开用户目录下的.bashrc文件

 

(2)在.bashrc文件末尾加入:  

set JAVA_HOME=/usr/share/jdk1.5.0_05

export JAVA_HOME

set PATH=$JAVA_HOME/bin:$PATH

export PATH

set CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

export CLASSPATH

 

(3)重新登录

 

3. 直接在shell下设置变量

不赞成使用这种方法,因为换个shell,你的设置就无效了,因此这种方法仅仅是临时使用,以后要使用的时候又要重新设置,比较麻烦。

 

只需在shell终端执行下列命令:

export JAVA_HOME=/usr/share/jdk1.5.0_05

export PATH=$JAVA_HOME/bin:$PATH

export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

posted @ 2011-09-01 11:21 马航 阅读(126) | 评论 (0)编辑 收藏

刚开始学着用linux,对vi命令不是很熟,在网上转接了一篇。

vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器,这里只是简单地介绍一下它的用法和一小部分指 令。由于 对Unix及Linux系统的任何版本,vi编辑器是完全相同的,因此您可以在其他任何介绍vi的地方进一步了解它。Vi也是Linux中最基本的文本编 辑器,学会它后,您将在Linux的世界里畅行无阻。

1、vi的基本概念
  基本上vi可以分为三种状态,分别是命令模式(command mode)、插入模式(Insert mode)和底行模式(last line mode),各模式的功能区分如下:
    1) 命令行模式command mode)
  控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode。
    2) 插入模式(Insert mode)
  只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。
    3) 底行模式(last line mode)
  将文件保存或退出vi,也可以设置编辑环境,如寻找字符串、列出行号……等。
 
    不过一般我们在使用时把vi简化成两个模式,就是将底行模式(last line mode)也算入命令行模式command mode)。
2、vi的基本操作 
a) 进入vi
    在系统提示符号输入vi及文件名称后,就进入vi全屏幕编辑画面:
   $ vi myfile
不过有一点要特别注意,就是您进入vi之后,是处于「命令行模式(command mode)」,您要切换到「插入模式(Insert mode)」才能够输入文字。初次使用vi的人都会想先用上下左右键移动光标,结果电脑一直哔哔叫,把自己气个半死,所以进入vi后,先不要乱动,转换到 「插入模式(Insert mode)」再说吧!
 
b) 切换至插入模式(Insert mode)编辑文件
  在「命令行模式(command mode)」下按一下字母「i」就可以进入「插入模式(Insert mode)」,这时候你就可以开始输入文字了。
 
c) Insert 的切换
  您目前处于「插入模式(Insert mode)」,您就只能一直输入文字,如果您发现输错了字!想用光标键往回移动,将该字删除,就要先按一下「ESC」键转到「命令行模式(command mode)」再删除文字。
 
d) 退出vi及保存文件
  在「命令行模式(command mode)」下,按一下「:」冒号键进入「Last line mode」,例如:
: w filename (输入 「w filename」将文章以指定的文件名filename保存)
: wq (输入「wq」,存盘并退出vi)
: q! (输入q!, 不存盘强制退出vi)

3、命令行模式(command mode)功能键
1). 插入模式
       按「i」切换进入插入模式「insert mode」,按"i"进入插入模式后是从光标当前位置开始输入文件;
  按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;
  按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。
 
2). 从插入模式切换为命令行模式
      按「ESC」键。
 
3). 移动光标
  vi可以直接用键盘上的光标来上下左右移动,但正规的vi是用小写英文字母「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移一格。
  按「ctrl」+「b」:屏幕往"后"移动一页。
  按「ctrl」+「f」:屏幕往"前"移动一页。
  按「ctrl」+「u」:屏幕往"后"移动半页。
  按「ctrl」+「d」:屏幕往"前"移动半页。
  按数字「0」:移到文章的开头。
  按「G」:移动到文章的最后。
  按「$」:移动到光标所在行的"行尾"。
  按「^」:移动到光标所在行的"行首"
  按「w」:光标跳到下个字的开头
  按「e」:光标跳到下个字的字尾
  按「b」:光标回到上个字的开头
  按「#l」:光标移到该行的第#个位置,如:5l,56l。
 
4). 删除文字
  「x」:每按一次,删除光标所在位置的"后面"一个字符。
  「#x」:例如,「6x」表示删除光标所在位置的"后面"6个字符。
  「X」:大写的X,每按一次,删除光标所在位置的"前面"一个字符。
  「#X」:例如,「20X」表示删除光标所在位置的"前面"20个字符。
  「dd」:删除光标所在行。
  「#dd」:从光标所在行开始删除#行
 
5). 复制
  「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
  「#yw」:复制#个字到缓冲区
  「yy」:复制光标所在行到缓冲区。
  「#yy」:例如,「6yy」表示拷贝从光标所在的该行"往下数"6行文字。
  「p」:将缓冲区内的字符贴到光标所在位置。注意:所有与"y"有关的复制命令都必须与"p"配合才能完成复制与粘贴功能。
 
6). 替换
  「r」:替换光标所在处的字符。
  「R」:替换光标所到之处的字符,直到按下「ESC」键为止。
 
7). 回复上一次操作
  「u」:如果您误执行一个命令,可以马上按下「u」,回到上一个操作。按多次"u"可以执行多次回复。
 
8). 更改
  「cw」:更改光标所在处的字到字尾处
  「c#w」:例如,「c3w」表示更改3个字
 
9). 跳至指定的行
  「ctrl」+「g」列出光标所在行的行号。
  「#G」:例如,「15G」,表示移动光标至文章的第15行行首。

4、Last line mode下命令简介
在使用「last line mode」之前,请记住先按「ESC」键确定您已经处于「command mode」下后,再按「:」冒号即可进入「last line mode」。

A) 列出行号

 「set nu」:输入「set nu」后,会在文件中的每一行前面列出行号。

B) 跳到文件中的某一行

 「#」:「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字15,再回车,就会跳到文章的第15行。

C) 查找字符

 「/关键字」:先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止。

 「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往前寻找到您要的关键字为止。

D) 保存文件

 「w」:在冒号输入字母「w」就可以将文件保存起来。

E) 离开vi

 「q」:按「q」就是退出,如果无法离开vi,可以在「q」后跟一个「!」强制离开vi。

 「qw」:一般建议离开时,搭配「w」一起使用,这样在退出的时候还可以保存文件。

5、vi命令列表
1、下表列出命令模式下的一些键的功能:

h
左移光标一个字符

l
右移光标一个字符

k
光标上移一行

j
光标下移一行

^
光标移动至行首

0
数字"0",光标移至文章的开头

G
光标移至文章的最后

$
光标移动至行尾

Ctrl+f
向前翻屏

Ctrl+b
向后翻屏

Ctrl+d
向前翻半屏

Ctrl+u
向后翻半屏

i
在光标位置前插入字符

a
在光标所在位置的后一个字符开始增加

o
插入新的一行,从行首开始输入

ESC
从输入状态退至命令状态

x
删除光标后面的字符

#x
删除光标后的#个字符

X
(大写X),删除光标前面的字符

#X
删除光标前面的#个字符

dd
删除光标所在的行

#dd
删除从光标所在行数的#行

yw
复制光标所在位置的一个字

#yw
复制光标所在位置的#个字

yy
复制光标所在位置的一行

#yy
复制从光标所在行数的#行

p
粘贴

u
取消操作

cw
更改光标所在位置的一个字

#cw
更改光标所在位置的#个字


2、下表列出行命令模式下的一些指令
w filename
储存正在编辑的文件为filename

wq filename
储存正在编辑的文件为filename,并退出vi

q!
放弃所有修改,退出vi

set nu
显示行号

/或?
查找,在/后输入要查找的内容

n
与/或?一起使用,如果查找的内容不是想要找的关键字,按n或向后(与/联用)或向前(与?联用)继续查找,直到找到为止。


对于第一次用vi,有几点注意要提醒一下:
1、 用vi打开文件后,是处于「命令行模式(command mode)」,您要切换到「插入模式(Insert mode)」才能够输入文字。切换方法:在「命令行模式(command mode)」下按一下字母「i」就可以进入「插入模式(Insert mode)」,这时候你就可以开始输入文字了。
2、编辑好后,需从插入模式切换为命令行模式才能对文件进行保存,切换方法:按「ESC」键。
3、保存并退出文件:在命令模式下输入:wq即可!(别忘了wq前面的:)

posted @ 2011-09-01 10:34 马航 阅读(136) | 评论 (0)编辑 收藏