盡管支持移動信息設(shè)備規(guī)范MIDP 1.0的手機大量地出現(xiàn)在市面上,不管是Nokia未來發(fā)售的各款手機,還是Motorola T720、V60i,Sony Ericsson P800等手機,每一款都支持MIDP 1.0。但是,Sun也沒忘記繼續(xù)和各家手機廠商制定下一代Java手機的規(guī)格—MIDP 2.0。

          何謂應(yīng)用程序管理員


          應(yīng)用程序管理員在規(guī)格中也稱作Application Management Software。這是一個用來執(zhí)行J2ME應(yīng)用程序的程序,它負(fù)責(zé)管理該裝置上所有的J2ME應(yīng)用程序。

          應(yīng)用程序管理員的設(shè)計方式會隨著平臺的不同而不同,但是大致上可以分成兩種方式:

          1. 在背后運作,使用者不知道應(yīng)用程序管理員的存在。這種類型的應(yīng)用程序管理員概念如圖1所示。



          圖1 背后運作的應(yīng)用程序管理員


          背后運作的應(yīng)用程序管理員的設(shè)計方式,使得一般的J2ME程序看起來和應(yīng)用程序管理員一樣,即使實際上應(yīng)用程序管理員在背后運作,使用者也很難感受到。這種設(shè)計可以在MIDP for Palm之中看到,Java HQ就是這樣的東西,如圖2所示。



          圖2 Java HQ


          但是,如果是程序開發(fā)者一旦安裝了Developer.prc,仍然可以透過Java HQ之中的Developer Preference里的MIDlets按鈕來觀察整個系統(tǒng)之中所安裝的每一個Java應(yīng)用程序。

          2. 一個單一的進入點,使用者必須先進入應(yīng)用程序管理員,然后才能啟動個別的Java應(yīng)用程序。這種類型的應(yīng)用程序管理員概念如圖3所示。



          圖3 單一進入點的應(yīng)用程序管理員


          這種應(yīng)用程序管理員設(shè)計可以在Motorola A388、Nokia 9210、Nokia 7650的手機上看到。

          JAD與JAR


          一個完整的MIDP應(yīng)用程序是由一個JAD文件(純文字文件)與JAR(ZIP壓縮檔)所組成。JAD與JAR之間的關(guān)系可以用圖4簡單描述。之所以有這樣的設(shè)計,主要為了下面兩個原因:



          圖4 JAD與JAR關(guān)系圖


          1. 網(wǎng)絡(luò)傳輸費用;

          2. 安全性。

          MIDP 2.0之后,為了保護JAR不受竄改,同時也讓安裝程序的人可以確定MIDlet的來源,所以特別增加了安全設(shè)計如圖5。



          圖5 安全設(shè)計JAD圖


          其中,MIDlet-Jar-RAS-SHA1屬性值為經(jīng)過base64編碼的執(zhí)行文件數(shù)字簽章。而MIDlet-Certificate-<n>-<m>屬性值為安全證明。在MIDlet安裝前,應(yīng)用程序管理員會使用安全證明來驗證公開金鑰的可靠性,然后再使用此公開金鑰解開數(shù)字簽章,確認(rèn)此執(zhí)行檔的來源并確定沒有受到非法竄改。

          最后請注意,并非每種裝置在安裝時都要求同時有JAD與JAR,有些裝置只需要JAR即可。不過,有JAD和沒有JAD的J2ME應(yīng)用程序在安全性上是有差異的。

          MIDP執(zhí)行環(huán)境


          根據(jù)MIDP規(guī)格,所謂MIDP執(zhí)行環(huán)境(MIDP Execution Environment)指的是下面幾項所構(gòu)成的集合:

          1. 實作CLDC中所定義的類別函數(shù)庫的類別檔(以Java撰寫)及原生程序(Native code,以C撰寫)。MIDlet Suite里不能有與CLDC類別函數(shù)庫同樣名稱的類別;

          2. 實作MIDP中所定義的類別函數(shù)庫的類別檔(以Java撰寫)及原生程序(Native code,以C撰寫)。MIDlet Suite里不能有與MIDP類別函數(shù)庫同樣名稱的類別;

          3. 所有來自同一個JAR檔中的類別檔。包括設(shè)計者自己所撰寫的類別、其它的JSR(例如Profile或Optional Package),或其它開放的函數(shù)庫(例如kXML或kSOAP);

          4. 所有來自同一個JAR檔之中的非類別檔(即資源文件),另外,記錄管理系統(tǒng)(RMS,MIDP版的數(shù)據(jù)庫管理系統(tǒng))也是可共享的資源之一;

          5. 權(quán)限確認(rèn)與連結(jié)外部資源;

          6. 描述文件與清單文件的內(nèi)容。

          以上這幾點構(gòu)成所謂的MIDP執(zhí)行環(huán)境。應(yīng)用程序管理員會保證這些資源都可以在執(zhí)行時期供MIDlet存取。而且,位于同一個MIDlet Suite的MIDlet會共享同一組MIDP執(zhí)行環(huán)境,并且可以彼此互動。MIDlet可以調(diào)用CLDC的類別函數(shù)庫,也可以調(diào)用MIDP的類別函數(shù)庫,如圖6所示。



          圖6 MIDlet執(zhí)行環(huán)境圖


          只有存取標(biāo)準(zhǔn)CLDC與MIDP函數(shù)庫的MIDlet Suite才可以跨平臺。通常手機廠商會針對自己的裝置開發(fā)專屬的API,例如Nokia的UI API、SMS API、Camera API。一旦程序使用了這些專屬API,那么除非其它廠商也在其虛擬機器之中實作這些API,否則很難達(dá)到跨平臺的目的。

          功能與資源


          位于JAR檔之中的類別檔可以被同一個JAR檔之中的MIDlet所使用。同一個JAR檔里的資源檔可以透過java.lang.Class.getResourceAsStream()來存取。描述檔的內(nèi)容則可以透過javax.microedition.mdilet. MIDlet.getAppProperty()取得,如圖7所示。



          圖7 JAR功能資源圖
          使用getResourceAsStream()時,必須給定一個URL,由于我們要存取JAR內(nèi)部的資源,如果使用「/」作為開頭,代表絕對路徑,「/」代表JAR文件之中的根目錄。如果沒有使用「/」,則視為相對路徑,要看調(diào)用getResourceAsStream()類別的所在路徑而定,這樣容易造成混淆。所以請盡量使用「/」作為開頭的絕對路徑。

          MIDlet的程序結(jié)構(gòu)


          如果曾經(jīng)撰寫過Java Applet或Java Servlet,就知道制作Java Applet,必須繼承自java.applet.Applet這個類別;制作Java Servlet,則程序必須繼承自javax.servlet. http.HttpServlet這個類別。同理,要撰寫能夠在手機上執(zhí)行的Java MIDlet必須要繼承自javax.microedition.midlet. MIDlet類別,如圖8所示。



          圖8 MIDlet繼承體系圖


          javax.microedition.midlet.MIDlet類別中定義了三個抽象方法,它們分別是:startApp() ==> 至運作狀態(tài);pauseApp() ==> 至停止?fàn)顟B(tài);destoryApp() ==> 至消滅狀態(tài)。應(yīng)用程序管理員就是透過這三個抽象方法來控制MIDlet的生命周期。因此,所有的MIDlet都必須實現(xiàn)這三個方法,才保證能正常運作。因此,一個MIDlet的程序外殼至少要如下:

          import javax.microedition.midlet.*;
                      public class HelloMIDlet extends MIDlet
                      {
                      public HelloMIDlet()
                      {
                      //建構(gòu)式
                      }
                      public void startApp()
                      {
                      }
                      public void pauseApp()
                      {
                      }
                      public void destroyApp(boolean unconditional)
                      {
                      }
                      }


          根據(jù)MIDP規(guī)格,MIDlet中不應(yīng)該有“public static void main(Straing[] args)”這個方法。如果有則應(yīng)用程序管理員會忽略不管。

          一旦MIDlet被JAM載入之后,首先會先呼叫MIDlet中沒有參數(shù)的建構(gòu)式以進行初始化的工作。如果熟悉Java語法一定知道Java語言的一些特性,就是如果沒有在程序中加入任何建構(gòu)式,編譯器會自動幫助加入一個預(yù)設(shè)建構(gòu)式。但是如果已經(jīng)撰寫了自己的建構(gòu)式(有任何參數(shù)),那么編譯器將不會自動幫助加上預(yù)設(shè)建構(gòu)式。因此,如果有必要撰寫有參數(shù)的建構(gòu)式,別忘了再手動加上一個沒有參數(shù)的建構(gòu)式,否則MIDlet將無法正確地初始化。

          MIDlet的起始行為


          當(dāng)MIDlet被應(yīng)用程序管理員產(chǎn)生之后,MIDlet就開始運作,程序設(shè)計師應(yīng)該做的事情如圖9所示。



          圖9 MIDlet起始行為圖


          我們會使用Display.getDisplay(this)來取得代表該MIDlet顯示屏的Display對象。從應(yīng)用程序管理員呼叫startApp()到MIDlet結(jié)束運作這段時間之內(nèi),不管何時呼叫Display.getDisplay(this),取得的都是同一份Display對象的參考。

          要設(shè)定顯示在屏幕上的畫面,會使用Display對象的參考,并調(diào)用其setCurrent()方法,即display.setCurrent( Displayable類別的子類別實體)。因此一個可以運作的MIDlet程序,至少如HelloMIDlet.java,代碼為:

          import javax.microedition.midlet.*;
                      import javax.microedition.lcdui.*;
                      public class HelloMIDlet extends MIDlet
                      {
                      private Display display;
                      public HelloMIDlet()
                      {
                      display = Display.getDisplay(this);
                      }
                      public void startApp()
                      {
                      Form t = new Form("畫面");
                      display.setCurrent(t);
                      }
                      public void pauseApp()
                      {
                      }
                      public void destroyApp(boolean unconditional)
                      {
                      }
                      }


          注意:根據(jù)規(guī)格MIDlet只能由應(yīng)用程序管理員產(chǎn)生,我們不能自己在程序里生成其它的MIDlet,并呼叫其生命周期函數(shù),這樣做將引發(fā)SecurityException異常。

          MIDlet的生命周期


          前面簡單地敘述了應(yīng)該如何撰寫一個MIDlet的輪廓。但事實上,MIDlet的運作稍微復(fù)雜一點。所以接下來要仔細(xì)探討MIDlet的運作細(xì)節(jié),也就是MIDlet的生命周期。

          當(dāng)MIDlet被應(yīng)用程序管理員成功地初始化之后,就開始展開了它的生命周期。圖10描述了一個MIDlet的生命周期。MIDlet的生命周期完全由應(yīng)用程序管理員控制,也就是說,當(dāng)MIDlet要從一個狀態(tài)變成另外一個狀態(tài)時,應(yīng)用程序管理員會呼叫對應(yīng)的回呼函數(shù)(Call Back,也就是MIDlet類別定義的那三個抽象方法)。MIDlet基本上有三種狀態(tài),分別是停止?fàn)顟B(tài)(Paused)、啟動狀態(tài)(Active)及毀滅狀態(tài)(Destroyed)。MIDlet開始時一定是先進入停止?fàn)顟B(tài),然后應(yīng)用程序管理員再將它轉(zhuǎn)換成啟動狀態(tài),然后調(diào)用startApp(),見圖10。只有當(dāng)應(yīng)用程序管理員認(rèn)為MIDlet的狀態(tài)必須改變時,才會呼叫圖中的相關(guān)函數(shù)。



          圖10 MIDlet生命周期圖
          以Active狀態(tài)來說,MIDlet先進入運作狀態(tài),然后才調(diào)用startApp(),而MIDlet會先調(diào)用pauseApp()或destroyApp(),然后再進入停止?fàn)顟B(tài)和毀滅狀態(tài),這就是之所以Active沒有被動式(字尾沒有加ed),而Paused和Destroyed都是被動式(字尾有加ed)的真正涵義。

          如果MIDlet自己調(diào)用這些函數(shù),通常不會發(fā)生錯誤(除非程序本身有邏輯上的錯誤),但是也不會造成狀態(tài)的轉(zhuǎn)換,只是一個單純的函數(shù)呼叫而已。如果MIDlet在狀態(tài)轉(zhuǎn)換回呼函數(shù)執(zhí)行時發(fā)生錯誤,那么就應(yīng)該丟出MIDletStateChange Exception異常,讓應(yīng)用程序管理員知道該如何處理。

          請使用下列程序HelloMIDlet.java代碼來驗證MIDlet的生命周期:

          import javax.microedition.midlet.*;
                      import javax.microedition.lcdui.*;
                      public class HelloMIDlet extends MIDlet
                      {
                      private Display display;
                      public HelloMIDlet()
                      {
                      System.out.println("Constructor") ;
                      display = Display.getDisplay(this);
                      }
                      public void startApp()
                      {
                      System.out.println("startApp Called") ;
                      Form t = new Form("畫面");
                      display.setCurrent(t);
                      }
                      public void pauseApp()
                      {
                      System.out.println("pauseApp Called") ;
                      }
                      public void destroyApp(boolean unconditional)
                      {
                      System.out.println("destroyApp Called :" + unconditional) ;
                      }
                      }


          執(zhí)行結(jié)果時我們會在訊息顯示窗口看到:

          Constructor
                      startApp Called


          此結(jié)果表示建構(gòu)式先被呼叫,然后startApp()才會呼叫。

          這時如果我們按下強制關(guān)閉應(yīng)用程序的按鈕 ,屏幕上就會出現(xiàn):

          destroyApp Called :true


          這代表當(dāng)我們使用裝置上的按鈕強制關(guān)閉MIDlet時,應(yīng)用程序管理員會調(diào)用destroyApp(),并傳入true作為參數(shù)。因此,撰寫程序時,可以假定destroyApp()傳入true時,是硬件強制關(guān)閉MIDlet的情形。

          從圖10中可以看出,startApp()很可能不只是被呼叫一次,而是每次從停止?fàn)顟B(tài)重新回到運作狀態(tài)的時候都會被應(yīng)用程序管理員調(diào)用,所以只需要被初始化一次的動作就不適合放在startApp()之中,請改用建構(gòu)式做初始化動作。如果startApp()丟出MIDletStateChangeException或RuntimeException或兩者的子類別,那么會立刻進入毀滅狀態(tài),而且系統(tǒng)會自動調(diào)用destroyApp(true)。

          由于規(guī)格告訴我們,startApp()的執(zhí)行時間應(yīng)該盡可能短。如果程序在執(zhí)行時,發(fā)生的錯誤是可以稍候就解決的(很可能是系統(tǒng)資源暫時不足),那么程序設(shè)計師就該直接丟出MIDletStateChangeException。攔截之后,再調(diào)用notifyPaused(),稍待一會再藉由異步事件呼叫resumeRequest(),重新試看看。如果發(fā)生錯誤即使稍待一會也無法解決,那么程序設(shè)計師就應(yīng)該直接調(diào)用notifyDestroyed()來結(jié)束程序。參見如下代碼:

          import javax.microedition.midlet.*;
                      import javax.microedition.lcdui.*;
                      public class HelloMIDlet extends MIDlet
                      {
                      private Display display;
                      public HelloMIDlet()
                      {
                      System.out.println("Constructor") ;
                      display = Display.getDisplay(this);
                      }
                      public void startApp()
                      {
                      System.out.println("startApp Called") ;
                      Form t = new Form("畫面");
                      display.setCurrent(t);
                      throw new RuntimeException() ;
                      }
                      /*
                      public void startApp() throws MIDletStateChangeException
                      {
                      System.out.println("startApp Called") ;
                      Form t = new Form("畫面");
                      display.setCurrent(t);
                      throw new MIDletStateChangeException() ;
                      }
                      */
                      public void pauseApp()
                      {
                      System.out.println("pauseApp Called") ;
                      }
                      public void destroyApp(boolean unconditional)
                      {
                      System.out.println("destroyApp Called :" + unconditional) ;
                      }
                      }


          執(zhí)行結(jié)果如下:

          Constructor
                      startApp Called
                      startApp threw an Exception
                      java.lang.RuntimeException
                      java.lang.RuntimeException
                      at HelloMIDlet.startApp(+33)
                      at javax.microedition.midlet.MIDletProxy.startApp(+7)
                      at com.sun.midp.midlet.Scheduler.schedule(+266)
                      at com.sun.midp.main.Main.runLocalClass(+28)
                      at com.sun.midp.main.Main.main(+88)
                      destroyApp Called :true


          一般來說,應(yīng)用程序管理員會因為某些狀況必須請MIDlet停止運作,例如手機突然來電、鬧鈴響了或者使用者切換到其它程序執(zhí)行。在這些情況下,為了避免MIDlet占用太多系統(tǒng)資源,應(yīng)用程序管理員就會調(diào)用該MIDlet的pauseApp()。這樣程序設(shè)計師應(yīng)該在pauseApp()之中適時釋放一些非必需的資源,等到回到運作狀態(tài)時,應(yīng)用程序管理員會重新呼叫startApp(),這時再將這些之前被pauseApp()釋放的資源重新找回來。
          當(dāng)MIDlet進入停止?fàn)顟B(tài)時,不應(yīng)該使用任何資源。如果應(yīng)用程序管理員調(diào)用pauseApp()時產(chǎn)生例外情形,MIDlet就應(yīng)該立刻進入毀滅狀態(tài)。

          同樣的情況也發(fā)生在destroyApp()。通常此方法被調(diào)用的時候,代表MIDlet要被關(guān)閉了,所以程序設(shè)計師應(yīng)該在這里釋放自己所配置的資源。只要MIDlet進入了毀滅狀態(tài),就無法再回頭。如果是系統(tǒng)自己調(diào)用destroyApp(),那么在destroyApp()執(zhí)行時萬一發(fā)生例外,這些例外將被忽略,MIDlet一樣會被關(guān)閉。根據(jù)規(guī)格,我們不能在MIDlet之中直接呼叫System.exit()或Runtime.exit()來結(jié)束程序的執(zhí)行。如果這樣做,就會引發(fā)java.lang.SecurityException異常。

          MIDlet管理自己的生命周期


          除了由應(yīng)用程序管理員來控制MIDlet的生命周期之外,MIDlet本身也可以決定自己的狀態(tài),但不是自己改變狀態(tài),而是MIDlet先呼叫上述相對應(yīng)的狀態(tài)改變函數(shù)。這些函數(shù)會發(fā)出訊息通知應(yīng)用程序管理員,請它來幫助改變MIDlet的狀態(tài),但是決定權(quán)在應(yīng)用程序管理員,不保證一定可行。狀態(tài)改變函數(shù)如圖11所示:



          圖11 MIDlet狀態(tài)改變函式圖


          對照以上這兩張與狀態(tài)有關(guān)的圖,舉個例子大家可能會比較清楚:假設(shè)今天如果是MIDlet主動要將MIDlet的狀態(tài)由運作狀態(tài)變成停止?fàn)顟B(tài),那么直接呼叫pauseApp()函數(shù)只會執(zhí)行pauseApp()之中的程序代碼,而無法改變MIDlet的狀態(tài)。MIDlet必須呼叫notifyPaused()以通知應(yīng)用程序管理員,應(yīng)用程序管理員收到通知之后,才會讓MIDlet進入停止?fàn)顟B(tài)。

          不過,由MIDlet調(diào)用notifyPaused()與應(yīng)用程序管理員主動要求停止,兩者之間是有所差別的。它們主要在于應(yīng)用程序管理員主動要求停止時,pauseApp()會被呼叫,而由MIDlet調(diào)用notifyPaused(),pauseApp()不會被調(diào)用。但是兩者都會讓MIDlet進入停止?fàn)顟B(tài)。所以在MIDlet調(diào)用notifyPaused()之前,最好自己也先調(diào)用pauseApp()比較適當(dāng)。

          同樣的情況也發(fā)生在notifyDestroyed()與destroyApp()。除非是系統(tǒng)強制關(guān)閉MIDlet,否則最好MIDlet先呼叫destroyApp(),然后再呼叫notifyDestroyed()。請應(yīng)用程序管理員幫助將MIDlet轉(zhuǎn)換到毀滅狀態(tài),最后結(jié)束MIDlet的運作。單單MIDlet自己呼叫destroyApp()是沒有用的。

          destroyApp()有個布爾值作為參數(shù),根據(jù)MIDP的規(guī)格,如果傳入true,那么MIDlet不管如何應(yīng)該無條件釋放所有資源,然后讓應(yīng)用程序管理員結(jié)束MIDlet的運作,這是系統(tǒng)或硬件強制關(guān)閉MIDlet的情形。如果使用者調(diào)用notifyDestroyed()來結(jié)束MIDlet,那么在調(diào)用destroyApp()時,最好傳入false,代表這并非系統(tǒng)或硬件強制關(guān)閉,這時如果MIDlet不希望結(jié)束執(zhí)行,那么它可以藉由丟出MIDletStateChangeException異常告知呼叫它的人:“我還不想被消滅,請待會再來。”

          這里可以看出startApp()、pauseApp()及destroyApp()并非控制MIDlet生命周期的函數(shù),它們只是一個提供初始化資源、釋放資源的地方而已。

          根據(jù)MIDP 規(guī)格書中所說,即使MIDlet處于停止?fàn)顟B(tài),它仍然可以處理異步事件(Asynchronous Event),比方說Timer的事件或是其它回呼函數(shù)(Callback)。這將涉及到resumeRequest()的使用。resumeRequest()會將MIDlet從停止?fàn)顟B(tài)回到運作狀態(tài),并調(diào)用startApp()。由于當(dāng)時MIDlet處于停止?fàn)顟B(tài),所以必須依靠異步事件使MIDlet重新回到運作狀態(tài)。

          在一些范例程序中,我們常常只呼叫notifyDestroyed(),而沒有呼叫destroyApp(),這是因為范例通常沒有釋放資源的需求,所以可以不用呼叫。但是如果是正規(guī)的程序,建議記得呼叫destroyApp(false)會比較好。

          總結(jié)


          通過本文的討論,相信大家都可以發(fā)現(xiàn)MIDP更成熟了,功能更強了,但是也更復(fù)雜了。

          附注:本文中出現(xiàn)的Java程序代碼已在Java 2 SDK 1.4.x與J2ME Wireless Toolkit 2.0的Win32版本上完成測試。本文所有操作皆在Windows 2000 Professional與Windows XP Professional中文版操作系統(tǒng)上經(jīng)過測試。本文的范例程序代碼請至Http://www.javatwo.net/experts/moli/publish/下載。

          名詞解釋

          MIDP: Java API中面向移動終端的集合。通過與J2ME中的面向移動終端產(chǎn)品配置CLDC配合使用,就能夠提供J2ME應(yīng)用程序所需的運行環(huán)境。主要用于手機、PDA及雙向?qū)ず魴C等低價位移動信息終端。

          MIDlet: 即一個可以執(zhí)行的J2ME/MIDP應(yīng)用程序基本單位。除了繼承自javax.microedition.midlet.MIDlet之外,還包括讓此類別可以順利執(zhí)行的所有其它類別和資源檔(只要是非類別檔都稱作資源檔)所構(gòu)成的集合,所以一般又稱做MIDlet應(yīng)用程序(MIDlet Application)。

          MIDlet Suite: 許多MIDlet所構(gòu)成的集合一般又叫做MIDlet應(yīng)用程序套件。MIDlet Suite和MIDlet的關(guān)系,就好像Office與Word、Excel、PowerPoint、Access的關(guān)系。

          JAR檔(.jar檔): 實際上包含MIDlet Suite的檔案,屬于ZIP檔格式。

          描述檔(.jad檔): 用來描述一個MIDlet Suite基本數(shù)據(jù),以及該MIDlet Suite內(nèi)含的MIDlet內(nèi)含的MIDlet相關(guān)信息(類別名稱、圖標(biāo)、程序名)的外部檔案(不在JAR檔內(nèi)部)。

          應(yīng)用程序管理員(Java Application Manager): 負(fù)責(zé)將MIDlet Suite安裝到機器上執(zhí)行,以及負(fù)責(zé)管理MIDlet生命周期的機制(或軟件)總稱。應(yīng)用程序管理員會根據(jù)使用者的需求安裝、啟動、停止或移除相對應(yīng)的MIDlet。

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           

          posts - 51, comments - 17, trackbacks - 0, articles - 0

          Copyright © 笨蛋啊帆

          主站蜘蛛池模板: 孟津县| 苏州市| 凤翔县| 乐清市| 赞皇县| 南漳县| 越西县| 通州区| 霍城县| 融水| 广灵县| 阿城市| 大丰市| 汪清县| 昌吉市| 雷州市| 佛冈县| 肇东市| 方山县| 都江堰市| 遂溪县| 四子王旗| 綦江县| 会昌县| 兴隆县| 滕州市| 阜新市| 咸宁市| 习水县| 精河县| 阳城县| 将乐县| 苍溪县| 开阳县| 贵阳市| 子长县| 汶川县| 南岸区| 平南县| 咸宁市| 唐海县|