综合能力:如何编写脚本让 ReactNative 自动启动 Android 模拟器

2,231 阅读3分钟

这篇文章记录笔者在 ReactNative 开发中遇到的一个具体的小问题,过程中新学到一些技巧,记录下来方便自己回顾也希望能帮助有同样需求的开发者。

跟 ReactNative 其实关系也不大,主要是写一点 Shell 脚本和学习使用 VSCode Task

背景

项目中需要用到 ReactNative,为了方便后期的开发、打断点调试。我给 VSCode 安装了react-native 插件。在根据文档简单地配置之后,点击开始按钮就可以在 VSCode 里面打断点、调试、查看日志输出等等 (不用再打开一个 Chrome 或 XCode 窗口)

在这之前,我已经安装了 XCode、Android Studio、Android Emulator;配置了环境变量 $ANDROID_SDK,并且终端可以正确运行 adb 命令。安装 Android Studio

问题

一切准备就绪,在项目根目录下创建配置 .vscode/launch.json 文件。点击 VSCode 的运行与调试按钮开始进入开发。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Android",
      "cwd": "${workspaceFolder}",
      "type": "reactnative",
      "request": "launch",
      "platform": "android"
    },
    {
      "name": "Debug iOS",
      "cwd": "${workspaceFolder}",
      "type": "reactnative",
      "request": "launch",
      "platform": "ios"
    }
  ]
}

一键启动开发

运行 Debug iOS 命令一切安好,react-native-cli 会自动启动模拟器,运行 packager 程序。而执行 Debug Android任务时,却没有响应 -_- !,查了终端提示报错 No connected devices!

也就是说 react-native run-android 命令不会自动启动模拟器。需要每次手动打开 Android Studio,启动模拟器。

这个体验不算太糟糕,只要把说明文档写好一点,错误日志明显一些还好。但是这种老鼠屎容易让新人开发者感到沮丧,把好不容易燃起的开发热情又给浇灭。

MR 上看,如果你使用的是 react-native^0.61.0 以上的版本不会有这个问题,不过也不影响阅读文章,或许能学到新的知识。

我们来看看怎么解决。

如何解决

Emulator API

源码 上了解到,Android Emulator 有提供命令行工具,文档在这里,我们可以使用 API 获取设备列表,启动模拟器等等操作。

钩子

我们还需要用到钩子方法,在 react-native run-android 命令执行之前执行脚本,启动模拟器。

查阅了 VSCode 的文档,找到 launch.json 支持 preLaunchTask,在这里可以指定前置任务。同时,VSCode 还有个 Task 的配置。

// .vscode/launch.json
{
  "name": "Debug Android",
  "cwd": "${workspaceFolder}",
  "type": "reactnative",
  "request": "launch",
  "platform": "android",
  "preLaunchTask": "run-android-emulator" // 对应的是 task 名字
}
// .vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "shell",
      "label": "run-android-emulator",
      "command": "[write your command here]",
      "presentation": {
        "reveal": "never" // 是否需要在终端展示,never | always
      }
    }
  ]
}

启动 Emulator

通过查阅文档,只需要运行 emulator 指定设备名即可。

$ANDROID_SDK/emulator/emulator -avd Pixel_2_API_27;

Pixel_2_API_27 是我本地模拟器的名字。

更灵活一点
直接写死设备名肯定是不可取的,一来脚本不通用,无法和其他同事分享;二来和设备强绑定,哪天设备删了还得改脚本。

从 Emulator 文档上找到另一个 API,可以获取当前电脑上的设备列表。

$ANDROID_SDK/emulator/emulator -list-avds

如何获取一个设备名字

上面的命令 -list-avds 获取到的是一个设备列表,并且输出流是指向 terminal 的,直接打印出来。如何只获取其中一个设备呢?(假设我们就只取第一个可用设备)

如果在 NodeJS 中,借助 child_process 模块同步执行脚本拿到返回值(Buffer 类型),toString() 一下再简单处理取第一条数据就能得到第一个设备名。

const emulators = require('child_process')
  .execSync('$ANDROID_SDK/emulator/emulator -list-avds')
  .toString()
 
 
const firstEmulatorName = emulators.split('\n')[0]
 
console.log(firstEmulatorName) // 得到 Pixel_2_API_27

但是这么简单的任务再单独映入一个脚本,实在没必要。借助 Linux 管道很容易完成这个需求。 可以是这样:

$ANDROID_SDK/emulator/emulator -list-avds | head -n 1

| 符号代表的是管道,它会将左边命令的执行结果的标准输出流转化为右边命令的标准输入流。

第二个命令使用 head,可以用于显示输入开头的内容,对应的命令是 tail,显示尾部的内容。-n 1 表示只截取一行。

现在我们有了第一台设备名,也掌握了如何启动指定设备,怎么把他们串起来?还是管道

在 Linux 中,并不是所有命令都支持管道,例如 echo,终端执行 一下代码,什么都不会输出。

$ ls | echo

对于不支持管道的命令,可以借助另一个命令 xargs 进行对接。

$ ls | xargs echo

Try Again.

... | head -n 1 | xargs -I device $ANDROID_SDK/emulator/emulator -avd device;

xargs 把后面的 emulator 命令和前面的管道对接起来,-I device 声明了变量名,在启动脚本中直接使用它。

更多用法可以看 xargs 命令教程

Run In Background

到目前为止,联通了 VSCode 和模拟器命令。点击开始调试按钮已经可以顺利启动 Android 模拟器了。

不过还有问题没解决,启动 emulator 命令是阻塞性的,也就是 task 一直不会结束,而 VSCode 会一直等到 Task 结束才执行 run-android 命令。

所以启动模拟器必须是异步的。后台执行命令有很多种方式,简单的话可以使用 screen -dm

... | xargs -I device screen -dm $ANDROID_SDK/emulator/emulator -avd device;

而异步又带来了新的问题,设备上启动模拟器可能需要一定时间,如果 CPU 跑得很满,导致 run-android 命令在模拟器启动之前执行,还是会有问题。

sleep 一下如何?使用 sleep 命令,让程序阻塞几秒钟,等待模拟器启动。

... | xargs -I device screen -dm $ANDROID_SDK/emulator/emulator -avd device; sleep 3;

不够优雅,如果时间设长了,会有不必要的等待时间,设短了又怕不够。Google 一下,找到 adb 其实支持 wait-for- api。一直等到设备可用之后,执行 run-android 正是需求要的,果断替换 sleep

最终代码:

// .vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "shell",
      "label": "run-android-emulator",
      "command": "$ANDROID_SDK/emulator/emulator -list-avds | head -n 1 | xargs -I device screen -dm $ANDROID_SDK/emulator/emulator -avd device; adb wait-for-device;",
      "presentation": {
        "reveal": "never"
      }
    }
  ]
}

效果

总结

通过这个例子,巩固学习了一些的知识:

  • 如何编写 VSCode Task
  • abdAndroid emulator 的一些基本操作
  • 基础的命令: echo/sleep/head/tail
  • xargs 和管道
  • 让命令在后台执行不挂断

前端不要自我设限,不要满足于只会写 JavaScript,会用 Vue/React 框架。大胆地去学习了解其他技术知识,转化为自己得心应手的武器,提升自己的综合能力也很重要。