Android随笔-Activity
一、Activity 是什么Activity 是 Android 四大组件Activity、Service、BroadcastReceiver、ContentProvider中唯一负责与用户交互的组件。它提供了一个窗口Window用于绘制 UI 并接收用户的触摸、按键等输入事件。1.1 继承体系java.lang.Object └─ android.content.Context ← 提供应用环境接口 └─ android.content.ContextWrapper ← 包装代理模式 └─ android.view.ContextThemeWrapper ← 主题支持 └─ android.app.Activity ← 核心实现Activity 间接继承自 Context因此它具备访问资源、启动其他组件、发送广播等能力。同时它实现了 Window.Callback、KeyEvent.Callback 等接口负责接收窗口和按键事件。1.2 核心职责职责说明UI 容器通过setContentView()加载布局管理 View 树生命周期管理系统通过回调方法通知 Activity 状态变化任务栈管理参与 Task 和 Back Stack 的调度数据传递通过Intent和Bundle在组件间传递数据配置响应响应屏幕旋转、语言切换等配置变更二、生命周期详解Activity 生命周期由7 个核心回调构成系统在不同状态转换时触发对应方法。2.1 完整生命周期图┌─────────────┐ │ onCreate │ ← 实例创建初始化 UI └──────┬──────┘ ▼ ┌─────────────┐ │ onStart │ ← 可见但不可交互 └──────┬──────┘ ▼ ┌─────────────┐ │ onResume │ ← 前台可交互 ★ └──────┬──────┘ │ ┌────────────────────────────┼────────────────────────────┐ │ 其他 Activity 部分覆盖 │ 其他 Activity 完全覆盖 │ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ onPause │ │ onPause │ │ │ │ (仍可见) │ └─────┬─────┘ │ │ └─────┬─────┘ ▼ │ │ │ ┌───────────┐ │ │ │ │ onStop │ ← 完全不可见 │ │ │ │ (后台状态) │ │ │ │ └─────┬─────┘ │ │ │ │ │ │ │ ┌────────────┘ │ │ │ ▼ │ │ │ ┌───────────┐ │ │ │ │ onRestart │ ← 从后台回到前台 │ │ │ └─────┬─────┘ │ │ │ │ │ │ └─────────────┘ │ │ ▼ │ ┌───────────┐ │ │ onDestroy │ ← 销毁 └───────────┘ │ │ ←─────────────────────────────────────────────────────────────────────┘ 返回/退出2.2 各回调方法详解回调触发时机典型操作调用次数onCreate()Activity 实例首次创建PhoneWindow、ContextImpl 赋值完成后setContentView()、初始化变量、恢复savedInstanceState1 次onStart()Activity 即将进入前台可见状态注册广播、启动动画多次onResume()Activity 进入前台可接收用户输入获取焦点、启动相机/定位等需要前台权限的操作多次onPause()Activity 失去焦点被部分覆盖或弹窗保存关键数据、停止动画、释放相机多次onStop()Activity 完全不可见注销广播、释放资源、停止定位多次onRestart()从onStop状态回到前台时刷新数据多次onDestroy()Activity 被销毁前释放所有资源、取消网络请求、解绑服务1 次2.3 关键状态定义Active/RunningonResume() 后位于前台可与用户交互PausedonPause() 后失去焦点但仍部分可见如被透明/对话框 Activity 覆盖StoppedonStop() 后完全不可见进入后台Killed/DestroyedonDestroy() 后等待 GC 回收2.4 生命周期回调顺序典型场景场景A 的生命周期B 的生命周期A 启动 BonPause()→onStop()onCreate()→onStart()→onResume()按返回键从 B 回到 A—onPause()→onStop()→onDestroy()onRestart()→onStart()→onResume()—按 Home 键onPause()→onStop()—从最近任务恢复onRestart()→onStart()→onResume()—屏幕旋转onPause()→onStop()→onDestroy()→onCreate()→onStart()→onResume()—重要新 Activity 的 onResume() 总是在旧 Activity 的 onPause() 之后执行。系统保证这两个回调的时序以确保前台 Activity 始终只有一个。三、启动模式与任务栈3.1 Task任务与 Back Stack返回栈Task是用户在执行某项任务时与之交互的 Activity 集合这些 Activity 按照打开顺序排列在Back Stack返回栈中。栈顶的 Activity 是当前正在显示的页面按返回键时栈顶 Activity 出栈并销毁。3.2 四种启动模式在 AndroidManifest.xml 中通过 android:launchMode 配置启动模式行为适用场景standard默认每次启动都创建新实例入栈普通页面singleTop栈顶复用若目标在栈顶调用onNewIntent()而非创建新实例不在栈顶则新建通知点击跳转、搜索页面singleTask栈内复用整个任务栈中只有一个实例。启动时若已存在将其上方所有 Activity 清除并调用onNewIntent()应用首页、WebView 页面singleInstance独占一个任务栈栈中只有该 Activity 一个实例来电界面、视频通话3.2.1 singleTask 的 “clearTop” 效果当前栈A → B → C → D 启动 BsingleTask后 结果栈A → B C、D 被清除3.2.2 singleTop 的 onNewIntent()当栈顶复用时Activity 不会被重建系统通过 onNewIntent(Intent) 传递新的 IntentOverrideprotectedvoidonNewIntent(Intentintent){super.onNewIntent(intent);setIntent(intent);// 重要更新 Intent否则 getIntent() 仍是旧的// 处理新数据}3.3 taskAffinity任务亲和性taskAffinity 定义了 Activity 倾向于归属哪个 Task默认值为应用包名。单独设置 taskAffinity 不会生效必须配合以下条件之一条件效果launchModesingleTaskActivity 启动时若不存在对应 affinity 的 Task则新建 TasklaunchModesingleInstance强制新建独立 TaskIntent.FLAG_ACTIVITY_NEW_TASK配合不同 taskAffinity 开启新 TaskallowTaskReparentingtrueActivity 可从启动它的 Task 迁移到 affinity 匹配的 Taskactivityandroid:name.MainActivityandroid:launchModesingleTaskandroid:taskAffinitycom.example.main/3.4 Intent Flag动态控制Intent Flag 的优先级高于 Manifest 中的静态配置Flag作用FLAG_ACTIVITY_NEW_TASK在新 Task 中启动通常配合taskAffinityFLAG_ACTIVITY_CLEAR_TOP若目标 Activity 存在清除其上所有 Activity 并复用目标FLAG_ACTIVITY_SINGLE_TOP同singleTop效果FLAG_ACTIVITY_CLEAR_TASK清除整个 Task目标 Activity 作为新根FLAG_ACTIVITY_NO_HISTORY启动后不保留在栈中典型用法退出登录后跳转登录页valintentIntent(this,LoginActivity::class.java).apply{flagsIntent.FLAG_ACTIVITY_CLEAR_TASKorIntent.FLAG_ACTIVITY_NEW_TASK}startActivity(intent)四、Activity 启动流程Framework 层用户点击图标 / startActivity() │ ▼ Instrumentation.execStartActivity() ← 应用进程 │ ▼ ActivityTaskManagerService.startActivity() ← Binder IPC 到 system_server │ ▼ ATMS 进行权限检查、Intent 解析、启动模式计算 │ ▼ ActivityStarter.execute() ← 决定如何启动新实例复用新 Task │ ▼ RootWindowContainer / Task 管理栈状态 │ ▼ 如果需要创建新进程 ActivityManagerService.startProcessLocked() Zygote fork 新进程 │ ▼ 新进程启动后 ActivityThread.main() → Looper.prepare() → Looper.loop() attachApplication() 到 AMS │ ▼ ATMS 调度 Activity 启动 │ ▼ ApplicationThread.scheduleTransaction() ← Binder 回调到应用进程 │ ▼ ClientTransactionHandler.handleLaunchActivity() │ ▼ Instrumentation.newActivity() ← 反射创建 Activity 实例 │ ▼ Activity.attach() ← 创建 PhoneWindow、WindowManager │ ▼ Instrumentation.callActivityOnCreate() ← 调用 onCreate() │ ▼ Activity.onCreate() → setContentView() │ ▼ WindowManager.addView(decorView) ← 将 DecorView 添加到 WMS │ ▼ Choreographer 触发 VSync → 测量/布局/绘制 → SurfaceFlinger 合成 → 显示关键关联点Instrumentation 是 Activity 启动的指挥棒ApplicationThread 是应用进程与 system_server 通信的 Binder 代理ActivityThread 是应用进程的主线程入口PhoneWindow 在 Activity.attach() 中被创建五、Activity 与 Window / View 的关系5.1 三者关系Activity ├── PhoneWindowWindow 的实现类 │ ├── DecorViewFrameLayout根 View │ │ ├── TitleBar系统标题栏可隐藏 │ │ └── ContentParentFrameLayoutid android.R.id.content │ │ └── 你的布局通过 setContentView 添加 │ │ │ └── WindowManager管理窗口的添加、更新、移除 │ └── Window.Callback接收窗口事件如 dispatchTouchEvent5.2 setContentView() 到底做了什么// Activity.setContentView()publicvoidsetContentView(LayoutResintlayoutResID){getWindow().setContentView(layoutResID);// 委托给 PhoneWindowinitWindowDecorActionBar();}// PhoneWindow.setContentView()publicvoidsetContentView(intlayoutResID){if(mContentParentnull){installDecor();// 1. 创建 DecorView ContentParent}// 2. 将用户布局 inflate 到 ContentParent 中mLayoutInflater.inflate(layoutResID,mContentParent);}流程拆解installDecor()若 DecorView 不存在创建它加载系统布局 screen_simple.xml 或 screen_title.xml找到 ContentParentid 为 R.id.content 的 FrameLayout将用户的布局文件 inflate 到 ContentParent 中5.3 从 onCreate 到显示在屏幕上onCreate() 中调用 setContentView() │ ▼ 布局被 inflate 到 ContentParent但尚未测量/布局 │ ▼ onResume() 执行完毕 │ ▼ Activity.makeVisible() 被调用 │ ▼ mDecor.setVisibility(View.VISIBLE) │ ▼ WindowManager.addView(mDecor, params) ← 第一次真正将 DecorView 添加到 WMS │ ▼ ViewRootImpl 被创建执行 requestLayout() │ ▼ performTraversals()measure → layout → draw │ ▼ Surface 锁定 Canvas → Skia/OpenGL 绘制 → SurfaceFlinger 合成 → 屏幕显示关键setContentView() 只是将布局加载到内存中的 View 树真正的测量、布局、绘制发生在 onResume() 之后、Window 首次变为可见时。六、数据传递与返回6.1 显式启动valintentIntent(this,TargetActivity::class.java)intent.putExtra(key,value)startActivity(intent)6.2 隐式启动通过 Action、Category、Data 匹配activityandroid:name.ShareActivityintent-filteractionandroid:nameandroid.intent.action.SEND/categoryandroid:nameandroid.intent.category.DEFAULT/dataandroid:mimeTypetext/plain//intent-filter/activityvalintentIntent(Intent.ACTION_SEND).apply{typetext/plainputExtra(Intent.EXTRA_TEXT,分享内容)}startActivity(intent)6.3 带返回值的启动// A 启动 BvalintentIntent(this,BActivity::class.java)startActivityForResult(intent,REQUEST_CODE)// 已废弃推荐使用 Activity Result API// B 返回数据valresultIntentIntent().apply{putExtra(result,data)}setResult(Activity.RESULT_OK,resultIntent)finish()// A 接收返回overridefunonActivityResult(requestCode:Int,resultCode:Int,data:Intent?){if(requestCodeREQUEST_CODEresultCodeRESULT_OK){valresultdata?.getStringExtra(result)}}现代推荐方式Activity Result APIprivatevallauncherregisterForActivityResult(ActivityResultContracts.StartActivityForResult()){result-if(result.resultCodeRESULT_OK){valdataresult.data?.getStringExtra(result)}}// 使用launcher.launch(Intent(this,BActivity::class.java))七、配置变更与状态保存7.1 配置变更类型屏幕旋转orientation键盘可用性变化keyboardHidden屏幕尺寸变化screenSize夜间模式切换uiMode语言切换locale默认情况下配置变更会导致Activity 销毁并重建走完整的 onDestroy() → onCreate() 流程。7.2 状态保存与恢复OverrideprotectedvoidonSaveInstanceState(NonNullBundleoutState){super.onSaveInstanceState(outState);outState.putString(edit_text,editText.getText().toString());}OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);if(savedInstanceState!null){StringtextsavedInstanceState.getString(edit_text);editText.setText(text);}}保存时机onSaveInstanceState() 在 onStop() 之前调用API 28 在 onStop() 之后用于保存瞬时 UI 状态。恢复时机onCreate(Bundle) 或 onRestoreInstanceState(Bundle)。7.3 自行处理配置变更若不想重建 Activity可在 Manifest 中声明activityandroid:name.MainActivityandroid:configChangesorientation|screenSize|keyboardHidden/此时配置变更时系统不重建Activity而是调用OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){super.onConfigurationChanged(newConfig);if(newConfig.orientationConfiguration.ORIENTATION_LANDSCAPE){// 手动处理横屏布局}}八、Activity 的注册要求Activity必须在AndroidManifest.xml中声明否则启动时会抛出ActivityNotFoundException。manifest...application...!-- 入口 Activity --activityandroid:name.MainActivityandroid:exportedtrueintent-filteractionandroid:nameandroid.intent.action.MAIN/categoryandroid:nameandroid.intent.category.LAUNCHER//intent-filter/activity!-- 普通 Activity --activityandroid:name.SecondActivity//application/manifestMAIN LAUNCHER标识应用的入口 Activity桌面图标点击后启动exported“true”Android 12 强制要求允许外部应用启动该 Activity九、总结维度核心要点本质用户交互的窗口容器管理 View 树和生命周期生命周期7 个核心回调掌握状态转换和时序关系启动模式4 种模式控制实例复用和栈行为taskAffinity控制 Task 归属启动流程Instrumentation→ATMS→ActivityStarter→ApplicationThread→ActivityThreadUI 链路setContentView()→PhoneWindow→DecorView→WindowManager→ViewRootImpl→ 屏幕数据传递IntentBundle显式/隐式启动Activity Result API配置变更默认重建可通过configChanges自行处理或配合onSaveInstanceState()