1. Activity创建与跳转核心解析
作为Android开发中最基础的组件,Activity承载了90%以上的用户交互界面。但很多初级开发者对Activity的创建和跳转只停留在会用的层面,今天我就结合8年踩坑经验,带大家深入理解这两个"基础"操作背后的技术细节。
先看一个典型场景:当用户点击APP首页的"个人中心"按钮时,我们需要完成:
- 创建PersonalActivity实例
- 关闭当前MainActivity
- 传递用户ID参数
- 设置转场动画
- 处理返回栈逻辑
这些操作看似简单,但每个环节都藏着魔鬼细节。比如在华为EMUI系统上,如果没正确处理Activity启动模式,可能会出现"双实例"问题;又比如在Android 12上,如果没适配新的PendingIntent可变性要求,跳转会直接崩溃。
2. Activity创建全流程剖析
2.1 基础创建方式
标准的Activity创建需要三个关键步骤:
<!-- AndroidManifest.xml声明 --> <activity android:name=".PersonalActivity" android:exported="false" android:theme="@style/AppTheme.NoActionBar"/>// 继承AppCompatActivity基类 class PersonalActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_personal) // 必须调用setContentView否则会白屏 } }关键提示:从Android 12开始,所有Activity必须显式声明android:exported属性,否则安装时会直接报错
2.2 进阶配置方案
实际项目中我们还需要考虑:
- 主题适配:深色模式/浅色模式的自动切换
<style name="AppTheme.NoActionBar" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="windowNoTitle">true</item> <item name="android:windowFullscreen">true</item> </style>- 生命周期优化:避免在onCreate()中执行耗时操作
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 错误示范:直接在这里请求网络 // 正确做法:在onStart()中启动协程 }- 内存泄漏防护:静态Handler必须使用WeakReference
private class SafeHandler( activity: PersonalActivity ) : Handler(Looper.getMainLooper()) { private val weakRef = WeakReference(activity) // ...处理消息时先检查weakRef.get() }3. Activity跳转的六种姿势
3.1 基础显式跳转
// 最简单的跳转方式 Intent(this, TargetActivity::class.java).apply { startActivity(this) }但这种方式存在三个典型问题:
- 没有请求码,无法处理返回结果
- 无法设置转场动画
- 在Android 10+上会有启动延迟
3.2 带参数传递的跳转
推荐使用Bundle传递数据而非直接putExtra:
Intent(this, TargetActivity::class.java).apply { putExtras(bundleOf( "user_id" to 123, "user_name" to "张三" )) startActivityForResult(this, REQUEST_CODE) }重要经验:传递对象时必须实现Parcelable接口,且数据量不要超过1MB,否则会触发TransactionTooLargeException
3.3 高级跳转配置
val options = ActivityOptions.makeCustomAnimation( context, R.anim.slide_in_right, // 进入动画 R.anim.slide_out_left // 退出动画 ).toBundle() Intent(this, TargetActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) startActivity(this, options) }动画资源示例:
<!-- res/anim/slide_in_right.xml --> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="100%" android:toXDelta="0%" android:duration="300"/> </set>4. 启动模式实战详解
4.1 四种标准模式对比
| 启动模式 | 特点 | 适用场景 | 典型问题 |
|---|---|---|---|
| standard | 默认模式,每次新建实例 | 普通页面 | 可能产生重复实例 |
| singleTop | 栈顶复用 | 通知跳转页 | 非栈顶时仍会新建 |
| singleTask | 栈内单例 | 主页/登录页 | 可能清空上方Activity |
| singleInstance | 全局单例 | 独立任务栈 | 与其他页面隔离过度 |
4.2 动态设置技巧
除了在AndroidManifest.xml中定义,还可以通过Intent动态设置:
Intent(this, MainActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) startActivity(this) }这种方式的优先级高于manifest配置,但要注意:
- 不能与FLAG_ACTIVITY_MULTIPLE_TASK混用
- 在Android 12上需要额外声明android:exported
5. 常见问题排查指南
5.1 启动崩溃问题
现象:点击跳转按钮后APP直接崩溃
- 检查清单:
- 目标Activity是否在manifest中注册
- Android 12+是否声明了exported属性
- 传递的数据是否实现了Parcelable
- 是否在主线程执行跳转
5.2 页面重叠问题
现象:新Activity覆盖在旧Activity上
- 解决方案:
// 方法1:设置透明主题 <style name="Theme.Transparent" parent="Theme.AppCompat.DayNight.NoActionBar"> <item name="android:windowIsTranslucent">true</item> </style> // 方法2:在跳转前finish当前Activity finish() startActivity(intent)5.3 返回栈异常
案例:从详情页返回时跳到了错误的页面
- 调试技巧:
// 打印任务栈信息 val am = getSystemService(ACTIVITY_SERVICE) as ActivityManager am.appTasks.forEach { task -> Log.d("TaskStack", task.taskInfo.baseIntent.toString()) }6. 性能优化实践
6.1 延迟加载技巧
使用ViewStub实现布局按需加载:
<ViewStub android:id="@+id/stub_ads" android:layout="@layout/ads_banner" android:inflatedId="@+id/ads_container" />// 需要时再加载 findViewById<ViewStub>(R.id.stub_ads).inflate()6.2 启动加速方案
- 预创建Window:
override fun onCreate(savedInstanceState: Bundle?) { window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS) super.onCreate(savedInstanceState) // ...其他初始化 }- 异步加载数据:
lifecycleScope.launch { val data = withContext(Dispatchers.IO) { repository.loadData() } bindData(data) // 主线程更新UI }7. 跨进程跳转方案
7.1 显式跨APP跳转
val packageName = "com.example.targetapp" val className = "com.example.targetapp.MainActivity" Intent().apply { setClassName(packageName, className) startActivity(this) }需要满足:
- 目标Activity exported=true
- 双方签名一致(或目标未验证签名)
- 用户已安装目标APP
7.2 隐式跳转处理
<!-- 在目标APP的manifest中声明 --> <activity android:name=".ShareActivity"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity>跳转代码:
Intent().apply { action = Intent.ACTION_SEND type = "text/plain" putExtra(Intent.EXTRA_TEXT, "分享内容") startActivity(this) }8. 最新适配要点
8.1 Android 13变更
- 必须声明POST_NOTIFICATIONS权限才能显示通知
- 附近Wi-Fi设备需要新权限
- 照片选择器需要适配新的API
8.2 折叠屏适配
val feature = packageManager.systemAvailableFeatures.find { it.name == "android.hardware.foldable" } if (feature != null) { // 处理折叠屏特有逻辑 window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES }在华为Mate X系列上还需要额外处理:
if (Build.MANUFACTURER.equals("HUAWEI", ignoreCase = true)) { // 华为折叠屏特殊适配 }9. 测试验证方案
9.1 单元测试用例
@Test fun testActivityLaunch() { val scenario = launchActivity<MainActivity>() onView(withId(R.id.btn_profile)).perform(click()) intended(hasComponent(ProfileActivity::class.java.name)) }9.2 Monkey测试策略
adb shell monkey -p com.example.app -v 500关键参数:
- --throttle 100 事件间隔100ms
- --pct-nav 30 增加导航事件比例
- --ignore-crashes 忽略崩溃继续测试
10. 终极避坑指南
Context选择:
- 用Activity的Context启动Activity
- 用Application的Context启动Service
Flags组合:
- FLAG_ACTIVITY_NEW_TASK必须与FLAG_ACTIVITY_CLEAR_TASK搭配使用
- 避免同时使用FLAG_ACTIVITY_SINGLE_TOP和FLAG_ACTIVITY_CLEAR_TOP
内存泄漏检查:
// 在BaseActivity中检测泄漏 override fun onDestroy() { super.onDestroy() if (!isChangingConfigurations) { LeakCanary.refWatcher.watch(this) } }- 跨版本兼容:
fun startActivitySafely(intent: Intent) { try { startActivity(intent) } catch (e: Exception) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+的特殊处理 intent.addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER) startActivity(intent) } } }