设计数据库的结构

开始之前

您必须先创建一个 Firebase 项目,并将 Firebase Unity SDK 软件包添加到您的 Unity 项目中,然后才能使用 Firebase 实时数据库

设置:

前提条件

Android

  • Unity 5.0 或更高版本
  • Android NDK 版本 10d 或更高版本

iOS

  • Unity 5.0 或更高版本
  • Xcode 8.0 或更高版本

如果您还没有 Unity 项目,可以下载一个快速入门示例,试用一下特定的 Firebase 功能。如果使用快速入门示例,请不要忘记从项目设置中获取软件包标识符,下一步中会用到该标识符。

在 Firebase 控制台中设置您的应用

要将 Firebase 添加到您的应用,您需要有一个 Firebase 项目以及适用于您的应用的 Firebase 配置文件。

如果您还没有 Firebase 项目,请在 Firebase 控制台中创建一个。如果您已经有与自己的移动应用相关联的现有 Google 项目,请点击导入 Google 项目。如果没有,请点击添加项目

Android

  1. 点击将 Firebase 添加到您的 Android 应用,然后按设置步骤操作。如果您是导入现有 Google 项目,系统可能会自动执行这些操作,您只需下载配置文件即可。
  2. 出现提示时,输入应用的软件包名称。请务必输入应用在使用的软件包名称;只有在将应用添加到 Firebase 项目时您才能进行此设置。
  3. 按照说明下载 google-services.json 文件。您随时可以再重新下载此文件
  4. 将此文件复制到项目的资源文件夹内的任意位置。

iOS

  1. 点击将 Firebase 添加到您的 iOS 应用,然后按设置步骤操作。如果您是导入现有 Google 项目,系统可能会自动执行这些操作,您只需下载配置文件即可。
  2. 出现提示时,输入应用的软件包 ID。请务必输入应用在使用的软件包 ID;只有在将应用添加到 Firebase 项目时您才能进行此设置。
  3. 按照说明下载 GoogleService-Info.plist 文件。您随时可以再重新下载此文件
  4. GoogleService-Info.plist 文件添加到项目中。

    • 将从 Firebase 控制台下载的 GoogleService-Info.plist 拖动到 Unity 项目中的任意文件夹内。

将 Firebase Unity SDK 添加到您的应用

  1. 下载 Firebase Unity SDK
  2. 选择“Assets > Import Package > Custom Package”菜单项。
  3. 从之前下载的 Firebase Unity SDK 导入 FirebaseDatabase.unitypackage 软件包。
  4. 当出现 Import Unity Package 窗口时,点击 Import 按钮。

初始化 SDK

Firebase Unity SDK (Android) 需要使用 Google Play 服务。必须先将 Google Play 服务更新为最新版本,然后才能使用该 SDK。应在您的应用开头处添加以下代码,以便检查是否有 Google Play 服务更新,并视情况需要将这些服务更新为 Firebase Unity SDK 要求的版本,然后再调用 SDK 中的任何其他方法。

Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
  var dependencyStatus = task.Result;
  if (dependencyStatus == Firebase.DependencyStatus.Available) {
    // Set a flag here indiciating that Firebase is ready to use by your
    // application.
  } else {
    UnityEngine.Debug.LogError(System.String.Format(
      "Could not resolve all Firebase dependencies: {0}", dependencyStatus));
    // Firebase Unity SDK is not safe to use here.
  }
});

编译您的应用

Android

  1. 依次选择 File > Build Settings 菜单选项。
  2. 选择 Platform 列表中的 Android
  3. 点击 Switch Platform,选择 Android 作为目标平台。
  4. 等待 Unity 状态栏右下角的旋转进度条(正在编译)图标停止旋转。
  5. 点击 Build and Run

iOS

  1. 依次选择 File > Build Settings 菜单选项。
  2. 选择 Platform 列表中的 iOS
  3. 点击 Switch Platform,选择 iOS 作为目标平台。
  4. 等待 Unity 状态栏右下角的旋转进度条(正在编译)图标停止旋转。
  5. 点击 Build and Run

设计数据结构

本指南介绍了关于数据架构的几个主要概念,以及在您的 Firebase 实时数据库中设计 JSON 数据结构的最佳做法。

构建一个结构合理的数据库需要预先进行大量计划。最重要的是,您需要对如何保存数据及之后如何检索数据做好计划,尽可能地简化流程。

数据的结构形式:JSON 树

所有 Firebase 实时数据库数据都会存储为 JSON 对象。您可将该数据库视为托管在云端的 JSON 树。该数据库与 SQL 数据库不同,没有表格或记录的概念。当您将数据添加至 JSON 树时,它会变为现有 JSON 结构中的一个节点,带有一个关联的键。您可以自行提供键(例如用户 ID 或语义名称),也可以使用 Push() 方法由系统为您提供键。

如果您要自行创建键,必须确保您的键采用 UTF-8 编码,长度不超过 768 个字节,且不包含 .$#[]/ 或 ASCII 控制字符 0-31 或 127。

例如,假设某个聊天应用允许用户存储基本个人资料和联系人列表。通常用户个人资料位于诸如 /users/$uid 之类的路径中。用户 alovelace 的数据库条目可能如下所示:

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

虽然数据库采用了 JSON 树,但数据库中存储的数据可以使用与可用 JSON 类型对应的某些原生类型来表示,便于您编写更易于维护的代码。

数据结构最佳做法

避免嵌套数据

Firebase 实时数据库允许嵌套最多 32 层数据,因此您可能会认为这应是默认结构。然而,当您提取数据库中某个位置的数据时,其所有子节点也会在检索范围内。另外,当您向某用户授予数据库中某个节点的读取或写入权限时,也会将该节点下所有数据的读取或写入权限授予该用户。因此,在实践中,最好使您的数据结构尽量扁平。

现以如下所示的多层嵌套结构为例,说明为什么嵌套数据不妥:

{
  // This is a poorly nested data architecture, because iterating the children
  // of the "chats" node to get a list of conversation titles requires
  // potentially downloading hundreds of megabytes of messages
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "messages": {
        "m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
        "m2": { ... },
        // a very long list of messages
      }
    },
    "two": { ... }
  }
}

若采用这种嵌套设计,循环访问数据就会出现问题。例如,要列出聊天会话标题,就需要将整个 chats 树(包括所有成员和消息数据)都下载到客户端。

扁平化数据结构

如果数据被拆分到不同路径(又称反规范化),则可根据需要通过不同调用有效地下载。请参考以下扁平化结构:

{
  // Chats contains only meta info about each conversation
  // stored under the chats's unique ID
  "chats": {
    "one": {
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}

现在,只需针对每个会话下载几个字节并快速提取元数据(以便在用户界面中列出或显示会话),即可遍历会话列表。消息可单独提取并在到达时显示,从而确保用户界面及时快速地响应。

创建规模可扩展的数据

构建应用时,最好下载列表的子集。当列表含有成千上万条记录时,这种做法尤其常见。当这种关系属于静态和单向关系时,您只需在父对象下嵌套子对象即可。

有时,这种关系更具动态性,或者需要对此数据进行反规范化。通常,您可以通过查询来检索该数据的子集,以便对该数据进行反规范化,如检索数据中所述。

但即使这样也可能无法解决问题。以用户与群组之间的双向关系为例。用户可属于某个群组,群组可包含一系列用户。当需要确定用户属于哪些群组时,情况就会比较复杂。

我们需要的是一种巧妙的方法,不仅要列出用户所属的群组,而且只提取这些群组的数据。群组索引可在此发挥巨大作用:

// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}

您可能注意到了,这种索引会同时在 Ada 的记录下及该群组的记录下存储相应关系数据,因此某些数据会重复。现在,alovelace 在一个群组的索引中,而 techpioneers 则列在 Ada 的个人资料中。所以,要从该群组删除 Ada,有两个地方需要更新。

对于双向关系而言,这是必要的冗余。这样,您就可以快速、高效地提取 Ada 的成员身份,而且即使用户列表或群组列表扩展到数百万条,或实时数据库安全规则阻止访问某些记录,也不会对提取造成影响。

这种方法(即,通过将 ID 列为键并将值设为 true 来反转数据)使对键的检查变得简单,只需读取 /users/$uid/groups/$group_id 然后检查它是否为 null。与查询或扫描数据相比,索引的速度更快、效率更高。

后续步骤

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面