# 小程序运行机制

## 小程序的生命周期

小程序从启动到最终被销毁，会经历很多不同的状态，小程序在不同状态下会有不同的表现。

![小程序生命周期](https://res8.wxqcloud.qq.com.cn/wxdoc/17d2926b-a4be-4e78-8ba2-881b24734ca1.svg)

### 小程序启动

从用户认知的角度看，广义的小程序启动可以分为两种情况，一种是**冷启动**，一种是**热启动**。

- 冷启动：如果用户首次打开，或小程序销毁后被用户再次打开，此时小程序需要重新加载启动，即冷启动。
- 热启动：如果用户已经打开过某小程序，然后在一定时间内再次打开该小程序，此时小程序并未被销毁，只是从后台状态进入前台状态，这个过程就是热启动。

从小程序生命周期的角度来看，一般讲的「**启动**」专指冷启动，热启动一般被称为后台切前台。

### 前台与后台

小程序启动后，界面被展示给用户，此时小程序处于「**前台**」状态。

当用户「关闭」小程序时，小程序并没有真正被关闭，而是进入了「**后台**」状态，此时小程序还可以短暂运行一小段时间，但部分 API 的使用会受到限制。切后台的方式包括但不限于以下几种：
- 点击右上角胶囊按钮离开小程序
- iOS 从屏幕左侧右滑离开小程序
- 安卓点击返回键离开小程序
- 小程序前台运行时直接把微信切后台（手势或 Home 键）
- 小程序前台运行时直接锁屏

当用户再次进入微信并打开小程序，小程序又会重新进入「**前台**」状态。

### 挂起

小程序进入「后台」状态一段时间后（目前是 5 秒），微信会停止小程序 JS 线程的执行，小程序进入「**挂起**」状态。此时小程序的内存状态会被保留，但开发者代码执行会停止，事件和接口回调会在小程序再次进入「前台」时触发。

当使用了[后台音乐播放](/miniprogram/dev/api/media/background-audio/wx.getBackgroundAudioManager.md)、[后台地理位置](/miniprogram/dev/api/location/wx.startLocationUpdateBackground.md)等能力时，小程序可以在「后台」持续运行，不会进入到「挂起」状态

### 小程序销毁

如果用户很久没有使用小程序，或者系统资源紧张，小程序会被「**销毁**」，即完全终止运行。具体而言包括以下几种情形：

- 当小程序进入后台并被「挂起」后，如果很长时间（目前是 30 分钟）都未再次进入前台，小程序会被销毁。
- 当小程序占用系统资源过高，可能会被系统销毁或被微信客户端主动回收。
  - 在 iOS 上，当微信客户端在一定时间间隔内连续收到系统内存告警时，会根据一定的策略，主动销毁小程序，并提示用户 「运行内存不足，请重新打开该小程序」。具体策略会持续进行调整优化。
  - 建议小程序在必要时使用 [wx.onMemoryWarning](/miniprogram/dev/api/device/memory/wx.onMemoryWarning.md) 监听内存告警事件，进行必要的内存清理。

> 基础库 1.1.0 及以上，1.4.0 以下版本: 当用户从扫一扫、转发等入口（[场景值](/miniprogram/dev/framework/app-service/scene)为1007, 1008, 1011, 1025）进入小程序，且没有置顶小程序的情况下退出，小程序会被销毁。

## 小程序启动时打开的页面

小程序冷启动时，打开的页面有以下情况

- （A 类场景）若启动的场景中不带 path，则遵循 重新启动策略 ，可能是首页或上次退出的页面
- （B 类场景）若启动的场景中带有 path，则启动进入对应 path 的页面

小程序热启动时，打开的页面有以下情况

- （A 类场景）若启动的场景中不带 path，则保留上次的浏览的状态
- （B 类场景）若启动的场景中带有 path，则 autoReLaunch 自动重加载到对应 path 的页面

A 类场景常见的有下列[场景值](/miniprogram/dev/framework/app-service/scene)：

| 场景值 ID | 说明                                                                                                       |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| 1001     | 发现栏小程序主入口，「最近使用」列表（基础库2.2.4版本起包含「我的小程序」列表）                            |
| 1003     | 星标小程序列表                                                                                             |
| 1023     | 系统桌面小图标打开小程序                                                                                   |
| 1038     | 从其他小程序返回小程序                                                                                     |
| 1056     | 聊天顶部音乐播放器右上角菜单，打开小程序                                                                   |
| 1080     | 客服会话菜单小程序入口，打开小程序                                                                         |
| 1083     | 公众号会话菜单小程序入口 ，打开小程序（只有腾讯客服小程序有）                                              |
| 1089     | 聊天主界面下拉，打开小程序/微信聊天主界面下拉，「最近使用」栏（基础库2.2.4版本起包含「我的小程序」栏）     |
| 1090     | 长按小程序右上角菜单，打开小程序                                                                           |
| 1103     | 发现-小程序主入口我的小程序，打开小程序                                                                    |
| 1104     | 聊天主界面下拉，从我的小程序，打开小程序                                                                   |
| 1113     | 安卓手机负一屏，打开小程序                                                                                 |
| 1114     | 安卓手机侧边栏，打开小程序                                                                                 |
| 1117     | 后台运行小程序的管理页中，打开小程序                                                                       |

## 重新启动策略

> 基础库 2.8.0 开始支持，低版本需做[兼容处理](/miniprogram/dev/framework/compatibility)。

小程序冷启动时，如果启动时不带 path（A 类场景），默认情况下将会进入小程序的首页。

在页面对应的 json 文件中（也可以全局配置在 app.json 的 window 段中），指定 `restartStrategy` 配置项可以改变这个默认的行为，使得从某个页面退出后，下次 A 类场景的冷启动可以回到这个页面。

```json
{
  "restartStrategy": "homePage"
}
```

`restartStrategy` 可选值：

| 可选值                | 含义                                                                                          |
| --------------------- | --------------------------------------------------------------------------------------------- |
| homePage              | （默认值）如果从这个页面退出小程序，下次将从首页冷启动                                        |
| homePageAndLatestPage | 如果从这个页面退出小程序，下次冷启动后立刻加载这个页面，页面的参数保持不变（不可用于 tab 页） |

注意：即使不配置为 `homePage` ，小程序如果退出过久（当前默认一天时间，可以使用**退出状态**来调整），下次冷启动时也将不再遵循 `restartStrategy` 的配置，而是直接从首页冷启动。

无论如何，页面中的状态并不会被保留，如输入框中的文本内容、 checkbox 的勾选状态等都不会还原。如果需要还原或部分还原，需要利用**退出状态**。

## 退出状态

> 基础库 2.7.4 开始支持，低版本需做[兼容处理](/miniprogram/dev/framework/compatibility)。

每当小程序可能被销毁之前，页面回调函数 `onSaveExitState` 会被调用。如果想保留页面中的状态，可以在这个回调函数中保存一些数据，下次启动时可以通过 `exitState` 获得这些已保存数据。

```json
{
  "restartStrategy": "homePageAndLatestPage"
}
```

```js
Page({
  onLoad: function() {
    var prevExitState = this.exitState // 尝试获得上一次退出前 onSaveExitState 保存的数据
    if (prevExitState !== undefined) { // 如果是根据 restartStrategy 配置进行的冷启动，就可以获取到
      prevExitState.myDataField === 'myData' 
    }
  },
  onSaveExitState: function() {
    var exitState = { myDataField: 'myData' } // 需要保存的数据
    return {
      data: exitState,
      expireTimeStamp: Date.now() + 24 * 60 * 60 * 1000 // 超时时刻
    }
  }
})
```

`onSaveExitState` 返回值可以包含两项：

| 字段名          | 类型   | 含义                                                                       |
| --------------- | ------ | -------------------------------------------------------------------------- |
| data            | Any    | 需要保存的数据（只能是 JSON 兼容的数据）                                   |
| expireTimeStamp | Number | 超时时刻，在这个时刻后，保存的数据保证一定被丢弃，默认为 (当前时刻 + 1 天) |

一个更完整的示例：[在开发者工具中预览效果](https://developers.weixin.qq.com/s/ELP5uTmN7E8l)

在实际使用时，有一些细节需要留意。

- 如果超过 `expireTimeStamp` ，保存的数据将被丢弃，且冷启动时不遵循 `restartStrategy` 的配置，而是直接从首页冷启动。
- `expireTimeStamp` 有可能被自动提前，如微信客户端需要清理数据的时候。
- 在小程序存活期间， `onSaveExitState` 可能会被多次调用，此时以最后一次的调用结果作为最终结果。
- 在某些特殊情况下（如微信客户端直接被系统杀死），这个方法将不会被调用，下次冷启动也不遵循 `restartStrategy` 的配置，而是直接从首页冷启动。
