Flutter应用运行管理插件app_runner的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter应用运行管理插件app_runner的使用

AppRunner简介

AppRunner 是一个用于快速且受控启动Flutter应用程序的配置器,它提供了一些runApp不具备的功能。通过使用AppRunner,你可以更优雅地处理应用启动过程中的各种问题,如自定义启动画面、错误处理等。

特性

  • 快速启动你的应用程序。
  • 不会阻塞应用程序启动时执行代码。
  • 显示独立的启动屏幕,几乎看不到原生的启动屏。
  • 可以根据需要重新加载小部件树。
  • 为Dart和Flutter代码创建安全的运行环境,捕获并处理错误,防止应用程序在运行时崩溃。
  • 更容易配置应用程序区域(Zone)。
  • 在调试和发布模式下轻松覆盖ErrorWidget
  • 允许使用自己的WidgetsBinding实现。
  • 最重要的是,集成到项目中非常简单,配置所需时间短。

安装

从2.2.0版本开始,默认使用Flutter 3.10.0+。如果你需要使用低于3.10.0版本的Flutter,请使用包版本2.1.2及以下。

将以下内容添加到你的项目的pubspec.yaml文件中:

dependencies:
  app_runner: <last version>

使用方法

下面是使用AppRunner的一个完整示例,该示例展示了如何初始化第三方库(例如Firebase),显示启动画面,并处理不同状态下的界面展示。

import 'package:app_runner/app_runner.dart';
import 'package:flutter/material.dart';

void main() {
  final WidgetConfiguration widgetConfiguration = WidgetConfiguration(
    child: AppBuilder<String>(
      preInitialize: (WidgetsBinding binding) async {
        // 初始化第三方库,比如Firebase
        await Firebase.initializeApp();
        
        // 这里可以进行其他初始化操作
        return 'Mad Brains'; // 返回的数据可以在builder中使用
      },
      builder: (
        BuildContext context,
        AsyncSnapshot<String?> snapshot,
        Widget? child,
      ) {
        late final Widget _child;
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.active:
          case ConnectionState.waiting:
            _child = const Splash(); // 启动时显示Splash页面
            break;
          case ConnectionState.done:
            final String? data = snapshot.data;
            debugPrint(data); // 打印preInitialize返回的数据
            _child = const MyApp(); // 加载完成显示主页面
            break;
          default:
            return AnimatedSwitcher(
              duration: const Duration(milliseconds: 150),
              child: _child,
            );
        }
        return _child;
      },
    ),
    errorBuilder: (BuildContext context, FlutterErrorDetails errorDetails) => MyErrorScreen(errorDetails), // 调试模式下的错误页面
    releaseErrorBuilder: (BuildContext context) => MyReleaseErrorScreen(), // 发布模式下的错误页面
    onFlutterError: (FlutterErrorDetails errorDetails) {
      // 处理Flutter错误
      debugPrint(
        errorDetails.toStringShort(),
        stackTrace: errorDetails.stack,
        error: errorDetails.exception,
      );
    },
    initializeBinding: () => CustomWidgetsFlutterBinding(), // 自定义WidgetsFlutterBinding
  );

  final ZoneConfiguration zoneConfiguration = ZoneConfiguration(
    onZoneError: (Object error, StackTrace stackTrace) {
      // 处理Dart错误
      debugPrint(
        error.runtimeType.toString(),
        stackTrace: stackTrace,
        error: error,
      );
    },
  );

  appRunner(
    kIsWeb
        ? RunnerConfiguration(
            widgetConfig: widgetConfiguration,
            onPlatformError: (Object exception, StackTrace stackTrace) {
              debugPrint(
                exception.runtimeType.toString(),
                stackTrace: stackTrace,
                error: exception,
              );
              return false;
            },
          )
        : RunnerConfiguration.guarded(
            widgetConfig: widgetConfiguration,
            zoneConfig: zoneConfiguration,
          ),
  );
}

// 如果需要重新加载小部件,可以调用如下方法:
// reloadWidget(context);
// 或者
// context.reloadWidget();

示例说明

  1. 预初始化 (preInitialize):在这个函数中,你可以执行所有需要在应用启动之前完成的任务,比如初始化数据库或网络连接。这个函数返回的数据可以通过builder中的snapshop.data访问。
  2. 构建UI (builder):根据preInitialize的状态来决定显示什么内容。当preInitialize还在进行时,可以显示一个简单的启动画面;当preInitialize完成后,则显示应用程序的主要界面。
  3. 错误处理:提供了两种类型的错误处理机制,一种是针对Flutter框架内部抛出的错误(onFlutterError),另一种是针对Dart语言级别的异常(onZoneError)。此外还支持平台特定的错误处理(onPlatformError)。
  4. 自定义绑定 (initializeBinding):允许你创建自己的WidgetsFlutterBinding实例,以便更好地控制渲染管道和其他底层行为。

以上就是关于app_runner的基本介绍及其使用方式,希望对大家有所帮助!如果有任何问题或者建议,欢迎随时交流讨论。


更多关于Flutter应用运行管理插件app_runner的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter应用运行管理插件app_runner的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,app_runner 是一个在 Flutter 应用中用于运行和管理其他应用的插件。尽管 app_runner 并不是一个广泛认知的官方 Flutter 插件,但基于你的要求,我将提供一个假设性的示例代码,展示如何在 Flutter 应用中使用一个类似功能的插件来启动和管理其他应用。

在实际开发中,Flutter 本身并不直接提供运行其他应用的 API,但可以通过调用原生代码(Android 的 Intent 和 iOS 的 URL Scheme)来实现这一功能。以下是一个基于 platform_channel 的示例,它展示了如何在 Flutter 中与原生代码交互来启动另一个应用。

Flutter 端代码

首先,创建一个 Flutter 插件接口,用于与原生代码通信。

import 'package:flutter/services.dart';

class AppRunner {
  static const MethodChannel _channel = MethodChannel('com.example.app_runner');

  static Future<void> launchApp(String packageName) async {
    try {
      await _channel.invokeMethod('launchApp', packageName);
    } on PlatformException catch (e) {
      print("Failed to launch app: '${e.message}'.");
    }
  }
}

Android 端代码

MainActivity.ktMainActivity.java 中处理来自 Flutter 的方法调用。

Kotlin 版本

package com.example.myapp

import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.example.app_runner"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "launchApp") {
                val packageName = call.argument<String>("packageName") ?: return@setMethodCallHandler result.error("INVALID_ARGUMENT", "Package name is required", null)
                try {
                    launchApp(packageName)
                    result.success(null)
                } catch (e: Exception) {
                    result.error("FAILED", e.message, null)
                }
            } else {
                result.notImplemented()
            }
        }
    }

    private fun launchApp(packageName: String) {
        val pm = packageManager
        val launchIntent = pm.getLaunchIntentForPackage(packageName) ?: return
        startActivity(launchIntent)
    }
}

Java 版本

package com.example.myapp;

import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "com.example.app_runner";

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
                .setMethodCallHandler(
                        (call, result) -> {
                            if (call.method.equals("launchApp")) {
                                String packageName = call.argument("packageName");
                                if (packageName == null) {
                                    result.error("INVALID_ARGUMENT", "Package name is required", null);
                                    return;
                                }
                                try {
                                    launchApp(packageName);
                                    result.success(null);
                                } catch (Exception e) {
                                    result.error("FAILED", e.getMessage(), null);
                                }
                            } else {
                                result.notImplemented();
                            }
                        }
                );
    }

    private void launchApp(String packageName) throws Exception {
        PackageManager pm = getPackageManager();
        Intent launchIntent = pm.getLaunchIntentForPackage(packageName);
        if (launchIntent == null) {
            throw new ActivityNotFoundException();
        }
        startActivity(launchIntent);
    }
}

iOS 端代码

在 iOS 中,你可以使用 URL Scheme 来启动其他应用。首先,在 Info.plist 中声明你的应用可以查询其他应用的 URL Scheme。然后,在 AppDelegate.swiftAppDelegate.m 中处理 Flutter 的方法调用。

Swift 版本

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.example.app_runner", binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if call.method == "launchApp" {
        guard let urlScheme = call.arguments as? String else {
          result(FlutterError(code: "INVALID_ARGUMENT", message: "URL Scheme is required", details: nil))
          return
        }
        if let url = URL(string: "\(urlScheme)://") {
          if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            result(success: nil)
          } else {
            result(FlutterError(code: "FAILED", message: "App not installed", details: nil))
          }
        } else {
          result(FlutterError(code: "INVALID_ARGUMENT", message: "Invalid URL Scheme", details: nil))
        }
      } else {
        result(FlutterMethodNotImplemented)
      }
    })
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Objective-C 版本

#import "AppDelegate.h"
#import <Flutter/Flutter.h>

@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  
  FlutterViewController *controller = (FlutterViewController *)self.window.rootViewController;
  FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"com.example.app_runner" binaryMessenger:controller.binaryMessenger];
  [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
    if ([call.method isEqualToString:@"launchApp"]) {
      NSString *urlScheme = call.arguments;
      if (!urlScheme) {
        result([FlutterError errorWithCode:@"INVALID_ARGUMENT"
                                   message:@"URL Scheme is required"
                                   details:nil]);
        return;
      }
      NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@://", urlScheme]];
      if (url && [[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
        result(nil);
      } else {
        result([FlutterError errorWithCode:@"FAILED"
                                   message:@"App not installed"
                                   details:nil]);
      }
    } else {
      result(FlutterMethodNotImplemented);
    }
  }];
  
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

使用插件

在你的 Flutter 应用中,你可以这样使用 AppRunner 插件:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('App Runner Demo'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              AppRunner.launchApp('com.example.targetapp'); // 替换为目标应用的包名或 URL Scheme
            },
            child: Text('Launch
回到顶部