Android基礎總結(7)——非同步訊息處理
服務(Service)是Android中實現程式後臺執行的解決方案,它非常適合用於去執行哪些不需要和使用者互動而且還要長期執行的任務。服務的執行不依賴任何使用者介面,即使當程式被切換到後臺,或者使用者打開了另外一個應用程式,服務仍然能夠保持正常執行。不過需要注意的是:服務並不是執行在一個獨立的程序當中,而是依賴於建立服務的應用程式程序,當某個應用程式程序被殺掉時,所有依賴於該程序的服務也會停止執行。
此外,我們也不要被服務的後臺概念所迷惑,實際上服務並不會自動開啟執行緒,所有的程式碼都是預設執行在主執行緒中。也就是說,我們需要自己手動地區建立子執行緒,並在這裡執行具體的任務,否則就有可能出現主執行緒被阻塞住的情況
1、執行緒的基本用法
Android多執行緒的程式設計其實並不比Java多執行緒特殊,基本都是使用相同的語法。常用的三種建立執行緒的方法可以參考自:http://www.cnblogs.com/mukekeheart/p/5709894.html
和許多其他的GUI庫一樣,Android的UI也是執行緒不安全的。也就是說,如果想要更新應用程式中的UI元素,則必須在主執行緒中進行,否則就會出現異常。
但是有些時候,我們必須利用多執行緒過去執行一些耗時任務,然後根據任務執行的結果開更新相應的UI控制元件,那麼,Android提供了兩種方法來解決UI操作存在的問題:
- 非同步訊息處理機制,完美解決了在子執行緒中進行UI操作的問題。
- 使用AsyncTask
2、非同步訊息機制原理
Android中的非同步訊息機制主要由四部分組成:Message、Handler、MessageQueue、Looper。
- Message:是線上程之間傳遞的訊息,它可以在內部攜帶少量的資訊(內部的欄位有int型別的what、arg1和arg2欄位,以及Object型別的obj欄位),用於不同執行緒之間交換資料。
-
Handler:顧名思義是處理者的意思,主要用於傳送和處理訊息。傳送者用Handler的sendMessage()方法傳送訊息,而發出的訊息經過一系列輾轉處理後,最終會傳遞到Handler的handMessage(Message msg)方法中
- MessageQueue:訊息佇列的意思,主要用於存放所有通過Handler傳送的訊息。這部分訊息會一直存放在訊息佇列中,等待被處理。每個執行緒只會有一個MessageQueue物件。
- Looper:是每個執行緒彙總MessageQueue的管家,呼叫Looper的loop()方法之後,就會進入一個無限迴圈之中,然後每當MessageQueue中存在一條訊息,就會將其取出,並傳遞到Handler的handMessage(Message msg)方法中。每個執行緒也只會有一個Looper物件。
所以非同步訊息機制的整體流程就是:
- 首先需要在主程式中建立一個Handler物件,並重寫handleMessage(Message msg)方法;
- 然後當子執行緒中需要進行UI操作時,就建立一個Message物件,並通過Handler的sendMessage()方法將訊息傳送出去;
- 之後這條訊息會被新增到MessageQueue的佇列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出帶處理訊息,最後分發回Handler的handleMessage(Message msg)方法中,由於Handler物件是在主程式中建立的,所以,此時handleMessage(Message msg)方法中的程式碼也是在主程式中執行,這樣我們就可以安心地進行UI操作了。
1 public class MainActivity extends Activity {
2
3 Button button ;
4 public static final int UPDATE = 1 ;
5
6 private Handler handler = new Handler(){
7 public void handleMessage(Message msg){
8 switch(msg.what){
9 //根據訊息型別不同進行不同的處理
10 case UPDATE :
11 //執行相關的UI操作
12 }
13 }
14 };
15
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 setContentView(R.layout.activity_main);
20
21 button = (Button) findViewById(R.id.button1) ;
22
23 button.setOnClickListener(new OnClickListener() {
24 @Override
25 public void onClick(View v) {
26
27 new Thread(new Runnable(){
28 @Override
29 public void run() {
30 //相關操作執行完之後,需要用到UI操作時
31 Message msg = new Message() ;
32 msg.what = UPDATE ;
33 handler.sendMessage(msg) ;
34 }
35
36 }).start(); ;
37 }
38 });
39
40 }
41 }
3、使用AsyncTask
為了更加方百年我們在子執行緒中對UI進行操作,Android還提供了另外一些好用的工具,AsyncTask就是其中之一。藉助AsyncTask,即使你對非同步訊息處理機制完全不理解,你也可以十分簡單地從子執行緒中切換到主執行緒中。當然,AsyncTask背後的實現原理也是基於非同步訊息處理機制的,只是Android幫我們做了很好的封裝而已。
AsyncTask的基本使用方法,首先AsyncTask是一個抽象類,我們要使用它就必須要建立一個AsyncTask的子類去繼承它。在繼承時我們可以為public abstract class AsyncTask<Params, Progress, Result>類指定三個泛型引數,這三個引數的用法如下:
- Params:在執行AsyncTask時需傳入的引數,可用於在後臺任務重使用。即傳入的引數型別
- Progess:後臺執行程式時,需要在介面上顯示當前的進度,則使用這裡指定的泛型作為進度單位。即進度顯示引數型別
- Result:當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值型別。即返回的引數型別
此外,對於AsyncTask,我們通常需要去重寫AsyncTask類下的四個方法:
- protected void onPreExecute():這個方法會在後臺任務開始執行之前呼叫,用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊等
- protected abstract Result doInBackground(Params... params):抽象方法,必須重寫的。這個方法中的所有程式碼都會在子執行緒中執行,我們應該在這裡處理所有的耗時任務操作。任務一旦完成可以通過return語句將任務的執行結果返回,如果AsyncTask的第三個泛型引數指定為void,就可以不用返回執行結果。注意,這個方法是不可以進行UI操作的。如果需要更新UI元素,比如說反饋當前任務執行進度,可以呼叫publishProgress(Progress ..)方法來完成。
- protected void onProgressUpdate(Progress... values):當在後臺中呼叫publishProgress(Progress ..)方法之後,這個方法很快就會被呼叫了,方法中攜帶的引數就是在後臺任務重傳遞過來的。在這個方法中可以對UI進行操作,利用引數中的數值可以對介面進行相應的更新。
- protected void onPostExecute(Result result):當後臺任務執行完畢並通過return語句進行返回時,這個方法很快就會被呼叫。返回的資料會作為引數傳遞到此方法中,可以利用返回的資料來進行一些UI操作,比如說提醒任務執行的結果,以及關閉進度條對話方塊等。
一個非同步任務的執行一般包括以下幾個步驟:
- execute(Params... params)執行一個非同步任務,需要我們在程式碼中呼叫此方法,觸發非同步任務的執行。
- onPreExecute(),在execute(Params... params)被呼叫後立即執行,一般用來在執行後臺任務前對UI做一些標記。
- doInBackground(Params... params),在onPreExecute()完成後立即執行,用於執行較為費時的操作,此方法將接收輸入引數和返回計算結果。在執行過程中可以呼叫publishProgress(Progress... values)來更新進度資訊。
- onProgressUpdate(Progress... values),在呼叫publishProgress(Progress... values)時,此方法被執行,直接將進度資訊更新到UI元件上。
- onPostExecute(Result result),當後臺操作結束時,此方法將會被呼叫,計算結果將做為引數傳遞到此方法中,直接將結果顯示到UI元件上。
在使用的時候,有幾點需要格外注意:
- 非同步任務的例項必須在UI執行緒中建立。
- execute(Params... params)方法必須在UI執行緒中呼叫。
- 不要手動呼叫onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)這幾個方法。
- 不能在doInBackground(Params... params)中更改UI元件的資訊。
- 一個任務例項只能執行一次,如果執行第二次將會丟擲異常。
示例程式碼,首先我們看一下我們的佈局檔案,
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent">
6 <Button
7 android:id="@+id/execute"
8 android:layout_width="fill_parent"
9 android:layout_height="wrap_content"
10 android:text="execute"/>
11 <Button
12 android:id="@+id/cancel"
13 android:layout_width="fill_parent"
14 android:layout_height="wrap_content"
15 android:enabled="false"
16 android:text="cancel"/>
17 <ProgressBar
18 android:id="@+id/progress_bar"
19 android:layout_width="fill_parent"
20 android:layout_height="wrap_content"
21 android:progress="0"
22 android:max="100"
23 style="?android:attr/progressBarStyleHorizontal"/>
24 <ScrollView
25 android:layout_width="fill_parent"
26 android:layout_height="wrap_content">
27 <TextView
28 android:id="@+id/text_view"
29 android:layout_width="fill_parent"
30 android:layout_height="wrap_content"/>
31 </ScrollView>
32 </LinearLayout>
下面是我們的Activity程式碼:
3 import java.io.ByteArrayOutputStream; 21
22 public class MainActivity extends Activity {
24 private static final String TAG = "ASYNC_TASK";
25
26 private Button execute;
27 private Button cancel;
28 private ProgressBar progressBar;
29 private TextView textView;
31 private MyTask mTask;
32
33 @Override
34 public void onCreate(Bundle savedInstanceState) {
35 super.onCreate(savedInstanceState);
36 setContentView(R.layout.main);
37
38 execute = (Button) findViewById(R.id.execute);
39 execute.setOnClickListener(new View.OnClickListener() {
40 @Override
41 public void onClick(View v) {
42 //注意每次需new一個例項,新建的任務只能執行一次,否則會出現異常
43 mTask = new MyTask();
44 mTask.execute("http://www.baidu.com");
45
46 execute.setEnabled(false);
47 cancel.setEnabled(true);
48 }
49 });
50 cancel = (Button) findViewById(R.id.cancel);
51 cancel.setOnClickListener(new View.OnClickListener() {
52 @Override
53 public void onClick(View v) {
54 //取消一個正在執行的任務,onCancelled方法將會被呼叫
55 mTask.cancel(true);
56 }
57 });
58 progressBar = (ProgressBar) findViewById(R.id.progress_bar);
59 textView = (TextView) findViewById(R.id.text_view);
60
61 }
62
63 private class MyTask extends AsyncTask<String, Integer, String> {
64 //onPreExecute方法用於在執行後臺任務前做一些UI操作
65 @Override
66 protected void onPreExecute() {
67 Log.i(TAG, "onPreExecute() called");
68 textView.setText("loading...");
69 }
70
71 //doInBackground方法內部執行後臺任務,不可在此方法內修改UI
72 @Override
73 protected String doInBackground(String... params) {
74 Log.i(TAG, "doInBackground(Params... params) called");
75 try {
76 HttpClient client = new DefaultHttpClient();
77 HttpGet get = new HttpGet(params[0]);
78 HttpResponse response = client.execute(get);
79 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
80 HttpEntity entity = response.getEntity();
81 InputStream is = entity.getContent();
82 long total = entity.getContentLength();
83 ByteArrayOutputStream baos = new ByteArrayOutputStream();
84 byte[] buf = new byte[1024];
85 int count = 0;
86 int length = -1;
87 while ((length = is.read(buf)) != -1) {
88 baos.write(buf, 0, length);
89 count += length;
90 //呼叫publishProgress公佈進度,最後onProgressUpdate方法將被執行
91 publishProgress((int) ((count / (float) total) * 100));
92 //為了演示進度,休眠500毫秒
93 Thread.sleep(500);
94 }
95 return new String(baos.toByteArray(), "gb2312");
96 }
97 } catch (Exception e) {
98 Log.e(TAG, e.getMessage());
99 }
100 return null;
101 }
102
103 //onProgressUpdate方法用於更新進度資訊
104 @Override
105 protected void onProgressUpdate(Integer... progresses) {
106 Log.i(TAG, "onProgressUpdate(Progress... progresses) called");
107 progressBar.setProgress(progresses[0]);
108 textView.setText("loading..." + progresses[0] + "%");
109 }
110
111 //onPostExecute方法用於在執行完後臺任務後更新UI,顯示結果
112 @Override
113 protected void onPostExecute(String result) {
114 Log.i(TAG, "onPostExecute(Result result) called");
115 textView.setText(result);
116
117 execute.setEnabled(true);
118 cancel.setEnabled(false);
119 }
120
121 //onCancelled方法用於在取消執行中的任務時更改UI
122 @Override
123 protected void onCancelled() {
124 Log.i(TAG, "onCancelled() called");
125 textView.setText("cancelled");
126 progressBar.setProgress(0);
127
128 execute.setEnabled(true);
129 cancel.setEnabled(false);
130 }
131 }
132 }
因為需要訪問網路,所以我們還需要在AndroidManifest.xml中加入訪問網路的許可權。
我們來看一下執行時的介面:
以上幾個截圖分別是初始介面、執行非同步任務時介面、執行成功後介面、取消任務後介面。執行成功後,整個過程日誌列印如下:
如果我們在執行任務時按下了“cancel”按鈕,日誌列印如下:
可以看到onCancelled()方法將會被呼叫,onPostExecute(Result result)方法將不再被呼叫。
上面介紹了AsyncTask的基本應用,有些朋友也許會有疑惑,AsyncTask內部是怎麼執行的呢,它執行的過程跟我們使用Handler又有什麼區別呢?答案是:AsyncTask是對Thread+Handler良好的封裝,在android.os.AsyncTask程式碼裡仍然可以看到Thread和Handler的蹤跡。下面就向大家詳細介紹一下AsyncTask的執行原理。
我們先看一下AsyncTask的大綱檢視:
我們可以看到關鍵幾個步驟的方法都在其中,doInBackground(Params... params)是一個抽象方法,我們繼承AsyncTask時必須覆寫此方法;onPreExecute()、onProgressUpdate(Progress... values)、onPostExecute(Result result)、onCancelled()這幾個方法體都是空的,我們需要的時候可以選擇性的覆寫它們;publishProgress(Progress... values)是final修飾的,不能覆寫,只能去呼叫,我們一般會在doInBackground(Params... params)中呼叫此方法;另外,我們可以看到有一個Status的列舉類和getStatus()方法,Status列舉類程式碼段如下:
1 //初始狀態
2 private volatile Status mStatus = Status.PENDING;
3
4 public enum Status {
5 /**
6 * Indicates that the task has not been executed yet.
7 */
8 PENDING,
9 /**
10 * Indicates that the task is running.
11 */
12 RUNNING,
13 /**
14 * Indicates that {@link AsyncTask#onPostExecute} has finished.
15 */
16 FINISHED,
17 }
18
19 /**
20 * Returns the current status of this task.
21 *
22 * @return The current status.
23 */
24 public final Status getStatus() {
25 return mStatus;
26 }
可以看到,AsyncTask的初始狀態為PENDING,代表待定狀態,RUNNING代表執行狀態,FINISHED代表結束狀態,這幾種狀態在AsyncTask一次生命週期內的很多地方被使用,非常重要。
介紹完大綱檢視相關內容之後,接下來,我們會從execute(Params... params)作為入口,重點分析一下AsyncTask的執行流程,我們來看一下execute(Params... params)方法的程式碼段:
1 public final AsyncTask<Params, Progress, Result> execute(Params... params) {
2 if (mStatus != Status.PENDING) {
3 switch (mStatus) {
4 case RUNNING:
5 //如果該任務正在被執行則丟擲異常
6 //值得一提的是,在呼叫cancel取消任務後,狀態仍未RUNNING
7 throw new IllegalStateException("Cannot execute task:"
8 + " the task is already running.");
9 case FINISHED:
10 //如果該任務已經執行完成則丟擲異常
11 throw new IllegalStateException("Cannot execute task:"
12 + " the task has already been executed "
13 + "(a task can be executed only once)");
14 }
15 }
16
17 //改變狀態為RUNNING
18 mStatus = Status.RUNNING;
20 //呼叫onPreExecute方法
21 onPreExecute();
22
23 mWorker.mParams = params;
24 sExecutor.execute(mFuture);
26 return this;
27 }
程式碼中涉及到三個陌生的變數:mWorker、sExecutor、mFuture,我們也會看一下他們的廬山真面目:
- 關於sExecutor,它是java.util.concurrent.ThreadPoolExecutor的例項,用於管理執行緒的執行。程式碼如下:
1 private static final int CORE_POOL_SIZE = 5;
2 private static final int MAXIMUM_POOL_SIZE = 128;
3 private static final int KEEP_ALIVE = 10;
4
5 //新建一個佇列用來存放執行緒
6 private static final BlockingQueue<Runnable> sWorkQueue =
7 new LinkedBlockingQueue<Runnable>(10);
8 //新建一個執行緒工廠
9 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
10 private final AtomicInteger mCount = new AtomicInteger(1);
11 //新建一個執行緒
12 public Thread newThread(Runnable r) {
13 return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
14 }
15 };
16 //新建一個執行緒池執行器,用於管理執行緒的執行
17 private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
18 MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
- mWorker實際上是AsyncTask的一個的抽象內部類的實現物件例項,它實現了Callable<Result>介面中的call()方法,程式碼如下:
1 private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
2 Params[] mParams;
3 }
- 而mFuture實際上是java.util.concurrent.FutureTask的例項,下面是它的FutureTask類的相關資訊:
1 /**
2 * A cancellable asynchronous computation.
3 * ...
4 */
5 public class FutureTask<V> implements RunnableFuture<V> {
1 public interface RunnableFuture<V> extends Runnable, Future<V> {
2 /**
3 * Sets this Future to the result of its computation
4 * unless it has been cancelled.
5 */
6 void run();
7 }
可以看到FutureTask是一個可以中途取消的用於非同步計算的類。
下面是mWorker和mFuture例項在AsyncTask中的體現:
1 private final WorkerRunnable<Params, Result> mWorker;
2 private final FutureTask<Result> mFuture;
3
4 public AsyncTask() {
5 mWorker = new WorkerRunnable<Params, Result>() {
6 //call方法被呼叫後,將設定優先順序為後臺級別,然後呼叫AsyncTask的doInBackground方法
7 public Result call() throws Exception {
8 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
9 return doInBackground(mParams);
10 }
11 };
12
13 //在mFuture例項中,將會呼叫mWorker做後臺任務,完成後會呼叫done方法
14 mFuture = new FutureTask<Result>(mWorker) {
15 @Override
16 protected void done() {
17 Message message;
18 Result result = null;
19
20 try {
21 result = get();
22 } catch (InterruptedException e) {
23 android.util.Log.w(LOG_TAG, e);
24 } catch (ExecutionException e) {
25 throw new RuntimeException("An error occured while executing doInBackground()",
26 e.getCause());
27 } catch (CancellationException e) {
28 //傳送取消任務的訊息
29 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
30 new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
31 message.sendToTarget();
32 return;
33 } catch (Throwable t) {
34 throw new RuntimeException("An error occured while executing "
35 + "doInBackground()", t);
36 }
37
38 //傳送顯示結果的訊息
39 message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
40 new AsyncTaskResult<Result>(AsyncTask.this, result));
41 message.sendToTarget();
42 }
43 };
44 }
我們看到上面的程式碼中,mFuture例項物件的done()方法中,如果捕捉到了CancellationException型別的異常,則傳送一條“MESSAGE_POST_CANCEL”的訊息;如果順利執行,則傳送一條“MESSAGE_POST_RESULT”的訊息,而訊息都與一個sHandler物件關聯。這個sHandler例項實際上是AsyncTask內部類InternalHandler的例項,而InternalHandler正是繼承了Handler,下面我們來分析一下它的程式碼:
private static final int MESSAGE_POST_RESULT = 0x1; //顯示結果
private static final int MESSAGE_POST_PROGRESS = 0x2; //更新進度
private static final int MESSAGE_POST_CANCEL = 0x3; //取消任務
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
//呼叫AsyncTask.finish方法
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
//呼叫AsyncTask.onProgressUpdate方法
result.mTask.onProgressUpdate(result.mData);
break;
case MESSAGE_POST_CANCEL:
//呼叫AsyncTask.onCancelled方法
result.mTask.onCancelled();
break;
}
}
}
我們看到,在處理訊息時,遇到“MESSAGE_POST_RESULT”時,它會呼叫AsyncTask中的finish()方法,我們來看一下finish()方法的定義:
1 private void finish(Result result) {
2 if (isCancelled()) result = null;
3 onPostExecute(result); //呼叫onPostExecute顯示結果
4 mStatus = Status.FINISHED; //改變狀態為FINISHED
5 }
原來finish()方法是負責呼叫onPostExecute(Result result)方法顯示結果並改變任務狀態的啊。
另外,在mFuture物件的done()方法裡,構建一個訊息時,這個訊息包含了一個AsyncTaskResult型別的物件,然後在sHandler例項物件的handleMessage(Message msg)方法裡,使用下面這種方式取得訊息中附帶的物件:
1 AsyncTaskResult result = (AsyncTaskResult) msg.obj;
這個AsyncTaskResult究竟是什麼呢,它又包含什麼內容呢?其實它也是AsyncTask的一個內部類,是用來包裝執行結果的一個類,讓我們來看一下它的程式碼結構:
1 @SuppressWarnings({"RawUseOfParameterizedType"})
2 private static class AsyncTaskResult<Data> {
3 final AsyncTask mTask;
4 final Data[] mData;
5
6 AsyncTaskResult(AsyncTask task, Data... data) {
7 mTask = task;
8 mData = data;
9 }
10 }
看以看到這個AsyncTaskResult封裝了一個AsyncTask的例項和某種型別的資料集,我們再來看一下構建訊息時的程式碼:
1 //傳送取消任務的訊息
2 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
3 new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
4 message.sendToTarget();
1 //傳送顯示結果的訊息
2 message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
3 new AsyncTaskResult<Result>(AsyncTask.this, result));
4 message.sendToTarget();
在處理訊息時是如何使用這個物件呢,我們再來看一下:
1 result.mTask.finish(result.mData[0]);
1 result.mTask.onProgressUpdate(result.mData);
概括來說,當我們呼叫execute(Params... params)方法後,execute方法會呼叫onPreExecute()方法,然後由ThreadPoolExecutor例項sExecutor執行一個FutureTask任務,這個過程中doInBackground(Params... params)將被呼叫,如果被開發者覆寫的doInBackground(Params... params)方法中呼叫了publishProgress(Progress... values)方法,則通過InternalHandler例項sHandler傳送一條MESSAGE_POST_PROGRESS訊息,更新進度,sHandler處理訊息時onProgressUpdate(Progress... values)方法將被呼叫;如果遇到異常,則傳送一條MESSAGE_POST_CANCEL的訊息,取消任務,sHandler處理訊息時onCancelled()方法將被呼叫;如果執行成功,則傳送一條MESSAGE_POST_RESULT的訊息,顯示結果,sHandler處理訊息時onPostExecute(Result result)方法被呼叫。
經過上面的介紹,相信朋友們都已經認識到AsyncTask的本質了,它對Thread+Handler的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。