콘솔로 이동
Cloud Firestore 사용해 보기: Firebase와 Google Cloud Platform의 유연하며 확장 가능한 데이터베이스를 살펴보세요. Cloud Firestore 자세히 알아보기

Android에서 오프라인 기능 사용 설정

Firebase 애플리케이션은 일시적으로 네트워크 연결이 끊겨도 정상적으로 작동합니다. 또한 Firebase는 로컬에 데이터를 유지하고, 접속 상태를 관리하고, 지연 시간을 처리하는 도구를 제공합니다.

디스크 지속성

Firebase 앱은 일시적인 네트워크 중단을 자동으로 처리합니다. 오프라인 상태에서는 캐시된 데이터를 사용할 수 있고, 네트워크 연결이 복원되면 Firebase에서 모든 쓰기 작업을 다시 전송합니다.

디스크 지속성을 사용 설정하면 앱의 데이터를 기기에 로컬로 저장하므로 오프라인 상태일 때도 앱이 현재 상태를 유지할 수 있으며, 사용자 또는 운영체제가 앱을 다시 시작하더라도 유지됩니다.

단 한 줄의 코드로 디스크 지속성을 사용 설정할 수 있습니다.

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

지속성 동작

지속성을 사용 설정하면 Firebase 실시간 데이터베이스 클라이언트가 온라인 상태에서 동기화하는 모든 데이터가 디스크에 유지되고 오프라인 상태에서 사용 가능해지며, 사용자 또는 운영체제가 앱을 다시 시작하더라도 마찬가지입니다. 따라서 캐시에 저장된 로컬 데이터를 사용하여 온라인일 때와 다름없이 앱이 작동합니다. 로컬 업데이트 시 리스너 콜백도 계속 발생합니다.

Firebase 실시간 데이터베이스 클라이언트는 앱이 오프라인일 때 수행된 모든 쓰기 작업을 자동으로 대기열에 유지합니다. 지속성을 사용하면 이 대기열이 디스크에도 유지되므로 사용자 또는 운영체제가 앱을 다시 시작해도 쓰기 작업이 사라지지 않습니다. 앱이 다시 연결되면 모든 작업이 Firebase 실시간 데이터베이스 서버로 전송됩니다.

앱이 Firebase 인증을 사용하는 경우 앱을 다시 시작해도 Firebase 실시간 데이터베이스 클라이언트에서 사용자의 인증 토큰을 유지합니다. 앱이 오프라인일 때 인증 토큰이 만료되면 앱에서 사용자를 다시 인증할 때까지 쓰기 작업이 일시중지되며, 인증되지 않으면 보안 규칙으로 인해 쓰기 작업에 실패할 수 있습니다.

최신 데이터 유지

Firebase 실시간 데이터베이스는 활성 리스너의 데이터를 동기화하고 로컬 사본을 저장합니다. 또한 특정 위치의 동기화를 유지할 수 있습니다.

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

Firebase 실시간 데이터베이스 클라이언트는 참조에 활성 리스너가 없어도 이러한 위치의 데이터를 자동으로 다운로드하고 동기화합니다. 동기화를 해제하려면 다음 코드를 사용합니다.

scoresRef.keepSynced(false);

기본적으로 이전에 동기화한 데이터 중 10MB가 캐시됩니다. 대부분의 애플리케이션에서는 이 용량으로 충분합니다. 구성된 크기보다 캐시가 커지면 Firebase 실시간 데이터베이스가 가장 오래전에 사용된 데이터를 삭제합니다. 동기화가 유지되는 데이터는 캐시에서 삭제되지 않습니다.

오프라인으로 데이터 쿼리

Firebase 실시간 데이터베이스는 쿼리가 반환한 데이터를 오프라인으로 사용하기 위해 저장합니다. 오프라인으로 쿼리를 작성한 경우 Firebase 실시간 데이터베이스는 이전에 로드한 데이터를 대상으로 계속 작동합니다. 요청한 데이터가 로드되지 않으면 로컬 캐시의 데이터가 로드됩니다. 네트워크에 다시 연결되면 데이터가 로드되고 쿼리가 반영됩니다.

예를 들어 다음 코드는 점수를 저장하는 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());
    }
});

연결이 끊겨서 오프라인으로 전환된 후 앱을 다시 시작했다고 가정해 보겠습니다. 계속 오프라인인 상태에서 같은 위치의 마지막 항목 2개를 쿼리합니다. 앱은 위 쿼리에서 항목 4개를 모두 로드했으므로 이 쿼리는 마지막 항목 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());
      }
});

위 예에서는 Firebase 실시간 데이터베이스 클라이언트가 지속성 캐시를 통해 최고 점수를 기록한 두 공룡에 대해 '하위 추가' 이벤트를 발생시킵니다. 그러나 온라인 상태에서는 해당 쿼리를 실행하지 않았으므로 '값' 이벤트는 발생하지 않습니다.

오프라인 상태인 앱에서 마지막 항목 6개를 쿼리하면 캐시된 항목 4개에 대한 '하위 추가' 이벤트가 즉시 발생합니다. 기기가 다시 온라인으로 전환되면 Firebase 실시간 데이터베이스 클라이언트가 서버와 동기화되고 최종적으로 2개의 '하위 추가' 및 '값' 이벤트가 발생합니다.

오프라인으로 트랜잭션 처리

앱이 오프라인일 때 수행되는 모든 트랜잭션은 대기열에 저장됩니다. 앱이 네트워크에 다시 연결되면 트랜잭션이 실시간 데이터베이스 서버로 전송됩니다.

접속 상태 관리

실시간 애플리케이션에서는 클라이언트가 연결되거나 연결이 해제되는 시점을 감지하는 것이 유용할 때가 많습니다. 예를 들어 클라이언트의 연결이 끊기면 사용자를 '오프라인'으로 표시할 수 있습니다.

Firebase 데이터베이스 클라이언트는 Firebase 데이터베이스 서버와 연결이 끊길 때 데이터베이스에 데이터를 쓰는 데 사용할 수 있는 간단한 기본 요소를 제공합니다. 이러한 업데이트는 클라이언트의 연결이 정상적으로 해제되었는지 여부와 관계없이 발생하므로 연결이 갑자기 끊기거나 클라이언트가 다운되어도 이 업데이트를 사용하여 데이터를 정리할 수 있습니다. 연결이 끊겨도 설정, 업데이트, 삭제 등의 모든 쓰기 작업을 수행할 수 있습니다.

다음은 onDisconnect 기본 요소를 사용하여 연결이 끊길 때 데이터를 쓰는 간단한 예입니다.

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

onDisconnect의 원리

onDisconnect() 작업을 설정하면 Firebase 실시간 데이터베이스 서버에 작업이 상주하게 됩니다. 서버는 보안 검사를 실행하여 요청된 쓰기 이벤트를 사용자가 수행할 수 있는지 확인하고 문제가 있으면 앱에 통보합니다. 이제 서버는 연결 상태를 모니터링하다가 연결이 만료되거나 실시간 데이터베이스 클라이언트에 의해 닫히면 보안 검사를 다시 실행하여 작업이 유효한지 재차 확인한 후 이벤트를 발생시킵니다.

앱에서는 쓰기 작업의 콜백을 사용하여 onDisconnect가 정상적으로 연결되었는지 확인할 수 있습니다.

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());
        }
    }
});

.cancel()을 호출하여 onDisconnect 이벤트를 취소할 수도 있습니다.

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

연결 상태 감지

접속 상태 관련 기능에서는 앱의 온라인 또는 오프라인 전환 시점을 확인하면 유용한 경우가 많습니다. Firebase 실시간 데이터베이스는 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");
  }
});

/.info/connected는 부울 값이며, 이 값은 클라이언트의 상태에 좌우되므로 실시간 데이터베이스 클라이언트 간에 동기화되지 않습니다. 즉, 클라이언트 중 하나에서 /.info/connected를 읽은 결과가 false이더라도 다른 클라이언트에서는 다른 값으로 읽힐 수 있습니다.

Android에서 Firebase는 연결 상태를 자동으로 관리하여 대역폭 및 배터리 사용량을 줄입니다. 클라이언트에 활성 리스너, 대기 중인 쓰기 또는 onDisconnect 작업이 없으며, goOffline 메소드로 명시적으로 연결이 끊기지 않은 경우 비활성 상태가 60초간 지속되면 Firebase는 연결을 닫습니다.

지연 시간 처리

서버 타임스탬프

Firebase 실시간 데이터베이스 서버는 서버에서 생성한 타임스탬프를 데이터로 삽입하는 메커니즘을 제공합니다. 이 기능과 onDisconnect를 함께 사용하면 실시간 데이터베이스 클라이언트의 연결이 끊긴 시간을 쉽고도 신뢰성 있게 기록할 수 있습니다.

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

시계 보정값

대부분의 읽기/쓰기 작업에서는 firebase.database.ServerValue.TIMESTAMP가 훨씬 더 정확하고 바람직하지만 때로는 Firebase 실시간 데이터베이스 서버를 기준으로 한 클라이언트 시계 보정값 예측이 유용할 수 있습니다. 이 값을 밀리초 단위로 가져오려면 /.info/serverTimeOffset 위치에 콜백을 연결합니다. Firebase 실시간 데이터베이스 클라이언트는 이 값을 로컬 보고 시간(밀리초 단위 기점 시간)에 더하여 서버 시간을 추정합니다. 이 오프셋의 정확성은 네트워크 지연 시간에 영향을 받을 수 있으므로 1초 이상의 상당한 시간 오차를 파악하는 데 주로 사용됩니다.

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");
  }
});

샘플 접속 상태 앱

연결 해제 작업과 연결 상태 모니터링 및 서버 타임스탬프를 결합하여 사용자 접속 상태 시스템을 구축할 수 있습니다. 이 시스템에서 각 사용자는 특정 데이터베이스 위치에 데이터를 저장하여 실시간 데이터베이스 클라이언트의 온라인 여부를 알립니다. 클라이언트는 이 위치를 온라인으로 전환될 때 true로, 연결이 끊길 때 타임스탬프로 설정합니다. 이 타임스탬프는 사용자가 마지막으로 온라인 상태였던 시간을 나타냅니다.

앱은 사용자 온라인 표시보다 연결 해제 작업을 대기열에서 앞에 두어야 두 명령이 서버로 전송되기 전에 클라이언트의 네트워크 연결이 끊겨도 경합 상태가 발생하지 않습니다.

다음은 간단한 사용자 접속 상태 시스템입니다.

// 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) {
      DatabaseReference con = myConnectionsRef.push();

      // 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);

      // add this device to my connections list
      // this value could contain info about the device or a timestamp too
      con.setValue(Boolean.TRUE);
    }
  }

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