当前位置: 首页 > news >正文

Flutter路由

路由作为一种页面切换的能力,非常重要。Flutter 中路由管理有几个重要的点。

Navigator 1.0:Flutter 早期路由系统,侧重于移动端 ,命令式编程风格,使用 Navigator.push() 和 Navigator.pop() 等方法来管理路由栈。

Navigator 2.0:Flutter1.22 版本以后新增,侧重于桌面端/网页端,声明式编程风格,使用 Router 和 RouteInformationParser 等类来描述和管理路由树。

Flutter路由重要的类

1.Route:应用程序页面的抽象, Navigator 管理 Route。

2.Navigator:负责路由管理的重要类,通过 push 和 pop 进行页面跳转。

Flutter 跳转方式

动态路由

适于单次导航的场景,直接在代码中创建和导航的路由。

Navigator.push(context,MaterialPageRoute(builder: (context) => RouterPageA()));

   Navigator.pop(context);
import 'package:flutter/material.dart';import 'dart_test_router1.dart';class TestRouterPage extends StatelessWidget {const TestRouterPage({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("Test Navigator"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("Test MaterialPageRoute"),ElevatedButton(onPressed: () {Navigator.push(context,MaterialPageRoute(builder: (context) => RouterPageA()));},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}
}
import 'package:flutter/material.dart';class RouterPageA extends StatelessWidget {const RouterPageA({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("RouterPageA"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("Test Navigator.pop"),ElevatedButton(onPressed: () {Navigator.pop(context);},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}
}

Navigator.push有两个参数,一个是BuildContext,另一个是Route。代码中使用的是MaterialPageRoute,执行与对应平台风格一致的切换动画(android 与 ios平台不同)。如果使用 CupertinoPageRoute,页面切换效果是左右滑动。

PageRoute可以自定义,实现自定义页面切换的动画和行为。如果想自定义下过渡动画,使用 PageRouteBuilder 创建自定义路由,通过 pageBuilder 和 transitionsBuilder 属性来定义页面和过渡动画。

通过 pageBuilder 实现页面渐入动画

                Navigator.of(context).push(PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) {return FadeTransition(opacity: animation, child: const RouterPageA());}));

pageBuilder + transitionsBuilder 实现过渡动画

Navigator.of(context).push(PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) =>const RouterPageA(),transitionsBuilder:(context, animation, secondaryAnimation, child) {//动画的起始位置,轴y方向屏幕下侧偏移起点var start = const Offset(0, 1);//动画的结束位置,0表示没有偏移var end = Offset.zero;//动画曲线,easeInOut 表示开始慢,中间加速,结束慢var curve = Curves.easeInOut;// 创建一个从begin到end的补间动画,.chain 表示与曲线结合var tween = Tween(begin: start, end: end).chain(CurveTween(curve: curve));// SlideTransition 是一个动画Widgetreturn SlideTransition(position: animation.drive(tween), child: child);}));

 静态路由

静态路由需要提前注册,首先给每个路由定义一个名称,通过这个名称来导航到对应的路由。

在应用根级别 (MaterialApp 或者 CupertinoApp ) 中定义路由,使用routes参数将路由名称映射到对应的Widget。

不带参数与返回值案例

Navigator.of(context).pushNamed("/dart_test_router1");
import 'package:flutter/material.dart';import 'dart_test_router1.dart';class TestStaticRouterPage extends StatelessWidget {const TestStaticRouterPage({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: "Test Router Demo",theme: ThemeData(useMaterial3: false,primarySwatch: Colors.blue,textButtonTheme: const TextButtonThemeData(style: ButtonStyle(splashFactory: NoSplash.splashFactory),),),home: const RealTestStaticRouterPage(),routes: {//  "/": (context) => const RealTestStaticRouterPage(),"/dart_test_router1": (context) => const RouterPageA()},// initialRoute: "/",);}
}class RealTestStaticRouterPage extends StatelessWidget {const RealTestStaticRouterPage({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("TEST STATIC ROUTER PAGE"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("Test Static Router Page"),ElevatedButton(onPressed: () {Navigator.of(context).pushNamed("/dart_test_router1");},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}

带参数与返回值

import 'package:flutter/material.dart';import 'dart_test_router1.dart';
import 'dart_test_router3.dart';class TestStaticRouterPage extends StatelessWidget {const TestStaticRouterPage({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: "Test Router Demo",theme: ThemeData(useMaterial3: false,primarySwatch: Colors.blue,textButtonTheme: const TextButtonThemeData(// 鍘绘帀 TextButton 鐨勬按娉㈢汗鏁堟灉style: ButtonStyle(splashFactory: NoSplash.splashFactory),),),home: const RealTestStaticRouterPage(),routes: {//  "/": (context) => const RealTestStaticRouterPage(),"/dart_test_router1": (context) => const RouterPageA(),"/dart_test_router3": (context) => const RouterPage3()},// initialRoute: "/",);}
}class RealTestStaticRouterPage extends StatelessWidget {const RealTestStaticRouterPage({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("TEST STATIC ROUTER PAGE"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("Test Static Router Page"),ElevatedButton(onPressed: () async {var result = await Navigator.of(context).pushNamed("/dart_test_router3",arguments: {"title": "Hello"});print(result);},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}
import 'package:flutter/material.dart';class RouterPage3 extends StatefulWidget {const RouterPage3({super.key});@overrideState<StatefulWidget> createState() {return _RouterPage3State();}
}class _RouterPage3State extends State<RouterPage3> {@overrideWidget build(BuildContext context) {var args =ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;final title = args?['title'] ?? "DEFAULT TITLE";return Scaffold(appBar: AppBar(title: Text("RouterPageA"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text("GET TITLE===========銆?title"),ElevatedButton(onPressed: () {Navigator.pop(context, "I went from RouterPage3");},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}
}

 

2024-09-12 10:01:46.569 25438-25629 flutter  I  I went from RouterPage3

路由操作

路由替换

像登录页跳首页的场景,我们希望页面跳转成功后,回到上上个页面。我们可以通过pushReplacement、pushReplacementNamed实现。

API一:动态路由替换

Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => RouterPageA()));

API二:静态路由替换

         var result = await Navigator.of(context).pushReplacementNamed("/dart_test_router3",arguments: {"title": "Hello"});

新路由入栈+移除之前的路由,直到条件满足

pushAndRemoveUntil 将给定路由推送给Navigator,删除先前的路由,直到该函数的参数predicate返回true为才停止。

import 'package:flutter/material.dart';import 'dart_test_router1.dart';
import 'dart_test_router3.dart';class TestRouterPage4 extends StatelessWidget {const TestRouterPage4({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: "Test pushAndRemoveUntil",theme: ThemeData(useMaterial3: false,primarySwatch: Colors.blue,textButtonTheme: const TextButtonThemeData(style: ButtonStyle(splashFactory: NoSplash.splashFactory),)),home: const RealTestRouterPage4(),);}
}class RealTestRouterPage4 extends StatelessWidget {const RealTestRouterPage4({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("Test pushAndRemoveUntil"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("test pushAndRemoveUntil"),ElevatedButton(onPressed: () {Navigator.pushAndRemoveUntil(context,MaterialPageRoute(builder: (context) => RouterPageA()),(Route<dynamic> route) => route.isFirst);},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)]),),);}
}

这个方式是跳转到某个页面,然后移除路由直到...为止

路由出栈,直到条件满足

在flutter 路由跳转中,我们想要回到特定的一个页面 比如:从 A -> B-> C ->D,我们向从 D页面 pop至 B 页面。我们可以使用 popUtil方法回到 B 页面。

popUntil 反复执行pop 直到该函数的参数predicate返回true为止。

import 'package:flutter/material.dart';import 'dart_test_router6.dart';class TestRouterPage5 extends StatelessWidget {const TestRouterPage5({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: "Test popUntil TestRouterPage5",theme: ThemeData(useMaterial3: false,primarySwatch: Colors.blue,textButtonTheme: const TextButtonThemeData(style: ButtonStyle(splashFactory: NoSplash.splashFactory),)),home: const RealTestRouterPage5(),);}
}class RealTestRouterPage5 extends StatelessWidget {const RealTestRouterPage5({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("Test popUntil TestRouterPage5"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("test popUntil TestRouterPage5"),ElevatedButton(onPressed: () {Navigator.push(context,MaterialPageRoute(builder: (context) => TestRouterPage6()));},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)]),),);}
}
import 'package:flutter/material.dart';import 'dart_test_router7.dart';class TestRouterPage6 extends StatelessWidget {const TestRouterPage6({super.key});@overrideWidget build(BuildContext context) {return RealTestRouterPage6();}
}class RealTestRouterPage6 extends StatelessWidget {const RealTestRouterPage6({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("Test popUntil TestRouterPage6"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("test popUntil TestRouterPage6"),ElevatedButton(onPressed: () {Navigator.push(context,MaterialPageRoute(builder: (context) => TestRouterPage7()));},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)]),),);}
}
import 'package:flutter/material.dart';class TestRouterPage7 extends StatelessWidget {const TestRouterPage7({super.key});@overrideWidget build(BuildContext context) {return RealTestRouterPage7();}
}class RealTestRouterPage7 extends StatelessWidget {const RealTestRouterPage7({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("Test popUntil TestRouterPage7"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("test popUntil TestRouterPage7"),ElevatedButton(onPressed: () {Navigator.of(context).popUntil((Route<dynamic> route) => route.isFirst);},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)]),),);}
}

上面的代码中调用Navigator.of(context)
                      .popUntil((Route<dynamic> route) => route.isFirst);直接回到了 TestRouterPage5。

 

删除指定路由

获得当前路由

ModalRoute.of(context);

 移除指定路由


if (route != null) {Navigator.of(context).removeRoute(route);
}

移除指定路由下方的单个路由

if (route != null) {Navigator.of(context).removeRouteBelow(route);
}

页面传参与数据回传

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:gsy_flutter_demo/widget/dart_test_router2.dart';import 'dart_test_router1.dart';class TestRouterPage extends StatelessWidget {const TestRouterPage({super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("Test Navigator"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[const Text("Test MaterialPageRoute"),ElevatedButton(onPressed: () async {String backContent = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => RouterPage2(title: "Custom Title")));print(backContent);},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}
}
import 'package:flutter/material.dart';class RouterPage2 extends StatefulWidget {final String title;const RouterPage2({super.key, required this.title});@overrideState<StatefulWidget> createState() {return _RouterPage2State();}
}class _RouterPage2State extends State<RouterPage2> {@overrideWidget build(BuildContext context) {var widgitTitle = widget.title;return Scaffold(appBar: AppBar(title: Text("RouterPageA"),),body: Container(alignment: Alignment.center,margin: const EdgeInsets.all(10),child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text("GET TITLE===========銆?widgitTitle"),ElevatedButton(onPressed: () {Navigator.pop(context, "I went from RouterPageA");},child: Text("Click ElevatedButton"),style: ElevatedButton.styleFrom(backgroundColor: Colors.blue,foregroundColor: Colors.white,elevation: 10,minimumSize: Size(double.infinity, 50),shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),)],),),);}
}

2024-09-11 14:51:16.211 24765-24840 flutter I  I went from RouterPageA


 

Navigator  的工作流程

Navigator  的核心是对路由栈的管理。当你调用 Navigator.push 时,一个新的路由被创建并推入栈顶;当你调用 Navigator.pop 时,栈顶的路由被移除。

  const Navigator({super.key,this.pages = const <Page<dynamic>>[],this.onPopPage,this.initialRoute,this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,this.onGenerateRoute,this.onUnknownRoute,this.transitionDelegate = const DefaultTransitionDelegate<dynamic>(),this.reportsRouteUpdateToEngine = false,this.clipBehavior = Clip.hardEdge,this.observers = const <NavigatorObserver>[],this.requestFocus = true,this.restorationScopeId,this.routeTraversalEdgeBehavior = kDefaultRouteTraversalEdgeBehavior,});

Navigator.push 

#Navigator.push

  @optionalTypeArgsstatic Future<T?> push<T extends Object?>(BuildContext context, Route<T> route) {return Navigator.of(context).push(route);}

Navigator.push 调用Navigator.of(context).push(route),内部 调用NavigatorState 的 push 方法:

  @optionalTypeArgsFuture<T?> push<T extends Object?>(Route<T> route) {_pushEntry(_RouteEntry(route, pageBased: false, initialState: _RouteLifecycle.push));return route.popped;}

 #NavigatorState

  void _pushEntry(_RouteEntry entry) {assert(!_debugLocked);assert(() {_debugLocked = true;return true;}());assert(entry.route._navigator == null);assert(entry.currentState == _RouteLifecycle.push);_history.add(entry);_flushHistoryUpdates();assert(() {_debugLocked = false;return true;}());_afterNavigation(entry.route);}

这里发生了一系列操作:

  • _history.add(route) : 将 新路由 添加到 路由栈 中。
  • route.install() : 安装路由,将页面挂载到 widget 树上。
  • route.didPush() : 通知 路由 已经被推入 栈顶 。
  • _cancelActivePointers() : 取消所有正在进行的触摸事件,防止发生意外事件。
  • route.didChange() : 通知 路由 状态发生变化。

push 方法返回一个 Future,该 Future 会在路由完成push操作时完成。

Navigator.pop

#Navigator.pop 

 Navigator.of(context).pop();
  @optionalTypeArgsvoid pop<T extends Object?>([ T? result ]) {assert(!_debugLocked);assert(() {_debugLocked = true;return true;}());final _RouteEntry entry = _history.lastWhere(_RouteEntry.isPresentPredicate);if (entry.pageBased) {if (widget.onPopPage!(entry.route, result) && entry.currentState == _RouteLifecycle.idle) {// The entry may have been disposed if the pop finishes synchronously.assert(entry.route._popCompleter.isCompleted);entry.currentState = _RouteLifecycle.pop;}entry.route.onPopInvoked(true);} else {entry.pop<T>(result);assert (entry.currentState == _RouteLifecycle.pop);}if (entry.currentState == _RouteLifecycle.pop) {_flushHistoryUpdates(rearrangeOverlay: false);}assert(entry.currentState == _RouteLifecycle.idle || entry.route._popCompleter.isCompleted);assert(() {_debugLocked = false;return true;}());_afterNavigation(entry.route);}
  • history.lastWhere:找到最后一个处于 “存在”状态 的路由条目。
  • entry.pageBased:路由是否是基于页面的。
  • widget.onPopPage:执行页面pop操作。
  • entry.route.onPopInvoked(true):通知路由pop操作被调用。
  • entry.pop<T>(result):直接调用pop。
  • _afterNavigation(entry.route):导航完成后回调。

相关文章:

  • JavaEE: 深入探索TCP网络编程的奇妙世界(五)
  • 基于SpringBoot+Vue的仓库管理系统
  • Electron 主进程与渲染进程、预加载preload.js
  • STM32F1+HAL库+FreeTOTS学习14——数值信号量
  • 【Go】-Websocket的使用
  • ThinkPHP一对多的关联模型运用
  • ClickHouse | 入门
  • 2024 年实验室设备管理系统的选择指南
  • 第四章-课后练习5:修正指数曲线模型——excel和python应用(2)
  • 力扣 简单 104.二叉树的最大深度
  • Llama 系列简介与 Llama3 预训练模型推理
  • springboot实战学习(9)(配置mybatis“驼峰命名“和“下划线命名“自动转换)(postman接口测试统一添加请求头)(获取用户详细信息接口)
  • 【数据治理-设计数据标准】
  • py-mmcif包pdbx_struct_assembly对象介绍
  • 困扰我们的,不是如何过上更幸福的生活,而是如何过上比别人更幸福的生活
  • 【React系列】如何构建React应用程序
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • 【个人向】《HTTP图解》阅后小结
  • CEF与代理
  • co模块的前端实现
  • js操作时间(持续更新)
  • js递归,无限分级树形折叠菜单
  • Just for fun——迅速写完快速排序
  • laravel 用artisan创建自己的模板
  • 经典排序算法及其 Java 实现
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 深入浅出Node.js
  • 微信支付JSAPI,实测!终极方案
  • 用 Swift 编写面向协议的视图
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • 怎样选择前端框架
  • 交换综合实验一
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • #、%和$符号在OGNL表达式中经常出现
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • $L^p$ 调和函数恒为零
  • (07)Hive——窗口函数详解
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (八)Flink Join 连接
  • (笔记)M1使用hombrew安装qemu
  • (第27天)Oracle 数据泵转换分区表
  • (第三期)书生大模型实战营——InternVL(冷笑话大师)部署微调实践
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (强烈推荐)移动端音视频从零到上手(下)
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (学习日记)2024.02.29:UCOSIII第二节
  • (转载)(官方)UE4--图像编程----着色器开发
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • .bat批处理(六):替换字符串中匹配的子串
  • .net 7和core版 SignalR
  • .Net core 6.0 升8.0
  • .net 程序发生了一个不可捕获的异常