1. 程式人生 > >23 個重難點突破,帶你吃透 Service 知識點「長達 1W+ 字」

23 個重難點突破,帶你吃透 Service 知識點「長達 1W+ 字」

前言

  • Android 有一段時間了,想必不少人也和我一樣,平時經常東學西湊,感覺知識點有些凌亂難成體系。所以趁著這幾天忙裡偷閒,把學的東西歸納下,捋捋思路。

這篇文章主要針對 Service 相關的知識點,進行詳細的梳理,祝大家食用愉快!

文章目錄

方便大家學習,我在 GitHub 建立了 倉庫


  • 倉庫內容與部落格同步更新。由於我在 稀土掘金 簡書 CSDN 部落格園 等站點,都有新內容釋出。所以大家可以直接關注該倉庫,以免錯過精彩內容!

  • 倉庫地址:
    超級乾貨!精心歸納 AndroidJVM 、演算法等,各位帥氣的老鐵支援一下!給個 Star !

# 第一篇:Service 是什麼

1.1 什麼是 Service

  • Service (服務) 是一個一種可以在後臺執行長時間執行操作而沒有使用者介面的應用元件。
  • 服務可由其他應用元件啟動(如 Activity ),服務一旦被啟動將在後臺一直執行,即使啟動服務的元件( Activity )已銷燬也不受影響。
  • 此外,元件可以繫結到服務,以與之進行互動,甚至是執行程序間通訊 ( IPC )。

1.2 Service 通常總是稱之為 “後臺服務”

  • 其中 “後臺” 一詞是相對於前臺而言的,具體是指:其本身的執行並不依賴於使用者可視的 UI 介面
  • 因此,從實際業務需求上來理解,Service 的適用場景應該具備以下條件:
  1. 並不依賴於使用者可視的 UI 介面(當然,這一條其實也不是絕對的,如前臺 Service 就是與 Notification 介面結合使用的)

  2. 具有較長時間的執行特性

  3. 注意: 是執行在主執行緒當中的

1.3 服務程序

  • 服務程序是通過 startService() 方法啟動的程序,但不屬於前臺程序和可見程序。例如,在後臺播放音樂或者在後臺下載就是服務程序。

  • 系統保持它們執行,除非沒有足夠記憶體來保證所有的前臺程序和可視程序。

# 第二篇:生命週期

2.1 Service 的生命週期

  • 我們先來看看 Service 的生命週期 的基本流程
  • 一張聞名遐邇的圖

2.2 開啟 Service 的兩種方式

2.2.1 startService()

  1. 定義一個類繼承 Service

  2. Manifest.xml 檔案中配置該 Service

  3. 使用 ContextstartService(intent) 方法開啟服務。

  4. 使用 ContextstopService(intent) 方法關閉服務。

  5. 該啟動方式,app 殺死、Activity 銷燬沒有任何影響,服務不會停止銷燬。

2.2.2 bindService()

  1. 建立 BindService 服務端,繼承 Service 並在類中,建立一個實現 IBinder 介面的例項物件,並提供公共方法給客戶端( Activity )呼叫。

  2. onBinder() 回撥方法返回該 Binder 例項。

  3. 在客戶端( Activity )中, 從 onServiceConnection() 回撥方法引數中接收 Binder ,通過 Binder 物件即可訪問 Service 內部的資料。

  4. manifests 中註冊 BindService , 在客戶端中呼叫 bindService() 方法開啟繫結 Service , 呼叫 unbindService() 方法登出解綁 Service

  5. 該啟動方式依賴於客戶端生命週期,當客戶端 Activity 銷燬時, 沒有呼叫 unbindService() 方法 , Service 也會停止銷燬。

2.3 Service 有哪些啟動方法,有什麼區別,怎樣停用 Service

  • Service 的生命週期中,被回撥的方法比 Activity 少一些,只有 onCreate , onStart , onDestroy , onBindonUnbind

  • 通常有兩種方式啟動一個 Service , 他們對 Service 生命週期的影響是不一樣的。

2.3.1 通過 startService

  • Service 會經歷 onCreateonStart ,然後處於執行狀態,stopService 的時候呼叫 onDestroy
    方法。

如果是呼叫者自己直接退出而沒有呼叫 stopService 的話,Service 會一直在後臺執行。

2.3.2 通過 bindService

Service 會執行 onCreate ,然後是呼叫 onBind , 這個時候呼叫者和 Service 繫結在一起。呼叫者退出了,Srevice 就會呼叫 onUnbind -> onDestroyed 方法。

所謂繫結在一起就共存亡了。呼叫者也可以通過呼叫 unbindService 方法來停止服務,這時候 Srevice 就會呼叫 onUnbind -> onDestroyed 方法。

2.3.3 需要注意的是如果這幾個方法交織在一起的話,會出現什麼情況呢?

  1. 一個原則是 ServiceonCreate 的方法只會被呼叫一次,就是你無論多少次的 startServicebindServiceService 只被建立一次。

  2. 如果先是 bind 了,那麼 start 的時候就直接執行 ServiceonStart 方法,如果先是 start ,那麼 bind 的時候就直接執行 onBind 方法。

  3. 如果 service 執行期間呼叫了 bindService ,這時候再呼叫 stopService 的話,service 是不會呼叫 onDestroy 方法的,servicestop 不掉了,只能呼叫 UnbindService , service 就會被銷燬

  4. 如果一個 service 通過 startServicestart 之後,多次呼叫 startService 的話,service 會多次調
    onStart 方法。多次呼叫 stopService 的話,service 只會呼叫一次 onDestroyed 方法。

  5. 如果一個 service 通過 bindServicestart 之後,多次呼叫 bindService 的話,service 只會呼叫一次 onBind 方法。多次呼叫 unbindService 的話會丟擲異常。

# 第三篇:Service 與 Thread

3.1 Service 和 Thread 的區別

3.1.1 首先第一點定義上

  1. thread 是程式執行的最小單元,他是分配 cpu 的基本單位安卓系統中,我們常說的主執行緒,UI 執行緒,也是執行緒的一種。當然,執行緒裡面還可以執行一些耗時的非同步操作。
  2. service 大家記住,它是安卓中的一種特殊機制,service 是執行在主執行緒當中的,所以說它不能做耗時操作,它是由系統程序託管,其實 service 也是一種輕量級的 IPC 通訊,因為 activity 可以和 service 繫結,可以和 service 進行資料通訊。
  3. 而且有一種情況,activityservice 是處於不同的程序當中,所以說它們之間的資料通訊,要通過 IPC 程序間通訊的機制來進行操作。

3.1.2 第二點是在實際開發的過程當中

  1. 在安卓系統當中,執行緒一般指的是工作執行緒,就是後臺執行緒,做一些耗時操作的執行緒,而主執行緒是一種特殊的執行緒,它只是負責處理一些 UI 執行緒的繪製,UI 執行緒裡面絕對不能做耗時操作,這裡是最基本最重要的一點。(這是 Thread 在實際開發過程當中的應用)
  2. service 是安卓當中,四大元件之一,一般情況下也是執行在主執行緒當中,因此 service 也是不可以做耗時操作的,否則系統會報 ANR 異常( ANR 全稱:Application Not Responding ),就是程式無法做出響應。
  3. 如果一定要在 service 裡面進行耗時操作,一定要記得開啟單獨的執行緒去做。

3.1.3 第三點是應用場景上

  1. 當你需要執行耗時的網路,或者這種檔案資料的查詢,以及其它阻塞 UI 執行緒的時候,都應該使用工作執行緒,也就是開啟一個子執行緒的方式。
  2. 這樣才能保證 UI 執行緒不被佔用,而影響使用者體驗。
  3. service 來說,我們經常需要長時間在後臺執行,而且不需要進行互動的情況下才會使用到服務,比如說,我們在後臺播放音樂,開啟天氣預報的統計,還有一些資料的統計等等。

3.2 為什麼要用 Service 而不是 Thread

  • Thread 的執行是獨立於 Activity 的,也就是當一個 Activityfinish 之後,如果沒有主動停止 Thread 或者 Thread 中的 run 沒有執行完畢時那麼這個執行緒會一直執行下去。
  • 因此這裡會出現一個問題:當 Activityfinish 之後,你不再持有該 Thread 的引用。
  • 另一方面,你沒有辦法在不同的 Activity 中對同一 Thread 進行控制。

3.3 Service 裡面是否能執行耗時的操作

  • service 裡面不能執行耗時的操作(網路請求,拷貝資料庫,大檔案 )

  • Service 不是獨立的程序,也不是獨立的執行緒,它是依賴於應用程式的主執行緒的,也就是說,在更多時候不建議在 Service 中編寫耗時的邏輯和操作(比如:網路請求,拷貝資料庫,大檔案),否則會引起 ANR

  • 如果想在服務中執行耗時的任務。有以下解決方案:

  1. service 中開啟一個子執行緒
new Thread(){}.start();
  1. 可以使用 IntentService 非同步管理服務( 有關 IntentService 的內容在後文中給出 )

3.4 Service 是否在 main thread 中執行

  • 預設情況, 如果沒有顯示的指 service 所執行的程序, Serviceactivity 是運 行在當前 app 所在程序的 main thread ( UI 主執行緒)裡面。
  • ServiceActivity 在同一個執行緒,對於同一 app 來說預設情況下是在同一個執行緒中的 main Thread ( UI Thread )
  • 特殊情況 ,可以在清單檔案配置 service 執行所在的程序 ,讓 service 在另 外的程序中執行 Service 不死之身

3.4.1 在 onStartCommand 方法中將 flag 設定為 START_STICKY ;

<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" >
</service>
return Service.START_STICKY;

3.4.2 在 xml 中設定了 android:priority

<!--設定服務的優先順序為MAX_VALUE-->
 <service android:name=".MyService"
          android:priority="2147483647"
          >
 </service>

3.4.3 在 onStartCommand 方法中設定為前臺程序

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  Notification notification = new Notification(R.mipmap.ic_launcher, "服務正在執行",System.currentTimeMillis());
   Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent,0);
    RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification);
    remoteView.setImageViewResource(R.id.image, R.mipmap.ic_launcher);
    remoteView.setTextViewText(R.id.text , "Hello,this message is in a custom expanded view");
    notification.contentView = remoteView;
    notification.contentIntent = pendingIntent;
    startForeground(1, notification);
    return Service.START_STICKY;
}

3.4.4 在 onDestroy 方法中重啟 service

@Override
public void onDestroy() {
    super.onDestroy();
    startService(new Intent(this, MyService.class));
}

3.4.5 用 AlarmManager.setRepeating(…) 方法迴圈傳送鬧鐘廣播, 接收的時候呼叫 serviceonstart 方法

Intent intent = new Intent(MainActivity.this,MyAlarmReciver.class);
PendingIntent sender = PendingIntent.getBroadcast( MainActivity.this, 0, intent, 0);

// We want the alarm to go off 10 seconds from now.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, 1);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
//重複鬧鐘
/**
 *  @param type
 * @param triggerAtMillis t 鬧鐘的第一次執行時間,以毫秒為單位
 * go off, using the appropriate clock (depending on the alarm type).
 * @param intervalMillis 表示兩次鬧鐘執行的間隔時間,也是以毫秒為單位
 * of the alarm.
 * @param operation 綁定了鬧鐘的執行動作,比如傳送一個廣播、給出提示等等
 */
am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2 * 1000, sender);

3.4.6 目前市場面的很多三方的訊息推送 SDK 喚醒 APP , 例如 Jpush

PS: 以上這些方法並不代表著你的 Service 就永生不死了,只能說是提高了程序的優先順序。迄今為止我沒有發現能夠通過常規方法達到流氓需求 (通過長按 home 鍵清除都清除不掉) 的方法,目前所有方法都是指通過 Android 的記憶體回收機制和普通的第三方記憶體清除等手段後仍然保持執行的方法,有些手機廠商把這些知名的 app 放入了自己的白名單中,保證了程序不死來提高使用者體驗(如微信、QQ 、陌陌都在小米的白名單中)。如果從白名單中移除,他們終究還是和普通 app 一樣躲避不了被殺的命運。

# 第四篇:InterService

  • 作為一個老司機,如果連 Interservice 都沒聽說過,那就有點那個啥了

4.1 什麼是 IntentService

  • IntentServiceService 的子類,比普通的 Service 增加了額外的功能。

  • 我們常用的 Service 存在兩個問題:

  1. Service 不會專門啟動一條單獨的程序,Service 與它所在應用位於同一個程序中

  2. Service 也不是專門一條新執行緒,因此不應該在 Service 中直接處理耗時的任務

4.2 IntentService 的特徵

  • 會建立獨立的 worker 執行緒來處理所有的 Intent 請求

  • 會建立獨立的 worker 執行緒來處理 onHandleIntent() 方法實現的程式碼,無需處理多執行緒問題

  • 所有請求處理完成後,IntentService 會自動停止,無需呼叫 stopSelf() 方法停止 Service

  • ServiceonBind() 提供預設實現,返回 null

  • ServiceonStartCommand 提供預設實現,將請求 Intent 新增到佇列中

4.3 Service 和 IntentService 區別

4.3.1 Service 是用於後臺服務的

  1. 當應用程式被掛到後臺的時候,為了保證應用某些元件仍然可以工作而引入了 Service 這個概念
  2. 那麼這裡面要強調的是:Service 不是獨立的程序,也不是獨立的執行緒,它是依賴於應用程式的主執行緒的,也就是說,在更多時候不建議在 Service 中編寫耗時的邏輯和操作,否則會引起 ANR

也就是,service 裡面不可以進行耗時的操作。雖然在後臺服務。但是也是在主執行緒裡面。

4.3.2 當我們編寫的耗時邏輯,不得不被 service 來管理的時候,就需要引入 IntentService

  1. IntentService 是繼承 Service 的,那麼它包含了 Service 的全部特性,當然也包含 service 的生命週期。
  2. 那麼與 service 不同的是,IntentService 在執行 onCreate 操作的時候,內部開了一個執行緒,去你執行你的耗時操作。

4.3.3 使用:

  1. 重寫 protected abstract void onHandleIntent(Intent intent)

4.3.4 IntentService 是一個通過 Context.startService(Intent) 啟動可以處理非同步請求的 Service

  1. 使用時你只需要繼承 IntentService 和重寫其中的 onHandleIntent(Intent) 方法接收一個 Intent 物件 , 在適當的時候會停止自己 ( 一般在工作完成的時候 ) 。
  2. 所有的請求的處理都在一個工作執行緒中完成 , 它們會交替執行 ( 但不會阻塞主執行緒的執行 ) ,一次只能執行一個請求。

4.3.5 是一個基於訊息的服務

  1. 每次啟動該服務並不是馬上處理你的工作,而是首先會建立對應的 LooperHandler 並且在 MessageQueue 中新增的附帶客戶 IntentMessage 物件。
  2. Looper 發現有 Message 的時候接著得到 Intent 物件通過在 onHandleIntent((Intent)msg.obj) 中呼叫你的處理程式,處理完後即會停止自己的服務。
  3. 意思是 Intent 的生命週期跟你的處理的任務是一致的,所以這個類用下載任務中非常好,下載任務結束後服務自身就會結束退出。

4.3.6 總結 IntentService 的特徵有:

  1. 會建立獨立的 worker 執行緒來處理所有的 Intent 請求;

  2. 會建立獨立的 worker 執行緒來處理 onHandleIntent() 方法實現的程式碼,無需處理多執行緒問題;

  3. 所有請求處理完成後,IntentService會自動停止,無需呼叫 stopSelf() 方法停止 Service

# 第五篇:Service 與 Activity

5.1 Activity 怎麼和 Service 繫結,怎麼在 Activity 中啟動對應的 Service

  • Activity 通過 bindService(Intent service, ServiceConnection conn, int flags)Service 進行繫結,當繫結成功的時候 Service 會將代理物件通過回撥的形式傳給 conn ,這樣我們就拿到了 Service 提供的服務代理物件。

  • Activity 中可以通過 startServicebindService 方法啟動 Service。一般情況下如果想獲取 Service 的服務物件那麼肯定需要通過 bindService() 方法,比如音樂播放器,第三方支付等。

  • 如果僅僅只是為了開啟一個後臺任務那麼可以使用 startService() 方法。

5.2 說說 Activity 、Intent 、Service 是什麼關係

  • 他們都是 Android 開發中使用頻率最高的類。其中 ActivityService 都屬於 Android 的四大元件。他倆都是 Context 類的子類 ContextWrapper 的子類,因此他倆可以算是兄弟關係吧。

  • 不過他們各有各自的本領,Activity 負責使用者介面的顯示和互動,Service 負責後臺任務的處理。

  • ActivityService 之間可以通過 Intent 傳遞資料,因此可以把 Intent 看作是通訊使者。

5.3 Service 和 Activity 在同一個執行緒嗎

對於同一 app 來說預設情況下是在同一個執行緒中的,main ThreadUI Thread )。

5.4 Service 裡面可以彈吐司麼

  • 可以
  • 彈吐司有個條件是:得有一個 Context 上下文,而 Service 本身就是 Context 的子類
  • 因此在 Service 裡面彈吐司是完全可以的。比如我們在 Service 中完成下載任務後可以彈一個吐司通知給使用者。

5.5 與 Service 互動方式

5.5.1 廣播互動

  1. Server 端將目前的下載進度,通過廣播的方式傳送出來,Client 端註冊此廣播的監聽器,當獲取到該廣播後,將廣播中當前的下載進度解析出來並更新到介面上。
  2. 定義自己的廣播,這樣在不同的 ActivityService 以及應用程式之間,就可以通過廣播來實現互動。

5.5.2 共享檔案互動

  1. 我們使用 SharedPreferences 來實現共享,當然也可以使用其它 IO 方法實現,通過這種方式實現互動時需要注意,對於檔案的讀寫的時候,同一時間只能一方讀一方寫,不能兩方同時寫。
  2. Server 端將當前下載進度寫入共享檔案中,Client 端通過讀取共享檔案中的下載進度,並更新到主介面上。

5.5.3 Messenger 互動 ( 信使互動 )

  1. Messenger 翻譯過來指的是信使,它引用了一個 Handler 物件,別人能夠向它傳送訊息 ( 使用 mMessenger.send ( Message msg ) 方法)。
  2. 該類允許跨程序間基於 Message 通訊,在服務端使用 Handler 建立一個 Messenger ,客戶端只要獲得這個服務端的 Messenger 物件就可以與服務端通訊了
  3. Server 端與 Client 端之間通過一個 Messenger 物件來傳遞訊息,該物件類似於資訊中轉站,所有資訊通過該物件攜帶

5.5.4 自定義介面互動

  1. 其實就是我們自己通過介面的實現來達到 ActivityService 互動的目的,我們通過在 ActivityService 之間架設一座橋樑,從而達到資料互動的目的,而這種實現方式和 AIDL 非常類似
  2. 自定義一個介面,該介面中有一個獲取當前下載進度的空方法。Server 端用一個類繼承自 Binder 並實現該介面,覆寫了其中獲取當前下載進度的方法。Client 端通過 ServiceConnection 獲取到該類的物件,從而能夠使用該獲取當前下載進度的方法,最終實現實時互動。

5.5.5 AIDL 互動

  1. 遠端服務一般通過 AIDL 來實現,可以進行程序間通訊,這種服務也就是遠端服務。
  2. AIDL 屬於 AndroidIPC 機制,常用於跨程序通訊,主要實現原理基於底層 Binder 機制。
  • Android 面試,與Service互動方式

# 第六篇:使用

6.1 什麼情況下會使用 Service

6.1.1 經驗總結:

  1. Service 其實就是背地搞事情,又不想讓別人知道
  2. 舉一個生活當中的例子,你想知道一件事情不需要直接去問,你可以通過側面瞭解。這就是 Service 設計的初衷

6.1.2 Service 為什麼被設計出來

  1. 根據 Service 的定義,我們可以知道需要長期在後臺進行的工作我們需要將其放在 Service 中去做。
  2. 得再通熟易懂一點,就是不能放在 Activity 中來執行的工作就必須得放到 Service 中去做。
  3. 如:音樂播放、下載、上傳大檔案、定時關閉應用等功能。這些功能如果放到 Activity 中做的話,那麼 Activity 退出被銷燬了的話,那這些功能也就停止了,這顯然是不符合我們的設計要求的,所以要將他們放在 Service 中去執行。

6.2 onStartCommand() 返回值 int 值的區別

  • 有四種返回值,不同值代表的意思如下:

6.2.1 START_STICKY :

  1. 如果 service 程序被 kill 掉,保留 service 的狀態為開始狀態,但不保留遞送的 intent 物件。
  2. 隨後系統會嘗試重新建立 service, 由於服務狀態為開始狀態,所以建立服務後一定會呼叫 onStartCommand ( Intent, int, int ) 方法。
  3. 如果在此期間沒有任何啟動命令被傳遞到 service , 那麼引數 Intent 將為 null

6.2.2 START_NOT_STICKY :

  1. “非粘性的”。
  2. 使用這個返回值時 , 如果在執行完 onStartCommand 後 , 服務被異常 kill 掉 ,系統不會自動重啟該服務。

6.2.3 START_REDELIVER_INTENT:

  1. 重傳 Intent
  2. 使用這個返回值時,如果在執行完 onStartCommand 後,服務被異常 kill 掉
  3. 系統會自動重啟該服務 , 並將 Intent 的值傳入。

6.2.4 START_STICKY_COMPATIBILITY:

  1. START_STICKY 的相容版本 , 但不保證服務被 kill 後一定能重啟。

6.3 在 service 的生命週期方法 onstartConmand() 可不可以執行網路操作?如何在 service 中執行網路操作?

  • 可以直接在 Service 中執行網路操作
  • onStartCommand() 方法中可以執行網路操作

6.4 提高 service 的優先順序

  • AndroidManifest.xml 檔案中對於 intent-filter 可以通過 android:priority = “1000” 這個屬性設定最高優先順序,1000 是最高值,如果數字越小則優先順序越低,同時實用於廣播。

  • onStartCommand 裡面呼叫 startForeground() 方法把 Service 提升為前臺程序級別,然後再 onDestroy 裡面要記得呼叫 stopForeground () 方法。

  • onStartCommand 方法,手動返回 START_STICKY

  • onDestroy 方法裡發廣播重啟 service
  1. service + broadcast 方式,就是當 serviceondestory 的時候,傳送一個自定義的廣播
  2. 當收到廣播的時候,重新啟動 service 。( 第三方應用或是在 setting 裡-應用強制停止時,APP 程序就直接被幹掉了,onDestroy 方法都進不來,所以無法保證會執行 )
  • 監聽系統廣播判斷 Service 狀態。
  1. 通過系統的一些廣播
  2. 比如:手機重啟、介面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的 Service 是否還存活。
  • Application 加上 Persistent 屬性。

6.5 Service 的 onRebind ( Intent ) 方法在什麼情況下會執行

  • 如果在 onUnbind() 方法返回 true 的情況下會執行 , 否則不執行。

# 總結

  1. 本文基本涵蓋了 Android Service 相關的知識點。由於篇幅原因,諸如 InterService 具體使用方法等,沒辦法詳細的介紹,大家很容易就能在網上找到資料進行學習。
  2. 重點:關於 Android 的四大元件,到現在為止我才總結完 ActivityService,我將繼續針對,BroadcastRecevier ContentProvider 等,以及四大元件之外的,事件分發、滑動衝突、新能優化等重要模組,進行全面總結,歡迎大家關注_yuanhao 的 部落格園 ,方便及時接收更新
  3. 開始前還以為總結不難,實際寫文章的過程中,才知道什麼是艱辛。也不知道自己能不能咬牙堅持下去,希望大家給我鼓勵,就算只是一個贊,也是我堅持下去的理由!

碼字不易,你的點贊是我總結的最大動力!


  • 由於我在「稀土掘金」「簡書」「CSDN」「部落格園」等站點,都有新內容釋出。所以大家可以直接關注我的 GitHub 倉庫,以免錯過精彩內容!

  • 倉庫地址:
    超級乾貨!精心歸納 AndroidJVM 、演算法等,各位帥氣的老鐵支援一下!給個 Star !

  • 1W 多字長文,加上精美思維導圖,記得點贊哦,歡迎關注 _yuanhao 的 部落格園 ,我們下篇文章見!

  • 相關文章均可在我的主頁、GitHub 上看到,這裡限於篇幅原因,也為了保持介面整潔,讓大家能有跟舒心的閱讀體驗就不給出了,我們下篇文章不見不散!