Android 中的異步任務經常使用的一種方式是:Handler + Thread 組合來實現的。Thread 負責子線程的耗時操做,Handler 負責線程間的通訊,用的最多的當屬子線程和主線程通訊。java
Android 爲了簡化操做,提供了 AsyncTask 類來實現異步任務,而且輕鬆實現子線程和主線程間的通訊。android
三個參數表明的含義git
package com.app; import android.os.AsyncTask; /** * Created by ${zyj} on 2016/8/2. */ public class MyTask<T> extends AsyncTask<T , Integer , T> { private TaskListener taskListener ; public MyTask(){ } //執行預處理,它運行於UI線程,能夠爲後臺任務作一些準備工做,好比繪製一個進度條控件。 @Override protected void onPreExecute() { if ( taskListener != null ){ taskListener.start(); } } //運行於UI線程,能夠對後臺任務的結果作出處理,結果就是doInBackground(Params...)的返回值。 @Override protected void onPostExecute(T t) { if ( taskListener != null ){ taskListener.result( t ); } } /** * 更新子線程進度,運行於UI線程 * @param values */ @Override protected void onProgressUpdate(Integer... values) {; if ( taskListener != null ){ taskListener.update( values[0] ); } } //運行與後臺線程 @Override protected T doInBackground(T... ts) { if ( taskListener != null ){ return (T) taskListener.doInBackground( ts[0] ) ; } return null; } public MyTask setTaskListener(TaskListener taskListener ){ this.taskListener = taskListener ; return this ; } /** * 更新進度 * @param progress */ public void updateProgress( int progress ){ publishProgress( progress ); } public interface TaskListener<T>{ void start() ; void update( int progress ) ; T doInBackground( T t ); void result( T t ); } /** * 取消一個正在執行的任務 */ public void cancle(){ if ( !isCancelled() ){ cancel( true ) ; } } }
package com.app; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import wifi.app.wei.com.myapplication.R; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new MyTask<String>().setTaskListener(new MyTask.TaskListener() { @Override public void start() { Log.d( "task--" , "start 開始了, 運行在主線程" ) ; } @Override public void update(int progress) { } @Override public Object doInBackground(Object o) { Log.d( "task--" , "doInBackground , 運行在子線程" ) ; return null; } @Override public void result(Object o) { Log.d( "task--" , "result , 運行在主線程" ) ; } }).execute( "" ) ; } }
package com.app; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.TextView; import wifi.app.wei.com.myapplication.R; public class MainActivity extends AppCompatActivity { private TextView textView ; private MyTask myTask ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById( R.id.tv1 ); myTask = new MyTask<String>().setTaskListener(new MyTask.TaskListener() { @Override public void start() { Log.d( "task--" , "start 開始了, 運行在主線程" ) ; textView.setText( "任務開始了" ); } @Override public void update(int progress) { textView.setText( "進度" + progress ); } @Override public Object doInBackground(Object o) { Log.d( "task--" , "doInBackground , 運行在子線程" ) ; for ( int i = 0 ; i < 100 ; i++ ){ try { Thread.sleep( 100 ) ; myTask.updateProgress( i ) ; //每隔100毫秒,更新一下進度 } catch (InterruptedException e) { e.printStackTrace(); } } return "結束了"; } @Override public void result(Object o) { Log.d( "task--" , "result , 運行在主線程" ) ; textView.setText( "" + o ); } }) ; //開始執行任務 myTask.execute( "" ) ; } }
執行效果圖github
(1)、若是異步任務須要聯網,則須要添加聯網權限緩存
<uses-permission android:name="android.permission.INTERNET"/>微信
(2)、AsyncTask實例必須在UI線程中建立,execute(Params…)方法必須在UI線程中調用。不用手動調用onPreExecute()。併發
(3)、一個任務只能被執行一次app
能夠調用 myTask.cancle() ; 框架
可是這個方法並無真正的結束任務,只是設置了一個標誌位,把當前線程中斷了。異步
取消任務實踐
package com.app; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.TextView; import wifi.app.wei.com.myapplication.R; public class MainActivity extends AppCompatActivity { private TextView textView ; private MyTask myTask ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById( R.id.tv1 ); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //取消任務 myTask.cancle(); } }); myTask = new MyTask<String>().setTaskListener(new MyTask.TaskListener() { @Override public void start() { Log.d( "task--" , "start 開始了, 運行在主線程" ) ; textView.setText( "任務開始了" ); } @Override public void update(int progress) { textView.setText( "進度" + progress ); } @Override public Object doInBackground(Object o) { Log.d( "task--" , "doInBackground , 運行在子線程" ) ; for ( int i = 0 ; i < 100 ; i++ ){ try { Thread.sleep( 100 ) ; myTask.updateProgress( i ) ; //每隔100毫秒,更新一下進度 } catch (InterruptedException e) { e.printStackTrace(); } } return "結束了"; } @Override public void result(Object o) { Log.d( "task--" , "result , 運行在主線程" ) ; textView.setText( "" + o ); } }) ; //開始執行任務 myTask.execute( "" ) ; } }
當點擊textView時,調用了 myTask.cancle() ;方法後,Android studio 控制檯拋出了異常
經過這裏咱們發現,AsyncTask 雖然提供了cancle( true ) 方法來中止任務,可是這個方法只是中斷了這個線程,可是並不能真正意思上的中止任務,這也是不少人說 AsyncTask 的弊端。極容易形成內存溢出的。
幾種結束任務的間接實現方式:
一、判斷標誌位的辦法:
咱們要知道在java的線程中,沒有辦法中止一個正在運行中的線程。在Android的AsyncTask中也是同樣的。若是必需要中止一個線程,咱們能夠採用這個線程中設置一個標誌位,而後在線程run方法或AsyncTask的doInBackground方法中的關鍵步驟判斷這個標誌位以決定是否繼續執行。而後在須要終止此線程的地方改變這個標誌位以達到中止線程的目的。
二、合理的利用Exception
從外部調用AsyncTask的cancel方法並不能中止一個已經啓動的AsyncTask。這個cancel方法的做用與線程的interrupt方法類似,調用了一個線程的interrupt方法以後線程仍然運行,可是若是該線程的run方法裏面調用過sleep的或者wait方法後,處於sleep或wait狀態,則sleep和wait當即結束而且拋出InterruptedException異常。AsyncTask的cancel方法也同樣,若是在這個Task的doInBackground方法中調用了sleep或wait方法,當在UI線程中調用了這個Task實例的cancel方法以後,sleep或wait當即結束而且拋出InterruptedException異常,可是若是捕獲該異常的代碼後面還有其餘代碼,則這些代碼還會繼續執行。
三、能夠在UI上作手腳
若是用戶在後臺線程正獲取內容時作出了取消的行爲,咱們能夠根據用戶的這種行爲在UI上當即作出反饋,此時,即便線程完成了數據的Loading,咱們也不讓數據顯示出來,算是一種投機取巧的辦法吧。
在上面的代碼演示中,執行任務用的都是 myTask.execute() , 這個默認是串行執行任務的。好比同一時刻有兩個任務要處理,AsyncTask 會先執行第一個任務,等第一個任務執行結束,而後纔會執行第二個任務。
在AsyncTask中還有一個並行處理任務的方法:executeOnExecutor( Executor exe , Params... params ) 。
下面是串行執行任務execute()的源碼
經過看源碼,發現其實串行執行任務也是調用了並行的方法 executeOnExecutor () , 只不過啓用了一個默認的 sDefaultExecutor (sDefaultExecutor 是一個串行的線程池)。
有串行線程池,那麼勢必就有一個並行線程池 , 在AsyncTask裏面源碼裏面定義了一個並行線程池: THREAD_POOL_EXECUTOR 。
能夠看到並行 THREAD_POOL_EXECUTOR 是經過 new ThreadPoolExecutor() 來建立的
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
參數說明:
corePoolSize: 線程池維護線程的最少數量
maximumPoolSize:線程池維護線程的最大數量
keepAliveTime: 線程池維護線程所容許的空閒時間
unit: 線程池維護線程所容許的空閒時間的單位
workQueue: 線程池所使用的緩衝隊列
handler: 線程池對拒絕任務的處理策略
咱們知道,受限於硬件、內存和性能,咱們不可能無限制的建立任意數量的線程,由於每一臺機器容許的最大線程是一個有界值。也就是說ThreadPoolExecutor管理的線程數量是有界的。線程池就是用這些有限個數的線程,去執行提交的任務。然而對於多用戶、高併發的應用來講,提交的任務數量很是巨大,必定會比容許的最大線程數多不少。爲了解決這個問題,必需要引入排隊機制,或者是在內存中,或者是在硬盤等容量很大的存儲介質中。J.U.C提供的ThreadPoolExecutor只支持任務在內存中排隊,經過BlockingQueue暫存尚未來得及執行的任務。
任務的管理是一件比較容易的事,複雜的是線程的管理,這會涉及線程數量、等待/喚醒、同步/鎖、線程建立和死亡等問題。ThreadPoolExecutor與線程相關的幾個成員變量是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它們共同負責線程的建立和銷燬。
corePoolSize:
線程池的基本大小,即在沒有任務須要執行的時候線程池的大小,而且只有在工做隊列滿了的狀況下才會建立超出這個數量的線程。這裏須要注意的是:在剛剛建立ThreadPoolExecutor的時候,線程並不會當即啓動,而是要等到有任務提交時纔會啓動,除非調用了prestartCoreThread/prestartAllCoreThreads事先啓動核心線程。再考慮到keepAliveTime和allowCoreThreadTimeOut超時參數的影響,因此沒有任務須要執行的時候,線程池的大小不必定是corePoolSize。
maximumPoolSize:
線程池中容許的最大線程數,線程池中的當前線程數目不會超過該值。若是隊列中任務已滿,而且當前線程個數小於maximumPoolSize,那麼會建立新的線程來執行任務。這裏值得一提的是largestPoolSize,該變量記錄了線程池在整個生命週期中曾經出現的最大線程個數。爲何說是曾經呢?由於線程池建立以後,能夠調用setMaximumPoolSize()改變運行的最大線程的數目。
poolSize:
線程池中當前線程的數量,當該值爲0的時候,意味着沒有任何線程,線程池會終止;同一時刻,poolSize不會超過maximumPoolSize。
keepAliveTime
當線程空閒時間達到keepAliveTime,該線程會退出,直到線程數量等於corePoolSize。若是allowCoreThreadTimeout設置爲true,則全部線程均會退出直到線程數量爲0。
瞭解了各個參數的含義以後,咱們來看看 AsyncTask 中默認的並行線程隊列 THREAD_POOL_EXECUTOR 各項的數值
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
小測試:我手上的手機是聯想 k50-t5 , 在設置裏面看處處理器爲 8 核1.7GHZ , 運行 Runtime.getRuntime().availableProcessors(); 方法獲得的值爲:8 。
另外咱們也能夠總結出:
總結:
//開始執行 串行任務 myTask.execute( "" ) ; 或者 myTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR , "" ) ; //開始執行 並行任務 myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;
上一部分咱們已經明白了AsyncTask 的默認並行線程池 THREAD_POOL_EXECUTOR 是經過 new ThreadPoolExecutor() 來建立的 , 那麼咱們也能夠本身定義一個線程池。
首先來看 ThreadPoolExecutor 的構造函數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
經過看構造方法,發現 corePoolSize 、maximunPoolSize 、keepAliveTime 、unit 、workQueue 是必需要寫的。
分析最後一個構造
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException();
corePoolSize :最小值 0
maximunPoolSize :最小值 1
corePoolSize 必須小於或者等於 maximunPoolSize
主要來看 workQueue , 這個是就是線程隊列了。
下面是AsyncTask並行線程池 THREAD_POOL_EXECUTOR 裏面所使用的線程隊列,128 表明線程隊列的長度
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
下面給出一個完整的例子:
//建立緩衝隊列 隊列長度:100 BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(100); //建立線程池 核心線程:5個 最大線程:10個 線程空閒存活時間:1秒 Executor executor = new ThreadPoolExecutor( 5 , 10 , 1 , TimeUnit.SECONDS , sPoolWorkQueue ) ; //添加任務到緩衝隊列 myTask1.executeOnExecutor( executor , "" ) ;
線程建立規則
一個任務經過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是 Runnable類型對象的run()方法。
當一個任務經過execute(Runnable)方法欲添加到線程池時:
一、 若是此時線程池中的數量小於corePoolSize,即便線程池中的線程都處於空閒狀態,也要建立新的線程來處理被添加的任務。
二、 若是此時線程池中的數量等於 corePoolSize,可是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
三、 若是此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,而且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
四、 若是此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,而且線程池中的數量等於maximumPoolSize,那麼經過 handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,若是三者都滿了,使用handler處理被拒絕的任務。
五、 當線程池中的線程數量大於 corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池能夠動態的調整池中的線程數。
線程池按如下行爲執行任務
任務隊列執行的邏輯:
FIFO 先進先出
在第8節咱們詳解了如何自定義線程池,講解了 ThreadPoolExecutor 構造方法的每一個參數的用法,可是若是自定義線程池都要寫那麼多參數,豈不是很麻煩。
幸運的是,系統的 java.util.concurrent 包下面 Executors 類提供了不少簡單的方法,供咱們使用,這對苦逼的碼農來講,是很好的福音。
Executors 裏面的方法有
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool (parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); } public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); } public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory)); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
猛一看,方法那麼多,其實把方法單獨領出來
public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) public static ExecutorService newSingleThreadExecutor() public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) public static ExecutorService newWorkStealingPool() public static ExecutorService newWorkStealingPool(int parallelism) public static ScheduledExecutorService newSingleThreadScheduledExecutor() public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) public static ExecutorService newCachedThreadPool() public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) public static ScheduledExecutorService newScheduledThreadPool ( int corePoolSize, ThreadFactory threadFactory)
這樣一歸類,就清晰不少了,就12個方法,而後每2個方法又能夠歸爲一組,也就是6組。
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
|
這兩個函數用於建立一個最大線程數目固定的線程池,該線程池用一個共享的無界隊列來存儲提交的任務。參數nThreads指定線程池的最大線程數,參數threadFactory是線程工廠類,主要用於自定義線程池中建立新線程時的行爲。須要說明的是,建立線程池時,若是線程池沒有接收到任何任務,則線程池中不會建立新線程,在線程池中線程數目少於最大線程數時,每來一個新任務就建立一個新線程,當線程數達到最大線程數時,再也不建立新線程,新來的任務存儲在隊列中,以後線程數目再也不變化!
建立一個線程數量固定大小,任務隊列無限大的線程池。當隊列中須要的線程數超出定義的線程的時候,全部任務將在隊列中排隊,等待空閒線程執行。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
線程池中的任務使用無界隊列存儲,第二個函數能夠指定threadFactory,自定義建立線程時的行爲。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
這個方法用於建立ForkJoin框架中用到的ForkJoinPool線程池,第一個函數中的參數用於指定並行數,第二個函數沒有參數,它默認使用當前機器可用的CPU個數做爲並行數。
public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }
該方法用於建立只有一個線程的線程池,而且該線程定時週期性地執行給定的任務
須要注意的是: 線程在週期性地執行任務時若是遇到Exception,則之後將再也不周期性地執行任務。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); }
方法用於建立線程數數目能夠隨着實際狀況自動調節的線程池,也有兩種類型的函數簽名:
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
|
這種線程池的最大線程數只受到操做系統能夠建立的最大線程數數目限制,當線程池有不少任務須要處理時,會不斷地建立新線程,當任務處理完畢以後,若是某個線程空閒時間大於60s,則該線程將會被銷燬。由於這種線程池可以自動調節線程數量,因此比較適合執行大量的短時間的小任務。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
建立一個大小無限的線程池,線程池中得線程可以週期性地執行給定的任務,有兩種函數簽名:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
總結:
一、本篇文章的demo例子已上傳至github: https://github.com/zyj1609wz/AsyncTaskDemo
二、本人微信公衆帳號:zhaoyanjun125 , 歡迎關注