posts - 33, comments - 0, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          2011年9月2日

                在第一篇時就說過框架要在URL上作文章,是的,本文就框架怎樣充分利用url上作盡可能詳細(xì)的說明。

                做web開發(fā)的不可能對url陌生,早在web1.0時代,url作為統(tǒng)一資源定位符,在對web中資源的如何獲得上起到巨大作用。不論用戶請求的時靜態(tài)頁面或者是各種圖片、腳本文件,通過url總能從web網(wǎng)站獲取要訪問的資源。Web2.0更是常常使用url作為get請求時參數(shù)的傳遞,如http://xxx.xxx.xxx/xxx.jsp?user=admin。以及近幾年很火restful web service 摒棄soap而使用url傳遞請求參數(shù) 都說明合理利用url的可行和流行。

                當(dāng)然不止是使用了url就算好的實踐,而是能夠做到優(yōu)雅的使用,保證層次分明和整體的簡潔,這才算是好的方式,這也正是本框架對使用url 所追求的目標(biāo)。

                首先來看幾個例子:

          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

                相信各位看官不用我說也能明白,這幾組的實現(xiàn)肯定第一種實現(xiàn)的方式更佳。拋開它能屏蔽服務(wù)器端使用的技術(shù)這一特性不說,它還能夠更好地說明動態(tài)網(wǎng)站的層次結(jié)構(gòu),讓用戶在訪問時能明確知道在網(wǎng)站的什么位置,而不會覺得是陷入了一個迷宮。

                當(dāng)然上面列舉的例子是網(wǎng)站前端所使用的url表現(xiàn)方式,因為表現(xiàn)方式可以多種多樣,個人喜好不同,本框架在設(shè)計時沒有給指定前端url的表現(xiàn)方式,而是定義接口,把這個權(quán)利留給使用的用戶。框架將考慮更多 通用性的東西而不是 個性 自由的東西。

           

                下面對框架里默認(rèn)使用的url Router AMPPathRouter做詳細(xì)的介紹,包括設(shè)計的思想和實現(xiàn)的方式。首先AMPPathRouter的用途定位為后臺使用。為了理解快速的理解它的工作原理,先來和struts做一下對比。

                Struts關(guān)于請求的配置:

           

          <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服務(wù)器時會把此url傳遞到struts框架交給它處理,之后struts會在struts.xml中尋找login的相關(guān)的配置,像上面例子,struts會找到LoginAction的類,并且調(diào)用其login的方法。

                寫到這里,我請問這是最好的方式嗎?當(dāng)然不是,至少我在使用struts時就認(rèn)為這是相當(dāng)撇腳的設(shè)計。上面例子只是列舉一個login方法,假如一個系統(tǒng)中要對后臺調(diào)用的方法是100個,那豈不是就需要在struts.xml中寫100個與之類似的配置。想想都頭大,這樣繁瑣的工作,應(yīng)該是由框架自己去處理,而不是人工給配置。

           

                再來看實現(xiàn)相同功能的Unicorn web框架的配置。

           

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

                當(dāng)然提交的url肯定需要包含多一些的信息,來保證能通過url正確調(diào)用框架Action里的方法。這里提交的url方式:http://xxx.xxx.xxx/UserAction/login/

                通過在url里附加調(diào)用的Action類的信息,可以省略為不同的方法都在xml里配置的麻煩。假如UserAction里有100個方法,框架也只需這一行的配置。

                有了大體的認(rèn)識之后,來看框架的核心部分AMPPathRouter的具體實現(xiàn)。

           

            /**
          * 檢查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;
          }
          /**
          * 匹配規(guī)則為:
          * 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方式的請求,并構(gòu)建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的全部內(nèi)容,其中在把請求分發(fā)到ActionSupport的子類 并調(diào)用相關(guān)方法時 是通過反射實現(xiàn),其他地方地方都是相當(dāng)容易理解的。

                空說無憑,把框架應(yīng)用到實戰(zhàn)中才是硬道理:

           

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

          posted @ 2011-11-25 18:18 馬航 閱讀(270) | 評論 (0)編輯 收藏

                上篇說過,所有提交到web程序的url都被此UrlFilter攔截。攔截到請求后,UrlFilter則召集它的好多個得力干將Router 們, 詢問他們:“誰能處理此URL啊 ?”

          這時一位叫做AMPRouter 首當(dāng)其沖 說:“這個url交給我了”。這時filter就會把此url全權(quán)交給AMPRouter來辦,至于如何去處理,filter也不再過問,它覺得:“我把任務(wù)都交給你了,怎么解決是你的事”。

                根據(jù)單一職責(zé)的原則,UrlFilter就負(fù)責(zé)上面情景中的分發(fā)urlRouter中的差事,url如何分發(fā)交給Router處理。并且Router實際是一個接口,使用框架的用戶完全可以自己實現(xiàn)Router,這樣用戶可以自主定義的url分發(fā)的策略。另外呢,框架初始化的一些操作它也是 推脫不掉的,像根據(jù)unicorn-config.xml初始化系統(tǒng)中的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優(yōu)先級最高,url先通過用戶定義的
          	Iterator<IPathRouter> iterator = routerList.iterator();
          	while(iterator.hasNext()) {
          		IPathRouter router = iterator.next();
          		if(router.route(relativeUri, this)) {
          			return ;
          		}
          	}
          	// 攔截不到的繼續(xù)訪問
          	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();
          		}
          	}
          	// 最后把框架默認(rèn)的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是要判斷所有提交到服務(wù)器的指定url的請求 是否已經(jīng)登錄過,沒有登錄,可能會把此請求遣送會登錄頁。以及初始化所有的Action,在Router處理完請求,分發(fā)給action時,可以從filter里面去取。

           


          posted @ 2011-11-25 12:35 馬航 閱讀(274) | 評論 (0)編輯 收藏

                承接上篇的簡單介紹,下面詳細(xì)介紹整個框架的大致結(jié)構(gòu)。

                先來看一下整個框架包的結(jié)構(gòu):

           

                可以看出框架包含的包很少,包的結(jié)構(gòu)也超簡單。這里 涉及FilterActionSupportRouter等三個概念,他們之間的關(guān)系,通過下圖來表示:

           

                圖也不規(guī)范,說不上來是哪個UML圖,不過通過它也能看出一個請求到達(dá)時,框架基本的處理流程。首先由Filter攔截到所有請求,然后把請求交給所有注冊的Router類,如果請求的Url正好是一個Router要攔截的,則把此請求交給這個Router,框架不再把請求向下傳遞。Router得到請求后,分析Url,通過Url里的信息把請求交給對應(yīng)的ActionSupport的子類來處理。

                這里攔截采用Filter來處理,這跟多數(shù)的web框架一樣,使用FilterServlet有更多的能力進行請求的分發(fā)。首先在一個web工程的web.xml文件中配置框架的UrlFilter類來攔截所有的請求。需要注意的一點是dispatcher 要設(shè)置為request,如果設(shè)置了forward的話,由框架內(nèi)部進行的forward又會被框架攔截,從而造成無限的循環(huán)。Url-pattern設(shè)置為/*,表示所有的請求都會攔截,從而把對url分發(fā)的權(quán)利交由框架本身,而不是采用jsp規(guī)范里的url分發(fā)策略。框架在處理所有請求的url 時,依次交給各個Router類來處理,如果Router類判斷是符合自己的url格式,則分發(fā)給 action 處理。如果不能處理再交給下一級的Router,最后url經(jīng)由所有Router處理完,剩下的資源文件的url,如http://xxx.xxx.xxx.jpg,則框架調(diào)用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>

                大致的原理就是這樣,在下篇介紹框架的詳細(xì)實現(xiàn)。

          posted @ 2011-11-25 11:43 馬航 閱讀(350) | 評論 (0)編輯 收藏

                我承認(rèn)有點標(biāo)題黨了,不過題目中所說的幾項技術(shù)確實有其相似之處,欲知事情原委,且聽我詳細(xì)道來。

                項目一開始只是不滿 struts 龐大的體積,于是想自己根據(jù)其原理實現(xiàn)一個tiny 版。后來的開發(fā)中覺得,完全可以把上述的ajax、Restuful web service的一些思想加入進來。經(jīng)過幾周的努力,便開發(fā)出了一個基本成型的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。框架奉行約定大于配置的思想,至于請求如何分發(fā),這個不需要人工配置,框架自動解決。這里要介紹兩個概念A(yù)ction 和 Router,熟悉Struts的肯定都知道Action,Action替代Servlet、JSP時代的Servlet,所有提交的請求由struts分發(fā)給不同的Action來處理。這里道理也是一樣的,Action就是經(jīng)過框架處理后的請求接受者。再來說一下Router,字面意思路由器,學(xué)過計算機網(wǎng)絡(luò)的都知道,ip數(shù)據(jù)包在網(wǎng)絡(luò)上之所以能夠順利到達(dá),就是因為路由器根據(jù)路由表來來確定出來傳輸?shù)耐緩健_@里Router也是這個作用,根據(jù)訪問服務(wù)器的URL來制定分發(fā)策略。Router是完全可以自定義的,用戶可以定義自己的Router來制定URL分發(fā)的策略,并且用戶自定義的Router比系統(tǒng)默認(rèn)的Router有更高的優(yōu)先權(quán)。

          二、URL上做文章

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

                先來介紹系統(tǒng)MethodRouter的處理方式。上面的url根據(jù)"/"分為三個部分,第一部分是請求的Action類,第二部分是類中的方法Method,第三部分是提交的參數(shù)Param。這一點受上篇文章優(yōu)酷的架構(gòu)里URL設(shè)計的啟發(fā)。

                經(jīng)過這樣的設(shè)計,就明白在上述配置文件中為何可以如此簡單了。

                當(dāng)然也可以不以這樣的方式,框架提供自定義Router的支持。比如你想這樣處理URL:/前臺頁面/子欄目/子欄目

                想實現(xiàn)上面的方式,就可以自己定義Router,在Router里面獲取上述的URL,然后做處理、forward到相應(yīng)的jsp頁面。

          三、使用Json傳輸數(shù)據(jù)

                Ajax請求很容易處理json數(shù)據(jù),ajax可以與系統(tǒng)輕松交互。

                當(dāng)初Web Service使用SOAP的xml格式傳輸數(shù)據(jù),如今也有人指責(zé)這是大費周折。Restful方式提倡遵循HTTP語義,完全使用URL結(jié)合GET、POST、PUT、DELETE來傳輸請求,結(jié)果在roil陣營里廣泛使用,認(rèn)為是web service更優(yōu)雅的方式。所以本框架也吸取他們的優(yōu)點,也完全可以通過url傳輸請求的數(shù)據(jù),如上述URL中的Param部分。不過沒有遵循Restful強調(diào)的Http語義,全部使用Get和POST的請求方式,當(dāng)然也可以制定為其他,這完全看你的心情,因為這對功能實現(xiàn)無關(guān)緊要。而且我覺得統(tǒng)一使用一種,更避免了需要指定請求方式的麻煩。

                數(shù)據(jù)的返回使用json格式,比SOAP更為輕量簡潔和優(yōu)雅,而且有更多的平臺直接支持。如在android平臺,本身就支持json格式的處理, 如果使用web service 的SOAP,你可能還要導(dǎo)入KSOAP的第三方庫。

                在非瀏覽器的客戶端,可以借助編寫的工具類,來完成web service方式的操作,

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

                經(jīng)過這樣的封裝,已經(jīng)與使用web service毫無差別,而且還會更加高效,因為處理json總比處理SOAP的xml要容易。

           

                先簡單寫這么多,之后的續(xù)篇詳細(xì)介紹。

          posted @ 2011-11-22 16:22 馬航 閱讀(285) | 評論 (0)編輯 收藏

          記得以前給大家介紹過視頻網(wǎng)站龍頭老大YouTube的技術(shù)架構(gòu),相信大家看了都會有不少的感觸,互聯(lián)網(wǎng)就是這么一個神奇的東西。今天我突然想到,優(yōu)酷網(wǎng)在國內(nèi)也算是視頻網(wǎng)站的老大了,不知道他的架構(gòu)相對于YouTube是怎么樣的,于是帶著這個好奇心去網(wǎng)上找了優(yōu)酷網(wǎng)架構(gòu)的各方面資料,雖然談得沒有YouTube那么詳細(xì),但多少還是挖掘了一點,現(xiàn)在總結(jié)一下,希望對喜歡架構(gòu)的朋友有所幫助。

          一、網(wǎng)站基本數(shù)據(jù)概覽

          • 據(jù)2010年統(tǒng)計,優(yōu)酷網(wǎng)日均獨立訪問人數(shù)(uv)達(dá)到了8900萬,日均訪問量(pv)更是達(dá)到了17億,優(yōu)酷憑借這一數(shù)據(jù)成為google榜單中國內(nèi)視頻網(wǎng)站排名最高的廠商。
          • 硬件方面,優(yōu)酷網(wǎng)引進的戴爾服務(wù)器主要以 PowerEdge 1950與PowerEdge 860為主,存儲陣列以戴爾MD1000為主,2007的數(shù)據(jù)表明,優(yōu)酷網(wǎng)已有1000多臺服務(wù)器遍布在全國各大省市,現(xiàn)在應(yīng)該更多了吧。

          二、網(wǎng)站前端框架

          從一開始,優(yōu)酷網(wǎng)就自建了一套CMS來解決前端的頁面顯示,各個模塊之間分離得比較恰當(dāng),前端可擴展性很好,UI的分離,讓開發(fā)與維護變得十分簡單和靈活,下圖是優(yōu)酷前端的模塊調(diào)用關(guān)系:

          這樣,就根據(jù)module、method及params來確定調(diào)用相對獨立的模塊,顯得非常簡潔。下面附一張優(yōu)酷的前端局部架構(gòu)圖:

           

          三、數(shù)據(jù)庫架構(gòu)

          應(yīng)該說優(yōu)酷的數(shù)據(jù)庫架構(gòu)也是經(jīng)歷了許多波折,從一開始的單臺MySQL服務(wù)器(Just Running)到簡單的MySQL主從復(fù)制、SSD優(yōu)化、垂直分庫、水平sharding分庫,這一系列過程只有經(jīng)歷過才會有更深的體會吧,就像MySpace的架構(gòu)經(jīng)歷一樣,架構(gòu)也是一步步慢慢成長和成熟的。

          1、簡單的MySQL主從復(fù)制:

          MySQL的主從復(fù)制解決了數(shù)據(jù)庫的讀寫分離,并很好的提升了讀的性能,其原來圖如下:

          其主從復(fù)制的過程如下圖所示:

          但是,主從復(fù)制也帶來其他一系列性能瓶頸問題:

          1. 寫入無法擴展
          2. 寫入無法緩存
          3. 復(fù)制延時
          4. 鎖表率上升
          5. 表變大,緩存率下降

          那問題產(chǎn)生總得解決的,這就產(chǎn)生下面的優(yōu)化方案,一起來看看。

           

          2、MySQL垂直分區(qū)

          如果把業(yè)務(wù)切割得足夠獨立,那把不同業(yè)務(wù)的數(shù)據(jù)放到不同的數(shù)據(jù)庫服務(wù)器將是一個不錯的方案,而且萬一其中一個業(yè)務(wù)崩潰了也不會影響其他業(yè)務(wù)的正常進行,并且也起到了負(fù)載分流的作用,大大提升了數(shù)據(jù)庫的吞吐能力。經(jīng)過垂直分區(qū)后的數(shù)據(jù)庫架構(gòu)圖如下:

          然而,盡管業(yè)務(wù)之間已經(jīng)足夠獨立了,但是有些業(yè)務(wù)之間或多或少總會有點聯(lián)系,如用戶,基本上都會和每個業(yè)務(wù)相關(guān)聯(lián),況且這種分區(qū)方式,也不能解決單張表數(shù)據(jù)量暴漲的問題,因此為何不試試水平sharding呢?

           

          3、MySQL水平分片(Sharding)

          這是一個非常好的思路,將用戶按一定規(guī)則(按id哈希)分組,并把該組用戶的數(shù)據(jù)存儲到一個數(shù)據(jù)庫分片中,即一個sharding,這樣隨著用戶數(shù)量的增加,只要簡單地配置一臺服務(wù)器即可,原理圖如下:

          如何來確定某個用戶所在的shard呢,可以建一張用戶和shard對應(yīng)的數(shù)據(jù)表,每次請求先從這張表找用戶的shard id,再從對應(yīng)shard中查詢相關(guān)數(shù)據(jù),如下圖所示:

          但是,優(yōu)酷是如何解決跨shard的查詢呢,這個是個難點,據(jù)介紹優(yōu)酷是盡量不跨shard查詢,實在不行通過多維分片索引、分布式搜索引擎,下策是分布式數(shù)據(jù)庫查詢(這個非常麻煩而且耗性能)

           

          四、緩存策略

          貌似大的系統(tǒng)都對“緩存”情有獨鐘,從http緩存到memcached內(nèi)存數(shù)據(jù)緩存,但優(yōu)酷表示沒有用內(nèi)存緩存,理由如下:

          1. 避免內(nèi)存拷貝,避免內(nèi)存鎖
          2. 如接到老大哥通知要把某個視頻撤下來,如果在緩存里是比較麻煩的

          而且Squid 的 write() 用戶進程空間有消耗,Lighttpd 1.5 的 AIO(異步I/O) 讀取文件到用戶內(nèi)存導(dǎo)致效率也比較低下。

          但為何我們訪問優(yōu)酷會如此流暢,與土豆相比優(yōu)酷的視頻加載速度略勝一籌?這個要歸功于優(yōu)酷建立的比較完善的內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN),它通過多種方式保證分布在全國各地的用戶進行就近訪問——用戶點擊視頻請求后,優(yōu)酷網(wǎng)將根據(jù)用戶所處地區(qū)位置,將離用戶最近、服務(wù)狀況最好的視頻服務(wù)器地址傳送給用戶,從而保證用戶可以得到快速的視頻體驗。這就是CDN帶來的優(yōu)勢,就近訪問,有關(guān)CDN的更多內(nèi)容,請大家Google一下。

          好了,就總結(jié)這么多了,有興趣的同學(xué)接著補充,雖然很多資料圖片都來自網(wǎng)絡(luò),但整理也不容易,歡迎轉(zhuǎn)載,轉(zhuǎn)載留個出處:青藤屋 原文鏈接

          posted @ 2011-11-02 11:23 馬航 閱讀(289) | 評論 (0)編輯 收藏

          windows系統(tǒng)使我們經(jīng)常使用的操作系統(tǒng)怎么才能使用我們現(xiàn)在經(jīng)常使用的操作系統(tǒng)不變的情況下繼續(xù)我們的SVN之旅,我們在綜合了好動種方法的同時感覺這些內(nèi)容非常貼近我們SVN在Windows種的應(yīng)用與配置.

          1.下載文件,

          下載最新版本subversion,我這里選擇svn-1.4.5-setup.exe

          下載 "Subversion Windows Service" 軟件包

          下載 TortoiseSVN shell integration utility

          2.安裝Subversion 服務(wù)器

          由于我下載的是setup.exe版本,安裝程序安裝后會自動設(shè)置系統(tǒng)變量.如果你下載的是zip版就需要手動設(shè)置系統(tǒng)變量.

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

          首先創(chuàng)建SVN儲存庫(repository)

          svnadmin create F:/svn/

          repository創(chuàng)建完畢后會在目錄下生成若干個文件和文件夾,dav目錄是提供給Apache與mod_dav_svn使用的目錄,讓它們存儲內(nèi)部數(shù)據(jù);db目錄就是所有版本控制的數(shù)據(jù)文件;hooks目錄放置hook腳本文件的目錄;locks用來放置Subversion文件庫鎖定數(shù)據(jù)的目錄,用來追蹤存取文件庫的客戶端;format文件是一個文本文件,里面只放了一個整數(shù),表示當(dāng)前文件庫配置的版本號;

          3.配置SVN服務(wù)器

          (這個位置就是在你建儲存庫的地方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,即為系統(tǒng)添加一個用戶名為admin,密碼為admin888的用戶

          4.運行SVN服務(wù)器

          運行SVN服務(wù)

          在命令行執(zhí)行

          svnserve --daemon --root F:/svn

          服務(wù)啟動,--daemon可簡寫為-d,--root可簡寫為-r,可以建立一個批處理文件并放在windows啟動組中便于開機就運行SVN服務(wù)(注意:這是臨時打開的服務(wù),命令執(zhí)行后不能關(guān)閉窗口)

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

          用后臺服務(wù)的方式可以設(shè)置開機自動執(zhí)行。

          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來啟動服務(wù)了 也可以在Sevices.msc來啟動了。

          5、用客戶端訪問

          格式:svn://服務(wù)器IP

           

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

           

          基于svnserve的服務(wù)器,權(quán)限文件authz配置的常見問題及解答  
           最近在我用Subversion論壇(http://www.iUseSVN.com/bbs)經(jīng)常有人提到這樣的問題: 
          為什么我的客戶端沒有寫權(quán)限? 
          為什么我的權(quán)限沒有起作用?

          總結(jié)他們的配置,發(fā)現(xiàn) 
          都是用svnserve作為服務(wù)器, 
          都在svnserve.conf中使用了authz-db選項

          原因可能如下:

          1,配置authz時,沒有注意svnserve啟動參數(shù)-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 
          應(yīng)該配置成

          [Copy to clipboard] [ - ]CODE: 
          [groups] 
          admin=user1 
          dev=user2 
          [/] 
          @admin=rw 
          @doc=r 
          因為[project1:/]表示庫project1的根目錄,而按上面的啟動參數(shù),是沒有庫的概念的。 
          使用類似這樣的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可以為多個版本庫工作, 
          這時如果想限制指定庫的指定目錄,就應(yīng)該指定具體的庫,像這樣

          [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,對中文目錄進行權(quán)限控制時,沒有將權(quán)限文件authz改為utf-8格式。

          svn對于非英文文件名和目錄名使用utf-8格式編碼處理,要對中文目錄進行正確控制, 
          應(yīng)該使用無BOM的utf-8格式,如何將默認(rèn)的文件轉(zhuǎn)為utf-8, 
          我使用的是UltraEdit的菜單"ASCII to UTF-8 (Unicode Editing)"。在UltraEdit的配置中,可以設(shè)置有無BOM  

          posted @ 2011-10-12 17:26 馬航 閱讀(424) | 評論 (0)編輯 收藏

          SIP協(xié)議

           

          SIP協(xié)議過程概念及分析

           

          SIP入門開發(fā)之路(含SIP開發(fā)需要學(xué)習(xí)的資源及網(wǎng)址)

           

          SIP揭密(中文版)

           

          使用Java的SIP Servlet進行SIP開發(fā)

           

           

          Asterisk:

           

          Asterisk安裝及測試

           

          Asterisk十問十答

           

          Asterisk入門教程

           

          Asterisk介紹-Asterisk RealTime SIP

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

           

          asterisk 官方文檔

           

          asterisk目錄及配置說明

           

          Asterisk功能整理

           

          Asterisk使用ODBC實現(xiàn)語音信箱

           

          使用Asterisk實現(xiàn)可視的語音交換

           

           

          OpenSIPS

           

          開源SIP服務(wù)器OpenSIPS應(yīng)用介紹

           

          Opensips 安裝

           

          Opensips 配置文件

           

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

           

          Opensips文檔之MediaProxy模塊

           

          使用OpenSIPS構(gòu)建電話通信系統(tǒng)-8媒體服務(wù)整合

           

          使用OpenSIPS構(gòu)建電話通信系統(tǒng)-4腳本及路由基礎(chǔ)

           

          Opensips文檔之TM模塊

           

          Opensips文檔之RR模塊

           

          Opensips文檔之TEXTOPS 模塊

           

          Opensips文檔之AVPOPS模塊

           

           

          NAT穿透(即SIP打洞)

           

          使用OpenSIPS構(gòu)建電話通信系統(tǒng)-SIP穿透NAT

           

          NAT穿透問題探討

           

          完美的NAT穿透技術(shù)ICE介紹

           

          ICE-SIP穿透NAT問題的終極解決方案

           

          NAT穿透技術(shù)ICE基礎(chǔ)教程

          posted @ 2011-10-07 20:31 馬航 閱讀(454) | 評論 (0)編輯 收藏

               摘要: Android中有一控件是ExpandableListView,比ListView更高級,ExpandableListView的效果很實用,比如因為需要查看一堆文件的目錄結(jié)構(gòu)或者開發(fā)像QQ好友那樣的界面,就應(yīng)該使用Expandablelistview。 本文最終效果如下: 首先是Activity代碼,實際開發(fā)中數(shù)據(jù)(包括父item,子item及圖片,Expandablelistview...  閱讀全文

          posted @ 2011-10-06 09:37 馬航 閱讀(5471) | 評論 (0)編輯 收藏

          前言:做完了手機全能播放器的項目, 又要告別幾個月來并肩作戰(zhàn),即將去北京發(fā)展的Manager zhu。準(zhǔn)備把做過的3GP/FLV/AVI格式整理一遍, 算是對幾個月辛苦成果的總結(jié), 也為后來者提供一些參考。

          1. 概述

          流行的文件格式背后都有大公司的支持。FLV得益于ADOBE公司推動的網(wǎng)絡(luò)視頻分享風(fēng)潮,而AVI則是MICROSOFT首創(chuàng)的RIFF即視頻和音頻交織在一起同步播放。 3GP/MP4是APPLE提出并得到ISO標(biāo)準(zhǔn)支持作為NOKIA等手機的默認(rèn)視頻格式。3GP是MP4格式在手機上的簡化版。MP4的codec組合一般是mpeg4 + AAC, 3GP則按版本演進分為3gpp r5(h.263/mpeg4 + AMR-NB/AMR WB), 3gpp r6(增加h.264視頻和aacPlus音頻支持)。

          有人會把MP4和MPEG4搞混, 前者是文件容器(container),后者是視頻編碼格式, 容器的作用是把壓縮編碼后的視頻和音頻數(shù)據(jù)盡可能緊湊的排布,就好像阿甘的巧克力盒子,你并不知道盒子里有什么, 但你可以按照既定的線索解開文件,取出你需要的數(shù)據(jù)。

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

          header: 標(biāo)記文件類型,音視頻碼流的基本屬性信息
          index: 索引表,每個frame有對應(yīng)的offset,size,timestamp.
          stream: 真正的音視頻流數(shù)據(jù)。
          任何文件格式都應(yīng)該有以上3要素。 當(dāng)然AVI視頻沒有索引也能播放,但不能拖放seek,需要自己重建索引。解析器(demuxer)根據(jù)frame_id找到其在文件中的offset和size,然后讀取出來解碼并播放。

          2. 文件格式分析

          下面來分析一下3GP/MP4文件格式。APPLE的格式有2個特點,1. 排布緊湊幾乎沒有冗余數(shù)據(jù)(AVI則有很多junk數(shù)據(jù)),2.音視頻碼流數(shù)據(jù)可隨意存放而不需按時間順序排布。

          3gp文件由一系列的box(atom)組成。每個box的結(jié)構(gòu)都是4字節(jié)的size,4字節(jié)的type, 還有一些data數(shù)據(jù)。用mp4info查看3gp文件的數(shù)據(jù)排布如下圖:

          如上圖, ftyp是表示文件的版本信息, mdat存放文字,音視頻等數(shù)據(jù)。你可能要問,這些音視頻數(shù)據(jù)怎么找到呢? 是通過moov box里的子box trak,里面存放著音視頻的屬性描述以及每個sample的索引。

          3. 關(guān)于sample atoms

             video和audio的碼流屬性(如視頻width/height,codec id, 音頻采樣率聲道數(shù)等)存放在stsd box里; 下面著重介紹MP4高效壓縮的精華:stts,stss,stsc,stsz,stco五個box。對比AVI的索引表是每個sample都有對應(yīng)的id,flag,offset,size,3GP的高效索引方式可以把AVI轉(zhuǎn)碼成同碼率的MP4后,文件size減小成原來的20-30%!

          1. stts atom(time to sample atoms,見quicktime format 文檔圖2-28 標(biāo)準(zhǔn)文檔點擊下載): 存儲了sample的時間信息。stts能讓很方便的根據(jù)timestamp找到對應(yīng)的sample,或者獲取某個sample對應(yīng)的timestamp. sttstable記錄著有相同duration的sample的數(shù)量count和時長dutation。

          2. stss atom(sync sample atom,見文檔圖2-31): 存儲了每個關(guān)鍵幀的sample id。 stss能讓你很方便的找到當(dāng)前幀最近的關(guān)鍵幀。

          3. stsc atom(sample to chunk atom): sample存放在chunk里為了允許優(yōu)化的數(shù)據(jù)讀取。比如音頻sample size都很小(amr-nb sample size為32字節(jié)), 每次讀取一個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.確定時間,相對于媒體時間坐標(biāo)系統(tǒng)

          2.檢查time-to-sample atom來確定給定時間的sample序號。

          3.檢查sample-to-chunk atom來發(fā)現(xiàn)對應(yīng)該sample的chunk。

          4.從chunk offset atom中提取該trunk的偏移量。

          5.利用sample size atom找到sample在trunk內(nèi)的偏移量和sample的大小。

          例如,如果要找第1秒的視頻數(shù)據(jù),過程如下:

          1. 第1秒的視頻數(shù)據(jù)相對于此電影的時間為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. 得到位置后,即可取出相應(yīng)數(shù)據(jù)進行解碼,播放

          ·       查找關(guān)鍵幀      

          查找過程與查找sample的過程非常類似,只是需要利用sync sample atom來確定key frame的sample序號

          確定給定時間的sample序號 
          檢查sync sample atom來發(fā)現(xiàn)這個sample序號之后的key frame 
          檢查sample-to-chunk atom來發(fā)現(xiàn)對應(yīng)該sample的chunk 
          從chunk offset atom中提取該trunk的偏移量 
          利用sample size atom找到sample在trunk內(nèi)的偏移量和sample的大小


          5 .3GP/MP4相關(guān)資源

               quicktime file format specification: 最權(quán)威的格式文檔 點擊下載
               開源的3GP/MP4解析器: ffmpeg, GPAC, helix, google opencore等 

          posted @ 2011-10-03 10:54 馬航 閱讀(696) | 評論 (0)編輯 收藏

          Android開發(fā)又將帶來新一輪熱潮,很多開發(fā)者都投入到這個浪潮中去了,創(chuàng)造了許許多多相當(dāng)優(yōu)秀的應(yīng)用。其中也有許許多多的開發(fā)者提供了應(yīng)用開 源項目,貢獻出他們的智慧和創(chuàng)造力。學(xué)習(xí)開源代碼是掌握技術(shù)的一個最佳方式。下面推薦幾個應(yīng)用開源項目,這些項目不僅提供了優(yōu)秀的創(chuàng)意,也可以直接掌握 Android內(nèi)核的接口使用:

          1、Android團隊提供的示例項目

          如果不是從學(xué)習(xí)Android SDK中提供的那些樣例代碼開始,可能沒有更好的方法來掌握在Android這個框架上開發(fā)。由Android的核心開發(fā)團隊提供了15個優(yōu)秀的示例項目,包含了游戲、圖像處理、時間顯示、開始菜單快捷方式等。
          地址:http://code.google.com/p/apps-for-android/

          2、 Remote Droid

          RemoteDroid是一個Android應(yīng)用,能夠讓用戶使用自己的無線網(wǎng)絡(luò)使用無線鍵盤、觸摸屏操作手機。這個項目為開發(fā)者提供了如網(wǎng)絡(luò)連接、觸摸屏手指運動等很好的樣例。
          地址:http://code.google.com/p/remotedroid/

          3、 TorProxy和Shadow

          TorProxy應(yīng)用實現(xiàn)了Android手機無線電電傳通訊(TOR),和Shadow應(yīng)用一起使用,可以使用手機匿名上網(wǎng)。從該項目源代碼中,可以掌握socket連接、管理cookie等方法。
          地址:http://www.cl.cam.ac.uk/research/dtg/code/svn/android-tor/

          4、 Android SMSPopup

          SMSPopup可以截獲短信內(nèi)容顯示在一個泡泡形狀的窗口中。從這個項目中可以掌握到如何使用內(nèi)置的短信SMS接口。
          地址:http://code.google.com/p/android-smspopup/

          5、 Standup Timer

          Standup Timer應(yīng)用用于控制站立會議時間,類似秒表倒計時,可以提醒每個人的講話時間已到,從而保證每個與會者使用時間一樣。從該項目的代碼中,可以學(xué)會如何使用時間函數(shù)。另外,這個項目的代碼是采用視圖view、模型model嚴(yán)格分離的設(shè)計思路。
          地址:http://github.com/jwood/standup-timer

          6、 Foursquare

          是Foursquare.com的一個客戶端應(yīng)用,該應(yīng)用主要分為兩個模塊:API(com.joelapenna.foursquare)和界面前端 (com.joelapenna.foursquared)兩部分。從該項目代碼中,可以學(xué)會如何同步、多線程、HTTP連接等技術(shù)。
          地址:http://code.google.com/p/foursquared/

          7、 Pedometer

          Pedometer應(yīng)用用于記錄你每天走路步數(shù)的。盡管記錄不一定精準(zhǔn),但是從這個項目中,可以學(xué)習(xí)幾個不同的技術(shù):加速器交互、語音更新、后臺運行服務(wù)等。
          地址:http://code.google.com/p/pedometer/

          8、 OpenSudoku-android

          OpenSudoku是一個簡單的九宮格數(shù)獨游戲。從代碼中可以學(xué)習(xí)到如何在視圖中顯示表格數(shù)據(jù),以及如何和一個網(wǎng)站交互等技術(shù)。
          地址:http://code.google.com/p/opensudoku-android/

          9、 ConnectBot

          ConnectBot是Android平臺的一個客戶端安全殼應(yīng)用。從該項目代碼中,可以學(xué)習(xí)到很多Android安全方面的內(nèi)容,這些是你在開發(fā)應(yīng)用時經(jīng)常需要考慮的安全問題。
          地址:http://code.google.com/p/connectbot/

          10、 WordPress的Android應(yīng)用

          當(dāng)然在最后不能不提WordPress的Android應(yīng)用了,這是WordPress官方開發(fā)團隊提供的一個項目。從代碼中可以學(xué)習(xí)到XMLRPC調(diào)用(當(dāng)然還有更多的優(yōu)秀內(nèi)容)。
          地址:http://android.svn.wordpress.org/trunk/

          posted @ 2011-10-03 09:47 馬航 閱讀(482) | 評論 (0)編輯 收藏

          導(dǎo)讀:對于Android開發(fā)者來說,成系列的技術(shù)文章對他們的技術(shù)成長幫助最大。如下是我們向您強烈推薦的主題為Android開發(fā)的第一個系列文章。

          文章皆來自CSDN網(wǎng)友maxleng的專欄,maxleng是名Android愛好者,長期從事嵌入式系統(tǒng)及手機軟件系統(tǒng)研究,自2010年4月起,在CSDN上先后發(fā)表28篇《Android核心分析》系列博文,收到網(wǎng)友們的極高評價。《Android核心分析》整理如下:

          1. 方法論探討之設(shè)計意圖

          2. 方法論探討之概念空間篇

          3. 手機之硬件形態(tài)

          4. 手機的軟件形態(tài)

          5. Android基本空間劃分

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

          7. Service詳解

          8. Android啟動過程詳解

          9. Zygote Service詳解

          10.Android GWES基本原理篇

          11.Android GWES消息系統(tǒng)篇

          12.Android核心分析之Android GEWS窗口管理基本架構(gòu)篇

          13.Android GWES窗口管理詳解

          14.Android GWES輸入系統(tǒng)篇

          15.Android GWES輸入系統(tǒng)之輸入路徑詳解

          16.Android電話系統(tǒng)-概述篇

          17.Android電話系統(tǒng)之Rild服務(wù)詳解

          18.Android電話系統(tǒng)之GSMCallTracker

          19.Android電話系統(tǒng)之RIL-Java

          20.Android應(yīng)用程序框架之無邊界設(shè)計意圖

          21.Android應(yīng)用框架之AndroidApplication

          22.Android應(yīng)用框架之Activity

          22.Andoird GDI之基本原理及其總體框架

          23.Android GDI之顯示緩沖管理

          24.Android GDI之共享緩沖區(qū)機制

          25.Android GDI之共享緩沖區(qū)機制

          26.Android GDI之SurfaceFlinger

          27.Android GDI之SurfaceFlinger之動態(tài)結(jié)構(gòu)示意圖

          28.Android GDI之Surface&Canvas

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

          posted @ 2011-09-23 15:34 馬航 閱讀(109) | 評論 (0)編輯 收藏

          新手學(xué)堂:嵌入式Linux操作系統(tǒng)學(xué)習(xí)規(guī)劃
          ARM+LINUX路線,主攻嵌入式Linux操作系統(tǒng)及其上應(yīng)用軟件開發(fā)目標(biāo):
          (1) 掌握主流嵌入式微處理器的結(jié)構(gòu)與原理(初步定為arm9)
          (2) 必須掌握一個嵌入式操作系統(tǒng) (初步定為uclinux或linux,版本待定)
          (3) 必須熟悉嵌入式軟件開發(fā)流程并至少做一個嵌入式軟件項目。
          從事嵌入式軟件開發(fā)的好處是:
          (1)目前國內(nèi)外這方面的人都很稀缺。這一領(lǐng)域入門門檻較高,所以非專業(yè)IT人員很難切入這一領(lǐng)域;另一方面,是因為這一領(lǐng)域較新,目前發(fā)展太快,大多數(shù)人無條件接觸。
          (2)與企業(yè)計算等應(yīng)用軟件不同,嵌入式領(lǐng)域人才的工作強度通常低一些(但收入不低)。
          (3)哪天若想創(chuàng)業(yè),搞自已的產(chǎn)品,嵌入式不像應(yīng)用軟件那樣容易被盜版。硬件設(shè)計一般都是請其它公司給訂做(這叫“貼牌”:OEM),都是通用的硬件,我們只管設(shè)計軟件就變成自己的產(chǎn)品了。
          (4)興趣所在,這是最主要的。
          從事嵌入式軟件開發(fā)的缺點是:
          (1)入門起點較高,所用到的技術(shù)往往都有一定難度,若軟硬件基礎(chǔ)不好,特別是操作系統(tǒng)級軟件功底不深,則可能不適于此行。
          (2)這方面的企業(yè)數(shù)量要遠(yuǎn)少于企業(yè)計算類企業(yè)。
          (3)有少數(shù)公司經(jīng)常要碩士以上的人搞嵌入式,主要是基于嵌入式的難度。但大多數(shù)公司也并無此要求,只要有經(jīng)驗即可。
          (4)平臺依托強,換平臺比較辛苦。
          興趣的由來:
          1、成功觀念不同,不虛度此生,就是我的成功。
          2、喜歡思考,挑戰(zhàn)邏輯思維。
          3、喜歡C
          C是一種能發(fā)揮思維極限的語言。關(guān)于C的精神的一些方面可以被概述成短句如下:
          相信程序員。
          不要阻止程序員做那些需要去做的。
          保持語言短小精干。
          一種方法做一個操作。
          使得它運行的夠快,盡管它并不能保證將是可移植的。
          4、喜歡底層開發(fā),討厭vb類開發(fā)工具(并不是說vb不好)。
          5、發(fā)展前景好,適合創(chuàng)業(yè),不想自己要死了的時候還是一個工程師。
          方法步驟:
          1、基礎(chǔ)知識:
          目的:能看懂硬件工作原理,但重點在嵌入式軟件,特別是操作系統(tǒng)級軟件,那將是我的優(yōu)勢。
          科目:數(shù)字電路、計算機組成原理、嵌入式微處理器結(jié)構(gòu)。
          匯編語言、C/C++、編譯原理、離散數(shù)學(xué)。
          數(shù)據(jù)結(jié)構(gòu)和算法、操作系統(tǒng)、軟件工程、網(wǎng)絡(luò)、數(shù)據(jù)庫。
          方法:雖科目眾多,但都是較簡單的基礎(chǔ),且大部分已掌握。不一定全學(xué),可根據(jù)需要選修。
          主攻書籍:the c++ programming language(一直沒時間讀)、數(shù)據(jù)結(jié)構(gòu)-C2。

          2、學(xué)習(xí)linux:
          目的:深入掌握linux系統(tǒng)。
          方法:使用linux—〉linxu系統(tǒng)編程開發(fā)—〉驅(qū)動開發(fā)和分析linux內(nèi)核。先看深,那主講原理。看幾遍后,看情景分析,對照深看,兩本交叉,深是綱,情是目。剖析則是0.11版,適合學(xué)習(xí)。最后深入代碼。
          主攻書籍:linux內(nèi)核完全剖析、unix環(huán)境高級編程、深入理解linux內(nèi)核、情景分析和源代。
          3、學(xué)習(xí)嵌入式linux:
          目的:掌握嵌入式處理器其及系統(tǒng)。
          方法:(1)嵌入式微處理器結(jié)構(gòu)與應(yīng)用:直接arm原理及匯編即可,不要重復(fù)x86。
          (2)嵌入式操作系統(tǒng)類:ucOS/II簡單,開源,可供入門。而后深入研究uClinux。
          (3)必須有塊開發(fā)板(arm9以上),有條件可參加培訓(xùn)(進步快,能認(rèn)識些朋友)。
          主攻書籍:毛德操的《嵌入式系統(tǒng)》及其他arm9手冊與arm匯編指令等。

          4、深入學(xué)習(xí):
          A、數(shù)字圖像壓縮技術(shù):主要是應(yīng)掌握MPEG、mp3等編解碼算法和技術(shù)。
          B、通信協(xié)議及編程技術(shù):TCP/IP協(xié)議、802.11,Bluetooth,GPRS、GSM、CDMA等。
          C、網(wǎng)絡(luò)與信息安全技術(shù):如加密技術(shù),數(shù)字證書CA等。
          D、DSP技術(shù):Digital Signal Process,DSP處理器通過硬件實現(xiàn)數(shù)字信號處理算法。
          說明:太多細(xì)節(jié)未說明,可根據(jù)實際情況調(diào)整。重點在于1、3,不必完全按照順序作。對于學(xué)習(xí)c++,理由是c++不只是一種語言,一種工具,她還是一種藝 術(shù),一種文化,一種哲學(xué)理念、但不是拿來炫耀得東西。對于linux內(nèi)核,學(xué)習(xí)編程,讀一些優(yōu)秀代碼也是有必要的。
          注意: 要學(xué)會舉一反多,有強大的基礎(chǔ),很多東西簡單看看就能會。想成為合格的程序員,前提是必須熟練至少一種編程語言,并具有良好的邏輯思維。一定要理論結(jié)合實踐。
          不要一味鉆研技術(shù),雖然擠出時間是很難做到的,但還是要留點余地去完善其他的愛好,比如宇宙,素描、機械、管理,心理學(xué)、游戲、科幻電影。還有一些不愿意做但必須要做的!
          技術(shù)是通過編程編程在編程編出來的。永遠(yuǎn)不要夢想一步登天,不要做浮躁的人,不要覺得路途漫上。而是要編程編程在編程,完了在編程,在編程!等機會來了在創(chuàng)業(yè)(不要相信有奇跡發(fā)生,盲目創(chuàng)業(yè)很難成功,即便成功了發(fā)展空間也不一定很大)。
          嵌入式書籍推薦

          Linux基礎(chǔ)
          1、《Linux與Unix Shell 編程指南》
          C語言基礎(chǔ)
          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應(yīng)用程序開發(fā)詳解》
          Linux內(nèi)核
          1、《深入理解Linux內(nèi)核》(第三版)
          2、《Linux內(nèi)核源代碼情景分析》毛德操 胡希明著
          研發(fā)方向
          1、《UNIX Network Programming》(UNP)
          2、《TCP/IP詳解》
          3、《Linux內(nèi)核編程》
          4、《Linux設(shè)備驅(qū)動開發(fā)》(LDD)

          5、《Linux高級程序設(shè)計》 楊宗德著
          硬件基礎(chǔ)
          1、《ARM體系結(jié)構(gòu)與編程》杜春雷著
          2、S3C2410 Datasheet
          英語基礎(chǔ)
          1、《計算機與通信專業(yè)英語》
          系統(tǒng)教程
          1、《嵌入式系統(tǒng)――體系結(jié)構(gòu)、編程與設(shè)計》
          2、《嵌入式系統(tǒng)――采用公開源代碼和StrongARM/Xscale處理器》毛德操 胡希明著
          3、《Building Embedded Linux Systems》

          4、《嵌入式ARM系統(tǒng)原理與實例開發(fā)》 楊宗德著
          理論基礎(chǔ)
          1、《算法導(dǎo)論》
          2、《數(shù)據(jù)結(jié)構(gòu)(C語言版)》
          3、《計算機組織與體系結(jié)構(gòu)?性能分析》
          4、《深入理解計算機系統(tǒng)》【美】Randal E. Bryant David O’Hallaron著
          5、《操作系統(tǒng):精髓與設(shè)計原理》
          6、《編譯原理》
          7、《數(shù)據(jù)通信與計算機網(wǎng)絡(luò)》
          8、《數(shù)據(jù)壓縮原理與應(yīng)用》

          C語言書籍推薦

          1. The C programming language 《C程序設(shè)計語言》
          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 編寫優(yōu)質(zhì)無錯C程序秘訣》
          6. Programming Embedded Systems in C and C++ 《嵌入式系統(tǒng)編程》
          7.《C語言嵌入式系統(tǒng)編程修煉》
          8.《高質(zhì)量C++/C編程指南》林銳

          盡可能多的編碼,要學(xué)好C,不能只注重C本身。算法,架構(gòu)方式等都很重要。

          posted @ 2011-09-22 14:25 馬航 閱讀(107) | 評論 (0)編輯 收藏

          網(wǎng)上搜了N多解決方法,但是很多將log級別的,用法的,更多的是如何在logcat中設(shè)置filter進行l(wèi)og的過濾與查看,但是我遇到的問題是,模擬器怎么著都OK,但真機、手機進行開發(fā)調(diào)試的時候卻看不到log信息,這是很惱人的事情(畢竟模擬器跑起來太慢了)。

          剛開始沒有查到好的方法,就用try catch把exception打到一個alertdialog中,但是這樣只能看個大概,繞這個圈子沒用,最后還是在eoe的論壇上看到了解決辦法,恐怕原因是rom本身沒有打開log的開關(guān)

          問題表現(xiàn):連接手機與電腦后,驅(qū)動安裝正確,USB調(diào)試模式打開,在DDMS中可以看到device及其進程的信息,但是logcat中就是沒有信息輸出
          問題原因:一些rom默認(rèn)關(guān)閉logcat
          問題說明:ddms中設(shè)備名字顯示為問號不影響,即adb get-serialno顯示為問號不影響.
          解決方法:
          1.需要root權(quán)限(部分rom不需要)
          2.打開logcat,并設(shè)置level,執(zhí)行命令如下(android 升級之后 adb 在 platform-tools中,不在tools中)
          adb shell
          echo 1 > /sys/kernel/logger/log_main/enable
          說明:將1寫入日志開關(guān)文件,1為開,0為關(guān)
          echo 2 >/sys/kernel/logger/log_main/priority
          說明:將代表level的2寫入優(yōu)先級文件
          3.重啟adb,如果使用eclipse,先關(guān)閉eclipse,再重啟adb,再啟動eclipse
          adb kill-server
          adb start-server
          4.此時logcat應(yīng)該可以工作了,如果仍舊不工作,則更新adb
          android update adb
          5.重復(fù)第三步,此時logcat應(yīng)該可以工作了,如果仍舊不工作,找到個人主目錄下的android目錄,如C:\Documents and Settings\Administrator\.android
          找到這個目錄下的adb_usb.ini文件,其內(nèi)容默認(rèn)只有三行,全為注釋,在后面添加一行,內(nèi)容為0x12d1
          6.重復(fù)第三步,此時logcat應(yīng)該可以工作了

          轉(zhuǎn)自:http://www.gobbin.cn/2011/02/16/android-phone-logcat/

          posted @ 2011-09-06 16:10 馬航 閱讀(11242) | 評論 (0)編輯 收藏

          1.          app2sd是什么
          app2sd
          就是把應(yīng)用程序放在SD卡上。有些android手機的用戶數(shù)據(jù)分區(qū)(userdata)比較小(比如G1只有76M),dalvikcache和用戶數(shù)據(jù)就占了大半,使得安裝了幾個軟件后就沒有空間了。為了安裝更多軟件,在SD卡上劃出部分空間用于存在新軟件和數(shù)據(jù),使我們的手機可以使用更多軟件。

          2.          原理
          一般情況下都SD卡都默認(rèn)分成一個windows可識別的分區(qū)(FAT)。因為有linux系統(tǒng)的權(quán)限問題,為了讓它可以存放軟件,需要把SD卡的一部分劃分成Linux的使用的ext2文件系統(tǒng),然后在開機時把此分區(qū)掛載到某處,并通過鏈接的方法,讓系統(tǒng)從SD卡中讀取軟件

          3.          實現(xiàn)

          1)         SD卡分區(qū)

          a)          使用Linux系統(tǒng)中的工具fdisk,它是命令行工具,很快很簡單

          b)         Windows下的圖形化工具
          具體步驟見http://www.3haoweb.cn/a/digital/mobile/2010/0609/2273.html

          2)         修改boot.img使得新分區(qū)在啟動時被自動掛載

          a)          說明:

                                                 i.              也可以從網(wǎng)上下載帶app2sd功能的update.zip包,升級整個系統(tǒng),但是那樣的話還要備份設(shè)置、數(shù)據(jù)、軟件太麻煩,所以我選擇修改我手機中自帶的boot.img,以最小的修改來實現(xiàn)功能

                                                ii.              修改boot.img中的initrc(系統(tǒng)啟動時運行的腳本,自動掛載SD卡的ext2分區(qū))

          b)         boot.img是什么
          boot.img
          包括了2K的文件頭,后面緊跟著是用gzip壓縮過的內(nèi)核,再后面是一個ramdisk內(nèi)存盤(系統(tǒng)基本目錄結(jié)構(gòu)的鏡像檔),然后緊跟著第二階段的載入器程序(這個載入器程序是可選的,在某些映像中或許沒有這部分)

          c)          修改本機的boot.img

                                                 i.              使用nandroid備份數(shù)據(jù)
          任何對系統(tǒng)的修改都要先備份系統(tǒng)數(shù)據(jù)

                                                ii.              從機器中取出當(dāng)前的boot.img
          $ export PATH=$PATH:$ANDROID_DIR/out/host/linux-x86/bin/
          $ adb shell
          # cat /proc/mtd/
          查看boot對應(yīng)的mtdx,一般是mtd2
          # cat /dev/mtd/mtd2 > /sdcard/boot.img
          假設(shè)boot對應(yīng)mtd2
          # mkdir /system/sd1
          建立目錄以掛載分區(qū)
          # exit
          $ adb pull /sdcard/boot.img ./                 
          復(fù)制到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                  
          解包,解出內(nèi)核和ramdisk包兩部分
          $ mkdir ramdisk; cd ramdisk
          $ gzip -dc ../boot.img-ramdisk.gz |cpio -i

                                              iv.              修改啟動腳本
          $ vi init.rc 
          如果是亂碼,請使用reset命令恢復(fù)一下
          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 (
          可能找錯沒空間,沒關(guān)系)
          # flash_image boot /sdcard/boot_new.im

                                             vii.              驗證是否成功
          然后重啟手機即可,重啟后用以下命令看一下是否分區(qū)是否被掛載
          $ adb shell
          $ df 
          如果看到/system/sd1項就成功了

          3)         做鏈接,使系統(tǒng)從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數(shù)據(jù)庫的關(guān)系,可能桌面上看不到原來的那些應(yīng)用了,不過主菜單里是有的,再建一遍快捷方式即可

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

          posted @ 2011-09-04 11:15 馬航 閱讀(1028) | 評論 (0)編輯 收藏

            一、 說明

          1.        下載編譯最基本的android源碼,只能在模擬器上使用,無法在真機上使用(不能生成boot.img)。這是因為沒有編譯相關(guān)機型的內(nèi)核和硬件驅(qū)動。以下介紹的是用android源碼編譯出對應(yīng)HTC G1的版本,和燒寫的過程。編譯生成的版本除相機以外,其它功能均正常,穩(wěn)定性不錯,也很順暢。

          2.        以下步驟都經(jīng)過驗證(只驗證G1手機),實驗系統(tǒng)ubuntu8.04,實驗日期2010712

          3.        關(guān)鍵字: 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  #
          新建下載配置文件,用以下載內(nèi)核,編輯內(nèi)容如下

          注意:其中msm是高通芯片組,path指明下載到源碼目錄中的位置,name指明git上的項目名
          $ repo sync           # 開始下載代碼,此時需要等待較長時間

          3.        編譯內(nèi)核及無線網(wǎng)絡(luò)驅(qū)動
          $ cd $ANDROID/kernel
          $ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- msm_defconfig          #
          設(shè)定默認(rèn)的msm配置

          $ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- #
          編譯內(nèi)核
          $ 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       
          #編譯無線網(wǎng)絡(luò)驅(qū)動
          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 #
          加入如下內(nèi)容,以支持中文

          CUSTOM_LOCALES:=zh_CN
          $ source build/envsetup.sh
          $ lunch full_dream-userdebug        
          #指定編譯機型
          $ make -j2

          6.        以打補丁的方式加入不提供源碼的庫
          此時的系統(tǒng)可以被燒寫,但電話音樂等基本功能均不正常,需要從系統(tǒng)或其它升級包中提取出源碼中不包含的庫,以支持相應(yīng)功能。
          HTC網(wǎng)站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  
          此時會提示有幾個庫找不后,后面有對應(yīng)解決辦法

          $ cd $ANDROID
          $ vi vendor/htc/dream/device_dream-vendor-blobs.mk
          刪除包含以下內(nèi)容的行,這是由于在update.zip中找不到相應(yīng)庫,為編譯通過,選去掉它們
          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
          #網(wǎng)卡驅(qū)動
          $ make snod        
          重新生成system.img

          三、 把編譯好的軟件燒寫到手機

          usb線連接手機到電腦,按home+power鍵將手機啟動到工程模式,按back鍵準(zhǔn)備燒寫
          $ 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

          燒寫系統(tǒng)后第一次啟動手機需要幾分鐘,請耐心等

          四、 修改

          1.        安裝中文字體(可以在燒寫前加入,加在此處用以說明在啟動后修改系統(tǒng)的方法)
          $ adb shell
          # su            
          取得root權(quán)限

          # mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system    
          使system分區(qū)可寫
          # chmod 777 /system/fonts     
          使某個目錄有寫權(quán)限
          # exit
          # exit
          $ adb push frameworks/base/data/fonts/DroidSansFallback.ttf /system/fonts/         
          加中文字體
          $ adb reboot

          2.        支持GPRS上網(wǎng)
          添加APN即可上網(wǎng)和發(fā)彩信,詳見http://www.andbeta.com/Basics/678.html

          3.        設(shè)置帳戶
          添加帳戶時,服務(wù)器填寫m.google.com

          五、 參考

          1.        刷寫部分未詳細(xì)描述,具體請參考文檔
          http://xy0811.spaces.live.com/blog/cns!F8AECD2A067A6B17!1452.entry

          2.        源碼編譯部分未詳細(xì)描述,具體請參考文檔
          http://xy0811.spaces.live.com/blog/cns!F8AECD2A067A6B17!1364.entry


          轉(zhuǎn)自:http://blog.csdn.net/xieyan0811/article/details/5931573

          posted @ 2011-09-04 11:03 馬航 閱讀(409) | 評論 (0)編輯 收藏

          在android的設(shè)計中,谷歌設(shè)計了一套專門為嵌入式設(shè)備使用的bionic C庫,以替換原有的GUN Libc,這個精簡的bionic庫據(jù)說只有200多K,所以如果只想使用這個精簡的C庫像在linux下一樣 開發(fā)C程序,基本是不可能的。當(dāng)然如果只想讓其在shell中運行還是可以做到的。

          因為編譯完的目標(biāo)程序是在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選項在這里是必須的,否則會出現(xiàn)”not found”的錯誤。

          然后就可以把編譯好的hello傳到手機上運行了。不過這里有個前提條件,要求android機器必須是root過的,好像簡單的z4root還不行,必須使用更徹底的root方法,關(guān)于如何root,這里就不再贅述了,可以參考相關(guān)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 馬航 閱讀(240) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 孟州市| 济南市| 张家川| 伊通| 宣化县| 社旗县| 佛坪县| 咸阳市| 河南省| 洪湖市| 二连浩特市| 阿巴嘎旗| 平昌县| 漳州市| 留坝县| 大安市| 横峰县| 扶沟县| 农安县| 左云县| 洛川县| 拜城县| 汾阳市| 福安市| 临湘市| 阳信县| 思南县| 邯郸县| 本溪市| 紫云| 云林县| 拜泉县| 冀州市| 顺义区| 临漳县| 扶沟县| 大邑县| 黄平县| 静海县| 宁化县| 齐河县|