posts - 101,  comments - 29,  trackbacks - 0

                  在Android系統(tǒng)中,廣播(Broadcast)是在組件之間傳播數(shù)據(jù)(Intent)的一種機制;這些組件甚至是可以位于不同的進程中,這樣它就像Binder機制一樣,起到進程間通信的作用;本文通過一個簡單的例子來學習Android系統(tǒng)的廣播機制,為后續(xù)分析廣播機制的源代碼作準備。

                  在Android系統(tǒng)中,為什么需要廣播機制呢?廣播機制,本質(zhì)上它就是一種組件間的通信方式,如果是兩個組件位于不同的進程當中,那么可以用Binder機制來實現(xiàn),如果兩個組件是在同一個進程中,那么它們之間可以用來通信的方式就更多了,這樣看來,廣播機制似乎是多余的。然而,廣播機制卻是不可替代的,它和Binder機制不一樣的地方在于,廣播的發(fā)送者和接收者事先是不需要知道對方的存在的,這樣帶來的好處便是,系統(tǒng)的各個組件可以松耦合地組織在一起,這樣系統(tǒng)就具有高度的可擴展性,容易與其它系統(tǒng)進行集成。

                  在軟件工程中,是非常強調(diào)模塊之間的高內(nèi)聚低耦合性的,不然的話,隨著系統(tǒng)越來越龐大,就會面臨著越來越難維護的風險,最后導致整個項目的失敗。Android應用程序的組織方式,可以說是把這種高內(nèi)聚低耦合性的思想貫徹得非常透徹,在任何一個Activity中,都可以使用一個簡單的Intent,通過startActivity或者startService,就可以把另外一個Activity或者Service啟動起來為它服務,而且它根本上不依賴這個Activity或者Service的實現(xiàn),只需要知道它的字符串形式的名字即可,而廣播機制更絕,它連接收者的名字都不需要知道。

                  不過話又說回來,廣播機制在Android系統(tǒng)中,也不算是什么創(chuàng)新的東西。如果讀者了解J2EE或者COM,就會知道,在J2EE中,提供了消息驅(qū)動Bean(Message-Driven Bean),用來實現(xiàn)應用程序各個組件之間的消息傳遞;而在COM中,提供了連接點(Connection Point)的概念,也是用來在應用程序各個組間間進行消息傳遞。無論是J2EE中的消息驅(qū)動Bean,還是COM中的連接點,或者Android系統(tǒng)的廣播機制,它們的實現(xiàn)機理都是消息發(fā)布/訂閱模式的事件驅(qū)動模型,消息的生產(chǎn)者發(fā)布事件,而使用者訂閱感興趣的事件。

                  廢話說了一大堆,現(xiàn)在開始進入主題了,和前面的文章一樣,我們通過具體的例子來介紹Android系統(tǒng)的廣播機制。在這個例子中,有一個Service,它在另外一個線程中實現(xiàn)了一個計數(shù)器服務,每隔一秒鐘就自動加1,然后將結(jié)果不斷地反饋給應用程序中的界面線程,而界面線程中的Activity在得到這個反饋后,就會把結(jié)果顯示在界面上。為什么要把計數(shù)器服務放在另外一個線程中進行呢?我們可以把這個計數(shù)器服務想象成是一個耗時的計算型邏輯,如果放在界面線程中去實現(xiàn),那么勢必就會導致應用程序不能響應界面事件,最后導致應用程序產(chǎn)生ANR(Application Not Responding)問題。計數(shù)器線程為了把加1后的數(shù)字源源不斷地反饋給界面線程,這時候就可以考慮使用廣播機制了。

                  首先在Android源代碼工程中創(chuàng)建一個Android應用程序工程,名字就稱為Broadcast吧。關于如何獲得Android源代碼工程,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文;關于如何在Android源代碼工程中創(chuàng)建應用程序工程,請參考在Ubuntu上為Android系統(tǒng)內(nèi)置Java應用程序測試Application Frameworks層的硬件服務一文。這個應用程序工程定義了一個名為shy.luo.broadcast的package,這個例子的源代碼主要就是實現(xiàn)在這里了。下面,將會逐一介紹這個package里面的文件。 

                  首先,我們在src/shy/luo/broadcast/ICounterService.java文件中定義計數(shù)器的服務接口:

          package shy.luo.broadcast;
          
          public interface ICounterService {
                  public void startCounter(int initVal);
                  public void stopCounter();
          }
          
                 這個接口很簡單,它只有兩個成員函數(shù),分別用來啟動和停止計數(shù)器;啟動計數(shù)時,還可以指定計數(shù)器的初始值。

           

                 接著,我們來看一個應用程序的默認Activity的實現(xiàn),在src/shy/luo/broadcast/MainActivity.java文件中:

          package shy.luo.broadcast;
          
          import android.app.Activity;
          import android.content.BroadcastReceiver;
          import android.content.ComponentName;
          import android.content.Context;
          import android.content.Intent;
          import android.content.IntentFilter;
          import android.content.ServiceConnection;
          import android.os.Bundle;
          import android.os.IBinder;
          import android.util.Log;
          import android.view.View;
          import android.view.View.OnClickListener;
          import android.widget.Button;
          import android.widget.TextView;
          
          public class MainActivity extends Activity implements OnClickListener {
          	private final static String LOG_TAG = "shy.luo.broadcast.MainActivity";
          	   
          	private Button startButton = null;
          	private Button stopButton = null;
          	private TextView counterText = null;
          	
          	private ICounterService counterService = null;
          	
                  @Override
                  public void onCreate(Bundle savedInstanceState) {
                      super.onCreate(savedInstanceState);
                      setContentView(R.layout.main);
                  
                      startButton = (Button)findViewById(R.id.button_start);
                      stopButton = (Button)findViewById(R.id.button_stop);
                      counterText = (TextView)findViewById(R.id.textview_counter);
                  
                      startButton.setOnClickListener(this);
                      stopButton.setOnClickListener(this);
                  
                      startButton.setEnabled(true);
                      stopButton.setEnabled(false);
                  
                      Intent bindIntent = new Intent(MainActivity.this, CounterService.class);
                      bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
                  
                      Log.i(LOG_TAG, "Main Activity Created.");
                  }
              
                  @Override 
                  public void onResume() {
              	    super.onResume();
              	
              	    IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
          	    registerReceiver(counterActionReceiver, counterActionFilter);
                  }
              
                  @Override
                  public void onPause() {
              	    super.onPause();
              	    unregisterReceiver(counterActionReceiver);
                  }
              
                  @Override
                  public void onDestroy() {
              	    super.onDestroy();
              	    unbindService(serviceConnection);
                  }
              
                  @Override
                  public void onClick(View v) {
              	    if(v.equals(startButton)) {
              		if(counterService != null) {
              			counterService.startCounter(0);
              			
          	    		startButton.setEnabled(false);
          	                stopButton.setEnabled(true);
              		}
              	    } else if(v.equals(stopButton)) {
              		if(counterService != null) {
              			counterService.stopCounter();
              			
              			startButton.setEnabled(true);
              			stopButton.setEnabled(false);
              		}
              	    }
                  }
              
                  private BroadcastReceiver counterActionReceiver = new BroadcastReceiver(){
          		public void onReceive(Context context, Intent intent) {
          			int counter = intent.getIntExtra(CounterService.COUNTER_VALUE, 0);
          			String text = String.valueOf(counter);
          			counterText.setText(text);
          			
          			Log.i(LOG_TAG, "Receive counter event");
          		}
          	};
          	
          	private ServiceConnection serviceConnection = new ServiceConnection() {
              	    public void onServiceConnected(ComponentName className, IBinder service) {
              		counterService = ((CounterService.CounterBinder)service).getService();
              		
              		Log.i(LOG_TAG, "Counter Service Connected");
              	    }
               	    public void onServiceDisconnected(ComponentName className) {
              		counterService = null;
              		Log.i(LOG_TAG, "Counter Service Disconnected");
              	    }
                  };
          }

                  MainActivity的實現(xiàn)也很簡單,它在創(chuàng)建(onCreate)的時候,會調(diào)用bindService函數(shù)來把計數(shù)器服務(CounterService)啟動起來,它的第二個參數(shù)serviceConnection是一個ServiceConnection實例。計數(shù)器服務啟動起來后,系統(tǒng)會調(diào)用這個實例的onServiceConnected函數(shù)將一個Binder對象傳回來,通過調(diào)用這個Binder對象的getService函數(shù),就可以獲得計數(shù)器服務接口。這里,把這個計數(shù)器服務接口保存在MainActivity的counterService成員變量中。同樣,當我們調(diào)用unbindService停止計數(shù)器服務時,系統(tǒng)會調(diào)用這個實例的onServiceDisconnected函數(shù)告訴MainActivity,它與計數(shù)器服務的連接斷開了。

                  注意,這里通過調(diào)用bindService函數(shù)來啟動Service時,這個Service與啟動它的Activity是位于同一個進程中,它不像我們在前面一篇文章Android系統(tǒng)在新進程中啟動自定義服務過程(startService)的原理分析中所描述那樣在新的進程中啟動服務,后面我們再寫一篇文章來分析bindService啟動服務的過程。

           

                  在MainActivity的onResume函數(shù)中,通過調(diào)用registerReceiver函數(shù)注冊了一個廣播接收器counterActionReceiver,它是一個BroadcastReceiver實例,并且指定了這個廣播接收器只對CounterService.BROADCAST_COUNTER_ACTION類型的廣播感興趣。當CounterService發(fā)出一個CounterService.BROADCAST_COUNTER_ACTION類型的廣播時,系統(tǒng)就會把這個廣播發(fā)送到counterActionReceiver實例的onReceiver函數(shù)中去。在onReceive函數(shù)中,從參數(shù)intent中取出計數(shù)器當前的值,顯示在界面上。

                 MainActivity界面上有兩個按鈕,分別是Start Counter和Stop Counter按鈕,點擊前者開始計數(shù),而點擊后者則停止計數(shù)。

                 計數(shù)器服務CounterService實現(xiàn)在src/shy/luo/broadcast/CounterService.java文件中:

          package shy.luo.broadcast;
          
          import android.app.Service;
          import android.content.Intent;
          import android.os.AsyncTask;
          import android.os.Binder;
          import android.os.IBinder;
          import android.util.Log;
          
          public class CounterService extends Service implements ICounterService {
          	private final static String LOG_TAG = "shy.luo.broadcast.CounterService";
          	
          	public final static String BROADCAST_COUNTER_ACTION = "shy.luo.broadcast.COUNTER_ACTION";
          	public final static String COUNTER_VALUE = "shy.luo.broadcast.counter.value";
          	
          	private boolean stop = false;
          	
          	private final IBinder binder = new CounterBinder();
          	
          	public class CounterBinder extends Binder {
          		public CounterService getService() {
          			return CounterService.this;
          		}
          	}
          	
          	@Override
                  public IBinder onBind(Intent intent) {
                          return binder;
                  }
          	
          	@Override
          	public void onCreate() {
          		super.onCreate();
          		
          		Log.i(LOG_TAG, "Counter Service Created.");
          	}
          	
          	@Override
                  public void onDestroy() {
              	        Log.i(LOG_TAG, "Counter Service Destroyed.");
                  }
          	
          	public void startCounter(int initVal) {
              	        AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer>() {	
          			@Override
          			protected Integer doInBackground(Integer... vals) {
          				Integer initCounter = vals[0];
          				
          				stop = false;
          				while(!stop) {
          					publishProgress(initCounter);
          					
          					try {
          						Thread.sleep(1000);
          					} catch (InterruptedException e) {
          						e.printStackTrace();
          					}
          					
          					initCounter++;
          				}
          				
          				return initCounter;
          			}
          			
          			@Override 
          			protected void onProgressUpdate(Integer... values) {
          				super.onProgressUpdate(values);
          				
          				int counter = values[0];
          				
          				Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
          				intent.putExtra(COUNTER_VALUE, counter);
          				
          				sendBroadcast(intent);
          			}
          			
          			@Override
          			protected void onPostExecute(Integer val) {
          				int counter = val;
          				
          				Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
          				intent.putExtra(COUNTER_VALUE, counter);
          				
          				sendBroadcast(intent);
          			}
          		
              	        };
          			
              	        task.execute(0);	
                  }
          
          	public void stopCounter() {
          		stop = true;
          	}
          }

                  這個計數(shù)器服務實現(xiàn)了ICounterService接口。當這個服務被binderService函數(shù)啟動時,系統(tǒng)會調(diào)用它的onBind函數(shù),這個函數(shù)返回一個Binder對象給系統(tǒng)。上面我們說到,當MainActivity調(diào)用bindService函數(shù)來啟動計數(shù)器服務器時,系統(tǒng)會調(diào)用MainActivity的ServiceConnection實例serviceConnection的onServiceConnected函數(shù)通知MainActivity,這個服務已經(jīng)連接上了,并且會通過這個函數(shù)傳進來一個Binder遠程對象,這個Binder遠程對象就是來源于這里的onBind的返回值了。

                  函數(shù)onBind返回的Binder對象是一個自定義的CounterBinder實例,它實現(xiàn)了一個getService成員函數(shù)。當系統(tǒng)通知MainActivity,計數(shù)器服務已經(jīng)啟動起來并且連接成功后,并且將這個Binder對象傳給MainActivity時,MainActivity就會把這個Binder對象強制轉(zhuǎn)換為CounterBinder實例,然后調(diào)用它的getService函數(shù)獲得服務接口。這樣,MainActivity就通過這個Binder對象和CounterService關聯(lián)起來了。

           

                  當MainActivity調(diào)用計數(shù)器服務接口的startCounter函數(shù)時,計數(shù)器服務并不是直接進入計數(shù)狀態(tài),而是通過使用異步任務(AsyncTask)在后臺線程中進行計數(shù)。這里為什么要使用異步任務來在后臺線程中進行計數(shù)呢?前面我們說過,這個計數(shù)過程是一個耗時的計算型邏輯,不能把它放在界面線程中進行,因為這里的CounterService啟動時,并沒有在新的進程中啟動,它與MainActivity一樣,運行在應用程序的界面線程中,因此,這里需要使用異步任務在在后臺線程中進行計數(shù)。

                  異步任務AsyncTask的具體用法可以參考官方文檔http://developer.android.com/reference/android/os/AsyncTask.html。它的大概用法是,當我們調(diào)用異步任務實例的execute(task.execute)方法時,當前調(diào)用線程就返回了,系統(tǒng)啟動一個后臺線程來執(zhí)行這個異步任務實例的doInBackground函數(shù),這個函數(shù)就是我們用來執(zhí)行耗時計算的地方了,它會進入到一個循環(huán)中,每隔1秒鐘就把計數(shù)器加1,然后進入休眠(Thread.sleep),醒過來,再重新這個計算過程。在計算的過程中,可以通過調(diào)用publishProgress函數(shù)來通知調(diào)用者當前計算的進度,好讓調(diào)用者來更新界面,調(diào)用publishProgress函數(shù)的效果最終就是直入到這個異步任務實例的onProgressUpdate函數(shù)中,這里就可以把這個進度值以廣播的形式(sendBroadcast)發(fā)送出去了,這里的進度值就定義為當前計數(shù)服務的計數(shù)值。

                  當MainActivity調(diào)用計數(shù)器服務接口的stopCounter函數(shù)時,會告訴函數(shù)doInBackground停止執(zhí)行計數(shù)(stop = true),于是,函數(shù)doInBackground就退出計數(shù)循環(huán),然后將最終計數(shù)結(jié)果返回了,返回的結(jié)果最后進入到onPostExecute函數(shù)中,這個函數(shù)同樣通過廣播的形式(sendBroadcast)把這個計數(shù)結(jié)果廣播出去。

                  計算器服務就介紹到這里了,下面我們看看應用程序的配置文件AndroidManifest.xml:

          <?xml version="1.0" encoding="utf-8"?>
          <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                package="shy.luo.broadcast"
                android:versionCode="1"
                android:versionName="1.0">
              <application android:icon="@drawable/icon" android:label="@string/app_name">
                  <activity android:name=".MainActivity"
                            android:label="@string/app_name">
                      <intent-filter>
                          <action android:name="android.intent.action.MAIN" />
                          <category android:name="android.intent.category.LAUNCHER" />
                      </intent-filter>
                  </activity>
          	<service android:name=".CounterService"
          		 android:enabled="true">
          	</service>
              </application>
          </manifest> 
                  這個配置文件很簡單,只是告訴系統(tǒng),它有一個Activity和一個Service。

           

                  再來看MainActivity的界面文件,它定義在res/layout/main.xml文件中:

          <?xml version="1.0" encoding="utf-8"?>
          <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent" 
              android:gravity="center">
              <LinearLayout
              	android:layout_width="fill_parent"
              	android:layout_height="wrap_content"
              	android:layout_marginBottom="10px"
              	android:orientation="horizontal" 
              	android:gravity="center">
          	    <TextView  
          	   	android:layout_width="wrap_content" 
          	    	android:layout_height="wrap_content" 
          	    	android:layout_marginRight="4px"
          	    	android:gravity="center"
          	    	android:text="@string/counter">
          	    </TextView>
          	    <TextView  
          	    	android:id="@+id/textview_counter"
          	   	android:layout_width="wrap_content" 
          	    	android:layout_height="wrap_content" 
          	    	android:gravity="center"
          	    	android:text="0">
          	    </TextView>
              </LinearLayout>
              <LinearLayout
              	android:layout_width="fill_parent"
              	android:layout_height="wrap_content"
              	android:orientation="horizontal" 
              	android:gravity="center">
          	    <Button 
          	    	android:id="@+id/button_start"
          	    	android:layout_width="wrap_content"
          	    	android:layout_height="wrap_content"
          	    	android:gravity="center"
          	    	android:text="@string/start">
          	    </Button>
          	    <Button 
          	    	android:id="@+id/button_stop"
          	    	android:layout_width="wrap_content"
          	    	android:layout_height="wrap_content"
          	    	android:gravity="center"
          	    	android:text="@string/stop" >
          	    </Button>
               </LinearLayout>  
          </LinearLayout>
          
                  這個界面配置文件也很簡單,等一下我們在模擬器把這個應用程序啟動起來后,就可以看到它的截圖了。

           

                  應用程序用到的字符串資源文件位于res/values/strings.xml文件中:

          <?xml version="1.0" encoding="utf-8"?>
          <resources>
              <string name="app_name">Broadcast</string>
              <string name="counter">Counter: </string>
              <string name="start">Start Counter</string>
              <string name="stop">Stop Counter</string>
          </resources>
                   最后,我們還要在工程目錄下放置一個編譯腳本文件Android.mk:

           

          LOCAL_PATH:= $(call my-dir)      
          include $(CLEAR_VARS)      
                
          LOCAL_MODULE_TAGS := optional      
                
          LOCAL_SRC_FILES := $(call all-subdir-java-files)      
                
          LOCAL_PACKAGE_NAME := Broadcast      
                
          include $(BUILD_PACKAGE)  
                    接下來就要編譯了。有關如何單獨編譯Android源代碼工程的模塊,以及如何打包system.img,請參考如何單獨編譯Android源代碼中的模塊一文。
                    執(zhí)行以下命令進行編譯和打包:
          USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Broadcast        
          USER-NAME@MACHINE-NAME:~/Android$ make snod   
                   這樣,打包好的Android系統(tǒng)鏡像文件system.img就包含我們前面創(chuàng)建的Broadcast應用程序了。
                   再接下來,就是運行模擬器來運行我們的例子了。關于如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。
                   執(zhí)行以下命令啟動模擬器:
          USER-NAME@MACHINE-NAME:~/Android$ emulator
                  模擬器啟動起,就可以App Launcher中找到Broadcast應用程序圖標,接著把它啟動起來,然后點擊界面上的Start Counter按鈕,就可以把計數(shù)器服務啟動起來了,計數(shù)器服務又通過廣播把計數(shù)值反饋給MainActivity,于是,我們就會在MainActivity界面看到計數(shù)器的值不斷地增加了:

           

                  這樣,使用廣播的例子就介紹完了。回顧一下,使用廣播的兩個步驟:

                  1. 廣播的接收者需要通過調(diào)用registerReceiver函數(shù)告訴系統(tǒng),它對什么樣的廣播有興趣,即指定IntentFilter,并且向系統(tǒng)注冊廣播接收器,即指定BroadcastReceiver:

          IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
          registerReceiver(counterActionReceiver, counterActionFilter);
                  這里,指定感興趣的廣播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的廣播接收器就是counterActonReceiver,它是一個BroadcastReceiver類型的實例。

           

                  2. 廣播的發(fā)送者通過調(diào)用sendBroadcast函數(shù)來發(fā)送一個指定的廣播,并且可以指定廣播的相關參數(shù):

          Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
          intent.putExtra(COUNTER_VALUE, counter);
          sendBroadcast(intent)
                  這里,指定的廣播為CounterService.BROADCAST_COUNTER_ACTION,并且附帶的帶參數(shù)當前的計數(shù)器值counter。調(diào)用了sendBroadcast函數(shù)之后,所有注冊了CounterService.BROADCAST_COUNTER_ACTION廣播的接收者便可以收到這個廣播了。

           

                  在第1步中,廣播的接收者把廣播接收器注冊到ActivityManagerService中;在第2步中,廣播的發(fā)送者同樣是把廣播發(fā)送到ActivityManagerService中,由ActivityManagerService去查找注冊了這個廣播的接收者,然后把廣播分發(fā)給它們。

                  在第2步的分發(fā)的過程,其實就是把這個廣播轉(zhuǎn)換成一個消息,然后放入到接收器所在的線程消息隊列中去,最后就可以在消息循環(huán)中調(diào)用接收器的onReceive函數(shù)了。這里有一個要非常注意的地方是,由于ActivityManagerService把這個廣播放進接收器所在的線程消息隊列后,就返回了,它不關心這個消息什么時候會被處理,因此,對廣播的處理是異步的,即調(diào)用sendBroadcast時,這個函數(shù)不會等待這個廣播被處理完后才返回。

                  下面,我們以一個序列圖來總結(jié)一下,廣播的注冊和發(fā)送的過程:


                  虛線上面Step 1到Step 4步是注冊廣播接收器的過程,其中Step 2通過LoadedApk.getReceiverDispatcher在LoadedApk內(nèi)部創(chuàng)建了一個IIntentReceiver接口,并且傳遞給ActivityManagerService;虛線下面的Step 5到Step 11是發(fā)送廣播的過程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver遠程接口,調(diào)用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通過ActivityThread.H接口的post函數(shù)將這個廣播消息放入到ActivityThread的消息隊列中去,最后這個消息在LoadedApk的Args.run函數(shù)中處理,LoadedApk.Args.run函數(shù)接著調(diào)用MainActivity.BroadcastReceiver的onReceive函數(shù)來最終處理這個廣播。

                  文章開始的時候,我們提到,舉這個例子的最終目的,是為了進一步學習Android系統(tǒng)的廣播機制,因此,在接下來的兩篇文章中,我們將詳細描述上述注冊廣播接收器和發(fā)送廣播的過程:

                  1. Android應用程序注冊廣播接收器(registerReceiver)的過程分析;

                  2. Android應用程序發(fā)送廣播(sendBroadcast)的過程分析。

                  相信學習完這兩篇文章后,能夠加深對Android系統(tǒng)廣播機制的了解,敬請關注。

          作者:Luoshengyang 發(fā)表于2011-8-31 1:12:32 原文鏈接
          閱讀:5451 評論:19 查看評論
          posted on 2012-04-17 21:32 mixer-a 閱讀(1251) 評論(0)  編輯  收藏

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


          網(wǎng)站導航:
           
          主站蜘蛛池模板: 响水县| 平阳县| 顺昌县| 米脂县| 邵武市| 得荣县| 东宁县| 安岳县| 西乡县| 化德县| 天台县| 民县| 方城县| 仁布县| 芦山县| 保康县| 琼海市| 平度市| 台州市| 偏关县| 金沙县| 新昌县| 花莲县| 梓潼县| 江阴市| 油尖旺区| 永仁县| 浦东新区| 金川县| 肇东市| 泸溪县| 梁河县| 彰武县| 晋江市| 瓮安县| 美姑县| 南投市| 沙河市| 淳安县| 抚顺县| 鸡西市|