添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

为什么不是 Android Studio ?

很久以前在同事的电脑上瞥过几眼 Android Studio 的界面,有点丑陋而且看着不太好用的样子。当然,这不是主要原因,作为广大网友强烈推荐的安卓开发工具必然有它的独到之处,而我选择 IntelliJ IDEA 也是有我的考量:

Android Studio 本来就是基于 IntelliJ IDEA 定制的( 后者 + 一堆定制插件 )。

对比了 Android Studio ( Arctic Fox | 2020.3.1 ) IntelliJ IDEA ( 2021.3 ) 之后,发现二者的界面功能就是一个模子刻出来的,而属于前者独有的功能:

C/C++ 语言、 CMake NDK 支持。 Jetpack Compose 支持。 Gradle 文件创建模板( Gradle Build Script )。
  • 内置对 Google Cloud Platform 的支持,可轻松集成 Google Cloud Messaging App Engine
  • 其他功能:
  • Profile or Debug APK
  • Troubleshoot Device Connections
  • Firebase
  • App Links Assistant
  • 其他不可见的功能。
  • 我在 IntelliJ IDEA 上安装了很多插件并做了较多配置,不想再重复一遍。而且能共用一个 IDE 也能节省很多磁盘空间。

  • 我在 IntelliJ IDEA 上本来就做多种语言的开发: 前端 Java 后端 脚本语言( Bat / Shell / Python ) 等,能多一个安卓那自然是更好的。

  • 理论上这个方案是没问题的,我实际开发也还没遇到过因为 IntelliJ IDEA 自身原因导致的问题。所以,不要无脑听别人说用什么就用什么,有疑惑就多试试。团队开发中,没能力还是建议使用团队统一的 IDE

    配置环境变量

    开发时下载的依赖包、缓存、临时配置等默认是保存在用户目录( ${user.home} )下,通过配置环境变量可以将它们安排在自定义的目录下。

    # 设置 Android SDK 安装目录的路径
    # ref: https://developer.android.google.cn/studio/command-line/variables?hl=en
    ANDROID_SDK_ROOT=E:\Android\SDK
    # ANDROID_HOME 也指向 SDK 安装目录,但已弃用,只是为了兼容 AGP < 3.4.0
    ANDROID_HOME=%ANDROID_SDK_ROOT%
    # 注意和 ANDROID_SDK_ROOT 的区别,一般这个变量代表安卓模拟器配置文件目录的父目录
    # adb 估计写死了路径, ${user.home}/.android/adbkey 无法根据这个变量迁移
    # 按英文版文档说明:Android Studio 4.2 起这个变量被 ANDROID_PREFS_ROOT 代替
    ANDROID_SDK_HOME=E:\Android
    # 修改 Gradle 的全局配置和缓存的目录,IntelliJ IDEA 能够自动识别这个路径
    GRADLE_USER_HOME=E:\Android\.gradle
    # 将 adb 加入到系统全局路径中
    Path=%ANDROID_SDK_ROOT%\platform-tools;%Path%
    

    配置 Gradle

    添加 Gradle 全局 初始化脚本 ,它们会在所有项目构建开始前执行,并可以对构建生命周期进行拦截,可以添加额外的配置和插件以及任务。( 参考

    官网用法介绍

  • 管理公司内部的配置,例如去哪里查找定制的插件。
  • 配置一些基于当前环境( 开发环境还是持续集成环境 )的属性。
  • 提供构建所需要的用户个人信息,例如仓库或数据库的用户名和密码。
  • 定义机器的环境,例如 JDK 安装在什么位置。
  • 注册一些监听器。这对一些需要监听 Gradle 事件的工具来说很有用。
  • 注册一些 Logger 。你可能希望去自定义如何输出 Gradle 产生的日志信息。
  • 下面所有脚本( 按需添加 )采用 Kotlin DSL 语法编写, Gradle 5.0 起可用,如使用旧版本请自行改为 Groovy 语法。

    没有代码提示的话,写 Gradle 脚本比批处理还恶心。

    Gradle 项目中打开下面的脚本才会有代码提示。

    .gradle/init.d/0.repos.init.gradle.kts

    * 国内的网络环境默认不设置镜像加速的话很大概率无法下载依赖包导致初始化报错,对于刚入门的新手来说直接劝退。 * 本脚本全局配置所有项目使用阿里云镜像源进行加速,这样就不用每个项目重复设置了,新项目拉下来就能跑。 * 文件名以 0 开头是为了把这个脚本的执行顺序排在前面。 * gradlew showRepos * gradlew switchDeleteRawRepos * @author anyesu <https://github.com/anyesu> val pluginRepositories = ArrayList<MavenArtifactRepository>() val CONFIG_FILE = File(gradleUserHomeDir, "init.d/.deleteRawRepos") /** 是否删除项目中配置的源地址 */ var deleteRawRepos = CONFIG_FILE.exists() val GROUP_NAME = "init.gradle" val TASK_NAME_SHOW_REPOS = "showRepos" val TASK_NAME_SWITCH_DELETE_RAW_REPOS = "switchDeleteRawRepos" // 源地址 val rawRepos = listOf( // central "https://repo1.maven.org/maven2/", "https://repo.maven.apache.org/maven2/", // jcenter "https://jcenter.bintray.com/", // google "https://maven.google.com/", "https://dl.google.com/dl/android/maven2/", // gradle-plugin "https://plugins.gradle.org/m2/" // 代理地址 object Repos { const val MAVEN_ALIYUN = "https://maven.aliyun.com/repository/public" const val MAVEN_JITPACK = "https://www.jitpack.io" const val GOOGLE_ALIYUN = "https://maven.aliyun.com/repository/google" const val GRADLE_PLUGIN_ALIYUN = "https://maven.aliyun.com/repository/gradle-plugin" const val GRADLE_PLUGIN_ALIYUN_OLD = "https://maven.aliyun.com/nexus/content/repositories/gradle-plugin" projectsLoaded { allprojects { if (deleteRawRepos) deleteRawRepos() configureRepos() tasks.register(TASK_NAME_SHOW_REPOS) { group = GROUP_NAME description = "查看当前项目最终依赖的仓库" doLast { printRepositories() } rootProject { tasks.named(TASK_NAME_SHOW_REPOS) { doFirst { displayScriptConfigs() pluginRepositories.print("pluginManagement.repositories") tasks.register(TASK_NAME_SWITCH_DELETE_RAW_REPOS) { group = GROUP_NAME description = "切换 - 是否删除项目中配置的源地址" doLast { if (deleteRawRepos) CONFIG_FILE.delete() else CONFIG_FILE.createNewFile() deleteRawRepos = !deleteRawRepos displayScriptConfigs() beforeSettings { configurePluginManagement() fun displayScriptConfigs() { println("[ Current Initialization Script Configs ]") println(" - deleteRawRepos [${if (deleteRawRepos) "enabled" else "disabled"}]") fun Settings.configurePluginManagement() = pluginManagement { repositories { all { if (this !is MavenArtifactRepository) return@all if (!deleteRawRepos) { pluginRepositories.add(this) return@all val path = url.toString().run { if (endsWith("/")) this else "$this/" } if (rawRepos.contains(path)) { logger.info("removed from `settings.pluginManagement.repositories`: ${display()}") remove(this) } else { pluginRepositories.add(this) // TODO 阿里云插件新版地址有时候 jar 会下载不下来,所以同时加上旧版的地址备用。 maven(Repos.GRADLE_PLUGIN_ALIYUN_OLD) maven(Repos.GRADLE_PLUGIN_ALIYUN) fun Project.configureRepos() { buildscript.repositories.configureRepos() repositories.configureRepos() fun Project.deleteRawRepos() { buildscript.repositories.deleteRawRepos(name, "buildscript.repositories") repositories.deleteRawRepos(name, "repositories") fun Project.printRepositories() { buildscript.repositories.print("buildscript.repositories") repositories.print("repositories") fun RepositoryHandler.deleteRawRepos(projectName: String = "", display: String = "") { // TODO: all 的逻辑不仅顺序执行还会作为回调 all { if (this is MavenArtifactRepository && rawRepos.contains(url.toString())) { logger.info("removed from `${projectName}.${display}`: ${display()}") remove(this) fun RepositoryHandler.configureRepos() { mavenLocal() mavenCentral { url = uri(Repos.MAVEN_ALIYUN) } maven(Repos.MAVEN_ALIYUN) google { url = uri(Repos.GOOGLE_ALIYUN) } maven(Repos.MAVEN_JITPACK) jcenter { url = uri(Repos.MAVEN_ALIYUN) } if (!deleteRawRepos) { mavenCentral() google() jcenter() fun RepositoryHandler.mavenArtifactRepositories() = map { if (it is MavenArtifactRepository) it else null }.filterNotNull() fun RepositoryHandler.print(display: String = "") { mavenArtifactRepositories().print(display) fun List<MavenArtifactRepository>.print(display: String = "") { println("\n[ ${display} ]") forEach { println(" - ${it.display()}") } fun MavenArtifactRepository.display() = "[ ${name.padEnd(32)} ] -> $url"

    .gradle/init.d/cacheToLocalMavenRepository.init.gradle.kts

    解决了原脚本的几个问题:

    parts[1] 可能包含 . ,不能将其替换为 / ,比如:

    org.jetbrains.intellij/org.jetbrains.intellij.gradle.plugin
    

    还要考虑更深的子目录,比如:

    com.jetbrains.intellij.idea/ideaIC/2021.1.3/fef2d88b0f4771ce5ac9a9963d3717080439cf4f/ideaIC-2021.1.3
     * 添加一个 Task 用来把 Gradle 缓存的 Jar 包复制到 Maven 的本地仓库,这样和 Maven 就可以共用缓存了,而且 Maven 的目录结构更方便查找。
     * 需要先设置环境变量 M2_HOME 为 Maven 的安装目录,否则会复制到默认的 ${user.home}/.m2 目录。
     * 需要手动执行, Gradle 原有缓存在迁移后自行手动删除。
     * gradlew cacheToLocalMavenRepository
     * @see https://blog.csdn.net/feinifi/article/details/81458639
     * @author anyesu <https://github.com/anyesu>
    val GROUP_NAME = "init.gradle"
    projectsLoaded {
        rootProject {
            tasks.register<Copy>("cacheToLocalMavenRepository") {
                group = GROUP_NAME
                description = "Gradle 缓存复制到 Maven 本地仓库"
                from(File(gradleUserHomeDir, "caches/modules-2/files-2.1"))
                into(repositories.mavenLocal().url)
                eachFile {
                    val parts = ArrayList(path.split("/"))
                    parts[0] = parts[0].replace('.', '/') // 路径分割
                    if (parts.size > 3) parts.removeAt(3) // 去除随机值
                    path = parts.joinToString("/")
                duplicatesStrategy = DuplicatesStrategy.EXCLUDE // 重复文件策略
                includeEmptyDirs = false
    

    .gradle/init.d/dependency-graph.init.gradle.kts

    * 为所有项目添加 gradle-dependency-graph-generator-plugin 插件,用于生成项目的依赖关系图。 * gradlew generateDependencyGraph * gradlew generateProjectDependencyGraph * @author anyesu <https://github.com/anyesu> val PLUGIN_ID = "com.vanniktech.dependency.graph.generator" projectsLoaded { allprojects { buildscript { repositories { mavenCentral() dependencies { classpath("com.vanniktech:gradle-dependency-graph-generator-plugin:+") afterEvaluate { // 和项目配置冲突,如果项目中已经配置了就忽略本配置 if (!plugins.hasPlugin(PLUGIN_ID)) { apply(plugin = PLUGIN_ID) logger.info("project '$name' apply plugin: $PLUGIN_ID")

    .gradle/init.d/dependencyUpdates.init.gradle.kts

    借助 Package Search 插件可以提供类似的功能,更直观。

    * 依赖版本管理,查看哪些依赖可以升级( 可自定义升级规则 )。 * gradlew dependencyUpdates * @see https://github.com/ben-manes/gradle-versions-plugin#using-a-gradle-init-script * @author anyesu <https://github.com/anyesu> import com.github.benmanes.gradle.versions.VersionsPlugin import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask val GROUP_NAME = "init.gradle" val PLUGIN_ID = "com.github.ben-manes.versions" initscript { repositories { mavenLocal() gradlePluginPortal() dependencies { classpath("com.github.ben-manes:gradle-versions-plugin:+") projectsEvaluated { allprojects { // 和项目配置冲突,如果项目中已经配置了就忽略本配置 if (!plugins.hasPlugin(PLUGIN_ID)) { configureDependencyUpdates() logger.info("project '$name' apply plugin: $PLUGIN_ID") val String.isNonStable get() = listOf("final", "rc", "m", "alpha", "beta", "ga").any { val regex = "^(?i).*[.-]${it}[.\\d-]*$".toRegex() regex.matches(this) // 版本号以 v 开头,比如:v1.1.1 val String.startsWithV get() = startsWith("v") fun Project.configureDependencyUpdates() { apply<VersionsPlugin>() tasks.withType<DependencyUpdatesTask> { group = GROUP_NAME rejectVersionIf { !currentVersion.isNonStable && candidate.version.isNonStable || currentVersion.startsWithV && !candidate.version.startsWithV

    配置 SDK

    打开 IntelliJ IDEA 新建一个安卓项目

    这时因为还没有安装 Android SDK 所以无法新建项目,会提示我们去安装( 如下图所示: Install SDK )。我们照着提示安装就好了,安装路径会自动识别为我们前面设置的环境变量 ANDROID_SDK_ROOT

  • com.android.tools.build:gradle:7.x
  • Minimum supported Gradle version is 7.0.2.
  • IntelliJ IDEA (or Android Studio) with version 2020.3.1 or newer.
  • 当然,也可以进设置页面自定义选择 SDK 版本进行安装( 参考 ), API 19 + 可以兼容目前大部分的项目和手机。

    打开新建的项目

    打开项目后会自动识别为 Gradle 项目,开始自动下载对应的 Gradle wrapper 和依赖包。刚开始使用的话因为本地没缓存所以下载会比较久,用一段时间以后攒够了缓存,之后再开任何项目就会比较快了。

    一般来说不用像 Maven 一样下载一个全局的 Gradle ,因为项目外基本用不上,而项目内每个项目设置的 Gradle 版本基本上都不一样,还是要下载多个版本的。

    同步项目的时候可能会出现下面的错误:

    你的主机中的软件中止了一个已建立的连接。
    # 查看 daemon 日志关键内容 .gradle/daemon/6.7.1/daemon-17316.out.log
    Ignoring unreachable local address on loopback interface /127.0.0.1
    Adding remote address /192.168.137.1
    Listening on [951c7aa5-4ce6-4311-8262-55d1396408a5 port:8653, addresses:[localhost/127.0.0.1]].
    Daemon server started.
    Cannot accept connection from remote address /127.0.0.1.
    

    看到 192.168.137.1 我就想起了热点,把热点关闭果然就好了,杀 daemon 后再打开热点就又不行了。总结下:

  • 有问题的 Gradle 版本为 6.4.1 ~ 6.8.3 ,新建的项目版本为 6.7.1 ,在这个区间内。
  • 应该是开启热点产生的虚拟网卡对 daemon 绑定端口和通讯的逻辑产生了干扰,导致 daemon 启动后就关闭了。
  • daemon 进程正常启动后再开启热点就不会有问题了。 Easy Gradle 插件的 Kill Gradle 按钮可以杀死 daemon 进程。

    Activity Stack View

    查看设备当前创建的 Activity 的堆栈状态,对应下面的命令:

    adb shell dumpsys activity activities
    

    此插件即将下架,感兴趣的可以看下 源码

    ADBHelper

    可以测试 URL Scheme

    ADB Idea

    提供常用的 adb 命令菜单( 启动、关闭、调试等 ),快捷键 Ctrl + Alt + Shift + A

    ADB Tools

    唯一有用的功能就是能修改手机的分辨率。

    ADB Wi-Fi

    无需手动敲命令,一键通过 WiFi 连接安卓手机。众多同类插件中颜值最高,还集成了 scrcpy

    使用 scrcpy 的时候可能会出现卡顿,我测试了两个手机发现 adb wifi 的带宽最多只能跑到 5 Mbps ,慢得离谱( USB 差不多能跑 80 Mbps ),要想流畅点就要降低比特率和分辨率。

    adb push D:/xxx /sdcard/
    

    按照网上的 方法 通过热点( 电脑开热点或者手机开热点都行 )直连的话,带宽可以提升到 14 Mbps ,还是远远不够,这个速度安装微信这种 APK 光复制文件都要好几分钟。

    Android Intent Sender

    自定义 Intent 调试。

    AndroidProGuard Pro

    一键生成 proguard-rules.pro 中的混淆配置,仅供参考。

    AndroidSourceViewer

    在线查看 Android 源码的插件。

    Android WiFiADB

    支持手动输入 IP 和端口进行连接,支持扫描局域网可用设备,设备列表会持久化而不丢失。

    AndroidZer

    APK 反编译为 Smali ( 可用 Jadx 打开 )。

    Android Drawable Preview

    Drawable 文件直接预览在项目视图的图标上,无需打开文件。

    用是能用,不过有时能显示出来有时又显示不出来,应该是性能上有问题。

    Dex 2 Java

    反编译 Dex 中的字节码,不过年久失修用不了了。

    Easy Gradle

    提供一个按钮强行终止 Gradle 进程。

    Gradle Clean Snapshot Cache

    清理 Gradle 缓存的 Snapshot 依赖包。

    Gradle Utilities

    扩展 Gradle 工具窗口,管理本地的 Gradle 信息和缓存。

    Gradle View

    查看 Gradle 依赖树。

    由于依赖 Gradle Tooling API 5.6.2 ,最高只能支持到 Gradle 6.x

    Install Apk

    在项目中右键安装 APK 而无需手动敲命令。

    Jadx Android Decompiler

    集成 Jadx GUI ,一个 DexJava 的反编译器,可在项目文件( apk, dex, jar, class, smali, zip, aar, arsc )视图中右键选择 在 Jadx GUI 中反编译

    Jadx 是目前我用过最好用的、最傻瓜式的 APK 反编译器 ,不过需要设置好内存参数 -Xmx参考 ),不然随便多开几个就能撑爆你的内存。

    Material Design Icon Generator

    辅助导入 Material Design 图标到项目中,快捷键 Ctrl + Alt + D

    scrcpy

    直接给 scrcpy 做了一个配置界面,使用更方便。当然,还是 QtScrcpy 的体验更好。

    scrcpy 是一个手机投屏到电脑的工具,有了它就可以在电脑上无缝操作手机,调试可以更加方便( 尤其是有多个开发设备 )。

    Spock ADB

    Plugin Helps you to have full control of your project and device.

    SQL Android

    看截图应该是个不错的数据库管理工具,不过太久没更新了,安装后使用没反应甚至会把 IDE 卡死。新版 IDE 可以考虑自带的 Database Inspector

    Vector Drawable Thumbnails

    显示安卓项目中所有的 Drawable 文件。

    其他配置和功能

    Settings -> Editor -> Code Style

    Kotlin

    gradle.properties 中的 kotlin.code.style 配置项会决定基础样式。( 参考

    按照 ktlint 的教程一键配置,应用到项目级别即可,免得恶心到其他项目。

    另外,开启样式检查可以帮助你快速找到项目中需要格式化的代码。

    Settings -> Editor -> Inspections

    Kotlin -> Style issues -> File is not formatted according to project settings

  • enable

  • Apply only to modified files

  • Kotlin Coding conventions
  • Kotlin 样式指南
  • 面向贡献者的 AOSP 代码样式指南
  • Android 代码规范大全
  • 静态代码分析工具 - detekt
  • Android/Kotlin 项目模板
  • Set from... -> Android

    导入 AndroidXML 代码样式。

    启用属性排列很有用,比如 layout 属性不再杂乱无章。( 参考

    Android 资源文件默认打开界面模式为纯代码编辑

    Settings -> Editor -> Android Layout Editor -> Default Editor Mode

    View -> Tool Windows -> Layout Inspector ( 仅安卓项目可见 )

    要启用实时刷新要安装 Layout Inspector image server for xxx 。( 使用时应该会自动安装 )

    使用 Database Inspector 调试数据库参考

    View -> Tool Windows -> [App Inspection] -> Database Inspector ( 仅安卓项目可见 )

    使用后台任务检查器调试 WorkManager 工作器Android Studio Arctic Fox+

    View -> Tool Windows -> App Inspection -> Background Task Inspector ( 仅安卓项目可见 )

    使用设备文件浏览器查看设备上的文件

    View -> Tool Windows -> Device File Explorer ( 仅安卓项目可见 )

    修改默认的文件下载目录:

    Settings -> Tools -> Android Device File Explorer -> Download location

    使用 APK 分析器分析您的 build

    双击打开 APK 文件即可。

    利用 uiautomatorviewer 来检查当前屏幕显示的布局层次结构

    uiautomatorviewer 工具位于 <android-sdk>/tools/bin 目录中。

    AGP 配置 可视化

    Android StudioProject Structure 对应为 IntelliJ IDEA

    Settings -> Build, Execution, Deployment -> Android Project Structure

    不成熟,动态生成的配置基本识别不出来。

    Quick Lists

    查看 Keymap 设置我们可以发现里面有很多的功能,不可能给它们全部设置快捷键。这时候就可以通过 quick list 把一些我们常用的功能整合在一起,然后设置一个快捷键来快速打开这个菜单面板,十分方便。

    External Tools

    将外部程序作为一个功能简单集成到 IDE 中( 命令行调用 ),可添加到 Quick Lists

    Tools -> External Tools -> xxx

    找不到 SDK

    SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.

    Android Studio 在同步 Gradle 前会自动创建 local.properties 文件,而 IntelliJ IDEA 不会。

    为了避免每次打开新项目要手动创建,可以添加一个环境变量 ANDROID_HOME参考 )。

    使用老旧 Gradle 的项目( 比如 )构建失败

    Executing tasks: [:app:assembleDebug] in project ...

    Gradle build failed with 1 error(s) in 24 ms

    找不到任何更多的错误信息,而且手动跑命令是完全没问题的。

    # gradlew :app:assembleDebug
    BUILD SUCCESSFUL in 0s
    25 actionable tasks: 1 executed, 24 up-to-date
    

    测试了 Android Studio ( Arctic Fox | 2020.3.1 )IntelliJ IDEA 2020.3 都没问题,但 IntelliJ IDEA 2021.x 全都不行。

    临时解决方案 :构建步骤使用 Gradle 命令代替 Gradle-aware Make 。( 参考

    Edit Configurations -> Android App -> app -> Before launch

    - Gradle-aware Make
    + Run Gradle task :app:assembleDebug
    

    如果继续报错,可以考虑把安装步骤也替换了。( 参考

    ApkProvisionException: No outputs for the main artifact of variant: debug

  • Android Studio 'Run' 按钮后面的秘密
  • 官方文档 - Android 开发者指南
  • 官方文档 - Android Studio
  • 官方文档 - 使用 Kotlin 开发 Android 应用
  • Kotlin 基础语法
  • Gradle 用户手册
  •