拥抱Firebase,Firebase之Realtime Database。(含github源码),欢迎关注。

来源:互联网 发布:unity3d打包apk 编辑:程序博客网 时间:2024/05/16 04:43

 1 

R

     I


•COMIC•


D

  3

5

   O 




Firebase可以帮助您构建更出色的移动应用并扩展您的业务。


如何工作?

Firebase Realtime Database 允许直接从客户端代码中直接安全访问数据库,因此您能够构建丰富的协作式应用。 数据保留在本地,即使处于离线状态,实时事件仍继续触发,给最终用户提供一种响应式体验。当设备重新取得连接时, Realtime Database 会将本地数据变化与客户端离线期间发生的远程更新同步,自动合并任何不一致数据。





实现步骤
  1. 集成 Firebase Realtime Database SDK。

    通过 Gradle、CococaPods 或脚本包含来快速包含客户端。

  2. 创建 Realtime Database 引用。

    为设置数据或订阅数据变化,请引用您的 JSON 数据,如"users/user:1234/phone_number"。

  3. 设置数据和侦听变化。

    使用此引用写入数据或订阅变化。

  4. 启用离线留存。

    允许将数据写入到设备的本地磁盘,以便离线时使用。

  5. 保障数据安全。

    使用 Firebase Realtime Database 安全规则保障您的数据安全。



实操一下又何妨
  1. Install the Firebase SDK.

    《将Firebase添加到项目》

    不会做超链接,有知道的告知下,谢谢

  2. Firebase控制台添加一个自己的项目。

  3. 添加依赖

  4. compile 'com.google.firebase:firebase-database:10.2.6'

    4. 配置Firebase Database规则

        默认情况下,您的数据库规则会授予完全读取和写入权限,但仅授予给已通过身份验证的用户。 以下为默认规则:

{
 
"rules": {
   
".read": "auth != null",
   
".write": "auth != null"
 
}
}

如果您只是刚开始使用并希望在配置安全规则之前先试用数据库,则可使用以下规则授予完全的公共数据库访问权限:

{
 
"rules": {
   
".read": true,
   
".write": true
 
}
}

在发布您的应用之前必须正确配置这些规则,以确保您的用户只能访问他们应该能够访问的数据。



如何使用安全规则保障数据安全


    代码混淆
# Add this global rule
-keepattributes
Signature

# This rule will properly ProGuard all the model classes in
# the package com.yourcompany.models. Modify to fit the structure
# of your app.
-keepclassmembers
class com.yourcompany.models.** {
  *;
}


组织您的数据库

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


所有 Firebase Realtime Database 数据都被存储为 JSON 对象。您可将该数据库视为云托管 JSON 树。 该数据库与 SQL 数据库不同,没有任何表格或记录。 当您将数据添加至 JSON 树时,它变为现有 JSON 结构中的一个节点。


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

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

数据结构最佳做法

  • 避免嵌套数据

    Firebase Realtime Database 允许嵌套数据的深度多达 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
 
// converation ID
 
"messages": {
   
"one": {
     
"m1": {
       
"name": "eclarke",
       
"message": "The relay seems to be malfunctioning.",
       
"timestamp": 1459361875337
      },
     
"m2": { ... },
     
"m3": { ... }
    },
   
"two": { ... },
   
"three": { ... }
  }
}

现在,每个对话只需下载几个字节即可循环访问房间列表,同时可以快速提取元数据,在 UI 中列出或显示房间。

在消息到达时,可单独提取和显示,从而确保 UI 的及时响应和速度。

  • 创建可扩展的数据

假设用户与群组之间存在双向关系。 用户可属于一个群组,且群组包含一个用户列表。 当需要决定用户属于哪些群组时,情况就会比较复杂。

我们需要的是一种完善方法,不仅列出用户所属群组,而且只提取这些群组的数据。 对群组的"索引"在此可能有很大的帮助:

// 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 的成员身份,而且即使用户或群组列表有数百万条记录,或 Realtime Database 安全规则会阻止访问某些记录,也不受影响。

这种方法通过将 ID 列为密钥并将值设为 true 来颠倒数据,使密钥检查变得像读取 /users/$uid/groups/$group_id 和检查是否 null 一样简单。与查询或扫描数据相比,索引的速度更快,效率更高。

    在Android上保存数据

有四种方法可以将数据写入 Firebase Realtime Database:

setValue():

常见用法:将数据写入或替换到定义的路径,如 users/<user-id>/<username>


push():

常见用法: 添加到数据列表。每次调用 push() 时,Firebase 均会生成唯一 ID,如 user-posts/<user-id>/<unique-post-id>


updateChildren():

常见用法:更新定义的路径中的部分键,而不替换所有数据。


runTransaction():

常见用法:更新可能因并发更新而损坏的复杂数据。


写入、更新或删除引用中的数据

基本写入操作

对于基本写入操作,您可以使用 setValue() 将数据保存至特定引用,替换该路径的任何现有数据。 可以使用该方法执行下列操作:

  • 传递与可用 JSON 类型对应的类型,如下所示:

    • String

    • Long

    • Double

    • Boolean

    • Map<String, Object>

    • List<Object>

  • 传递自定义 Java 对象(如果定义该对象的类的默认构造函数不接受参数,且为要指定的属性提供了公用 getter)。 如果使用 Java 对象,则对象的内容将自动以嵌套方式映射到子位置。使用 Java 对象通常还会提高代码的可读性,使其更易于维护。例如,如果应用包含用户的基本个人资料,则 User 对象可能如下所示:

@IgnoreExtraProperties
public class User {

   
public String username;
   
public String email;

   
public User() {
       
// Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

   
public User(String username, String email) {
       
this.username = username;
       
this.email = email;
    }

}

可以使用 setValue() 添加用户,如下所示:

private void writeNewUser(String userId, String name, String email) {
   
User user = new User(name, email);

    mDatabase.child(
"users").child(userId).setValue(user);
}

以这种方式使用 setValue() 将覆盖指定位置的数据,包括所有子节点。但是,您仍可在不重写整个对象的情况下更新子节点。 如果要允许用户更新其个人资料,则可按照如下所示更新用户名:

mDatabase.child("users").child(userId).child("username").setValue(name);

追加到数据列表

使用 push() 方法可将数据追加到多用户应用中的列表。每次将新子节点添加到指定的 Firebase 引用时,push() 方法均会生成唯一 ID。

通过为列表中的各新元素使用自动生成的键,多个客户端可以同时向同一位置添加子节点,而不存在写入冲突。

push() 生成的唯一 ID 基于时间戳,因此列表项目会自动按时间顺序排列。

您可以使用对新数据(由 push() 方法返回)的引用获取子节点自动生成的键的值或为子节点设置数据。 对 push() 引用调用 getKey() 将返回自动生成的键值。

更新特定字段

要同时向一个节点的特定子节点写入数据,而不覆盖其他子节点,请使用 updateChildren() 方法。

调用 updateChildren() 时,可以通过为键指定路径来更新较低级别的子值。

例如,社交博客应用可能具有 Post 类,如下所示:

@IgnoreExtraProperties
public class Post {

   
public String uid;
   
public String author;
   
public String title;
   
public String body;
   
public int starCount = 0;
   
public Map<String, Boolean> stars = new HashMap<>();

   
public Post() {
       
// Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

   
public Post(String uid, String author, String title, String body) {
       
this.uid = uid;
       
this.author = author;
       
this.title = title;
       
this.body = body;
    }

   
@Exclude
   
public Map<String, Object> toMap() {
       
HashMap<String, Object> result = new HashMap<>();
        result.put(
"uid", uid);
        result.put(
"author", author);
        result.put(
"title", title);
        result.put(
"body", body);
        result.put(
"starCount", starCount);
        result.put(
"stars", stars);

       
return result;
    }

}

要创建一篇博文并同时更新为最新的活动源和发布用户的活动源,该博客应用需使用如下代码:

private void writeNewPost(String userId, String username, String title, String body) {
   
// Create new post at /user-posts/$userid/$postid and at
   
// /posts/$postid simultaneously
   
String key = mDatabase.child("posts").push().getKey();
   
Post post = new Post(userId, username, title, body);
   
Map<String, Object> postValues = post.toMap();

   
Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put(
"/posts/" + key, postValues);
    childUpdates.put(
"/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

此示例使用 push() 在节点(其中包含 /posts/$postid 内所有用户博文)中创建一篇博文,同时使用 getKey() 检索相应键。

然后,可以使用该键在用户博文(位于 /user-posts/$userid/$postid 内)中创建第二个条目。

通过使用这些路径,只需调用 updateChildren() 一次即可同步更新 JSON 树中的多个位置,例如,该示例如何在两个位置同时创建新博文。

通过这种方式同步更新具有原子性:要么所有更新全部成功,要么全部失败。

删除数据

删除数据最简单的方法是在引用上对这些数据所处的位置调用 removeValue()

此外,还可以通过将 null 指定为另一个写入操作(例如,setValue() 或 updateChildren())的值来删除数据。 您可以结合使用此方法与 updateChildren(),在单一 API 调用中删除多个子节点。

接收完成回调

要知道将数据提交到 Firebase Realtime Database 服务器的时间,可以添加完成侦听器。 setValue() 和 updateChildren() 均接受可选的完成侦听器。写入的数据提交到数据库时,系统将调用该侦听器。

如果调用因故失败,系统将为该侦听器传递一个错误对象,说明失败的原因。

将数据另存为事务

处理可能因并发修改而损坏的复杂数据(例如,增量计数器)时,可以使用事务处理操作。您为此操作提供两个参数,即:更新函数和可选完成回调。

更新函数将数据的当前状态视为参数,并将返回您要写入的所需新状态。 如果另一个客户端在您成功写入新值前写入该位置,则会使用新的当前值再次调用更新函数,然后重试写入。

例如,在示例社交博客应用中,您可以允许用户对博文加星和取消加星,并跟踪博文获得的加星数,如下所示:

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(
new Transaction.Handler() {
       
@Override
       
public Transaction.Result doTransaction(MutableData mutableData) {
           
Post p = mutableData.getValue(Post.class);
           
if (p == null) {
               
return Transaction.success(mutableData);
            }

           
if (p.stars.containsKey(getUid())) {
               
// Unstar the post and remove self from stars
                p.starCount = p.starCount -
1;
                p.stars.remove(getUid());
            }
else {
               
// Star the post and add self to stars
                p.starCount = p.starCount +
1;
                p.stars.put(getUid(),
true);
            }

           
// Set value and report transaction success
            mutableData.setValue(p);
           
return Transaction.success(mutableData);
        }

       
@Override
       
public void onComplete(DatabaseError databaseError, boolean b,
                               
DataSnapshot dataSnapshot) {
           
// Transaction completed
           
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

如果多个用户同时对同一博文加星或客户端存在过时数据,使用事务处理可防止加星计数出错。 如果事务处理被拒绝,则服务器会将当前值返回到客户端,然后客户端使用更新后的值再次运行事务处理。

系统将重复此过程,直到事务处理被接受或尝试次数过多为止。

注:由于 doTransaction() 将多次调用,因此必须能够处理 null 数据。 即使远程数据库中已有数据,但在运行事务处理函数时可能不会本地缓存这些数据,从而导致 null 成为初始值。

离线写入数据

如果客户端失去网络连接,您的应用将继续正常运行。

连接到 Firebase 数据库的每个客户端均维护任何活动数据各自的内部版本。 写入数据时,首先将其写入此本地版本。 然后,Firebase 客户端尽最大程度将这些数据与远程数据库服务器以及其他客户端同步。

因此,在将任何数据写入服务器之前,对数据库执行的所有写入会立即触发本地事件。 这意味着应用将保持随时响应,无论网络延迟或连接情况如何均是如此。

重新建立连接之后,您的应用将收到一组适当的事件,以便客户端与当前服务器状态同步,而不必编写任何自定义代码。



在Android上检索数据

接下来将介绍检索数据的基础知识以及如何对 Firebase 数据进行排序和过滤。


附加事件侦听器

Firebase 数据可通过将异步侦听器附加到 FirebaseDatabase 引用来检索。 侦听器由数据的初始状态触发一次,并在数据发生任何更改时再次触发。


可以侦听以下类型的数据检索事件:

ValueEventListener-》onDataChange()

典型用法:

读取和侦听对路径的全部内容所做的更改。


ChildEventListener-》onChildAdded()

典型用法:

检索项目列表,或侦听项目列表中是否添加了新项目。 建议与 onChildChanged()和 onChildRemoved() 配合使用,从而监控对列表所做的更改。

-》onChildChanged()

典型用法:

侦听对列表中的项目所做的更改。与 onChildAdded() 和 onChildRemoved() 结合使用,从而监控对列表所做的更改。

-》onChildRemoved()

典型用法:

侦听正从列表中删除的项目。与 onChildAdded() 和 onChildChanged() 结合使用, 从而监控对列表所做的更改。

-》onChildMoved()

典型用法:

与经过排序的数据配合使用,从而侦听对项目优先级所做的更改。


要添加值事件侦听器,请使用 addValueEventListener() 或 addListenerForSingleValueEvent() 方法。 要添加子事件侦听器,请使用 addChildEventListener() 方法。


再详细一点:

值事件

使用 onDataChange() 方法来读取事件发生时某一给定路径下存在的内容的静态快照。此方法将在附加侦听器时触发一次,并在数据(包括子节点)发生任何更改时再次触发。


系统将为事件回调传递一个包含该位置中所有数据(包括子节点数据)的快照。如果没有任何数据,则返回的快照为 null


重要说明:

重要说明: 

重要说明: 

每当数据在指定的数据库引用处发生更改(包括更改子节点)时,系统就会调用 onDataChange() 方法。要限制快照的大小,请仅在需要观察更改的最低级别附加侦听器。例如,建议不要在数据库的根目录附加侦听器。


以下示例演示了社交博客应用如何从数据库中检索博文详细信息:

ValueEventListener postListener = new ValueEventListener() {
   
@Override
   
public void onDataChange(DataSnapshot dataSnapshot) {
       
// Get Post object and use the values to update the UI
       
Post post = dataSnapshot.getValue(Post.class);
       
// ...
   
}

   
@Override
   
public void onCancelled(DatabaseError databaseError) {
       
// Getting Post failed, log a message
       
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
       
// ...
   
}
};
mPostReference
.addValueEventListener(postListener);

侦听器接收到一个 DataSnapshot,其中包含事件发生时数据库中指定位置的数据。对快照调用 getValue() 时,将返回数据的 Java 对象表示形式。如果该位置不存在任何数据,则调用 getValue() 将返回 null

在本示例中,ValueEventListener 还定义了 onCancelled() 方法。如果取消读取,则将调用该方法。 例如,如果客户端没有权限从 Firebase 数据库位置读取数据,则可取消读取。系统将为此方法传递一个 DatabaseError 对象,说明失败的原因。

子事件

为了响应通过某项操作(例如,通过 push() 方法添加新子节点或通过 updateChildren() 方法更新子节点)而对节点的子节点执行的特定操作,系统将触发子事件。对于侦听对数据库中特定节点所做的更改,结合使用上述每一种方法非常有用。

例如,社交博客应用可以结合使用这些方法来监控博文评论中的活动,如下所示:

ChildEventListener childEventListener = new ChildEventListener() {
   
@Override
   
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
       
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());

       
// A new comment has been added, add it to the displayed list
       
Comment comment = dataSnapshot.getValue(Comment.class);

       
// ...
   
}

   
@Override
   
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
       
Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());

       
// A comment has changed, use the key to determine if we are displaying this
       
// comment and if so displayed the changed comment.
       
Comment newComment = dataSnapshot.getValue(Comment.class);
       
String commentKey = dataSnapshot.getKey();

       
// ...
   
}

   
@Override
   
public void onChildRemoved(DataSnapshot dataSnapshot) {
       
Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());

       
// A comment has changed, use the key to determine if we are displaying this
       
// comment and if so remove it.
       
String commentKey = dataSnapshot.getKey();

       
// ...
   
}

   
@Override
   
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
       
Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());

       
// A comment has changed position, use the key to determine if we are
       
// displaying this comment and if so move it.
       
Comment movedComment = dataSnapshot.getValue(Comment.class);
       
String commentKey = dataSnapshot.getKey();

       
// ...
   
}

   
@Override
   
public void onCancelled(DatabaseError databaseError) {
       
Log.w(TAG, "postComments:onCancelled", databaseError.toException());
       
Toast.makeText(mContext, "Failed to load comments.",
               
Toast.LENGTH_SHORT).show();
   
}
};
ref.addChildEventListener(childEventListener);

onChildAdded() 回调通常用于在 Firebase 数据库中检索项目列表。onChildAdded() 回调将针对每个现有的子节点触发一次,并在每次向指定的路径添加新的子节点时再次触发。

系统将为侦听器传递包含新子节点数据的快照。

每次修改子节点时,均会触发 onChildChanged() 回调。这包括对子节点的后代所做的任何修改。该回调通常与 onChildAdded() 和 onChildRemoved() 回调一起使用,以响应对项目列表所做的更改。传递给事件侦听器的快照包含子节点的更新数据。

删除直接子节点时,将会触发 onChildRemoved() 回调。该回调通常与 onChildAdded() 和 onChildChanged() 回调一起使用。传递给事件回调的快照包含已删除的子节点的数据。

每当因更新(导致子节点重新排序)而触发 onChildChanged() 回调时,系统就会触发 onChildMoved() 回调。 该回调与按优先级排序的数据结合使用。

分离侦听器

通过在 Firebase 数据库引用上调用 removeEventListener() 方法即可删除回调。

如果多次将侦听器添加到某一数据位置,则将为每个事件多次调用侦听器,并且您必须分离相同的次数才能将其完全删除。

在父侦听器上调用 removeEventListener() 时不会自动删除在其子节点上注册的侦听器;还必须在任何子侦听器上调用 removeEventListener() 才能删除回调。


读取数据一次(丧心病狂)

在某些情况下,您可能希望调用一次回调后立即将其删除(例如,在初始化预期不会发生更改的 UI 元素时)。您可以使用 addListenerForSingleValueEvent() 方法简化这种情况:回调仅触发一次,以后不会再次触发。

对于只需加载一次且预计不会频繁变化或需要主动侦听的数据,这非常有用。 例如,上述示例中的博客应用使用此方法在用户开始撰写新博文时加载其个人资料:

final String userId = getUid();
mDatabase
.child("users").child(userId).addListenerForSingleValueEvent(
       
new ValueEventListener() {
           
@Override
           
public void onDataChange(DataSnapshot dataSnapshot) {
               
// Get user value
               
User user = dataSnapshot.getValue(User.class);

               
// ...
           
}

           
@Override
           
public void onCancelled(DatabaseError databaseError) {
               
Log.w(TAG, "getUser:onCancelled", databaseError.toException());
               
// ...
           
}
       
});


排序和过滤数据

您可以使用 Realtime Database Query 类检索按键、按值、按子节点的值或按优先级排序的数据。 您还可以对排序后的结果进行过滤,从而得到特定数量的结果或一系列键或值。

注:过滤和排序操作的开销可能会很大,在客户端执行这些操作时尤其如此。 如果您的应用使用了查询,请定义 .indexOn 规则,以便在服务器上将这些键编制索引并提高查询性能

排序数据

要检索排序数据,请先指定排序依据方法之一,确定如何对结果排序(4种方法):

orderByChild()

用法:按指定子键的值对结果排序。


orderByKey()

用法:按子键对结果排序。


orderByValue()

用法:按子值对结果排序。


orderByPriority()

用法:按节点的指定优先级对结果排序。

每次只能使用一种排序依据方法。对同一查询多次调用排序依据方法会引发错误。

以下示例演示了如何检索用户置顶博文(按星级数排序)的列表:

// My top posts by number of stars
String myUserId = getUid();
Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
       
.orderByChild("starCount");

过滤数据

要过滤数据,您可以结合使用限制或范围方法之一与排序依据方法来构造查询(5种方法)。

limitToFirst()

用法:设置要从排序结果列表开头返回的最大项目数。


limitToLast()

用法:设置要从排序结果列表结尾返回的最大项目数。


startAt()

用法:返回大于或等于指定键、值或优先级的项目,具体取决于所选的排序依据方法。


endAt()

用法:返回小于或等于指定键、值或优先级的项目,具体取决于所选的排序依据方法。


equalTo()

用法:返回等于指定键、值或优先级的项目,具体取决于所选的排序依据方法。


与排序依据方法不同,您可以结合使用多种限制或范围函数。例如,您可以结合使用 startAt() 与 endAt() 方法将结果限制在值的指定范围内。


限制结果数

使用 limitToFirst() 和 limitToLast() 方法设置要针对给定回调同步的最大子节点数。 例如,如果使用 limitToFirst() 限定为 100,则起初最多会收到 100 个 onChildAdded() 回调。

如果您在 Firebase 数据库中存储的项目不到 100 个,则每个项目均会触发一次 onChildAdded() 回调。

随着项目发生更改,对于进入查询的项目,您将收到 onChildAdded() 回调;对于退出查询的项目,则将收到 onChildRemoved() 回调,从而使总数始终保持为 100。

以下示例演示了示例博客应用如何定义查询以按所有用户检索 100 篇最新博文的列表:

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
       
.limitToFirst(100);

本示例仅定义了一个查询,要实际同步数据,需要附加侦听器。

按键、值或优先级过滤

您可以使用 startAt()endAt() 和 equalTo() 为查询选择任意起点、终点和当量点。 这对于将数据分页或查找其子节点为特定值的项目非常有用。


对查询数据进行排序

介绍一下如何通过 Query 类中的每种排序依据方法对数据进行排序。

orderByChild当使用 orderByChild() 时,系统将按如下方式对包含指定子键的数据进行排序:

  1. 指定子键为 null 值的子节点显示在最前面。

  2. 指定子键为 false 值的子节点紧随其后。 如果多个子节点的值均为 false,则按键以字典顺序对其进行排序。

  3. 指定子键为 true 值的子节点紧随其后。 如果多个子节点的值均为 true,则按键以字典顺序对其进行排序。

  4. 指定子键为数值的子节点紧随其后,按升序排序。如果多个子节点的指定子键具有相同的数值,则按键对其进行排序。

  5. 字符串显示在数字后面并按字典顺序以升序排列。 如果多个子节点的指定子键具有相同的值,则按键以字典顺序对其进行排序。

  6. 对象放在最后,并根据键名按字典顺序以升序排列。

orderByKey

当使用 orderByKey() 对数据进行排序时,系统会按键名以升序返回数据。

  1. 键可以解析为 32 位整数的子节点显示在前面,按升序排序。

  2. 以字符串值作为键的子节点紧随其后,按字典顺序以升序排列。

orderByValue

使用 orderByValue() 时,系统将按相应值对子节点进行排序。排序标准与 orderByChild() 中相同,但这里使用节点的值而非指定子键的值。

orderByPriority

如果已通过 setPriority() 指定数据的优先级,则可使用 orderByPriority() 对数据进行排序。 在这种情况下,子节点的排序取决于其优先级和键,如下所示:

  1. 无优先级(默认设置)的子节点显示在前面。

  2. 以数字作为优先级的子节点紧随其后。它们按优先级以数字顺序从小到大进行排序。

  3. 以字符串作为优先级的子节点放在最后。它们按优先级以字典顺序进行排序。

  4. 当两个子节点的优先级相同(包括无优先级)时,将按键对其进行排序。 数字键显示在最前面(按数字以升序排列),接着是其余键(按字典顺序以升序排列)。

优先级值只能是数字或字符串。 数字优先级以 IEEE 754 双精度浮点数形式存储并排序。 键始终存储为字符串,仅当可以解析为 32 位整数时才视为数字。


在Android中启用离线功能

Firebase 应用具有强大的离线功能,且有几款功能能够显著提升离线使用体验。启用磁盘持久化可让应用在重启后依然保留其所有状态。我们提供一些用于监控在线和连接状态的工具。

Firebase会自动处理网络中断时的数据缓存,当网络重新连接时,再重新发送缓存的数据。通过代码运行Firebase缓存时使用磁盘持久化,这样即使app被重启,数据依然有效。

FirebaseDatabase.getInstance().setPersistenceEnabled(true);


保持最新的数据

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef
.keepSynced(true);

这样客户端会自动从服务器下载更新数据,如果禁止自动更新:

scoresRef.keepSynced(false);

Firebase默认的缓存空间为10MB,如何缓存数据超过10MB,最早的数据将被清除。如果数据设置了keepSynced,则不会被清除。


离线数据查询

当app处于离线状态时,查询数据时,会从缓存数据中进行查询,当app重新连接网络时,Firebase会从网络下载最新的数据,并触发查询。


以下代码示例演示了查询博客最新的4条评分数据。

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef
.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() {
   
@Override
   
public void onChildAdded(DataSnapshot snapshot, String previousChild) {
     
System.out.println("The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
   
}
});

现在,如果app网络连接断开,还是以上查询,我们查询最新的2条数据:

scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() {
   
@Override
   
public void onChildAdded(DataSnapshot snapshot, String previousChild) {
       
System.out.println("The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
     
}
});

处理离线事务

任何离线事务操作,将会被放到请求队列中,当app重新连接网络时,重新发送。


注意:事务请求,不会被持久化到磁盘,app重启后会消失。


处理网络断开那一刻(事必躬亲)

DatabaseRef presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef
.onDisconnect().setValue("I disconnected!");

以下示例,确保离线时的操作成功进行:

presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() {
   
@Override
   
public void onComplete(DatabaseError error, DatabaseReference firebase) {
       
if (error != null) {
           
System.out.println("could not establish onDisconnect event:" + error.getMessage());
       
}
   
}
});

onDisconnect事件操作也可以中途取消(我反悔了)

OnDisconnect onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef
.setValue("I disconnected");
// some time later when we change our minds
onDisconnectRef
.cancel();


处理网络连接上那一刻(事必躬亲) 


Firebase为网络连接状态提供了一个特殊位置/.info/connected 

DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef
.addValueEventListener(new ValueEventListener() {
 
@Override
 
public void onDataChange(DataSnapshot snapshot) {
   
boolean connected = snapshot.getValue(Boolean.class);
   
if (connected) {
     
System.out.println("connected");
   
} else {
     
System.out.println("not connected");
   
}
 
}

 
@Override
 
public void onCancelled(DatabaseError error) {
   
System.err.println("Listener was cancelled");
 
}
});


在android设备上,Firebase会自动管理数据的网络连接,这样可以节约带宽和电量。如果客户端没有活跃的listenter,没有等待的write或onDisconnect并且没有被明确断开(goOffline),Firebase会在60秒后自动断开网络连接。


处理异常

Firebase能够插入时间戳,以下示例能清楚知道客户端短线的时间:

DatabaseReference userLastOnlineRef = FirebaseDatabse.getInstance().getReference("users/joe/lastOnline");
userLastOnlineRef
.onDisconnect().setValue(ServerValue.TIMESTAMP);

时钟偏斜(Clock Skew)处理

DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset");
offsetRef
.addValueEventListener(new ValueEventListener() {
 
@Override
 
public void onDataChange(DataSnapshot snapshot) {
   
double offset = snapshot.getValue(Double.class);
   
double estimatedServerTimeMs = System.currentTimeMillis() + offset;
 
}

 
@Override
 
public void onCancelled(DatabaseError error) {
   
System.err.println("Listener was cancelled");
 
}
});

Sample Presence App

// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections");

// stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline");

final DatabaseReference connectedRef = database.getReference(".info/connected");
connectedRef
.addValueEventListener(new ValueEventListener() {
 
@Override
 
public void onDataChange(DataSnapshot snapshot) {
   
boolean connected = snapshot.getValue(Boolean.class);
   
if (connected) {
     
// add this device to my connections list
     
// this value could contain info about the device or a timestamp too
     
DatabaseReference con = myConnectionsRef.push();
      con
.setValue(Boolean.TRUE);

     
// when this device disconnects, remove it
      con
.onDisconnect().removeValue();

     
// when I disconnect, update the last time I was seen online
      lastOnlineRef
.onDisconnect().setValue(ServerValue.TIMESTAMP);
   
}
 
}

 
@Override
 
public void onCancelled(DatabaseError error) {
   
System.err.println("Listener was cancelled at .info/connected");
 
}
});



github源码奉上

https://github.com/zhangbinangel/FirebaseExample.git


有图有真相:










原创粉丝点击