什麼是 Flutter 外掛包?
Flutter 外掛包與 Android Gradle 中的依賴包一樣的意思。一些官方或者開源組織開發的包能顯著提升我們的開發效率,減少開發成本。同時我們能從一些優秀的開源框架中學到很多知識,作為一個程式設計師,經常去 Github 逛一逛看看專案,或者 Fork 開源專案貢獻程式碼,能得到很大的提升。
Flutter 包分為下面兩類。
- Dart包:不依賴於特定平臺,對 Flutter 框架具有依賴性,這種包僅用於Flutter。
- 外掛包:依賴於特定平臺,一種專用的 Dart 包,其中包含用 Dart 程式碼編寫的API,以及針對Android(使用Java或Kotlin)和針對iOS(使用OC或Swift)平臺的特定實現,也就是說外掛包括原生程式碼。
本文僅介紹 Flutter 外掛包的整個開發與釋出流程,至於 Dart 包,過程都是類似的,讀者可以查閱相關文章進行了解。
建立 Flutter 外掛包專案
正文開始前,讀者需要對 Flutter 的平臺通道有所瞭解,如果你還不知道,可以先閱讀我之前寫的Flutter | 如何優雅的呼叫 Android 原生方法?,然後再回來繼續本文的學習。如果你已經掌握平臺通道的相關知識,跟著我的步伐,繼續往下~
本文將帶讀者在 Android 平臺上實現一個調節音量大小的外掛包專案,併發布到 Dart 倉庫。首先開啟 Android Studio,建立 Flutter Plugin 專案,如下。
整個專案建立完成後,目錄結構是下面這個樣子的,和 Flutter 專案差不多。系統會根據你建立專案所填的包名(我的包名是 cn.blogss.volume_control
),自動在 android 和 lib 目錄下生成兩個類,分別是VolumeControlPlugin.kt
和 volume_control.dart
。
VolumeControlPlugin.kt
實現了 FlutterPlugin 和 MethodCallHandler 介面。可以發現這和我們編寫 Android 端平臺通道程式碼基本一樣。例項化了一個名叫 volume_control 的平臺通道,之後我們只要在 onMethodCall 方法中根據業務邏輯處理來自平臺的訊息並返回結果即可。
/** VolumeControlPlugin */
class VolumeControlPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel : MethodChannel
// Flutter Engine 啟動時會自動呼叫這個方法,例項化平臺通道
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "volume_control")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { // 這裡處理來自平臺的訊息
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
// Flutter Engine關閉時,釋放記憶體
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
複製程式碼
volume_control.dart
和我們編寫 Flutter 端平臺通道程式碼基本一樣。內部例項化一個平臺通道,然後可以在內部編寫各種非同步方法,來與特定平臺進行通訊,接收平臺返回的結果。
class VolumeControl {
static const MethodChannel _channel =
const MethodChannel('volume_control');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
複製程式碼
以上就是建立 Flutter 外掛專案的整個過程,向讀者介紹了系統為我們自動生成的兩個重要類。之後我們的開發重心圍繞這兩個類來開展。
編寫調節音量程式碼並測試
Android 端程式碼
首先在 VolumeControlPlugin.kt
編寫需要實現的方法。如下,我寫了四個對應的方法名供 Flutter 端來呼叫。分別是設定音量最大範圍、獲取當前電量、改變媒體音量、改變系統音量。VolumeManager
內部實現了這四個方法的具體邏輯,由於篇幅關係,且本文的目的是帶讀者熟悉整個 Flutter 外掛開發流程,這裡不貼出 VolumeManager
類的原始碼,也不講解其實現細節。程式碼放在 volume_flutter,感興趣的讀者可以去看看。
/** VolumeControlPlugin */
class VolumeControlPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel : MethodChannel
private lateinit var volumeManager: VolumeManager
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "volume_control")
channel.setMethodCallHandler(this)
volumeManager = VolumeManager(flutterPluginBinding.applicationContext)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when(call.method){
"setMaxVol" -> { // 設定最大音量範圍
volumeManager.setMaxVol(call.arguments as Double);
}
"getCurrentVol" -> { // 獲取當前音量
volumeManager.setAudioType(call.arguments as Int)
result.success(volumeManager.currentVolume);
}
"changeMediaVoice" -> { // 改變媒體音量
volumeManager.setAudioType(VolumeManager.TYPE_MUSIC)
val curVoice = volumeManager.setVoice(call.arguments as Double);
result.success(curVoice)
}
"changeSysVoice" -> { //改變系統音量
volumeManager.setAudioType(VolumeManager.TYPE_SYSTEM)
val curVoice = volumeManager.setVoice(call.arguments as Double);
result.success(curVoice)
}
else -> {
result.notImplemented()
}
}
}
}
複製程式碼
Flutter 端程式碼
在 volume_control.dart
中編寫 4 個非同步方法,來呼叫上面 Android 端我們寫好的處理方法,如下。
class VolumeControl {
static const MethodChannel _channel = const MethodChannel('volume_control');
/// 設定音量最大範圍
/// setMaxVol 方法考慮到了音量的最大值可以自由設定,如果不使用這個方法,預設音量最大值是 100
static Future<void> setMaxVol(double num) async{
await _channel.invokeMethod("setMaxVol",num);
}
/// 獲取當前音量
static Future<double> getCurrentVol(AudioType audioType) async{
return await _channel.invokeMethod("getCurrentVol",_getStreamInt(audioType)) as double;
}
/// 改變媒體音量
static Future<double> changeMediaVoice(double num) async{
return await _channel.invokeMethod("changeMediaVoice",num) as double;
}
/// 改變系統音量
static Future<double> changeSysVoice(double num) async{
return await _channel.invokeMethod("changeSysVoice",num) as double;
}
}
enum AudioType {
/// Controls the Voice Call volume
STREAM_VOICE_CALL,
/// Controls the system volume
STREAM_SYSTEM,
/// Controls the ringer volume
STREAM_RING,
/// Controls the media volume
STREAM_MUSIC,
// Controls the alarm volume
STREAM_ALARM,
/// Controls the notification volume
STREAM_NOTIFICATION
}
int _getStreamInt(AudioType audioType) {
switch (audioType) {
case AudioType.STREAM_VOICE_CALL:
return 0;
case AudioType.STREAM_SYSTEM:
return 1;
case AudioType.STREAM_RING:
return 2;
case AudioType.STREAM_MUSIC:
return 3;
case AudioType.STREAM_ALARM:
return 4;
case AudioType.STREAM_NOTIFICATION:
return 5;
default:
return null;
}
}
複製程式碼
在 Flutter 頁面看看效果
Android 端和 Flutter 端的程式碼我們編寫完畢,現在在專案生成的 example 目錄下的 main.dart
來編寫頁面示例程式碼,來展示外掛的功能。注意 example 是開發者寫給使用者看的,告訴他們這個外掛如何使用的一個 Flutter 專案,相當於幫助文件,我覺得這點很好,極大了加快了我們的上手速度。
在 main.dart
中用一個 Slider 滑塊元件來展示下效果。按以下步驟編碼。
- 進入頁面的時候呼叫 getCurrentVol 方法來獲取當前媒體音量,顯示初始狀態。
- 滑動滑塊呼叫 changeMediaVoice 方法來改變媒體音量。
main.dart
頁面程式碼如下,也很簡單。
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double _musicVoice;
@override
void initState() {
super.initState();
///1.獲取當前媒體音量
initCurrentVol();
}
/// 獲取當前媒體音量
Future<void> initCurrentVol () async{
_musicVoice = await VolumeControl.getCurrentVol(AudioType.STREAM_MUSIC);
if(!mounted) return;
setState(() {});
}
/// 改變媒體音量
Future<void> changeMediaVoice(double vol) async{
await VolumeControl.changeMediaVoice(vol);
_musicVoice = vol;
setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: (_musicVoice != null) ? Slider(
value: _musicVoice,
min: 0,
max: 100,
inactiveColor: Colors.grey,
activeColor: Colors.blue,
onChanged: (vol){
/// 2. 滑動改變媒體音量
changeMediaVoice(vol);
},
): Container(),
),
),
);
}
}
複製程式碼
實際效果如下,滑動滑塊時,系統媒體音量也隨之改變。我的測試機型是小米 MI 6X。其他機型可能會有差異,請讀者注意。
將開發好的外掛包上傳到 Dart 倉庫
我們的 Flutter 外掛包整個開發流程就結束了。現在將它上傳到 Dart 倉庫,方便其他開發者可以使用這個外掛包。在釋出之前,檢查 LICENSE
、pubspec.yaml
、README.md
以及 CHANGELOG.md
四個檔案。
選擇開源許可證(LICENSE)
軟體開源許可證,大概有上百種。最流行的六種 --- GPL、BSD、MIT、Mozilla、Apache 和 LGPL。讀者可以從 Choose an open source license 選擇適合自己的證照,我這裡選擇 MIT。
將複製的內容貼上到 LICENSE
,用當前年份替換掉 [year]
,版權所有者替換掉 [fullname]
。如下圖,證照就算是弄完了。
修改 pubspec.yaml
name: volume_control
description: A new Flutter plugin.
version: 0.0.1
author:
homepage:
複製程式碼
這裡按實際情況修改 description
外掛的簡要描述,version
外掛的版本,homepage
專案主頁,其中 author
已經不支援使用了,讀者需要直接刪除,不然後面檢查會不通過,修改後如下。
name: volume_control
description: A Flutter plugin which can control android volume.
version: 0.0.1
homepage: https://github.com/liqvip/volume_control
複製程式碼
修改 README.md 和 CHANGELOG.md
README.md 檔案不用多說,讀者可以根據自己外掛是幹什麼的、有什麼用、使用方法等自由發揮。 CHANGELOG.md 檔案用來記錄每個版本的更改。也是根據實際情況來填寫。
## 0.0.1
initial commit
複製程式碼
很簡單,對於 0.0.1
版本我只填了一句話,嘻嘻~
開始上傳
- 首先在 Android Studio Termial 中輸入如下命令,來檢查我們編寫的好的上述檔案是否符合釋出的要求。
flutter pub publish --dry-run
複製程式碼
- 如果檢查沒有問題,控制檯會輸出如下提示資訊。
Package has 0 warnings.
複製程式碼
- 然後輸入如下命令,開始上傳
flutter pub publish --server=https://pub.dartlang.org
複製程式碼
會提示你一旦釋出就是永久的,不能夠取消釋出。輸入 y 繼續下一步
Publishing is forever; packages cannot be unpublished.
Policy details are available at https://pub.dev/policy
Do you want to publish volume_control 0.0.1 (y/N)?
複製程式碼
控制檯接著會輸出一個連結,這裡我們要複製這個連結到瀏覽器開啟,然後會提示你登入驗證谷歌郵箱,沒有的需要用 VPN 註冊一個谷歌郵箱。
Do you want to publish volume_control 0.0.1 (y/N)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A
%2F%2Flocalhost%3A55779&code_challenge=t9GweRvzHgPt6F1-1I42-3e8eg1MeA7xovsNLCsDHks&code_challenge_method=S256&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".
Waiting for your authorization...
複製程式碼
下面是我用自己的郵箱,驗證通過了的結果。
回到控制檯,你可能會看到下面的報錯資訊。報錯資訊告訴我們,收到了驗證資訊正在處理但最終卻走不到下一步,最後只能超時了。很明顯這是網路問題。
Waiting for your authorization...
Authorization received, processing...
It looks like accounts.google.com is having some trouble.
Pub will wait for a while before trying to connect again.
OS Error: 訊號燈超時時間已到
, errno = 121, address = accounts.google.com, port = 56479
pub finished with exit code 69
複製程式碼
這是因為即使你設定了代理,此時終端中的 http 和 https 並不會被代理,所以我們需要設定一下終端代理。根據下面提供的命令,讀者可以在不同的作業系統上設定終端代理,注意 http 和 https 都要設定,還有我的 ssr 代理埠是 1080,讀者需要根據你的實際代理埠填寫。
Windows
# 設定代理
set http_proxy=http://127.0.0.1:1080
set https_proxy=http://127.0.0.1:1080
# 驗證代理是否設定成功
curl -vv http://www.google.com
# 取消代理
set http_proxy=
set https_proxy=
Linux
export http_proxy=http://127.0.0.1:1080;
export https_proxy=http://127.0.0.1:1080;
複製程式碼
根據上面提供的命令,在 Windows 下設定終端代理後,測試下代理是否設定成功,只需請求一下 Google。返回如下結果表示代理設定成功。
代理設定完了之後,繼續執行釋出命令。
flutter pub publish --server=https://pub.dartlang.org
複製程式碼
結果如下,顯然這次網路問題已經解決了,但是 Dart 倉庫上有一個和我們同名的外掛包。所以我們將volume_control
改成 volume_flutter
,並將其他相關的類名也修改一下,然後繼續釋出。這裡專案名最好先去 Dart 倉庫搜一搜有沒有被佔用。如果被佔用了就取個不同的名字。不然這裡這很難受了,555~
10分鐘過去,我名稱改完了,兄弟們,繼續執行釋出命令。上傳成功了,激動得飛起,嘻嘻~
在 Dart 倉庫檢視
最後一步去 Dart 倉庫 volume_flutter 檢視最後的戰果。注意倉庫會有延遲,沒那麼快就可以找到你剛剛上傳的外掛,需要等待個幾分鐘。結果如下,我們完成了整個外掛的開發與釋出過程。
寫在最後
本文帶領讀者實現了一個在 Flutter 中調節 Android 音量的外掛專案,並將其釋出到了 Dart 倉庫。之後如果有開發者想使用這個外掛,只需要在 pubspec.yaml
中新增如下依賴即可。使用方法和我們在 exmaple 目錄編寫的示例程式碼一致。
dependencies:
volume_flutter: ^0.0.1
複製程式碼
通過本文,讀者應該能夠完全掌握如何開發一個外掛包並將其釋出到 Dart 倉庫。這中間我們遇到了很多困難,踩過很多坑,尤其在最後的釋出步驟。但都一個個解決了。
如果你對我感興趣,請移步到 blogss.cn , 或關注公眾號:程式設計師小北,進一步瞭解。
- 如果本文幫助到了你,歡迎點贊和關注 ❤️
- 由於作者水平有限,文中如果有錯誤,歡迎在評論區指正 ✔️
- 本文首發於掘金,未經許可禁止轉載 ©️