国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Android > 正文

深入Android Handler,MessageQueue與Looper關(guān)系

2019-10-21 21:41:42
字體:
供稿:網(wǎng)友

關(guān)聯(lián)篇:Handler內(nèi)存泄漏詳解及其解決方案

一說到Android的消息機(jī)制,自然就會聯(lián)想到Handler,我們知道Handler是Android消息機(jī)制的上層接口,因此我們在開發(fā)過程中也只需要和Handler交互即可,很多人認(rèn)為Handler的作用就是更新UI,這也確實(shí)沒錯,但除了更新UI,Handler其實(shí)還有很多其他用途,比如我們需要在子線程進(jìn)行耗時的I/O操作,可能是讀取某些文件或者去訪問網(wǎng)絡(luò)等,當(dāng)耗時操作完成后我們可能需要在UI上做出相應(yīng)的改變,但由于Android系統(tǒng)的限制,我們是不能在子線程更新UI控件的,否則就會報異常,這個時候Handler就可以派上用場了,我們可以通過Handler切換到主線程中執(zhí)行UI更新操作。

下面是Handler一些常用方法:

void handleMessage(Message msg):處理消息的方法,該方法通常會被重寫。

final boolean hasMessages(int what):檢測消息隊(duì)列中是否包含what屬性為指定值的消息。

Message obtainMessage():獲取消息的方法,此函數(shù)有多個重載方法。

sendEmptyMessage(int what):發(fā)送空消息。

final boolean sendEmptyMessageDelayed(int what , long delayMillis):指定多少毫秒后發(fā)送空消息。

final boolean sendMessage(Message msg):立即發(fā)送消息。

final boolean sendMessageDelayed(Message msg ,long delayMillis):指定多少毫秒后發(fā)送消息。

final boolean post(Runnable r):執(zhí)行runnable操作。

final boolean postAtTime(Runnable r, long upTimeMillis):在指定時間執(zhí)行runnable操作。

final boolean postDelayed(Runnable r, long delayMillis):指定多少毫秒后執(zhí)行runnable操作。

介紹完方法后,我們就從一個簡單的例子入手吧,然后一步步的分析:

public class MainActivity extends AppCompatActivity {  public static final int MSG_FINISH = 0X001; //創(chuàng)建一個Handler的匿名內(nèi)部類 private Handler handler = new Handler() {  @Override public void handleMessage(Message msg) {  switch (msg.what) {  case MSG_FINISH:   LogUtils.e("handler所在的線程id是-->" + Thread.currentThread().getName());   break;  } }  };  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //啟動耗時操作 consumeTimeThread(findViewById(R.id.tv));// handler.post() }  //啟動一個耗時線程 public void consumeTimeThread(View view) { new Thread() {  public void run() {  try {   LogUtils.e("耗時子線程的Name是--->" + Thread.currentThread().getName());   //在子線程運(yùn)行   Thread.sleep(2000);   //完成后,發(fā)送下載完成消息   handler.sendEmptyMessage(MSG_FINISH);  } catch (InterruptedException e) {   e.printStackTrace();  }  } }.start(); }}

運(yùn)行結(jié)果:

Android,Handler,MessageQueue,Looper

上面的例子其實(shí)就是Handler的基本使用,在主線中創(chuàng)建了一個Handler對象,然后通過在子線程中模擬一個耗時操作完成后通過sendEmptyMessage(int)方法發(fā)送一個消息通知主線程的Handler去執(zhí)行相應(yīng)的操作。通過運(yùn)行結(jié)果我們也可以知道Handler確實(shí)也是在主線程運(yùn)行的。

那么問題來了,通過Handler發(fā)送的消息是怎么到達(dá)主線程的呢?接下來我們就來掰掰其中的奧妙,前方高能,請集中注意力!為了更好的理解Handler的工作原理,我們先來介紹與Handler一起工作的幾個組件:

Message:Handler接收和處理消息的對象。

Looper:每個線程只能有一個Looper。它的loop方法負(fù)責(zé)讀取MessageQueue中的消息,讀到消息后把消息發(fā)送給Handler進(jìn)行處理。

MessageQueue:消息隊(duì)列,它采用先進(jìn)先出的方式來管理Message。程序創(chuàng)建Looper對象時,會在它的構(gòu)造方法中創(chuàng)建MessageQueue對象。

Handler:它的作用有兩個—發(fā)送消息和處理消息,程序使用Handler發(fā)送消息,由Handler發(fā)送的消息必須被送到指定的MessageQueue;否則消息就沒有在MessageQueue進(jìn)行保存了。而MessageQueue是由Looper負(fù)責(zé)管理的,也就是說,如果希望Handler正常工作的話,就必須在當(dāng)前線程中有一個Looper對象。我們先對上面的幾個組件有大概的了解就好,后面我們都會詳細(xì)分析,既然消息是從Handler發(fā)送出去,那么我們就先從Handler入手吧。先來看看Handler 的構(gòu)造方法源碼:

public class Handler {	/**	 * 未實(shí)現(xiàn)的空方法handleMessage()	 */	public void handleMessage(Message msg) {	}	/**	 * 我們通常用于創(chuàng)建Handler的構(gòu)造方法之一	 */	public Handler() {		this(null, false);	}	// 構(gòu)造方法的內(nèi)調(diào)用的this(null, false)的具體實(shí)現(xiàn)	public Handler(Callback callback, boolean async) {//檢查Handler是否是static的,如果不是的,那么有可能導(dǎo)致內(nèi)存泄露 if (FIND_POTENTIAL_LEAKS) {   final Class<? extends Handler> klass = getClass();   if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&    (klass.getModifiers() & Modifier.STATIC) == 0) {   Log.w(TAG, "The following Handler class should be static or leaks might occur: " +    klass.getCanonicalName());   }  }  //重要的組件出現(xiàn)啦!Looper我們先理解成一個消息隊(duì)列的管理者,用來從消息隊(duì)列中取消息的,后續(xù)會詳細(xì)分析 mLooper = Looper.myLooper();  if (mLooper == null) {   //這個異常很熟悉吧,Handler是必須在有Looper的線程上執(zhí)行,這個也就是為什么我在HandlerThread中初始化Handler   //而沒有在Thread里面初始化,如果在Thread里面初始化需要先調(diào)用Looper.prepare方法   throw new RuntimeException(   "Can't create handler inside thread that has not called Looper.prepare()");  }  //將mLooper里面的消息隊(duì)列復(fù)制到自身的mQueue,這也就意味著Handler和Looper是公用一個消息隊(duì)列  mQueue = mLooper.mQueue;  //回調(diào)函數(shù)默認(rèn)是Null  mCallback = null; 	}

分析:Handler的構(gòu)造方法源碼不是很多,也比較簡單,但是我們從源碼中也可以得知,在創(chuàng)建Handler時,Handler內(nèi)部會去創(chuàng)建一個Looper對象,這個Looper對象是通過Looper.myLooper()創(chuàng)建的(后續(xù)會分析這個方法),同時還會創(chuàng)建一個消息隊(duì)列MessageQueue,而這個MessageQueue是從Looper中獲取的,這也就意味著Handler和Looper共用一個消息隊(duì)列,當(dāng)然此時Handler,Looper以及MessageQueue已經(jīng)捆綁到一起了。上面還有一個情況要說明的,那就是:

if (mLooper == null) {  //這個異常很熟悉吧,Handler是必須在有Looper的線程上執(zhí)行,這個也就是為什么我在HandlerThread中初始化Handler  //而沒有在Thread里面初始化,如果在Thread里面初始化需要先調(diào)用Looper.prepare方法  throw new RuntimeException(  "Can't create handler inside thread that has not called Looper.prepare()");  } 

這里先回去判斷Looper是否為空,如果為null,那么就會報錯,這個錯誤對我們來說應(yīng)該比較熟悉吧,那為什么會報這個錯誤呢?我們在前面說過Handler的作用有兩個—發(fā)送消息和處理消息,我們在使用Handler發(fā)送消息,由Handler發(fā)送的消息必須被送到指定的MessageQueue;否則就無法進(jìn)行消息循環(huán)。而MessageQueue是由Looper負(fù)責(zé)管理的,也就是說,如果希望Handler正常工作的話,就必須在當(dāng)前線程中有一個Looper對象。那么又該如何保障當(dāng)前線程中一定有Looper對象呢?這里其實(shí)分兩種情況:

(1)在主UI線程中,系統(tǒng)已經(jīng)初始化好了一個Looper對象,因此我們可以直接創(chuàng)建Handler并使用即可。

(2)在子線程中,我們就必須自己手動去創(chuàng)建一個Looper對象,并且去啟動它,才可以使用Handler進(jìn)行消息發(fā)送與處理。使用事例如下:

class childThread extends Thread{ public Handler mHandler;  @Override public void run() {  //子線程中必須先創(chuàng)建Looper  Looper.prepare();    mHandler =new Handler(){  @Override  public void handleMessage(Message msg) {   super.handleMessage(msg);   //處理消息  }  };  //啟動looper循環(huán)  Looper.loop(); } }

分析完Handler的構(gòu)造方法,我們接著看看通過Handler發(fā)送的消息到底是發(fā)送到哪里了?我們先來看看Handler的幾個主要方法源碼:

// 發(fā)送一個空消息的方法,實(shí)際上添加到MessagerQueue隊(duì)列中public final boolean sendEmptyMessage(int what) {	return sendEmptyMessageDelayed(what, 0);}// 給上一個方法調(diào)用public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {	Message msg = Message.obtain();	msg.what = what;	return sendMessageDelayed(msg, delayMillis);}// 給上一個方法調(diào)用public final boolean sendMessageDelayed(Message msg, long delayMillis) {	if (delayMillis < 0) {		delayMillis = 0;	}	return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}// 給上一個方法調(diào)用public boolean sendMessageAtTime(Message msg, long uptimeMillis) {	MessageQueue queue = mQueue;	if (queue == null) {		RuntimeException e = new RuntimeException(this				+ " sendMessageAtTime() called with no mQueue");		Log.w("Looper", e.getMessage(), e);		return false;	}	return enqueueMessage(queue, msg, uptimeMillis);}// 最后調(diào)用此方法添加到消息隊(duì)列中private boolean enqueueMessage(MessageQueue queue, Message msg,		long uptimeMillis) {	msg.target = this;// 設(shè)置發(fā)送目標(biāo)對象是Handler本身	if (mAsynchronous) {		msg.setAsynchronous(true);	}	return queue.enqueueMessage(msg, uptimeMillis);// 添加到消息隊(duì)列中}// 在looper類中的loop()方法內(nèi)部調(diào)用的方法public void dispatchMessage(Message msg) {	if (msg.callback != null) {		handleCallback(msg);	} else {		if (mCallback != null) {			if (mCallback.handleMessage(msg)) {				return;			}		}		handleMessage(msg);	}}

分析:通過源碼我們可以知道,當(dāng)我們調(diào)用sendEmptyMessage(int)發(fā)送消息后。最終Handler內(nèi)部會去調(diào)用enqueueMessage(MessageQueue queue,Message msg)方法把發(fā)送的消息添加到消息隊(duì)列MessageQueue中,同時還有設(shè)置msg.target=this此時就把當(dāng)前handler對象綁定到msg.target中了,這樣就完成了Handler向消息隊(duì)列存放消息的過程。這個還有一個要注意的方法 dispatchMessage(Message),這個方法最終會在looper中被調(diào)用(這里我們先知道這點(diǎn)就行,后續(xù)還會分析)。話說我們一直在說MessageQueue消息隊(duì)列,但這個消息隊(duì)列到底是什么啊?其實(shí)在Android中的消息隊(duì)列指的也是MessageQueue,MessageQueue主要包含了兩種操作,插入和讀取,而讀取操作本身也會伴隨著刪除操作,插入和讀取對應(yīng)的分別是enqueueMessage和next,其中enqueueMessage是向消息隊(duì)列中插入一條消息,而next的作用則是從消息隊(duì)列中取出一條消息并將其從隊(duì)列中刪除。雖然我們一直稱其為消息隊(duì)列但是它的內(nèi)部實(shí)現(xiàn)并不是隊(duì)列,而是通過一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護(hù)消息列表的,因?yàn)槲覀冎绬捂湵碓诓迦牒蛣h除上比較有優(yōu)勢。至內(nèi)MessageQueue的內(nèi)部實(shí)現(xiàn),這個屬于數(shù)據(jù)結(jié)構(gòu)的范疇,我們就不過多討論了,還是回到原來的主題上來,到這里我們都知道Handler發(fā)送的消息最終會添加到MessageQueue中,但到達(dá)MessageQueue后消息又是如何處理的呢?還記得我們前面說過MessageQueue是由Looper負(fù)責(zé)管理的吧,現(xiàn)在我們就來看看Looper到底是如何管理MessageQueue的?

public final class Looper {	// sThreadLocal.get() will return null unless you've called prepare().	//存放線程的容器類,為確保獲取的線程和原來的一樣	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();	private static Looper sMainLooper; // guarded by Looper.class	//消息隊(duì)列	final MessageQueue mQueue;	final Thread mThread;	//perpare()方法,用來初始化一個Looper對象	public static void prepare() {		prepare(true);	}			private static void prepare(boolean quitAllowed) {		if (sThreadLocal.get() != null) {		throw new RuntimeException("Only one Looper may be created per thread");		}		sThreadLocal.set(new Looper(quitAllowed));	}	//handler調(diào)用的獲取Looper對象的方法。實(shí)際是在ThreadLocal中獲取。	public static Looper myLooper() {		return sThreadLocal.get();	}			//Looper類的構(gòu)造方法,可以發(fā)現(xiàn)創(chuàng)建Looper的同時也創(chuàng)建了消息隊(duì)列MessageQueue對象	private Looper(boolean quitAllowed) {		mQueue = new MessageQueue(quitAllowed);		mRun = true;		mThread = Thread.currentThread();	}		//這個方法是給系統(tǒng)調(diào)用的,UI線程通過調(diào)用這個線程,從而保證UI線程里有一個Looper //需要注意:如果一個線程是UI線程,那么myLooper和getMainLooper是同一個Looper  public static final void prepareMainLooper() { 	prepare(); 	setMainLooper(myLooper()); 	if (Process.supportsProcesses()) { 	 myLooper().mQueue.mQuitAllowed = false; 	 } 	} 	 //獲得UI線程的Looper,通常我們想Hanlder的handleMessage在UI線程執(zhí)行時通常會new Handler(getMainLooper());  public synchronized static final Looper getMainLooper() { 	 return mMainLooper;  } 		//looper中最重要的方法loop(),該方法是個死循環(huán),會不斷去消息隊(duì)列MessageQueue中獲取消息,然后調(diào)dispatchMessage(msg)方法去執(zhí)行 public static void loop() {	final Looper me = myLooper();	if (me == null) {		throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");		}	 final MessageQueue queue = me.mQueue;	 //死循環(huán)	 for (;;) {		Message msg = queue.next(); // might block		if (msg == null) {		// No message indicates that the message queue is quitting.				return;		}//這里其實(shí)就是調(diào)用handler中的方法,而在Handler的源碼中也可以知道dispatchMessage(msg)內(nèi)部調(diào)用的就是handlerMessage()方法		msg.target.dispatchMessage(msg);		msg.recycle();	}}

分析:代碼不算多,我們拆分開慢慢說,在Looper源碼中我們可以得知其內(nèi)部是通過一個ThreadLocal的容器來存放Looper的對象本身的,這樣就可以確保每個線程獲取到的looper都是唯一的。那么Looper對象是如何被創(chuàng)建的呢?通過源碼我們可以知道perpare()方法就可以創(chuàng)建Looper對象:

//perpare()方法,用來初始化一個Looper對象public static void prepare() {	prepare(true);}		private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {	throw new RuntimeException("Only one Looper may be created per thread");	}	sThreadLocal.set(new Looper(quitAllowed));}

在創(chuàng)建Looper對象前先會去判斷ThreadLocal中是否已經(jīng)存在Looper對象,如果不存在就新創(chuàng)建一個Looper對象并且存放ThreadLocal中。這里還有一個要注意的是在Looper創(chuàng)建的同時MessageQueue消息隊(duì)列也被創(chuàng)建完成,這樣的話Looper中就持有了MessageQueue對象。

//Looper類的構(gòu)造方法,可以發(fā)現(xiàn)創(chuàng)建Looper的同時也創(chuàng)建了消息隊(duì)列MessageQueue對象	private Looper(boolean quitAllowed) {		mQueue = new MessageQueue(quitAllowed);		mRun = true;		mThread = Thread.currentThread();	}

那么我們?nèi)绾潍@取已經(jīng)創(chuàng)建好的Looper對象呢?通過源碼我們知道m(xù)yLooper()方法就可以獲取到Looper對象:

//handler調(diào)用的獲取Looper對象的方法。實(shí)際是在ThreadLocal中獲取。	public static Looper myLooper() {		return sThreadLocal.get();	}

Looper對象的創(chuàng)建和獲取,還有MessageQueue對象的創(chuàng)建,現(xiàn)在我們都很清楚了,但是Looper到底是怎么管理MessageQueue對象的呢?這就要看looper()方法了:

//looper中最重要的方法loop(),該方法是個死循環(huán),//會不斷去消息隊(duì)列MessageQueue中獲取消息,//然后調(diào)dispatchMessage(msg)方法去執(zhí)行 public static void loop() {	final Looper me = myLooper();	if (me == null) {		throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");		}	 final MessageQueue queue = me.mQueue;	 //死循環(huán)	 for (;;) {		Message msg = queue.next(); // might block		if (msg == null) {		// No message indicates that the message queue is quitting.				return;		}//這里其實(shí)就是調(diào)用handler中的方法,而在Handler的源碼中也可以知道dispatchMessage(msg)內(nèi)部調(diào)用的就是handlerMessage()方法		msg.target.dispatchMessage(msg);		msg.recycle();	}

通過looper()方法內(nèi)部源碼我們可以知道,首先會通過myLoooper()去獲取一個Looper對象,如果Looper對象為null,就會報出一個我們非常熟悉的錯誤提示,“No Looper;Looper.prepare() wasn't called on this thread”,要求我們先通過Looper.prepare()方法去創(chuàng)建Looper對象;如果Looper不為null,那么就會去獲取消息隊(duì)列MessageQueue對象,接著就進(jìn)入一個for的死循環(huán),不斷從消息隊(duì)列MessageQueue對象中獲取消息,如果消息不為空,那么久會調(diào)用msg.target的dispatchMessage(Message)方法,那么這個target又是什么,沒錯target就是我們創(chuàng)建的Handler對象,還記得我們前面分析Handler源碼時說過的那個方法嘛?

// 在looper類中的loop()方法內(nèi)部調(diào)用的方法public void dispatchMessage(Message msg) {	if (msg.callback != null) {		handleCallback(msg);	} else {		if (mCallback != null) {			if (mCallback.handleMessage(msg)) {				return;			}		}		handleMessage(msg);	}

現(xiàn)在明白了吧?首先,檢測Message的callback是否為null,不為null就通過handleCallback方法來處理消息,那么Message的callback是什么?其實(shí)就是一個Runnable對象,實(shí)際上就是Handler的post方法所傳遞的Runnable參數(shù),我們順便看看post方法源碼:

public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); }

現(xiàn)在明白Message的callback是什么了吧?而對應(yīng)handleCallback方法邏輯也比較簡單:

private static void handleCallback(Message message) { message.callback.run(); }

嗯,是的,因此最終執(zhí)行的還是通過post方法傳遞進(jìn)來的Runnable參數(shù)的run方法。好了,我們繼續(xù)dispatchMessage()方法的分析,接著會去檢查mCallback是否為null,不為null,則調(diào)用mCallback的handleMessage方法來處理消息。至于Callback則就是一個接口定義如下:

/** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. * * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public interface Callback { public boolean handleMessage(Message msg); }

這個接口有什么用呢?其實(shí)通過Callback接口我們就可以采取如下方法來創(chuàng)建Handler對象:

Handler handler =new Handler(callback)

那么這樣做到底有什么意義,其實(shí)這樣做可以用callback來創(chuàng)建一個Handler的實(shí)例而無需派生Handler的子類。在我們的開發(fā)過程中,我們經(jīng)常使用的方法就是派生一個Hanlder子類并重寫其handleMessage方法來處理具體的消息,而Callback給我們提供了另外一種方式,那就是當(dāng)我們不想派生子類的時候,可以通過Callback來實(shí)現(xiàn)。繼續(xù)dispatchMessage()方法的分析,最后如果以上條件都不成立的話,就會去調(diào)用Handler的handleMessage方法來處理消息。而 我們的Handler是在主線程創(chuàng)建的,也就是說Looper也是主線程的Looper,因此handleMessage內(nèi)部處理最終都會在主線程上執(zhí)行,就這樣整個流程都執(zhí)行完了。下面提供一個圖解幫助大家理解:

Android,Handler,MessageQueue,Looper

最后我們來個小總結(jié):Android中的Looper類主要作用是來封裝消息循環(huán)和消息隊(duì)列的,用于在android線程中進(jìn)行消息處理。handler是用來向消息隊(duì)列中插入消息的并最好對消息進(jìn)行處理。

(1) Looper類主要是為每個線程開啟的單獨(dú)的消息循環(huán)。 默認(rèn)情況下android中新誕生的線程是沒有開啟消息循環(huán)的。(主線程除外,主線程系統(tǒng)會自動為其創(chuàng)建Looper對象,開啟消息循環(huán)) Looper對象負(fù)責(zé)管理MessageQueue,而MessageQueue主要是用來存放handler發(fā)送的消息,而且一個線程只能有一個Looper,對應(yīng)一個MessageQueue。

(2) 我們通常是通過Handler對象來與Looper進(jìn)行交互的。Handler可看做是Looper的一個接口,用來向指定的Looper中的MessageQueue發(fā)送消息并且Handler還必須定義自己的處理方法。 默認(rèn)情況下Handler會與其被定義時所在線程的Looper綁定,如Handler在主線程中定義,它是與主線程的Looper綁定。 mainHandler = new Handler() 等價于 new Handler(Looper.myLooper())Looper.myLooper():獲取當(dāng)前進(jìn)程的looper對象, Looper.getMainLooper() 用于獲取主線程的Looper對象。

(3) 在非主線程中直接new Handler() 會報如下的錯誤: Can't create handler inside thread that has not called Looper.prepare() 原因是非主線程中默認(rèn)沒有創(chuàng)建Looper對象,需要先調(diào)用Looper.prepare()啟用Looper,然后再調(diào)用Looper.loop()。

(4) Looper.loop():啟動looper中的循環(huán)線程,Handler就會從消息隊(duì)列里取消息并進(jìn)行對應(yīng)處理。 最后要注意的是寫在Looper.loop()之后的代碼不會被執(zhí)行,這個函數(shù)內(nèi)部應(yīng)該是一個循環(huán),當(dāng)調(diào)用mHandler.getLooper().quit()后,loop()才會中止,其后的代碼才能得以運(yùn)行。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持VEVB武林網(wǎng)。


注:相關(guān)教程知識閱讀請移步到Android開發(fā)頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 额敏县| 怀化市| 宾阳县| 玉树县| 廊坊市| 弥渡县| 广灵县| 康马县| 繁昌县| 福贡县| 泗阳县| 绥阳县| 漳平市| 绩溪县| 剑河县| 湟源县| 藁城市| 开江县| 崇明县| 上饶市| 张家川| 高陵县| 民丰县| 忻州市| 伊通| 肥西县| 金华市| 莒南县| 阿图什市| 彭州市| 昭觉县| 连江县| 韶关市| 安塞县| 桐梓县| 应用必备| 霞浦县| 锦州市| 道真| 普兰县| 松溪县|