Flutter中移动应用程序的性能改进

184 阅读8分钟

性能是任何应用程序或产品的关键因素,多种因素影响着它。一般来说,当您在Flutter中构建应用程序时,性能结果足够好,但您的应用程序的性能仍可能面临问题。

这就是为什么你需要在开发本身中注意Flutter应用程序的最佳实践和性能改进--提前修复问题,为你的最终用户提供完美的体验。

本文的目的是引导您了解Flutter应用程序的性能改进的具体最佳实践。我将告诉您如何。

  1. 避免重新构建小部件
  2. 利用恒定的小部件
  3. 高效加载列表项--按需加载
  4. 使用async/await
  5. 有效地使用运算符
  6. 使用插值技术
  7. 减少你的应用程序的大小

1.避免重新构建小部件

最常见的性能反模式之一是使用setState 来重建 [StatefulWidgets](https://blog.logrocket.com/difference-between-stateless-stateful-widgets-flutter/).每次用户与小组件互动时,整个视图都会被刷新,影响到脚手架、后台小组件和容器--这大大增加了应用程序的加载时间。

在这种情况下,只重建我们必须要更新的东西是一个很好的策略。这可以通过Flutter中的Bloc模式来实现。像flutter_bloc、MobX和Provider这样的包是流行的。

但是,你知道这也可以在没有任何外部包的情况下实现吗?让我们看一下下面的例子。

class _CarsListingPageState extends State<CarsListingPage> {
  final _carColorNotifier = ValueNotifier<CarColor>(Colors.red);
  Random _random = new Random();

  void _onPressed() {
    int randomNumber = _random.nextInt(10);
    _carColorNotifier.value =
        Colors.primaries[randomNumber % Colors.primaries.lengths];
  }

  @override
  void dispose() {
    _carColorNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('building `CarsListingPage`');
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: _onPressed,
        child: Icon(Icons.colorize),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: BackgroundWidget(),
          ),
          Center(
            child: ValueListenableBuilder(
              valueListenable: _colorNotifier,
              builder: (_, value, __) => Container(
                height: 100,
                width: 100,
                color: value,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

_CarsListingPageState ,描述了基于状态的可能行动的行为,如_onPressed 。框架的build 方法正在根据提供给该方法的context 建立一个Widget 的实例。它创建了一个floatingActionButton 的实例,并指定了诸如颜色、高度和宽度等属性。

当用户在屏幕上按下FloatingActionButtononPressed 被调用,并从 _CarsListingPageState 调用_onPressed 。然后从主调色板中分配一个随机的颜色,然后通过builder 返回,颜色被填充在屏幕的中心。

在这里,每次,上述代码中的build 方法都不会在控制台打印输出building CarsListingPage 。这意味着这个逻辑工作正常--它只是在构建我们需要的小部件。

2.利用常量小部件

普通小部件和常量小部件之间有什么区别?就像定义所建议的那样,将const ,在编译时将初始化该部件。

这意味着将widget声明为常量将在编译时而不是运行时初始化widget和它的所有附属物。这也将允许你尽可能多地利用widget,同时避免不必要的重建。

下面是一个如何利用常量widget的例子。

class _CarListingPageState extends State<CarListingPage> {
  int _counter = 0;

  void _onPressed() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: _onPressed,
        child: Icon(Icons.play_arrow),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: const DemoWidget(),
          ),
          Center(
              child: Text(
                _counter.toString(),
              )),
        ],
      ),
    );
  }
}

class DemoWidget extends StatelessWidget {
  const DemoWidget();

  @override
  Widget build(BuildContext context) {
    print('building `DemoWidget`');
    return Image.asset(
      'assets/images/logo.jpg',
      width: 250,
    );
  }
}

_CarListingPageState 类指定了一个状态:_onPressed ,它调用setState 并增加_counter 的值。build 方法生成了一个FloatingActionButton 和树中的其他元素。DemoWidget 里面的第一行创建了一个新的实例,并声明它是一个常量。

每次按下FloatingActionButton ,计数器就会增加,计数器的值会写在屏幕上的子项里面。在这个执行过程中,DemoWidget 被重复使用,整个小组件的再生被跳过,因为它被声明为一个常量小组件。

在下面的GIF中可以看到,"构建DemoWidget"的语句只在小部件第一次构建时被打印一次,然后就被重复使用。

然而,每次你热重载或重启应用程序时,你会看到打印的语句 "构建DemoWidget"。

Constant widget demo

3.高效加载列表项--按需加载

当处理列表项时,开发者通常使用小工具SingleChildScrollViewColumn 的组合。

当处理大型列表时,如果你继续使用这组相同的部件,事情很快就会变得很混乱。这是因为每个项目都被附加到列表上,然后在屏幕上渲染,这增加了系统的整体负载。

在这种情况下,使用ListView builder是个好主意。这可以在很高的水平上提高性能。让我们来看看一个构建器对象的例子。

ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('Row: ${items[index]}'),
);},);

4.使用async/await

在编写执行流程时,确定代码是允许同步运行还是异步运行是很重要的。异步代码更难调试和改进,但仍有一些方法可以在Flutter中编写异步代码,这包括使用Futureasync/await ,以及其他。

当与async 结合时,代码的可读性会得到改善,因为编写代码的结构和模式被遵循。另一方面,由于它能够在需要的地方招待故障安全策略--在这种情况下,try ... catch ,整体执行性能得到了提高。让我们来看看下面的例子。

// Inappropriate
Future<int> countCarsInParkingLot() {
  return getTotalCars().then((cars) {


    return cars?.length ?? 0;

  }).catchError((e) {
    log.error(e);
    return 0;
  });
}

// Appropriate
Future<int> countCarsInParkingLot() async {     // use of async
  try {
    var cars = await getTotalCars();


    return cars?.length ?? 0;


  } catch (e) {
    log.error(e);
    return 0;
  }
}

5.有效地使用运算符

Flutter有很多语言特有的功能。其中之一就是运算符。

如果你想减少开发时间,写出健壮的代码以避免逻辑错误,同时提高代码的可读性,建议使用空值检查运算符、可空值运算符和其他适当的运算符。

让我们看一下下面的一些例子。

car = van == null ? bus : audi;         // Old pattern

car = audi ?? bus;                      // New pattern

car = van == null ? null : audi.bus;    // Old pattern

car = audi?.bus;                        // New pattern

(item as Car).name = 'Mustang';         // Old pattern

if (item is Car) item.name = 'Mustang'; // New pattern

6.利用插值技术

使用操作符+ ,进行字符串操作和连锁是一种常见的做法。我们不这样做,而是利用字符串插值,这样可以提高代码的可读性,减少出错的几率。

// Inappropriate
var discountText = 'Hello, ' + name + '! You have won a brand new ' + brand.name + 'voucher! Please enter your email to redeem. The offer expires within ' + timeRemaining.toString() ' minutes.';

// Appropriate
var discountText = 'Hello, $name! You have won a brand new ${brand.name} voucher! Please enter your email to redeem. The offer expires within ${timeRemaining} minutes.';

正如规定的那样,内联访问变量提高了指定文本的可读性与价值,而且由于字符串被分割成更少的部分,代码变得不容易出错。

7.减少你的应用程序大小

在你的开发过程中,真的很容易在你的代码中添加大量的包。正如你可能知道的,这可能会变成臃肿的软件。

让我们用一个Android应用程序作为例子。你可以使用Gradle,一个强大的开源构建工具,带有大量的配置选项,以减少应用程序的大小。

你还可以生成安卓应用包,这是谷歌推出的一个新的打包系统。

应用包在多个方面都很高效。只有特定目标设备所需的代码才会从Google Play商店下载。这是因为谷歌应用商店只为目标设备的屏幕密度、平台架构、支持的硬件功能等重新打包和运送必要的文件和资源。

Google Play控制台统计显示,在大多数情况下,当你选择应用包而不是APK时,应用的下载大小会减少40%至60%。

生成一个应用程序捆绑包的命令是。

flutter build appbundle

为了混淆Dart语言代码,你需要在构建命令中使用obfuscate--split-debug-info 标志。该命令看起来像这样。

flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory>

上述命令生成了一个符号映射文件。这个文件对去混淆堆栈痕迹很有用。

ProGuard和保持规则

下面是一个应用了ProGuard和其他配置的应用程序级build.gradle 文件的例子。

android {
    ...

    def proguard_list = [
            "../buildsettings/proguard/proguard-flutter.pro",
            "../buildsettings/proguard/proguard-firebase.pro",
            "../buildsettings/proguard/proguard-google-play-services.pro",
            ...
    ]

    buildTypes {
        release {
            debuggable false                // make app non-debuggable
            crunchPngs true                 // shrink images
            minifyEnabled true              // obfuscate code and remove unused code
            shrinkResources true            // shrink and remove unused resources
            useProguard true                // apply proguard
            proguard_list.each {            
               pro_guard -> proguardFile pro_guard
           }
            signingConfig signingConfigs.release
        }
    }

减少APK大小的最佳做法之一是将ProGuard规则应用于你的Android应用。ProGuard应用的规则可以从最终生成的包中删除未使用的代码。在构建生成过程中,上述代码使用ProGuard从指定位置对代码和资源应用各种配置。

下面是一个为Firebase指定的ProGuard规则的例子。

-keepattributes EnclosingMethod
-keepattributes InnerClasses
-dontwarn org.xmlpull.v1.**
-dontnote org.xmlpull.v1.**
-keep class org.xmlpull.** { *; }
-keepclassmembers class org.xmlpull.** { *; }

上面的声明被称为保持规则。保持规则是在ProGuard配置文件内指定的。这些规则定义了在代码缩减和混淆阶段,当keep规则的指定模式匹配时,该如何处理文件、属性、类、成员声明和其他注释。

你可以使用破折号和声明规则关键字来指定哪些要保留,哪些要忽略,像这样。

-keep class org.xmlpull.** { *; }

上述规则在应用ProGuard时,在代码缩减阶段不会删除该类或任何类的内容。

在使用时,你仍然需要谨慎,因为如果操作不当,它可能会引入错误。原因是,如果你指定的规则删除了一个代码块、一个类或任何被声明并用于运行代码执行的成员,该规则可能会引入编译时错误、运行时错误,甚至是致命的错误,如空指针异常。

你可以从安卓官方开发者社区了解更多关于如何以正确方式实现ProGuard规则的信息。

.IPA iOS的构建步骤

同样,对于iOS,你需要执行以下的.IPA 构建步骤。

  1. 进入XCode,在右窗格中的 "档案"部分点击 "分发应用程序 "。
  2. 在选择分发方法后,例如开发,然后点击 "下一步"按钮,进入App Thinning部分。Select a method of distribution menu in App Thinning
  3. 在App Thinning部分,选择All compatible device variantsIn development distribution options, choose All compatible device variants
  4. 然后选择Rebuild from BitcodeandStrip Swift symbols。然后签署并导出.IPA 文件。它还会生成一个应用瘦身尺寸报告文件。

总结

在这篇文章中,我们已经讨论了提高Flutter应用程序性能的技术。虽然Flutter作为一个框架有很多功能,并且不断有新的更新,但性能始终是一个关键的考虑因素。

在占领全球市场时,应用程序的性能一直是并将是一个巨大的决定性因素。当考虑到移动应用程序的不同方面,如应用程序的大小、设备分辨率、代码的执行速度和硬件能力,提高性能可以带来巨大的差异,特别是在针对大量受众的时候。

The postPerformance Improvement for mobile apps in Flutterappeared first onLogRocket Blog.