国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Deferred Components-實(shí)現(xiàn)Flutter運(yùn)行時(shí)動(dòng)態(tài)下發(fā)Dart代碼 | 京東云技術(shù)團(tuán)隊(duì)

這篇具有很好參考價(jià)值的文章主要介紹了Deferred Components-實(shí)現(xiàn)Flutter運(yùn)行時(shí)動(dòng)態(tài)下發(fā)Dart代碼 | 京東云技術(shù)團(tuán)隊(duì)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

導(dǎo)讀

Deferred Components,官方實(shí)現(xiàn)的Flutter代碼動(dòng)態(tài)下發(fā)的方案。本文主要介紹官方方案的實(shí)現(xiàn)細(xì)節(jié),探索在國內(nèi)環(huán)境下使用Deferred Components,并且實(shí)現(xiàn)了最小驗(yàn)證demo。讀罷本文,你就可以實(shí)現(xiàn)Dart文件級(jí)別代碼的動(dòng)態(tài)下發(fā)。

一、引言

Deferred Components是Flutter2.2推出的功能,依賴于Dart2.13新增的對(duì)Split AOT編譯支持。將可以在運(yùn)行時(shí)每一個(gè)可單獨(dú)下載的Dart庫、assets資源包稱之為延遲加載組件,即Deferred Components。Flutter代碼編譯后,所有的業(yè)務(wù)邏輯都會(huì)打包在libapp.so一個(gè)文件里。但如果使用了延遲加載,便可以分拆為多個(gè)so文件,甚至一個(gè)Dart文件也可以編譯成一個(gè)單獨(dú)的so文件。

這樣帶來的好處是顯而易見的,可以將一些不常用功能放到單獨(dú)的so文件中,當(dāng)用戶使用時(shí)再去下載,可以大大降低安裝包的大小,提高應(yīng)用的下載轉(zhuǎn)換率。另外,因?yàn)镕lutter具備了運(yùn)行時(shí)動(dòng)態(tài)下發(fā)的能力,這讓大家看到了實(shí)現(xiàn)Flutter熱修復(fù)的另一種可能。截止目前來講,官方的實(shí)現(xiàn)方案必須依賴Google Play,雖然也針對(duì)中國的開發(fā)者給出了不依賴Google Play的自定義方案,但是并沒有給出實(shí)現(xiàn)細(xì)節(jié),市面上也沒有自定義實(shí)現(xiàn)的文章。本文會(huì)先簡單介紹官方實(shí)現(xiàn)方案,并探究其細(xì)節(jié),尋找自定義實(shí)現(xiàn)的思路,最終會(huì)實(shí)現(xiàn)一個(gè)最小Demo供大家參考。

二、官方實(shí)現(xiàn)方案探究

2.1 基本步驟

2.1.1.引入play core依賴。

dependencies {
  implementation "com.google.android.play:core:1.8.0"
}

2.1.2.修改Application類的onCreate方法和attachBaseContext方法。

@Override
protected void onCreate(){
 super.onCreate()
// 負(fù)責(zé)deferred components的下載與安裝
 PlayStoreDeferredComponentManager deferredComponentManager = new
  PlayStoreDeferredComponentManager(this, null);
FlutterInjector.setInstance(new FlutterInjector.Builder()
    .setDeferredComponentManager(deferredComponentManager).build());
}


@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

2.1.3.修改pubspec.yaml文件。

flutter:
    deferred-components:

2.1.4.在flutter工程里新增box.dart和some_widgets.dart兩個(gè)文件,DeferredBox就是要延遲加載的控件,本例中box.dart被稱為一個(gè)加載單元,即loading_unit,每一個(gè)loading_unit對(duì)應(yīng)唯一的id,一個(gè)deferred component可以包含多個(gè)加載單元。記得這個(gè)概念,后續(xù)會(huì)用到。

// box.dart


import 'package:flutter/widgets.dart';


/// A simple blue 30x30 box.
class DeferredBox extends StatelessWidget {
  DeferredBox() {}


  @override
  Widget build(BuildContext context) {
    return Container(
      height: 30,
      width: 30,
      color: Colors.blue,
    );
  }
}
import 'box.dart' deferred as box;


class SomeWidget extends StatefulWidget {
  @override
  _SomeWidgetState createState() => _SomeWidgetState();
}


class _SomeWidgetState extends State<SomeWidget> {
  Future<void> _libraryFuture;


  @override
  void initState() {
 //只有調(diào)用了loadLibrary方法,才會(huì)去真正下載并安裝deferred components.
    _libraryFuture = box.loadLibrary();
    super.initState();
  }


  @override
  Widget build(BuildContext context) {
    return FutureBuilder<void>(
      future: _libraryFuture,
      builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          }
          return box.DeferredBox();
        }
        return CircularProgressIndicator();
      },
    );
  }
}

2.1.5.然后在main.dart里面新增一個(gè)跳轉(zhuǎn)到SomeWidget頁面的按鈕。

 Navigator.push(context, MaterialPageRoute(
      builder: (context) {
        return const SomeWidget();
      },
    ));

2.1.6.terminal里運(yùn)行?flutter build appbundle?命令。此時(shí),gen_snapshot不會(huì)立即去編譯app,而是先運(yùn)行一個(gè)驗(yàn)證程序,目的是驗(yàn)證此工程是否符合動(dòng)態(tài)下發(fā)dart代碼的格式,第一次構(gòu)建時(shí)肯定不會(huì)成功,你只需要按照編譯提示去修改即可。當(dāng)全部修改完畢后,會(huì)得到最終的.aab類型的安裝包。

以上便是官方實(shí)現(xiàn)方案的基本步驟,更多細(xì)節(jié)可以參考官方文檔
https://docs.flutter.dev/perf/deferred-components

2.2 本地驗(yàn)證

在將生成的aab安裝包上傳到Google Play上之前,最好先本地驗(yàn)證一下。

首先你需要下載bundletool,然后依次運(yùn)行下列命令就可以將aab安裝包裝在手機(jī)上進(jìn)行最終的驗(yàn)證了。

java -jar bundletool.jar build-apks --bundle=<your_app_project_dir>/build/app/outputs/bundle/release/app-release.aab --output=<your_temp_dir>/app.apks --local-testing


java -jar bundletool.jar install-apks --apks=<your_temp_dir>/app.apks

2.3 loadLibrary()方法調(diào)用的生命周期

Deferred Components-實(shí)現(xiàn)Flutter運(yùn)行時(shí)動(dòng)態(tài)下發(fā)Dart代碼 | 京東云技術(shù)團(tuán)隊(duì)

圖1 官方實(shí)現(xiàn)方案介紹圖

(來源:https://github.com/flutter/flutter/wiki/Deferred-Components)

從官方的實(shí)現(xiàn)方案中可以知道,只有調(diào)用了loadLibrary方法后,才會(huì)去真正執(zhí)行deferred components的下載與安裝工作,現(xiàn)在著重看下此方法的生命周期。

調(diào)用完loadLibrary方法后,dart會(huì)在內(nèi)部查詢此加載單元的id,并將其一直向下傳遞,當(dāng)?shù)竭_(dá)jni層時(shí),jni負(fù)責(zé)將此加載單元對(duì)應(yīng)的deferred component的名字以及此加載單元id一塊傳遞給
PlayStoreDynamicFeatureManager,此類負(fù)責(zé)從Google Play Store服務(wù)器下載對(duì)應(yīng)的Deferred Components并負(fù)責(zé)安裝。安裝完成后會(huì)逐層通知,最終告訴dart層,在下一幀渲染時(shí)展示動(dòng)態(tài)下發(fā)的控件。

三、自定義實(shí)現(xiàn)

3.1 思路

梳理了loadLibrary方法調(diào)用的生命周期后,只需要自己實(shí)現(xiàn)一個(gè)類來代替
PlayStoreDynamicFeatureManager的功能即可。在官方方案中具體負(fù)責(zé)完成PlayStoreDynamicFeatureManager功能的實(shí)體類是io.flutter.embedding.engine.deferredcomponents.PlayStoreDeferredComponentManager,其繼承自DeferredComponentManager,分析源碼得知,它最重要的兩個(gè)方法是installDeferredComponent和loadDartLibrary。

  • installDeferredComponent:這個(gè)方法主要負(fù)責(zé)component的下載與安裝,下載安裝完成后會(huì)調(diào)用loadLibrary方法,如果是asset-only component,那么也需要調(diào)用DeferredComponentChannel.completeInstallSuccess或者DeferredComponentChannel.completeInstallError方法。
  • loadDartLibrary:主要是負(fù)責(zé)找到so文件的位置,并調(diào)用FlutterJNI dlopen命令打開so文件,你可以直接傳入apk的位置,flutterJNI會(huì)直接去apk里加載so,避免處理解壓apk的邏輯。

那基本思路就有了,自己實(shí)現(xiàn)一個(gè)實(shí)體類,繼承DeferredComponentManager,實(shí)現(xiàn)這兩個(gè)方法即可。

3.2 代碼實(shí)現(xiàn)

本例只是最小demo實(shí)現(xiàn),cpu架構(gòu)采用arm64,且暫不考慮asset-only類型的component。

3.2.1.新增
CustomDeferredComponentsManager類,繼承DeferredComponentManager。

3.2.2.實(shí)現(xiàn)installDeferredComponent方法,將so文件放到外部SdCard存儲(chǔ)里,代碼負(fù)責(zé)將其拷貝到應(yīng)用的私有存儲(chǔ)中,以此來模擬網(wǎng)絡(luò)下載過程。代碼如下:

@Override
public void installDeferredComponent(int loadingUnitId, String componentName) {
    String resolvedComponentName = componentName != null ? componentName : loadingUnitIdToComponentNames.get(loadingUnitId);
    if (resolvedComponentName == null) {
         Log.e(TAG, "Deferred component name was null and could not be resolved from loading unit id.");
         return;
     }
     // Handle a loading unit that is included in the base module that does not need download.
     if (resolvedComponentName.equals("") && loadingUnitId > 0) {
     // No need to load assets as base assets are already loaded.
         loadDartLibrary(loadingUnitId, resolvedComponentName);
         return;
     }
     //耗時(shí)操作,模擬網(wǎng)絡(luò)請(qǐng)求去下載android module
     new Thread(
         () -> {
//將so文件從外部存儲(chǔ)移動(dòng)到內(nèi)部私有存儲(chǔ)中
              boolean result = moveSoToPrivateDir();
              if (result) {
                 //模擬網(wǎng)絡(luò)下載,添加2秒網(wǎng)絡(luò)延遲
                 new Handler(Looper.getMainLooper()).postDelayed(
                                () -> {
                                    loadAssets(loadingUnitId, resolvedComponentName);
                                    loadDartLibrary(loadingUnitId, resolvedComponentName);
                                    if (channel != null) {
                                        channel.completeInstallSuccess(resolvedComponentName);
                                    }
                                }
                                , 2000);
                 } else {
                        new Handler(Looper.getMainLooper()).post(
                                () -> {
                                    Toast.makeText(context, "未在sd卡中找到so文件", Toast.LENGTH_LONG).show();


                                    if (channel != null) {
                                        channel.completeInstallError(resolvedComponentName, "未在sd卡中找到so文件");
                                    }


                                    if (flutterJNI != null) {
                                        flutterJNI.deferredComponentInstallFailure(loadingUnitId, "未在sd卡中找到so文件", true);
                                    }
                                }
                        );
                  }
              }
        ).start();
    }

3.2.3.實(shí)現(xiàn)loadDartLibrary方法,可以直接拷貝
PlayStoreDeferredComponentManager類中的此方法,注釋已加,其主要作用就是在內(nèi)部私有存儲(chǔ)中找到so文件,并調(diào)用FlutterJNI dlopen命令打開so文件。

  @Override
    public void loadDartLibrary(int loadingUnitId, String componentName) {
        if (!verifyJNI()) {
            return;
        }
        // Loading unit must be specified and valid to load a dart library.
        //asset-only的component的unit id為-1,不需要加載so文件
        if (loadingUnitId < 0) {
            return;
        }


        //拿到so的文件名字
        String aotSharedLibraryName = loadingUnitIdToSharedLibraryNames.get(loadingUnitId);
        if (aotSharedLibraryName == null) {
            // If the filename is not specified, we use dart's loading unit naming convention.
            aotSharedLibraryName = flutterApplicationInfo.aotSharedLibraryName + "-" + loadingUnitId + ".part.so";
        }


        //拿到支持的abi格式--arm64_v8a
        // Possible values: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64
        String abi;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            abi = Build.SUPPORTED_ABIS[0];
        } else {
            abi = Build.CPU_ABI;
        }
        String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths.


        // TODO(garyq): Optimize this apk/file discovery process to use less i/o and be more
        // performant and robust.


        // Search directly in APKs first
        List<String> apkPaths = new ArrayList<>();
        // If not found in APKs, we check in extracted native libs for the lib directly.
        List<String> soPaths = new ArrayList<>();


        Queue<File> searchFiles = new LinkedList<>();
        // Downloaded modules are stored here--下載的 modules 存儲(chǔ)位置
        searchFiles.add(context.getFilesDir());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //第一次通過appbundle形式安裝的split apks位置
            // The initial installed apks are provided by `sourceDirs` in ApplicationInfo.
            // The jniLibs we want are in the splits not the baseDir. These
            // APKs are only searched as a fallback, as base libs generally do not need
            // to be fully path referenced.
            for (String path : context.getApplicationInfo().splitSourceDirs) {
                searchFiles.add(new File(path));
            }
        }


        //查找apk和so文件
        while (!searchFiles.isEmpty()) {
            File file = searchFiles.remove();
            if (file != null && file.isDirectory() && file.listFiles() != null) {
                for (File f : file.listFiles()) {
                    searchFiles.add(f);
                }
                continue;
            }
            String name = file.getName();
            // Special case for "split_config" since android base module non-master apks are
            // initially installed with the "split_config" prefix/name.
            if (name.endsWith(".apk")
                    && (name.startsWith(componentName) || name.startsWith("split_config"))
                    && name.contains(pathAbi)) {
                apkPaths.add(file.getAbsolutePath());
                continue;
            }
            if (name.equals(aotSharedLibraryName)) {
                soPaths.add(file.getAbsolutePath());
            }
        }


        List<String> searchPaths = new ArrayList<>();


        // Add the bare filename as the first search path. In some devices, the so
        // file can be dlopen-ed with just the file name.
        searchPaths.add(aotSharedLibraryName);


        for (String path : apkPaths) {
            searchPaths.add(path + "!lib/" + abi + "/" + aotSharedLibraryName);
        }
        for (String path : soPaths) {
            searchPaths.add(path);
        }
//打開so文件
        flutterJNI.loadDartDeferredLibrary(loadingUnitId, searchPaths.toArray(new String[searchPaths.size()]));
    }

3.2.4.修改Application的代碼并刪除
com.google.android.play:core的依賴。

override fun onCreate() {
        super.onCreate()
        val deferredComponentManager = CustomDeferredComponentsManager(this, null)
        val injector = FlutterInjector.Builder().setDeferredComponentManager(deferredComponentManager).build()
        FlutterInjector.setInstance(injector)

至此,核心代碼全部實(shí)現(xiàn)完畢,其他細(xì)節(jié)代碼可以見
https://coding.jd.com/jd_logistic/deferred_component_demo/,需要加權(quán)限的聯(lián)系shenmingliang1即可。

3.3 本地驗(yàn)證

  • 運(yùn)行 flutter build appbundle --release --target-platform android-arm64 命令生成app-release.aab文件。
  • .運(yùn)行下列命令將app-release.aab解析出本地可以安裝的apks文件:java -jar bundletool.jar build-apks --bundle=app-release.aab --output=app.apks --local-testing
  • 解壓上一步生成的app.apks文件,在加壓后的app文件夾下找到splits/scoreComponent-arm64_v8a_2.apk,繼續(xù)解壓此apk文件,在生成的scoreComponent-arm64_v8a_2文件夾里找到lib/arm64-v8a/libapp.so-2.part.so 文件。
  • 執(zhí)行 java -jar bundletool.jar install-apks --apks=app.apks命令安裝app.apks,此時(shí)打開安裝后的app,點(diǎn)擊首頁右下角的按鈕跳轉(zhuǎn)到DeferredPage頁面,此時(shí)頁面不會(huì)成功加載,并且會(huì)提示你“未在sd卡中找到so文件”。
  • 將第3步找到的lipase.so-2.part.so push到指定文件夾下,命令如下 adb push libapp.so-2.part.so /storage/emulated/0/Android/data/com.example.deferred_official_demo/files。重啟app進(jìn)程,并重新打開DeferredPage界面即可。

四、 總結(jié)

官方實(shí)現(xiàn)方案對(duì)國內(nèi)的使用來講,最大的限制無疑是Google Play,本文實(shí)現(xiàn)了一個(gè)脫離Google Play限制的最小demo,驗(yàn)證了deferred components在國內(nèi)使用的可行性。

參考:

  1. https://docs.flutter.dev/perf/deferred-components
  2. https://github.com/flutter/flutter/wiki/Deferred-Components

作者:京東物流 沈明亮

內(nèi)容來源:京東云開發(fā)者社區(qū)文章來源地址http://www.zghlxwxcb.cn/news/detail-455892.html

到了這里,關(guān)于Deferred Components-實(shí)現(xiàn)Flutter運(yùn)行時(shí)動(dòng)態(tài)下發(fā)Dart代碼 | 京東云技術(shù)團(tuán)隊(duì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Flutter系列文章-Flutter環(huán)境搭建和Dart基礎(chǔ)

    Flutter系列文章-Flutter環(huán)境搭建和Dart基礎(chǔ)

    Flutter是Google推出的一個(gè)開源的、高性能的移動(dòng)應(yīng)用開發(fā)框架,可以用一套代碼庫開發(fā)Android和iOS應(yīng)用。Dart則是Flutter所使用的編程語言。讓我們來看看如何搭建Flutter開發(fā)環(huán)境,并了解Dart語言的基礎(chǔ)知識(shí)。 1. 安裝Flutter SDK 首先,訪問Flutter官網(wǎng)下載Flutter SDK。選擇適合你操作系統(tǒng)

    2024年02月15日
    瀏覽(17)
  • 【Flutter】下載安裝Flutter并使用學(xué)習(xí)dart語言

    【Flutter】下載安裝Flutter并使用學(xué)習(xí)dart語言

    安裝flutter, 并使用flutter內(nèi)置的dartSDK學(xué)習(xí)使用dart語言。 編輯器: Android Studio fluuter 版本 : flutter_windows_3.13.1 內(nèi)置dartSDK : 3.1.0 dart路徑路徑: flutter安裝路徑bincachedart-sdk flutter下載地址 官網(wǎng)的下載描述蠻詳細(xì)的,直接用就行。 Android Studio 需要到官網(wǎng)下載安裝包。 如果你c盤容

    2024年02月09日
    瀏覽(27)
  • Flutter Dart語言(05)異步

    該系列教程主要是為有一定語言基礎(chǔ) C/C++的程序員,快速學(xué)習(xí)一門新語言所采用的方法,屬于在C/C++基礎(chǔ)上擴(kuò)展新語言的模式。 在Dart語言中,雖然沒有像其他語言(如Java、C++、Python)中的傳統(tǒng)多線程概念,但它采用了異步(asynchronous)編程模型來處理并發(fā)任務(wù)。Dart使用asy

    2024年02月14日
    瀏覽(22)
  • 無涯教程-Flutter - Dart簡介

    Dart是一種開源通用編程語言,它最初是由Google開發(fā)的, Dart是一種具有C樣式語法的面向?qū)ο蟮恼Z言,它支持諸如接口,類之類的編程概念,與其他編程語言不同,Dart不支持?jǐn)?shù)組, Dart集合可用于復(fù)制數(shù)據(jù)結(jié)構(gòu),例如數(shù)組,泛型和可選類型。 以下代碼顯示了一個(gè)簡單的Dart程序

    2024年02月10日
    瀏覽(24)
  • flutter的引擎,Dart語言概括

    flutter的引擎,Dart語言概括

    Dart是谷歌開發(fā)的, 類型安全的 , 面向?qū)ο?的編程語言,被應(yīng)用于 Web、服務(wù)器、移動(dòng)應(yīng)用和物聯(lián)網(wǎng) 等領(lǐng)域。 dart是谷歌在2011年推出的編程語言。谷歌希望使用dart來取代JavaScript。谷歌是一個(gè)顛覆式創(chuàng)新公司,谷歌退出golang是為了取代java,c++。谷歌退出flutter就是為了取代R

    2023年04月22日
    瀏覽(30)
  • Flutter Dart語言(04)庫操作

    該系列教程主要是為有一定語言基礎(chǔ) C/C++的程序員,快速學(xué)習(xí)一門新語言所采用的方法,屬于在C/C++基礎(chǔ)上擴(kuò)展新語言的模式。 引入代碼如下所示: 一般從官方網(wǎng)站:Page 1 | Top packages中 搜索需要的第三方庫,打開項(xiàng)目中的配置文件,名為:pubspec.yaml,找到dependencies選項(xiàng),這

    2024年02月14日
    瀏覽(33)
  • Flutter 四:main.dart簡單介紹

    Flutter 四:main.dart簡單介紹

    main.dart簡單介紹 運(yùn)行結(jié)果

    2024年02月03日
    瀏覽(25)
  • 【Flutter】dart構(gòu)造函數(shù)、工廠構(gòu)造函數(shù)

    在OOP中,我們會(huì)使用類來定義一類對(duì)象的屬性,和行為。通過調(diào)用該類的構(gòu)造函數(shù)來創(chuàng)建類的實(shí)例對(duì)象。在通過調(diào)用方法來實(shí)現(xiàn)操作行為。 和大多數(shù) OOP 語言一樣, dart 的構(gòu)造函數(shù),采用和類同名的函數(shù)名作為構(gòu)造函數(shù), 不顯示聲明構(gòu)造函數(shù)會(huì)自動(dòng)創(chuàng)建無參構(gòu)造,構(gòu)造函數(shù)不

    2024年01月21日
    瀏覽(20)
  • 【Flutter】Dio 強(qiáng)大的Dart/Flutter HTTP客戶端

    Dio是一個(gè)強(qiáng)大的Dart/Flutter HTTP客戶端,支持全局配置、攔截器、FormData、請(qǐng)求取消、文件上傳/下載、超時(shí)等功能。 首先,

    2024年02月11日
    瀏覽(24)
  • 【Flutter】Dart/Flutter SDK如何降低版本、回退到指定版本

    【Flutter】Dart/Flutter SDK如何降低版本、回退到指定版本

    因?yàn)閐art3.0以后不再支持 no-sound-null-safety;但是有些項(xiàng)目不得以切換到dart3.0以前繼續(xù)使用運(yùn)行項(xiàng)目 方法1: 通過 命令,將flutter降級(jí)為當(dāng)前通道的上一個(gè)活動(dòng)版本; 如果沒有存在老版本則會(huì)提示 flutter downgrade There is no previously recorded version for channel “stable”. 這樣的話則可以通

    2024年02月16日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包