一、Activity

什么是 Activity?

Activity 是 Android 四大组件之一,承担着用户界面交互的核心职责。由于移动端应用涉及前后台切换、旋转、内存回收、任务栈管理等复杂情况,理解 Activity 的生命周期对于开发健壮的应用至关重要。

  • 每个 Activity 都有一个窗口,该窗口可以全屏幕填充,也可以是一个小窗口浮动在其他窗口上。

  • 一个应用程序通常由多个 Activity 组成,它会指定应用程序中的某个 Activity 作为主 Activity,这意味着当用户第一次启动应用程序时呈现给用户的活动,并且 Activity 可以相互跳转来执行不同的操作。

生命周期

一个 Activity 从创建到销毁,大致会经历以下主要方法:

  • onCreate():初始化逻辑:加载布局、初始化 ViewModel/数据源、注册监听等;

  • onStart():Activity 对用户可见,但还不能交互;

  • onResume():Activity 进入前台、获取焦点,用户可交互;

  • onPause():Activity 暂停,失去焦点(如弹出 Dialog 或跳转新界面)。适合保存少量数据、停止动画/传感器等;

  • onStop():Activity 对用户完全不可见。此时应释放较重的资源(如摄像头、传感器、广播);

  • onDestory():Activity 即将销毁,用于释放最后的资源,解绑引用避免内存泄漏。

可结合官方文档中的经典图示,可以发现:

  • onResume → onPause 属于前台可交互与失去焦点的切换;

  • onStart → onStop 属于可见与不可见的切换;

  • onCreate → onDestory 属于整个 Activity 的完整生命轨迹。

典型场景下的生命周期流程:

  • 启动 Activity A:onCreate → onStart → onResume

  • 在 A 页面打开新的 Activity B 时:

    • A:onPause → onStop

    • B:onCreate → onStart → onResume

  • 按下返回键,返回 A 页面时:

    • A:onRestart → onStart → onResume

    • B:onPause → onStop → onDestory

  • 按下 Home 键切换到桌面时,A:onPause → onStop

  • 从桌面再次打开软件时,A:onRestart → onStart → onResume

  • 打开任务栏时,A:onPause → onStop

  • 从任务栏杀掉当前应用时:会直接杀死整个应用进程,不是正常销毁 Activity,因此不会产生生命周期回调。

Activity 启动方式

Android 提供了四种启动模式(LaunchMode),用于控制 Activity 在 任务栈(Task Stack) 中的行为。

从 Launcher(桌面应用)启动应用时,应用的第一个 Activity(通常是 MainActivity)会放到一个新的任务栈里。每个 Activity 可以设置不同的启动方式:

  • standard(默认):每次启动都会创建新的实例并入栈,适合大多数页面;

  • singleTop:如果目标 Activity 已位于栈顶,则复用该实例,并调用 onNewIntent() ,如果不在栈顶,则新建实例;

  • singleTask:栈内只会存在一个实例,如果已存在则直接复用,并清除其上的其他 Activity。适合主页、搜索页等需要唯一入口的页面;

  • singleInstance:独立运行在一个新的任务栈中,并且该栈中只允许存在此一个 Activity。典型应用:闹钟、来电界面等全局独立页面。

搭建一个 A1 → B → A2 → A3 的页面切换场景(A1、A2、A3 表示同一个 Activity A),观察不同启动模式下的情况:

  • standard:B 启动的 A2 页面是新建的,A2 启动的 A3 也是新建的,从 A3 返回时:A3 → A2 → B → A1;

  • singleTop:B 启动 A2 时,A1 存在与任务栈中,但不是栈顶,所以 A2 页面是新建的。从 A2 启动 A3 时,A2 已经是栈顶,所以 A3 会复用 A2(不会新建),生命周期回调为:onPause → onNewIntent → onResume。返回时:A3 → B → A1

  • singleTask:从 A1 启动 B 页面时是常规情况(A1 到 onStop,B 到 onResume),后面的情况略有不同:

    • B 启动 A2 页面:由于任务栈中已经存储 Activity A 的实例了,因此会将 A 页面移至栈顶(onRestart → onStart → onNewIntent → onResume),B 页面会被弹出任务栈销毁(onPause → onStop → onDestory)。

    • A2 启动 A3 页面:和 singleTop 一样,Activity A 实例已经存在与栈顶了,不会创建新页面,直接复用(onPause → onNewIntent → onResume)。

  • singleInstance:Activity A 是单独的任务栈且只有一个实例。

    • B 启动 A2 页面:重新启动 A 页面(onRestart → onStart → onNewIntent → onResume),B 页面被暂停(onPause → onStop)

    • A2 启动 A3 页面:和 singleTop 一样,Activity A 实例已经存在与栈顶了,不会创建新页面,直接复用(onPause → onNewIntent → onResume)。

    • A3 返回时:A 页面从自己的任务栈中弹出被销毁(onPause → onStop → onDestory),B 页面重新启动(onRestart → onStart → onResume)。

Activity 的启动流程

todo...

二、Service

什么是 Service?

Service 是 Android 中四大组件之一,它是一种没有界面、可以在后台长期运行的组件。常见场景包括:

  • 音乐播放器:即使切换到其他应用,后台依然能播放音乐;

  • 文件下载:退出应用后仍可保持下载任务;

  • 长连接:即时通讯类应用会在后台维持连接,接收消息。

创建 Service 的基本方式:

// 创建类继承 Service
class MyService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        return null // 普通启动服务返回 null
    }
}

然后在 AndroidManifest.xml 中配置:

<application>
    <service
        android:name=".MyService"
        android:enabled="true"
        android:exported="false" />
</application>

Service 的生命周期

核心的回调方法:

  • onCreate():首次创建时调用,一次性初始化。

  • onStartCommand():每次调用 startService() 都会触发。

  • onBind():当使用 bindService() 时触发,返回一个 Binder 对象。

  • onUnbind():调用 unbindService() 解除绑定时调用。

  • onDestroy():销毁时调用,用于释放资源。

服务的两种启动方式

startService()

当调用 startService() 方法时表示启动服务。

  • 生命周期流程是:onCreate()(仅执行一次)→ onStartCommand(),每调用一次 startService()onStartCommand() 都会执行一次;

  • Service 会一直运行,直到调用 stopService()stopSelf(),生命周期进入 onDestroy()

  • 不能与 Activity 通信,适合执行后台任务(如音乐播放、下载)。

val intent = Intent(this, MyService::class.java)
startService(intent)

bindService()

startService() 方法表示绑定服务。

  • 每次调用 bindService(),系统都会尝试和目标 Service 建立一个绑定关系,如果 Service 还没有启动,会先触发 onCreate(),然后触发 onBind() 返回一个 IBinder 对象可以与 Service 进行通信;

  • 同一个 Activity/Fragment 对同一个 Service 多次调用 bindService(),系统会复用已有的连接,不会重复创建新的 Service 实例。

  • 绑定服务适合需要与 Service 通信的场景,如音乐播放器的播放/暂停控制;

val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)

通过 ServiceConnection 获取 Service 返回的 Binder 对象:

private val connection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
        val myBinder = binder as MyBinder
        myBinder.doSomething()
    }
    override fun onServiceDisconnected(name: ComponentName?) {}
}

注意:默认的 Service 是本地服务,所有生命周期回调运行在应用的主线程(UI 线程),如果要执行耗时操作,必须配合子线程(如 ThreadHandlerThreadExecutorServiceCoroutine)使用,否则会导致 ANR。

远程服务

远程服务独立于主进程,是一个独立的进程。不受其他进程的影响,多个进程都可以通过 AIDL(跨进程通信方式)单独与该服务进行 IPC 通讯,因此较为复杂,且这种服务一般长期运行在后台。典型的例子就是系统服务。

前台服务

从 Android 8.0 (API 26) 开始,如果应用需要在后台长时间运行(如音乐播放、导航、健康监测),必须使用前台服务(Foreground Service),否则会因为系统限制而被回收。

  • 前台服务必须在服务启动后 5 秒内调用 startForeground(),并显示一个常驻通知

  • 优先级高,不容易被系统杀死。

class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = NotificationCompat.Builder(this, "music_channel")
            .setContentTitle("正在播放")
            .setContentText("音乐名 - 歌手")
            .setSmallIcon(R.drawable.ic_music_note)
            .build()
        startForeground(1, notification)
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

三、BroadcastReceiver

什么是 BroadcastReceiver?

在安卓系统中有广播消息,可以由系统、应用程序或其他组件发出。广播接收器(BroadcastReceiver)就是一种用于接收和响应广播消息的组件。

广播的分类

Android 中的广播主要可以分为两种类型:

  • 标准广播:在广播发出之后,所有的 BroadcastReceiver 几乎会在同一时刻收到这条广播消息,因此它们之间没有任何先后顺序可言。

  • 有序广播:是一种同步执行的广播,在广播发出之后,同一时刻只会有一个 BroadcastReceiver 能够收到消息,等待逻辑处理完毕之后,广播才会继续传递。此时的 BroadcastReceiver 是有先后顺序的,且前面的 BroadcastReceiver 可以截断正在传递的广播,这样后面就无法收到广播消息了。

静态/动态注册广播

  • 静态注册不受 app 是否运行所影响,即使 app 没有运行,app 里静态注册的广播接收者也可以接收广播,因此比较耗电,占内存。适合长时间监听广播,如系统广播。

  • 动态注册的广播在组件结束之前,也会随之结束(当然是手动注销的),适合只在特殊时刻监听广播。

  • 动态注册的优先级高于静态广播。

本地广播

本地广播用于应用内部传递消息,比 BroadcastReceiver 更加高效和安全。

与 BroadcastReceiver 是以 Binder 通讯方式为底层实现的机制不同,众所周知 Binder 是跨进程的。

而 LocalBroadcastManager 是利用应用内部的 Handler 来实现,只是利用到了 IntentFilter 的 match 功能,因为是 Handler 实现的应用内的通信,自然安全性更好,效率更高。

四、ContentProvider

Android 出于安全考虑,每个应用运行在独立的沙箱中,默认无法直接访问其他应用的数据。

ContentProvider 它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用 ContentProvider 是 Android 实现跨程序共享数据的标准方式。

ContentProvider 的用法一般有两种:

  • 一种是使用现有的 ContentProvider 读取和操作其他程序中的数据;

  • 另一种是创建自己的 ContentProvider,给程序的数据提供外部访问接口。