在上一篇文章中,我從工程架構與工作模式兩個層面,與你介紹了設計 Flutter 混合框架需要關注的基本設計原則,即確定分工邊界。
在工程架構維度,由于 Flutter 模塊作為原生工程的一個業務依賴,其運行環境是由原生工程提供的,因此我們需要將它們各自抽象為對應技術棧的依賴管理方式,以分層依賴的方式確定二者的邊界。
而在工作模式維度,考慮到 Flutter 模塊開發是原生開發的上游,因此我們只需要從其構建產物的過程入手,抽象出開發過程中的關鍵節點和高頻節點,以命令行的形式進行統一管理。構建產物是 Flutter 模塊的輸出,同時也是原生工程的輸入,一旦產物完成構建,我們就可以接入原生開發的工作流了。
可以看到,在 Flutter 混合框架中,Flutter 模塊與原生工程是相互依存、互利共贏的關系:
* Flutter 跨平臺開發效率高,渲染性能和多端體驗一致性好,因此在分工上主要專注于實現應用層的獨立業務(頁面)的渲染閉環;
* 而原生開發穩定性高,精細化控制力強,底層基礎能力豐富,因此在分工上主要專注于提供整體應用架構,為 Flutter 模塊提供穩定的運行環境及對應的基礎能力支持。
那么,在原生工程中為 Flutter 模塊提供基礎能力支撐的過程中,面對跨技術棧的依賴管理,我們該遵循何種原則呢?對于 Flutter 模塊及其依賴的原生插件們,我們又該如何以標準的原生工程依賴形式進行組件封裝呢?
在今天的文章中,我就通過一個典型案例,與你講述這兩個問題的解決辦法。
## 原生插件依賴管理原則
在前面[第 26](https://time.geekbang.org/column/article/127601)和[31 篇](https://time.geekbang.org/column/article/132818)文章里,我與你講述了為 Flutter 應用中的 Dart 代碼提供原生能力支持的兩種方式,即:在原生工程中的 Flutter 應用入口注冊原生代碼宿主回調的輕量級方案,以及使用插件工程進行獨立拆分封裝的工程化解耦方案。
無論使用哪種方式,Flutter 應用工程都為我們提供了一體化的標準解決方案,能夠在集成構建時自動管理原生代碼宿主及其相應的原生依賴,因此我們只需要在應用層使用 pubspec.yaml 文件去管理 Dart 的依賴。
但**對于混合工程而言,依賴關系的管理則會復雜一些**。這是因為,與 Flutter 應用工程有著對原生組件簡單清晰的單向依賴關系不同,混合工程對原生組件的依賴關系是多向的:Flutter 模塊工程會依賴原生組件,而原生工程的組件之間也會互相依賴。
如果繼續讓 Flutter 的工具鏈接管原生組件的依賴關系,那么整個工程就會陷入不穩定的狀態之中。因此,對于混合工程的原生依賴,Flutter 模塊并不做介入,完全交由原生工程進行統一管理。而 Flutter 模塊工程對原生工程的依賴,體現在依賴原生代碼宿主提供的底層基礎能力的原生插件上。
接下來,我就以網絡通信這一基礎能力為例,與你展開說明原生工程與 Flutter 模塊工程之間應該如何管理依賴關系。
## 網絡插件依賴管理實踐
在第 24 篇文章“[HTTP 網絡編程與 JSON 解析](https://time.geekbang.org/column/article/121163)”中,我與你介紹了在 Flutter 中,我們可以通過 HttpClient、http 與 dio 這三種通信方式,實現與服務端的數據交換。
但在混合工程中,考慮到其他原生組件也需要使用網絡通信能力,所以通常是由原生工程來提供網絡通信功能的。因為這樣不僅可以在工程架構層面實現更合理的功能分治,還可以統一整個 App 內數據交換的行為。比如,在網絡引擎中為接口請求增加通用參數,或者是集中攔截錯誤等。
關于原生網絡通信功能,目前市面上有很多優秀的第三方開源 SDK,比如 iOS 的 AFNetworking 和 Alamofire、Android 的 OkHttp 和 Retrofit 等。考慮到 AFNetworking 和 OkHttp 在各自平臺的社區活躍度相對最高,因此我就以它倆為例,與你演示混合工程的原生插件管理方法。
## 網絡插件接口封裝
要想搞清楚如何管理原生插件,我們需要先使用方法通道來建立 Dart 層與原生代碼宿主之間的聯系。
原生工程為 Flutter 模塊提供原生代碼能力,我們同樣需要使用 Flutter 插件工程來進行封裝。關于這部分內容,我在第[31](https://time.geekbang.org/column/article/132818)和[39](https://time.geekbang.org/column/article/141164)篇文章中,已經分別為你演示了推送插件和數據上報插件的封裝方法,你也可以再回過頭來復習下相關內容。所以,今天我就不再與你過多介紹通用的流程和固定的代碼聲明部分了,而是重點與你講述與接口相關的實現細節。
**首先,我們來看看 Dart 代碼部分。**
對于插件工程的 Dart 層代碼而言,由于它僅僅是原生工程的代碼宿主代理,所以這一層的接口設計比較簡單,只需要提供一個可以接收請求 URL 和參數,并返回接口響應數據的方法 doRequest 即可:
~~~
class FlutterPluginNetwork {
...
static Future<String> doRequest(url,params) async {
// 使用方法通道調用原生接口 doRequest,傳入 URL 和 param 兩個參數
final String result = await _channel.invokeMethod('doRequest', {
"url": url,
"param": params,
});
return result;
}
}
~~~
Dart 層接口封裝搞定了,我們再來看看**接管真實網絡調用的 Android 和 iOS 代碼宿主如何響應 Dart 層的接口調用**。
我剛剛與你提到過,原生代碼宿主提供的基礎通信能力是基于 AFNetworking(iOS)和 OkHttp(Android)做的封裝,所以為了在原生代碼中使用它們,我們**首先**需要分別在 flutter\_plugin\_network.podspec 和 build.gradle 文件中將工程對它們的依賴顯式地聲明出來:
在 flutter\_plugin\_network.podspec 文件中,聲明工程對 AFNetworking 的依賴:
~~~
Pod::Spec.new do |s|
...
s.dependency 'AFNetworking'
end
~~~
在 build.gradle 文件中,聲明工程對 OkHttp 的依賴:
~~~
dependencies {
implementation "com.squareup.okhttp3:okhttp:4.2.0"
}
~~~
**然后**,我們需要在原生接口 FlutterPluginNetworkPlugin 類中,完成例行的初始化插件實例、綁定方法通道工作。
最后,我們還需要在方法通道中取出對應的 URL 和 query 參數,為 doRequest 分別提供 AFNetworking 和 OkHttp 的實現版本。
對于 iOS 的調用而言,由于 AFNetworking 的網絡調用對象是 AFHTTPSessionManager 類,所以我們需要這個類進行實例化,并定義其接口返回的序列化方式(本例中為字符串)。然后剩下的工作就是用它去發起網絡請求,使用方法通道通知 Dart 層執行結果了:
~~~
@implementation FlutterPluginNetworkPlugin
...
// 方法通道回調
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
// 響應 doRequest 方法調用
if ([@"doRequest" isEqualToString:call.method]) {
// 取出 query 參數和 URL
NSDictionary *arguments = call.arguments[@"param"];
NSString *url = call.arguments[@"url"];
[self doRequest:url withParams:arguments andResult:result];
} else {
// 其他方法未實現
result(FlutterMethodNotImplemented);
}
}
// 處理網絡調用
- (void)doRequest:(NSString *)url withParams:(NSDictionary *)params andResult:(FlutterResult)result {
// 初始化網絡調用實例
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 定義數據序列化方式為字符串
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSMutableDictionary *newParams = [params mutableCopy];
// 增加自定義參數
newParams[@"ppp"] = @"yyyy";
// 發起網絡調用
[manager GET:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// 取出響應數據,響應 Dart 調用
NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
result(string);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// 通知 Dart 調用失敗
result([FlutterError errorWithCode:@"Error" message:error.localizedDescription details:nil]);
}];
}
@end
~~~
Android 的調用也類似,OkHttp 的網絡調用對象是 OkHttpClient 類,所以我們同樣需要這個類進行實例化。OkHttp 的默認序列化方式已經是字符串了,所以我們什么都不用做,只需要 URL 參數加工成 OkHttp 期望的格式,然后就是用它去發起網絡請求,使用方法通道通知 Dart 層執行結果了:
~~~
public class FlutterPluginNetworkPlugin implements MethodCallHandler {
...
@Override
// 方法通道回調
public void onMethodCall(MethodCall call, Result result) {
// 響應 doRequest 方法調用
if (call.method.equals("doRequest")) {
// 取出 query 參數和 URL
HashMap param = call.argument("param");
String url = call.argument("url");
doRequest(url,param,result);
} else {
// 其他方法未實現
result.notImplemented();
}
}
// 處理網絡調用
void doRequest(String url, HashMap<String, String> param, final Result result) {
// 初始化網絡調用實例
OkHttpClient client = new OkHttpClient();
// 加工 URL 及 query 參數
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (String key : param.keySet()) {
String value = param.get(key);
urlBuilder.addQueryParameter(key,value);
}
// 加入自定義通用參數
urlBuilder.addQueryParameter("ppp", "yyyy");
String requestUrl = urlBuilder.build().toString();
// 發起網絡調用
final Request request = new Request.Builder().url(requestUrl).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
// 切換至主線程,通知 Dart 調用失敗
registrar.activity().runOnUiThread(new Runnable() {
@Override
public void run() {
result.error("Error", e.toString(), null);
}
});
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
// 取出響應數據
final String content = response.body().string();
// 切換至主線程,響應 Dart 調用
registrar.activity().runOnUiThread(new Runnable() {
@Override
public void run() {
result.success(content);
}
});
}
});
}
}
~~~
需要注意的是,**由于方法通道是非線程安全的,所以原生代碼與 Flutter 之間所有的接口調用必須發生在主線程。**而 OktHtp 在處理網絡請求時,由于涉及非主線程切換,所以需要調用 runOnUiThread 方法以確保回調過程是在 UI 線程中執行的,否則應用可能會出現奇怪的 Bug,甚至是 Crash。
有些同學可能會比較好奇,**為什么 doRequest 的 Android 實現需要手動切回 UI 線程,而 iOS 實現則不需要呢?**這其實是因為 doRequest 的 iOS 實現背后依賴的 AFNetworking,已經在數據回調接口時為我們主動切換了 UI 線程,所以我們自然不需要重復再做一次了。
在完成了原生接口封裝之后,Flutter 工程所需的網絡通信功能的接口實現,就全部搞定了。
## Flutter 模塊工程依賴管理
通過上面這些步驟,我們以插件的形式提供了原生網絡功能的封裝。接下來,我們就需要在 Flutter 模塊工程中使用這個插件,并提供對應的構建產物封裝,提供給原生工程使用了。這部分內容主要包括以下 3 大部分:
* 第一,如何使用 FlutterPluginNetworkPlugin 插件,也就是模塊工程功能如何實現;
* 第二,模塊工程的 iOS 構建產物應該如何封裝,也就是原生 iOS 工程如何管理 Flutter 模塊工程的依賴;
* 第三,模塊工程的 Android 構建產物應該如何封裝,也就是原生 Android 工程如何管理 Flutter 模塊工程的依賴。
接下來,我們具體看看每部分應該如何實現。
## 模塊工程功能實現
為了使用 FlutterPluginNetworkPlugin 插件實現與服務端的數據交換能力,我們首先需要在 pubspec.yaml 文件中,將工程對它的依賴顯示地聲明出來:
~~~
flutter_plugin_network:
git:
url: https://github.com/cyndibaby905/44_flutter_plugin_network.git
~~~
然后,我們還得在 main.dart 文件中為它提供一個觸發入口。在下面的代碼中,我們在界面上展示了一個 RaisedButton 按鈕,并在其點擊回調函數時,使用 FlutterPluginNetwork 插件發起了一次網絡接口調用,并把網絡返回的數據打印到了控制臺上:
~~~
RaisedButton(
child: Text("doRequest"),
// 點擊按鈕發起網絡請求,打印數據
onPressed:()=>FlutterPluginNetwork.doRequest("https://jsonplaceholder.typicode.com/posts", {'userId':'2'}).then((s)=>print('Result:$s')),
)
~~~
運行這段代碼,點擊 doRequest 按鈕,觀察控制臺輸出,可以看到,接口返回的數據信息能夠被正常打印,證明 Flutter 模塊的功能表現是完全符合預期的。
:-: 
圖 1 Flutter 模塊工程運行示例
## 構建產物應該如何封裝?
我們都知道,模塊工程的 Android 構建產物是 aar,iOS 構建產物是 Framework。而在第[28](https://time.geekbang.org/column/article/129754)和[42](https://time.geekbang.org/column/article/144156)篇文章中,我與你介紹了不帶插件依賴的模塊工程構建產物的兩種封裝方案,即手動封裝方案與自動化封裝方案。這兩種封裝方案,最終都會輸出同樣的組織形式(Android 是 aar,iOS 則是帶 podspec 的 Framework 封裝組件)。
如果你已經不熟悉這兩種封裝方式的具體操作步驟了,可以再復習下這兩篇文章的相關內容。接下來,我重點與你講述的問題是:**如果我們的模塊工程存在插件依賴,封裝過程是否有區別呢?**
答案是,對于模塊工程本身而言,這個過程沒有區別;但對于模塊工程的插件依賴來說,我們需要主動告訴原生工程,哪些依賴是需要它去管理的。
由于 Flutter 模塊工程把所有原生的依賴都交給了原生工程去管理,因此其構建產物并不會攜帶任何原生插件的封裝實現,所以我們需要遍歷模塊工程所使用的原生依賴組件們,為它們逐一生成插件代碼對應的原生組件封裝。
在第 18 篇文章“[依賴管理(二):第三方組件庫在 Flutter 中要如何管理?](https://time.geekbang.org/column/article/114180)”中,我與你介紹了 Flutter 工程管理第三方依賴的實現機制,其中.packages 文件存儲的是依賴的包名與系統緩存中的包文件路徑。
類似的,插件依賴也有一個類似的文件進行統一管理,即**.flutter-plugins**。我們可以通過這個文件,找到對應的插件名字(本例中即為 flutter\_plugin\_network)及緩存路徑:
~~~
flutter_plugin_network=/Users/hangchen/Documents/flutter/.pub-cache/git/44_flutter_plugin_network-9b4472aa46cf20c318b088573a30bc32c6961777/
~~~
插件緩存本身也可以被視為一個 Flutter 模塊工程,所以我們可以采用與模塊工程類似的辦法,為它生成對應的原生組件封裝。
對于 iOS 而言,這個過程相對簡單些,所以我們先來看看模塊工程的 iOS 構建產物封裝過程。
### iOS 構建產物應該如何封裝?
在插件工程的 ios 目錄下,為我們提供了帶 podspec 文件的源碼組件,podspec 文件提供了組件的聲明(及其依賴),因此我們可以把這個目錄下的文件拷貝出來,連同 Flutter 模塊組件一起放到原生工程中的專用目錄,并寫到 Podfile 文件里。
原生工程會識別出組件本身及其依賴,并按照聲明的依賴關系依次遍歷,自動安裝:
~~~
#Podfile
target 'iOSDemo' do
pod 'Flutter', :path => 'Flutter'
pod 'flutter_plugin_network', :path => 'flutter_plugin_network'
end
~~~
然后,我們就可以像使用不帶插件依賴的模塊工程一樣,把它引入到原生工程中,為其設置入口,在 FlutterViewController 中展示 Flutter 模塊的頁面了。
不過需要注意的是,由于 FlutterViewController 并不感知這個過程,因此不會主動初始化項目中的插件,所以我們還需要在入口處手動將工程里所有的插件依次聲明出來:
~~~
//AppDelegate.m:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 初始化 Flutter 入口
FlutterViewController *vc = [[FlutterViewController alloc]init];
// 初始化插件
[FlutterPluginNetworkPlugin registerWithRegistrar:[vc registrarForPlugin:@"FlutterPluginNetworkPlugin"]];
// 設置路由標識符
[vc setInitialRoute:@"defaultRoute"];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
~~~
在 Xcode 中運行這段代碼,點擊 doRequest 按鈕,可以看到,接口返回的數據信息能夠被正常打印,證明我們已經可以在原生 iOS 工程中順利的使用 Flutter 模塊了。
:-: 
圖 2 原生 iOS 工程運行示例
我們再來看看模塊工程的 Android 構建產物應該如何封裝。
### Android 構建產物應該如何封裝?
與 iOS 的插件工程組件在 ios 目錄類似,Android 的插件工程組件在 android 目錄。對于 iOS 的插件工程,我們可以直接將源碼組件提供給原生工程,但對于 Andriod 的插件工程來說,我們只能將 aar 組件提供給原生工程,所以我們不僅需要像 iOS 操作步驟那樣進入插件的組件目錄,還需要借助構建命令,為插件工程生成 aar:
~~~
cd android
./gradlew flutter_plugin_network:assRel
~~~
命令執行完成之后,aar 就生成好了。aar 位于 android/build/outputs/aar 目錄下,我們打開插件緩存對應的路徑,提取出對應的 aar(本例中為 flutter\_plugin\_network-debug.aar)就可以了。
我們把生成的插件 aar,連同 Flutter 模塊 aar 一起放到原生工程的 libs 目錄下,最后在 build.gradle 文件里將它顯式地聲明出來,就完成了插件工程的引入。
~~~
//build.gradle
dependencies {
...
implementation(name: 'flutter-debug', ext: 'aar')
implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
implementation "com.squareup.okhttp3:okhttp:4.2.0"
...
}
~~~
然后,我們就可以在原生工程中為其設置入口,在 FlutterView 中展示 Flutter 頁面,愉快地使用 Flutter 模塊帶來的高效開發和高性能渲染能力了:
~~~
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute");
setContentView(FlutterView);
}
}
~~~
不過**需要注意的是**,與 iOS 插件工程的 podspec 能夠攜帶組件依賴不同,Android 插件工程的封裝產物 aar 本身不攜帶任何配置信息。所以,如果插件工程本身存在原生依賴(像 flutter\_plugin\_network 依賴 OkHttp 這樣),我們是無法通過 aar 去告訴原生工程其所需的原生依賴的。
面對這種情況,我們需要在原生工程中的 build.gradle 文件里手動地將插件工程的依賴(即 OkHttp)顯示地聲明出來。
~~~
//build.gradle
dependencies {
...
implementation(name: 'flutter-debug', ext: 'aar')
implementation(name: 'flutter_plugin_network-debug', ext: 'aar')
implementation "com.squareup.okhttp3:okhttp:4.2.0"
...
}
~~~
**至此,將模塊工程及其插件依賴封裝成原生組件的全部工作就完成了,原生工程可以像使用一個普通的原生組件一樣,去使用 Flutter 模塊組件的功能了。**
在 Android Studio 中運行這段代碼,并點擊 doRequest 按鈕,可以看到,我們可以在原生 Android 工程中正常使用 Flutter 封裝的頁面組件了。
:-: 
圖 3 原生 Android 工程運行示例
當然,考慮到手動封裝模塊工程及其構建產物的過程,繁瑣且容易出錯,我們可以把這些步驟抽象成命令行腳本,并把它部署到 Travis 上。這樣在 Travis 檢測到代碼變更之后,就會自動將 Flutter 模塊的構建產物封裝成原生工程期望的組件格式了。
關于這部分內容,你可以參考我在[flutter\_module\_demo](https://github.com/cyndibaby905/44_flutter_module_demo)里的[generate\_aars.sh](https://github.com/cyndibaby905/44_flutter_module_demo/blob/master/generate_aars.sh)與[generate\_pods.sh](https://github.com/cyndibaby905/44_flutter_module_demo/blob/master/generate_pods.sh)實現。如果關于這部分內容有任何問題,都可以直接留言給我。
## 總結
好了,關于 Flutter 混合開發框架的依賴管理部分我們就講到這里。接下來,我們一起總結下今天的主要內容吧。
Flutter 模塊工程的原生組件封裝形式是 aar(Android)和 Framework(Pod)。與純 Flutter 應用工程能夠自動管理插件的原生依賴不同,這部分工作在模塊工程中是完全交給原生工程去管理的。因此,我們需要查找記錄了插件名稱及緩存路徑映射關系的.flutter-plugins 文件,提取出每個插件所對應的原生組件封裝,集成到原生工程中。
從今天的分享可以看出,對于有著插件依賴的 Android 組件封裝來說,由于 aar 本身并不攜帶任何配置信息,因此其操作以手工為主:我們不僅要執行構建命令依次生成插件對應的 aar,還需要將插件自身的原生依賴拷貝至原生工程,其步驟相對 iOS 組件封裝來說要繁瑣一些。
為了解決這一問題,業界出現了一種名為[fat-aar](https://github.com/adwiv/android-fat-aar)的打包手段,它能夠將模塊工程本身,及其相關的插件依賴統一打包成一個大的 aar,從而省去了依賴遍歷和依賴聲明的過程,實現了更好的功能自治性。但這種解決方案存在一些較為明顯的不足:
* 依賴沖突問題。如果原生工程與插件工程都引用了同樣的原生依賴組件(OkHttp),則原生工程的組件引用其依賴時會產生合并沖突,因此在發布時必須手動去掉原生工程的組件依賴。
* 嵌套依賴問題。fat-aar 只會處理 embedded 關鍵字指向的這層一級依賴,而不會處理再下一層的依賴。因此,對于依賴關系復雜的插件支持,我們仍需要手動處理依賴問題。
* Gradle 版本限制問題。fat-aar 方案對 Gradle 插件版本有限制,且實現方式并不是官方設計考慮的點,加之 Gradle API 變更較快,所以存在后續難以維護的問題。
* 其他未知問題。fat-aar 項目已經不再維護了,最近一次更新還是 2 年前,在實際項目中使用“年久失修”的項目存在較大的風險。
考慮到這些因素,fat-aar 并不是管理插件工程依賴的好的解決方案,所以**我們最好還是得老老實實地去遍歷插件依賴,以持續交付的方式自動化生成 aar。**
我把今天分享涉及知識點打包上傳到了 GitHub 中,你可以把[插件工程](https://github.com/cyndibaby905/44_flutter_plugin_network)、[Flutter 模塊工程](https://github.com/cyndibaby905/44_flutter_module_demo)、[原生 Android](https://github.com/cyndibaby905/44_AndroidDemo)和[iOS 工程](https://github.com/cyndibaby905/44_iOSDemo)下載下來,查看其 Travis 持續交付配置文件的構建執行命令,體會在混合框架中如何管理跨技術棧的組件依賴。
## 思考題
最后,我給你留一道思考題吧。
原生插件的開發是一個需要 Dart 層代碼封裝,以及原生 Android、iOS 代碼層實現的長鏈路過程。如果需要支持的基礎能力較多,開發插件的過程就會變得繁瑣且容易出錯。我們都知道 Dart 是不支持反射的,但是原生代碼可以。我們是否可以利用原生的反射去實現插件定義的標準化呢?
提示:在 Dart 層調用不存在的接口(或未實現的接口),可以通過 noSuchMethod 方法進行統一處理。
~~~
class FlutterPluginDemo {
// 方法通道
static const MethodChannel _channel =
const MethodChannel('flutter_plugin_demo');
// 當調用不存在接口時,Dart 會交由該方法進行統一處理
@override
Future<dynamic> noSuchMethod(Invocation invocation) {
// 從字符串 Symbol("methodName") 中取出方法名
String methodName = invocation.memberName.toString().substring(8, string.length - 2);
// 參數
dynamic args = invocation.positionalArguments;
print('methodName:$methodName');
print('args:$args');
return methodTemplate(methodName, args);
}
// 某未實現的方法
Future<dynamic> someMethodNotImplemented();
// 某未實現的帶參數方法
Future<dynamic> someMethodNotImplementedWithParameter(param);
}
~~~
- 前言
- 開篇詞
- 預習篇
- 01丨預習篇 · 從0開始搭建Flutter工程環境
- 02丨預習篇 · Dart語言概覽
- Flutter開發起步
- 03丨深入理解跨平臺方案的歷史發展邏輯
- 04丨Flutter區別于其他方案的關鍵技術是什么?
- 05丨從標準模板入手,體會Flutter代碼是如何運行在原生系統上的
- Dart語言基礎
- 06丨基礎語法與類型變量:Dart是如何表示信息的?
- 07丨函數、類與運算符:Dart是如何處理信息的?
- 08丨綜合案例:掌握Dart核心特性
- Flutter基礎
- 09丨Widget,構建Flutter界面的基石
- 10丨Widget中的State到底是什么?
- 11丨提到生命周期,我們是在說什么?
- 12丨經典控件(一):文本、圖片和按鈕在Flutter中怎么用?
- 13丨ListView在Flutter中是什么?
- 14 丨 經典布局:如何定義子控件在父容器中排版位置?
- 15 丨 組合與自繪,我該選用何種方式自定義Widget?
- 16 丨 從夜間模式說起,如何定制不同風格的App主題?
- 17丨依賴管理(一):圖片、配置和字體在Flutter中怎么用?
- 18丨依賴管理(二):第三方組件庫在Flutter中要如何管理?
- 19丨用戶交互事件該如何響應?
- 20丨關于跨組件傳遞數據,你只需要記住這三招
- 21丨路由與導航,Flutter是這樣實現頁面切換的
- Flutter進階
- 22丨如何構造炫酷的動畫效果?
- 23丨單線程模型怎么保證UI運行流暢?
- 24丨HTTP網絡編程與JSON解析
- 25丨本地存儲與數據庫的使用和優化
- 26丨如何在Dart層兼容Android-iOS平臺特定實現?(一)
- 27丨如何在Dart層兼容Android-iOS平臺特定實現?(二)
- 28丨如何在原生應用中混編Flutter工程?
- 29丨混合開發,該用何種方案管理導航棧?
- 30丨為什么需要做狀態管理,怎么做?
- 31丨如何實現原生推送能力?
- 32丨適配國際化,除了多語言我們還需要注意什么
- 33丨如何適配不同分辨率的手機屏幕?
- 34丨如何理解Flutter的編譯模式?
- 35丨HotReload是怎么做到的?
- 36丨如何通過工具鏈優化開發調試效率?
- 37丨如何檢測并優化FlutterApp的整體性能表現?
- 38丨如何通過自動化測試提高交付質量?
- Flutter綜合應用
- 39丨線上出現問題,該如何做好異常捕獲與信息采集?
- 40丨衡量FlutterApp線上質量,我們需要關注這三個指標
- 41丨組件化和平臺化,該如何組織合理穩定的Flutter工程結構?
- 42丨如何構建高效的FlutterApp打包發布環境?
- 43丨如何構建自己的Flutter混合開發框架(一)?
- 44丨如何構建自己的Flutter混合開發框架(二)?
- 結束語
- 結束語丨勿畏難,勿輕略