1. 这不是“看一遍就懂”的源码课,而是Android开发者绕不开的Flutter落地必修课
如果你正在用Flutter开发一个需要深度集成原生能力的App——比如要调起系统相册并处理返回结果、在后台持续采集传感器数据、或者把Flutter页面嵌入到已有的大型Android原生架构中,那么你迟早会卡在一个地方:为什么onActivityResult收不到回调?为什么Fragment的生命周期和Flutter页面对不上?为什么Activity重建后Flutter页面白屏或状态丢失?这些问题,表面是API调用不对,根子却扎在Flutter引擎与Android原生组件之间那层看不见的胶水逻辑里。而这个“胶水”,就是标题里提到的Activity/Fragment流程——它不是Flutter文档里轻描淡写的“Platform Channels”或“Plugin开发指南”,而是整个Flutter Android端运行时的骨架。我带过的几个团队,有从React Native转过来的,也有纯原生干了八年的老Android,但只要一碰Flutter混合栈、页面跳转拦截、后台保活或深度定制启动流程,几乎无一例外地要回到这里来翻源码、打日志、画时序图。这篇文章不讲“怎么写一个插件”,也不堆砌FlutterEngine、FlutterActivity、FlutterFragment的类继承树截图,而是直接拆开flutter.jar和flutter_embedding_debug模块,带你走一遍从startActivity()按下那一刻,到Flutter页面真正渲染出第一帧像素的完整链路。你会看到FlutterActivity如何接管onCreate()又悄悄把控制权交还给Flutter引擎;FlutterFragment怎样在FragmentManager的夹缝中维持自己的FlutterEngine生命周期;以及最关键的——当用户按返回键、系统回收内存、甚至应用被强制杀死再拉起时,那一套精巧的SavedInstanceState序列化/反序列化机制是如何保住你的页面状态不丢的。这不是为面试准备的八股文,而是你在真实项目里改一行代码、加一个监听器、甚至只是想搞清楚为什么setInitialRoute有时生效有时失效时,必须手握的底层地图。
2. 整体设计思路:为什么Flutter不直接用标准Activity/Fragment,而要自己造一套“壳”?
2.1 核心矛盾:Flutter的跨平台抽象层与Android原生生命周期的天然错位
Flutter的设计哲学是“一切皆Widget”,它的UI更新、状态管理、动画调度全部跑在Dart isolate里,由Skia引擎直接绘制到Surface上。这带来极致的性能和一致性,但也彻底切断了它与AndroidView体系的默认绑定。一个标准的Activity,其生命周期(onCreate→onStart→onResume→onPause→onStop→onDestroy)是Android Framework强管控的,而Flutter Engine本身并不理解这些回调——它只关心“我有没有被赋予一个有效的Surface”、“我的Dart isolate是否还在运行”、“我的资源是否被释放”。如果让开发者直接继承Activity并手动桥接这些生命周期,代码会迅速变得脆弱且不可维护。举个最典型的例子:当系统因内存紧张调用onSaveInstanceState(Bundle)时,原生Activity会自动保存View树的状态(比如EditText里的文字、ListView的滚动位置),但Flutter Widget的状态全在Dart堆里,Bundle根本存不了Dart对象。这就逼出了Flutter的“壳”设计:用一层薄薄的Java/Kotlin包装类,把Android生命周期事件翻译成Flutter Engine能听懂的信号,并在关键节点(如onDestroy)主动触发Dart侧的状态持久化逻辑。这个“壳”,就是FlutterActivity和FlutterFragment。
2.2 方案选型:为什么是FlutterActivity而不是FlutterView?为什么FlutterFragment要分CachedEngine和NewEngine两种模式?
FlutterView是Flutter最原始的视图容器,它只负责把Flutter Engine的渲染结果画到一个SurfaceView或TextureView上,完全不参与生命周期管理。它适合嵌入到已有Activity的布局中(比如FrameLayout里放一个FlutterView),但代价是你得自己处理所有生命周期同步——onResume()时要调flutterView.onResume(),onPause()时要调flutterView.onPause(),稍有遗漏就会导致黑屏、卡顿或内存泄漏。而FlutterActivity则是一站式解决方案:它继承自AppCompatActivity,内部持有一个FlutterView,并在onCreate()、onStart()等方法里自动调用对应FlutterView的方法,同时还会初始化FlutterEngine、加载Dart入口、处理Intent参数。它解决的是“快速启动一个独立Flutter页面”的场景,是绝大多数Flutter App的默认选择。
FlutterFragment的复杂性则源于Android Fragment的特殊性。Fragment的生命周期比Activity更细碎(onAttach→onCreate→onCreateView→onViewCreated→onStart…),且可以被FragmentManager动态添加、移除、替换。Flutter为此提供了两种模式:
CachedEngine模式:Fragment复用同一个全局FlutterEngine实例。优点是启动快(省去Engine初始化耗时),内存占用低;缺点是所有使用该Engine的Fragment共享同一个Dart isolate,状态容易互相污染,且无法为每个Fragment指定不同的初始路由(initialRoute)。适用于Tab页切换、列表项内嵌Flutter小部件等轻量场景。NewEngine模式:每次创建Fragment都新建一个FlutterEngine。优点是完全隔离,每个Fragment拥有独立的Dart堆和路由栈,适合需要深度定制(如不同Fragment加载不同Dart bundle)或对状态隔离要求极高的场景;缺点是内存开销大,首次启动慢。FlutterFragment的newEngine()静态工厂方法就是为此服务的。
提示:
FlutterActivity内部其实也封装了一个FlutterFragment(默认是CachedEngine模式),所以FlutterActivity本质上是一个“单Fragment Activity”。这种设计统一了Activity和Fragment的实现逻辑,降低了维护成本,也方便开发者在两者间平滑迁移。
2.3 架构优势:解耦、可扩展、可控——为什么这套“壳”能撑起百万级App的混合开发?
这套设计最精妙的地方在于职责分离。FlutterActivity/FlutterFragment只做三件事:生命周期桥接、Engine生命周期管理、Intent/Arguments传递。所有与UI渲染、Dart执行、平台通道通信相关的逻辑,全部下沉到FlutterEngine和FlutterView中。这意味着:
- 解耦:你可以随时替换
FlutterView的实现(比如用TextureView替代SurfaceView以支持透明背景),而不影响Activity/Fragment的生命周期逻辑; - 可扩展:通过继承
FlutterActivity并重写configureFlutterEngine()方法,你能轻松注入自定义的PlatformChannel、注册MethodChannel处理器、甚至修改FlutterEngine的配置(如启用AOT编译、设置Dart VM参数); - 可控:当需要深度干预启动流程时(比如根据用户登录状态决定跳转到
/login还是/home),你可以在FlutterActivity的onCreate()里读取Intent,解析参数,再调用flutterEngine.getNavigationChannel().pushRoute("/home"),全程在Java/Kotlin层完成,无需Dart侧任何改动。
这种“壳-核”分离的架构,正是Flutter能在保持跨平台一致性的同时,又能深度拥抱Android原生生态的根本原因。它不是为了炫技而造的轮子,而是为了解决真实世界里“既要又要”的工程困境。
3. 核心细节解析:从startActivity()到第一帧渲染,关键环节逐帧拆解
3.1 启动阶段:FlutterActivity的onCreate()里到底发生了什么?
我们从最常用的startActivity(new Intent(this, MainActivity.class))开始。假设MainActivity继承自FlutterActivity,它的onCreate()方法会被调用。此时,FlutterActivity的父类AppCompatActivity会先执行super.onCreate(savedInstanceState),完成Activity自身的初始化(如Window、ActionBar)。紧接着,FlutterActivity的onCreate()开始工作:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { // Step 1: 创建FlutterView实例(但此时并未attach到Window) flutterView = new FlutterView(this, null); // Step 2: 如果savedInstanceState为空,说明是首次启动,需要创建新的FlutterEngine // 否则,尝试从savedInstanceState中恢复之前保存的Engine ID if (savedInstanceState == null) { // 创建新Engine,并关联到当前Activity flutterEngine = new FlutterEngine(this.getApplicationContext()); // 配置Engine:加载Dart入口、注册Plugins、设置Platform Channels configureFlutterEngine(flutterEngine); // 将Engine与FlutterView绑定 flutterView.attachToFlutterEngine(flutterEngine); } else { // 从savedInstanceState中取出Engine ID,尝试获取已存在的Engine String engineId = savedInstanceState.getString(ENGINE_ID_KEY); flutterEngine = FlutterEngineCache.getInstance().get(engineId); if (flutterEngine != null) { flutterView.attachToFlutterEngine(flutterEngine); } else { // Engine已被销毁,只能重新创建 flutterEngine = new FlutterEngine(this.getApplicationContext()); configureFlutterEngine(flutterEngine); flutterView.attachToFlutterEngine(flutterEngine); } } // Step 3: 将FlutterView作为ContentView设置到Activity setContentView(flutterView); }这段代码揭示了三个关键点:
FlutterView是核心载体:它不是ViewGroup,而是一个SurfaceView/TextureView的子类,负责提供渲染Surface并接收输入事件。setContentView(flutterView)这一步,才真正把Flutter的渲染画布挂载到Android的Window上。FlutterEngine的生命周期管理是核心难点:FlutterActivity没有简单地在onCreate()里每次都new FlutterEngine(),而是优先尝试从savedInstanceState中恢复。这是因为Activity可能因配置变更(如横竖屏旋转)被系统销毁重建,此时savedInstanceState是唯一能跨重建保留数据的途径。FlutterEngine本身不能被序列化,所以Flutter采用“ID映射”策略:FlutterEngineCache是一个全局单例Map,以字符串ID为Key,缓存所有活跃的FlutterEngine实例。savedInstanceState里存的只是这个ID,而非Engine本身。configureFlutterEngine()是定制入口:这是FlutterActivity留给开发者的钩子方法。默认实现会调用GeneratedPluginRegistrant.registerWith(flutterEngine)来注册所有pubspec.yaml里声明的插件。你可以在这里添加自定义的MethodChannel、EventChannel,或者修改FlutterEngine的DartExecutor配置。
注意:
FlutterView.attachToFlutterEngine(flutterEngine)这行代码至关重要。它不仅建立了View与Engine的引用关系,还会触发Engine的onSurfaceAvailable()回调,通知Dart侧“我已经有了渲染目标,可以开始绘制了”。如果这一步失败(比如Engine还没初始化好),FlutterView会进入等待状态,直到Engine就绪。
3.2 生命周期桥接:onResume()/onPause()如何精准同步到Flutter Engine?
FlutterActivity的生命周期方法,本质是向FlutterView转发信号,而FlutterView再将信号传递给FlutterEngine。我们以onResume()为例:
@Override protected void onResume() { super.onResume(); // 转发给FlutterView flutterView.onResume(); } // 在FlutterView.java中 public void onResume() { // 1. 恢复Surface(如果之前被暂停) if (surface != null && surface.isValid()) { nativeSurfaceCreated(surface); } // 2. 告知Engine:我已恢复,可以继续渲染和处理输入 if (flutterEngine != null) { flutterEngine.getRenderer().resumeRendering(); flutterEngine.getPlatformViewsController().onResume(); } }onPause()则是对称操作:
@Override protected void onPause() { super.onPause(); flutterView.onPause(); } // 在FlutterView.java中 public void onPause() { // 1. 暂停渲染,避免在后台消耗GPU资源 if (flutterEngine != null) { flutterEngine.getRenderer().pauseRendering(); } // 2. 释放Surface引用(可选,取决于Surface类型) if (surface != null) { nativeSurfaceDestroyed(); } }这里的关键洞察是:onResume()/onPause()的同步,核心目的是控制渲染和输入。FlutterEngine的Renderer模块会根据这些信号决定是否向GPU提交绘制命令;PlatformViewsController则负责管理嵌入的原生View(如WebView、MapView)的生命周期。如果onPause()没被正确调用,WebView可能在后台继续播放视频、消耗CPU,导致应用被系统判定为“高耗电”而强制杀掉。
3.3 状态保存与恢复:onSaveInstanceState()和onRestoreInstanceState()的深层逻辑
这是最容易被忽略,却最关乎用户体验的一环。当Activity因内存压力或配置变更被系统销毁时,onSaveInstanceState()会被调用,此时FlutterActivity必须做两件事:
- 保存
FlutterEngine的ID:以便重建时能从FlutterEngineCache中找回它; - 触发Dart侧的状态持久化:这是Flutter框架层的工作,
FlutterActivity只需确保Engine还活着。
@Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); // 保存Engine ID if (flutterEngine != null) { String engineId = generateEngineId(); // 生成唯一ID outState.putString(ENGINE_ID_KEY, engineId); FlutterEngineCache.getInstance().put(engineId, flutterEngine); } // 关键:调用FlutterView的saveState,它会触发Dart侧的WidgetsBinding.instance.addObserver() flutterView.saveState(outState); }flutterView.saveState(outState)这行代码,会调用FlutterView内部的saveState()方法,最终通过JNI调用到Dart侧的WidgetsBinding.instance.addObserver()注册的RestorationManager。这个RestorationManager会遍历所有标记了@Restorable注解的Widget(如RestorableTextEditingController),调用它们的saveState()方法,将状态序列化为Map<String, dynamic>,再由FlutterView将其打包进outState的Bundle中。
onRestoreInstanceState()则相反:
@Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // 从savedInstanceState中恢复Engine ID,并尝试获取Engine String engineId = savedInstanceState.getString(ENGINE_ID_KEY); flutterEngine = FlutterEngineCache.getInstance().get(engineId); if (flutterEngine != null) { flutterView.attachToFlutterEngine(flutterEngine); } // 触发Dart侧的状态恢复 flutterView.restoreState(savedInstanceState); }flutterView.restoreState(savedInstanceState)会将Bundle中的数据反序列化,调用对应Widget的restoreState()方法,从而还原TextField的内容、PageView的当前页码等。
实操心得:很多开发者以为只要
Activity重建了,Flutter页面就自动恢复了。实际上,Dart侧的状态恢复依赖于Widget是否实现了Restorable协议。如果你自定义了一个复杂的表单Widget,必须手动实现saveState()和restoreState(),否则重建后所有输入都会清空。Flutter官方提供的Restorable*系列Widget(如RestorableTextFormField)已经帮你做好了,直接用即可。
4. 实操过程:手把手复现关键流程,附带调试技巧与参数详解
4.1 环境准备:如何快速定位到Flutter Android源码并打上断点?
Flutter的Android端源码并非直接放在你的项目里,而是以AAR包(flutter.jar)的形式通过Gradle依赖引入。要阅读和调试源码,你需要:
- 下载Flutter SDK源码:在终端执行
flutter doctor -v,找到Flutter SDK path(如/Users/xxx/flutter),这就是源码根目录。 - 定位关键模块:
flutter.jar对应的源码在flutter/packages/flutter/lib/src(Dart层)和flutter/packages/flutter/lib/src/android(Java/Kotlin层)。但更常用的是flutter/packages/flutter_tools/gradle/下的构建脚本,它决定了flutter.jar如何被打包。 - 在Android Studio中关联源码:打开你的Flutter项目,在
android/app/build.gradle中,确保flutter依赖是通过implementation引入的(默认如此)。然后,在Android Studio的Project视图中,展开External Libraries→Flutter for Android→flutter.jar,右键点击flutter.jar→Download Sources。如果失败,手动将flutter/packages/flutter/lib/src/android路径添加为Source Root(右键文件夹 →Mark Directory as→Sources Root)。 - 打关键断点:在
FlutterActivity.java的onCreate()、onResume()、onSaveInstanceState()方法的第一行打上断点。运行App,当MainActivity启动时,断点就会被触发。注意:首次调试可能需要等待几秒,因为Android Studio需要加载符号表。
提示:
FlutterFragment的源码在flutter/packages/flutter/lib/src/android/FlutterFragment.java。如果你想调试Fragment嵌入场景,把断点打在onCreate()和onViewCreated()里,效果一样明显。
4.2 核心环节实现:一个完整的“Activity重建状态不丢”Demo
我们来写一个最小可行Demo,验证onSaveInstanceState()/onRestoreInstanceState()是否真的有效。目标:在TextField里输入文字,旋转屏幕后文字不消失。
Step 1:创建一个带TextField的Flutter页面
class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> with RestorationMixin { // 1. 定义RestorationBucket ID @override String get restorationId => 'home_page'; // 2. 创建RestorableTextEditingController final RestorableTextEditingController _controller = RestorableTextEditingController(); @override void restoreState(RestorationBucket? oldBucket, bool initialRestore) { // 3. 注册控制器到RestorationBucket registerForRestoration(_controller, '_controller'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('状态恢复Demo')), body: Padding( padding: const EdgeInsets.all(16.0), child: TextField( controller: _controller, decoration: InputDecoration(hintText: '旋转屏幕试试'), ), ), ); } }Step 2:确保FlutterActivity启用了状态保存
在android/app/src/main/java/com/example/myapp/MainActivity.java中,确认继承的是FlutterActivity,并且没有重写onSaveInstanceState()或onRestoreInstanceState()来干扰默认逻辑。
Step 3:实测与日志验证
- 运行App,在
TextField中输入“Hello World”。 - 按下
Ctrl+F12(Mac是Cmd+Fn+F12)触发横竖屏旋转。 - 观察:文字是否还在?如果还在,说明状态恢复成功。
- 为了验证底层流程,我们在
FlutterActivity.java的onSaveInstanceState()里加一行日志:
在Log.d("FlutterActivity", "onSaveInstanceState called, saving engine ID: " + engineId);onRestoreInstanceState()里也加一行:
再次旋转,查看Logcat输出,你会看到这两行日志依次出现,证明整个流程被完整触发。Log.d("FlutterActivity", "onRestoreInstanceState called, restoring engine ID: " + engineId);
4.3 参数详解:FlutterActivity的configureFlutterEngine()方法里能做什么?
configureFlutterEngine()是FlutterActivity最强大的定制点。除了默认的插件注册,你还可以:
1. 注册自定义MethodChannel:
@Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); // 创建一个名为"com.example/my_channel"的MethodChannel final MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), "com.example/my_channel"); // 设置处理器 channel.setMethodCallHandler((call, result) -> { if (call.method.equals("getBatteryLevel")) { // 获取电池电量的原生逻辑 int batteryLevel = getBatteryLevel(); if (batteryLevel != -1) { result.success(batteryLevel); } else { result.error("UNAVAILABLE", "Battery level not available.", null); } } else { result.notImplemented(); } }); }2. 修改FlutterEngine配置:
@Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); // 启用Dart VM的JIT编译(仅Debug模式有效,提升热重载速度) flutterEngine.getFlutterJNI().setIsJITCompilationEnabled(true); // 设置Dart VM参数(如增加堆内存) flutterEngine.getFlutterJNI().setVMArgs("--enable-asserts --old-gen-heap-size=512"); }3. 注入自定义PlatformView:
@Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); // 注册一个自定义的PlatformView(如一个原生图表) flutterEngine.getPlatformViewsController().registerViewFactory( "com.example/chart_view", new ChartViewFactory() ); }注意:所有在
configureFlutterEngine()里注册的Channel和PlatformView,其生命周期与FlutterEngine绑定。如果Engine被销毁(如FlutterActivity被finish()),这些注册也会自动失效,无需手动清理。
5. 常见问题与排查技巧实录:那些年踩过的坑,都在这里了
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
FlutterActivity启动后白屏,Logcat显示E/FlutterView: Tried to send a platform message to Flutter, but FlutterJNI was not attached. | FlutterEngine未正确初始化或FlutterView未attach | 1. 在onCreate()中检查flutterEngine是否为null2. 查看 configureFlutterEngine()是否被调用3. 检查 flutterView.attachToFlutterEngine(flutterEngine)是否执行 | 确保flutterEngine创建成功后再调用attachToFlutterEngine();检查configureFlutterEngine()中是否有异常抛出 |
onActivityResult()在FlutterActivity中收不到回调 | FlutterActivity未重写onActivityResult()或未调用super.onActivityResult() | 1. 检查FlutterActivity的onActivityResult()方法是否存在2. 查看是否调用了 super.onActivityResult(requestCode, resultCode, data) | 继承FlutterActivity后,如需处理onActivityResult,必须重写该方法并显式调用super.onActivityResult(),否则回调会被FlutterView拦截 |
FlutterFragment在FragmentManager中replace后,页面黑屏或闪退 | Fragment的FlutterEngine被意外销毁或重复创建 | 1. 检查FlutterFragment的构造方式(CachedEnginevsNewEngine)2. 查看 onDestroyView()中是否错误地调用了flutterEngine.destroy() | 使用CachedEngine模式时,不要在onDestroyView()中销毁Engine;使用NewEngine模式时,确保每次replace都创建新实例 |
TextField内容在Activity重建后丢失 | Dart侧Widget未实现RestorationMixin或未正确注册RestorableController | 1. 检查Widget是否with RestorationMixin2. 检查 restorationId是否唯一3. 检查 RestorableTextEditingController是否被registerForRestoration() | 严格遵循RestorationMixin的三步走:定义restorationId、创建Restorable*控制器、在restoreState()中调用registerForRestoration() |
FlutterView在onPause()后无法恢复渲染,一直黑屏 | Surface被错误释放或FlutterEngine的Renderer状态异常 | 1. 在onPause()中检查nativeSurfaceDestroyed()是否被多次调用2. 查看 FlutterEngine的Renderer日志 | 确保Surface的生命周期与FlutterView严格一致;避免在onPause()中手动调用flutterEngine.destroy() |
5.2 独家避坑技巧:来自真实项目的血泪经验
技巧1:FlutterEngine的“缓存陷阱”很多团队为了追求启动速度,会全局缓存一个FlutterEngine,并在多个FlutterActivity中复用。这看似高效,但埋下巨大隐患:FlutterEngine的Dart isolate是单线程的,所有Dart代码都在同一个isolate里执行。如果两个Activity同时加载不同的Dart bundle(比如A页面加载main.dart,B页面加载admin.dart),它们会互相覆盖,导致不可预知的崩溃。正确做法是:按业务域划分Engine缓存池。例如,创建FlutterEngineCache.getInstance().put("user_engine", userEngine)和FlutterEngineCache.getInstance().put("admin_engine", adminEngine),在启动不同Activity时,从对应Key中取Engine。
技巧2:onNewIntent()的“静默失效”当Activity的launchMode设为singleTop时,再次启动同一Activity会触发onNewIntent()而非onCreate()。但FlutterActivity的默认实现没有重写onNewIntent()!这意味着,如果你在onNewIntent()里想更新initialRoute,它根本不会生效。解决方案是:在你的MainActivity中重写onNewIntent(),并手动调用flutterEngine.getNavigationChannel().pushRoute(intent.getStringExtra("route"))。
技巧3:Fragment的onHiddenChanged()与FlutterView的“假死”当Fragment被hide()/show()时,onHiddenChanged()被调用,但FlutterView的onPause()/onResume()不会被触发!这会导致FlutterView在后台继续渲染,消耗大量GPU资源。必须手动干预:在FlutterFragment的onHiddenChanged()中,根据hidden参数,手动调用flutterView.onPause()或flutterView.onResume()。
技巧4:SavedInstanceState的“大小限制”Android对Bundle的大小有限制(通常约1MB)。如果你的Flutter页面状态非常庞大(比如缓存了大量图片Base64字符串),onSaveInstanceState()会直接抛出TransactionTooLargeException。终极方案是:放弃Bundle,改用SharedPreferences或本地数据库。在onSaveInstanceState()中,只保存一个指向本地存储的Key;在onRestoreInstanceState()中,根据Key去读取完整状态。
最后分享一个小技巧:当你在调试
FlutterActivity时,发现断点总是不触发,或者flutterEngine为null,别急着怀疑代码。先检查android/app/src/main/AndroidManifest.xml中,<activity>标签的android:name属性是否正确指向了你的MainActivity(如.MainActivity),而不是默认的io.flutter.embedding.android.FlutterActivity。这个配置错误,是新人踩得最多、也最隐蔽的坑。