Google is committed to advancing racial equity for Black communities. See how.
本頁面由 Cloud Translation API 翻譯而成。
Switch to English

將您的Parse Android應用遷移到Firebase

如果您是Parse用戶,正在尋找替代的後端即服務解決方案,則Firebase可能是您的Android應用的理想選擇。

本指南介紹瞭如何將特定服務集成到您的應用中。有關Firebase的基本設置說明,請參閱Android安裝指南。

谷歌分析

Google Analytics(分析)是一種免費的應用評估解決方案,可提供有關應用使用情況和用戶參與度的見解。 Analytics(分析)整合了Firebase功能,並為您提供多達500個不同事件的無限報告,您可以使用Firebase SDK進行定義。

請參閱Google Analytics(分析)文檔以了解更多信息。

建議的遷移策略

使用不同的分析提供程序是一種常見情況,可以輕鬆應用於Google Analytics(分析)。只需將其添加到您的應用程序中,即可受益於Analytics(分析)自動收集的事件和用戶屬性,例如首次打開,應用程序更新,設備型號,年齡。

對於自定義事件和用戶屬性,可以同時使用Parse Analytics和Google Analytics(分析)來記錄事件和屬性的雙重寫入策略,這使您可以逐步推出新的解決方案。

代碼比較

解析分析

 // Start collecting data
ParseAnalytics.trackAppOpenedInBackground(getIntent());

Map<String, String> dimensions = new HashMap<String, String>();
// Define ranges to bucket data points into meaningful segments
dimensions.put("priceRange", "1000-1500");
// Did the user filter the query?
dimensions.put("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
dimensions.put("dayType", "weekday");

// Send the dimensions to Parse along with the 'search' event
ParseAnalytics.trackEvent("search", dimensions);
 

谷歌分析

 // Obtain the FirebaseAnalytics instance and start collecting data
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);

Bundle params = new Bundle();
// Define ranges to bucket data points into meaningful segments
params.putString("priceRange", "1000-1500");
// Did the user filter the query?
params.putString("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
params.putString("dayType", "weekday");

// Send the event
mFirebaseAnalytics.logEvent("search", params);
 

Firebase實時數據庫

Firebase實時數據庫是NoSQL雲託管的數據庫。數據存儲為JSON,並實時同步到每個連接的客戶端。

請參閱Firebase實時數據庫文檔以了解更多信息。

解析數據的差異

對象

在Parse中,您存儲一個ParseObject或其子類,其中包含JSON兼容數據的鍵值對。數據是無模式的,這意味著您無需指定每個ParseObject上存在的鍵。

所有Firebase實時數據庫數據都存儲為JSON對象,並且ParseObject沒有等效的數據;您只需將與可用JSON類型相對應的類型的JSON樹值寫入即可。您可以使用Java對像簡化對數據庫的讀取和寫入。

以下是如何保存遊戲高分的示例。

解析
 @ParseClassName("GameScore")
public class GameScore {
        public GameScore() {}
        public GameScore(Long score, String playerName, Boolean cheatMode) {
            setScore(score);
            setPlayerName(playerName);
            setCheatMode(cheatMode);
        }

        public void setScore(Long score) {
            set("score", score);
        }

        public Long getScore() {
            return getLong("score");
        }

        public void setPlayerName(String playerName) {
            set("playerName", playerName);
        }

        public String getPlayerName() {
            return getString("playerName");
        }

        public void setCheatMode(Boolean cheatMode) {
            return set("cheatMode", cheatMode);
        }

        public Boolean getCheatMode() {
            return getBoolean("cheatMode");
        }
}

// Must call Parse.registerSubclass(GameScore.class) in Application.onCreate
GameScore gameScore = new GameScore(1337, "Sean Plott", false);
gameScore.saveInBackground();
 
火力基地
 // Assuming we defined the GameScore class as:
public class GameScore {
        private Long score;
        private String playerName;
        private Boolean cheatMode;

        public GameScore() {}
        public GameScore(Long score, String playerName, Boolean cheatMode) {
            this.score = score;
            this.playerName = playerName;
            this.cheatMode = cheatMode;
        }

        public Long getScore() {
            return score;
        }

        public String getPlayerName() {
            return playerName;
        }

        public Boolean getCheatMode() {
            return cheatMode;
        }
}

// We would save it to our list of high scores as follows:
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
GameScore score = new GameScore(1337, "Sean Plott", false);
mFirebaseRef.child("scores").push().setValue(score);
 
有關更多詳細信息,請參閱《 Android上的讀寫數據》指南。

數據之間的關係

一個ParseObject可以與另一個ParseObject有關係:任何對像都可以將其他對像用作值。

在Firebase實時數據庫中,可以使用平面數據結構更好地表達關係,該結構將數據分成單獨的路徑,以便可以在單獨的調用中有效地下載它們。

以下是如何構建博客應用程序中的帖子與其作者之間的關係的示例。

解析
 // Create the author
ParseObject myAuthor = new ParseObject("Author");
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");

// Create the post
ParseObject myPost = new ParseObject("Post");
myPost.put("title", "Announcing COBOL, a New Programming Language");

// Add a relation between the Post and the Author
myPost.put("parent", myAuthor);

// This will save both myAuthor and myPost
myPost.saveInBackground();
 
火力基地
 DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
// Create the author
Map<String, String> myAuthor = new HashMap<String, String>();
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");

// Save the author
String myAuthorKey = "ghopper";
firebaseRef.child('authors').child(myAuthorKey).setValue(myAuthor);

// Create the post
Map<String, String> post = new HashMap<String, String>();
post.put("author", myAuthorKey);
post.put("title", "Announcing COBOL, a New Programming Language");
firebaseRef.child('posts').push().setValue(post);
 

結果是以下數據佈局。

{
  // Info about the authors
  "authors": {
    "ghopper": {
      "name": "Grace Hopper",
      "date_of_birth": "December 9, 1906",
      "nickname": "Amazing Grace"
    },
    ...
  },
  // Info about the posts: the "author" fields contains the key for the author
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "ghopper",
      "title": "Announcing COBOL, a New Programming Language"
    }
    ...
  }
}
有關更多詳細信息,請參閱《 結構化數據庫》指南。

讀取數據

在Parse中,您可以使用特定Parse對象的ID讀取數據,也可以使用ParseQuery執行查詢。

在Firebase中,您可以通過將異步偵聽器附加到數據庫引用來檢索數據。數據的初始狀態會觸發一次偵聽器,數據發生更改時會再次觸發該偵聽器,因此您無需添加任何代碼即可確定數據是否發生了更改。

下面是一個示例,該示例基於“對象”部分中顯示的示例,如何檢索特定玩家的得分。

解析
 ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan Stemkoski");
query.findInBackground(new FindCallback<ParseObject>() {
    public void done(List<ParseObject> scoreList, ParseException e) {
        if (e == null) {
            for (ParseObject score: scoreList) {
                Log.d("score", "Retrieved: " + Long.toString(score.getLong("score")));
            }
        } else {
            Log.d("score", "Error: " + e.getMessage());
        }
    }
});
 
火力基地
 DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
Query mQueryRef = mFirebaseRef.child("scores").orderByChild("playerName").equalTo("Dan Stemkoski");

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
mQueryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        // This will fire for each matching child node.
        GameScore score = snapshot.getValue(GameScore.class);
        Log.d("score", "Retrieved: " + Long.toString(score.getScore());
    }
});
 
有關事件偵聽器的可用類型以及如何排序和過濾數據的更多詳細信息,請參閱《 Android上的讀寫數據》指南。

建議的遷移策略

重新考慮您的數據

Firebase實時數據庫經過優化,可以在所有連接的客戶端之間以毫秒為單位同步數據,並且生成的數據結構與Parse核心數據不同。這意味著遷移的第一步是考慮數據需要進行哪些更改,包括:

  • 您的Parse對象應如何映射到Firebase數據
  • 如果您有親子關係,如何將數據拆分到不同的路徑,以便可以在單獨的調用中有效地下載數據。

遷移數據

在決定如何在Firebase中構建數據結構之後,您需要計劃如何處理應用程序需要同時寫入兩個數據庫的時間段。您的選擇是:

後台同步

在這種情況下,您有兩個版本的應用程序:使用Parse的舊版本和使用Firebase的新版本。兩個數據庫之間的同步由Parse Cloud Code(解析為Firebase)處理(您的代碼偵聽Firebase上的更改並將這些更改與Parse同步)。在開始使用新版本之前,您必須:

  • 將您現有的解析數據轉換為新的Firebase結構,並將其寫入Firebase實時數據庫。
  • 編寫使用Firebase REST API的Parse Cloud Code函數,以將舊客戶端在Parse Data中進行的更改寫入Firebase Realtime Database。
  • 編寫和部署可偵聽Firebase上的更改並將其同步到Parse數據庫的代碼。

這種情況確保了新舊代碼的清晰分離,並使客戶端保持簡單。這種情況下的挑戰是在初始導出中處理大型數據集,並確保雙向同步不會產生無限遞歸。

雙重寫

在這種情況下,您將編寫同時使用Firebase和Parse的應用程序的新版本,並使用Parse Cloud Code將舊客戶端所做的更改從Parse Data同步到Firebase Realtime Database。從應用程序的僅解析版本遷移了足夠多的人後,您可以從雙重寫入版本中刪除解析代碼。

此方案不需要任何服務器端代碼。其缺點是無法遷移無法訪問的數據,並且兩個SDK的使用都會增加應用程序的大小。

Firebase身份驗證

Firebase身份驗證可以使用密碼和流行的聯合身份提供商(例如Google,Facebook和Twitter)對用戶進行身份驗證。它還提供UI庫,以節省您在所有平台上實施和維護應用程序的完整身份驗證體驗所需的大量投資。

請參閱Firebase身份驗證文檔以了解更多信息。

解析身份驗證的差異

Parse提供了一個稱為ParseUser的專用用戶類,該類自動處理用戶帳戶管理所需的功能。 ParseUser是的一個子類ParseObject ,該裝置的用戶數據可在分析數據,並且可以與像任何其他額外的字段擴展ParseObject

FirebaseUser具有一組固定的基本屬性,即唯一的ID,主電子郵件地址,名稱和照片URL,這些屬性存儲在單獨的項目的用戶數據庫中;這些屬性可以由用戶更新。您不能將其他屬性直接添加到FirebaseUser對象。相反,您可以將其他屬性存儲在Firebase實時數據庫中。

以下是如何註冊用戶並添加其他電話號碼字段的示例。

解析
 ParseUser user = new ParseUser();
user.setUsername("my name");
user.setPassword("my pass");
user.setEmail("email@example.com");

// other fields can be set just like with ParseObject
user.put("phone", "650-253-0000");

user.signUpInBackground(new SignUpCallback() {
    public void done(ParseException e) {
        if (e == null) {
            // Hooray! Let them use the app now.
        } else {
            // Sign up didn't succeed. Look at the ParseException
            // to figure out what went wrong
        }
    }
});
 
火力基地
 FirebaseAuth mAuth = FirebaseAuth.getInstance();

mAuth.createUserWithEmailAndPassword("email@example.com", "my pass")
    .continueWithTask(new Continuation<AuthResult, Task<Void>> {
        @Override
        public Task<Void> then(Task<AuthResult> task) {
            if (task.isSuccessful()) {
                FirebaseUser user = task.getResult().getUser();
                DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
                return firebaseRef.child("users").child(user.getUid()).child("phone").setValue("650-253-0000");
            } else {
                // User creation didn't succeed. Look at the task exception
                // to figure out what went wrong
                Log.w(TAG, "signInWithEmail", task.getException());
            }
        }
    });
 

建議的遷移策略

遷移帳戶

要將用戶帳戶從Parse遷移到Firebase,請將您的用戶數據庫導出到JSON或CSV文件,然後使用Firebase CLI的auth:import命令將文件導入到Firebase項目中。

首先,從“解析”控制台或您的自託管數據庫導出用戶數據庫。例如,從Parse控制台導出的JSON文件可能如下所示:

{ // Username/password user
  "bcryptPassword": "$2a$10$OBp2hxB7TaYZgKyTiY48luawlTuYAU6BqzxJfpHoJMdZmjaF4HFh6",
  "email": "user@example.com",
  "username": "testuser",
  "objectId": "abcde1234",
  ...
},
{ // Facebook user
  "authData": {
    "facebook": {
      "access_token": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
      "expiration_date": "2017-01-02T03:04:05.006Z",
      "id": "1000000000"
    }
  },
  "username": "wXyZ987654321StUv",
  "objectId": "fghij5678",
  ...
}

然後,將導出的文件轉換為Firebase CLI所需的格式。將您的Parse用戶的objectId用作Firebase用戶的localId 。另外,base64對Parse中的bcryptPassword值進行編碼,並在passwordHash字段中使用它們。例如:

{
  "users": [
    {
      "localId": "abcde1234",  // Parse objectId
      "email": "user@example.com",
      "displayName": "testuser",
      "passwordHash": "JDJhJDEwJE9CcDJoeEI3VGFZWmdLeVRpWTQ4bHVhd2xUdVlBVTZCcXp4SmZwSG9KTWRabWphRjRIRmg2",
    },
    {
      "localId": "fghij5678",  // Parse objectId
      "displayName": "wXyZ987654321StUv",
      "providerUserInfo": [
        {
          "providerId": "facebook.com",
          "rawId": "1000000000",  // Facebook ID
        }
      ]
    }
  ]
}

最後,使用Firebase CLI導入轉換後的文件,並將bcrypt指定為哈希算法:

firebase auth:import account_file.json --hash-algo=BCRYPT

遷移用戶數據

如果要為用戶存儲其他數據,則可以使用“ 數據遷移”部分中介紹的策略將其遷移到Firebase Realtime數據庫。如果您使用“ 帳戶遷移”部分中描述的流程遷移帳戶,則Firebase帳戶具有與“解析”帳戶相同的ID,從而使您可以輕鬆地遷移和復制由用戶ID鍵入的任何關係。

Firebase雲消息傳遞

Firebase雲消息傳遞(FCM)是一種跨平台的消息傳遞解決方案,可讓您可靠地免費傳遞消息和通知。 Notifications作曲者是一項基於Firebase Cloud Messaging的免費服務,可為移動應用程序開發人員提供有針對性的用戶通知。

請參閱Firebase Cloud Messaging文檔以了解更多信息。

解析推送通知的差異

在註冊了通知的設備上安裝的每個Parse應用程序都有一個關聯的Installation對象,您可以在其中存儲定位通知所需的所有數據。 InstallationParseUser的子類,這意味著您可以將所需的任何其他數據添加到Installation實例。

Notifications作曲器根據諸如應用程序,應用程序版本和設備語言之類的信息提供預定義的用戶細分。您可以使用Google Analytics(分析)事件和屬性來建立更複雜的用戶群,以建立受眾群體。請參閱受眾幫助指南以了解更多信息。這些定位信息在Firebase實時數據庫中不可見。

建議的遷移策略

遷移設備令牌

在撰寫本文時,Parse Android SDK使用了較早版本的FCM註冊令牌,該令牌與Notifications作曲家提供的功能不兼容。

您可以通過將FCM SDK添加到您的應用中來獲取新令牌;但是,這可能會使Parse SDK用來接收通知的令牌無效。如果要避免這種情況,可以將Parse SDK設置為同時使用Parse的發件人ID和發件人ID。這樣,您不會使Parse SDK使用的令牌無效,但是請注意,當Parse關閉其項目時,此解決方法將停止工作。

將渠道遷移到FCM主題

如果使用解析通道發送通知,則可以遷移到FCM主題,它們提供相同的發布者-訂閱者模型。要處理從Parse到FCM的過渡,您可以編寫新版本的應用程序,該應用程序使用Parse SDK取消訂閱Parse頻道,並使用FCM SDK訂閱相應的FCM主題。在此版本的應用中,您應禁用在Parse SDK上接收通知,並從應用的清單中刪除以下內容:

 <service android:name="com.parse.PushService" />
<receiver android:name="com.parse.ParsePushBroadcastReceiver"
  android:exported="false">
<intent-filter>
<action android:name="com.parse.push.intent.RECEIVE" />
<action android:name="com.parse.push.intent.DELETE" />
<action android:name="com.parse.push.intent.OPEN" />
</intent-filter>
</receiver>
<receiver android:name="com.parse.GcmBroadcastReceiver"
  android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />

<!--
IMPORTANT: Change "com.parse.starter" to match your app's package name.
-->
<category android:name="com.parse.starter" />
</intent-filter>
</receiver>

<!--
IMPORTANT: Change "YOUR_SENDER_ID" to your GCM Sender Id.
-->
<meta-data android:name="com.parse.push.gcm_sender_id"
  android:value="id:YOUR_SENDER_ID" />;
 

例如,如果您的用戶訂閱了“巨人”主題,則可以執行以下操作:

 ParsePush.unsubscribeInBackground("Giants", new SaveCallback() {
    @Override
    public void done(ParseException e) {
        if (e == null) {
            FirebaseMessaging.getInstance().subscribeToTopic("Giants");
        } else {
            // Something went wrong unsubscribing
        }
    }
});
 

使用此策略,您可以將消息發送到“解析”通道和相應的FCM主題,從而同時支持舊版本和新版本的用戶。從應用程序的僅解析版本遷移了足夠多的用戶後,您可以停用該版本並僅使用FCM開始發送。

請參閱FCM主題文檔以了解更多信息。

Firebase遠程配置

Firebase Remote Config是一項云服務,可讓您更改應用程序的行為和外觀,而無需用戶下載應用程序更新。使用Remote Config時,您將創建應用程序內默認值來控制應用程序的行為和外觀。然後,您以後可以使用Firebase控制台為所有應用程序用戶或用戶庫的部分覆蓋應用程序內默認值。

如果您想測試不同的解決方案並能夠將更多客戶端動態轉移到其他提供商,則Firebase Remote Config在遷移期間非常有用。例如,如果您的應用程序版本同時使用Firebase和Parse來獲取數據,則可以使用隨機百分比規則來確定哪些客戶端從Firebase讀取,並逐漸增加百分比。

要了解有關Firebase Remote Config的更多信息,請參閱Remote Config簡介

解析配置的差異

使用Parse config,您可以在Parse Config Dashboard上將鍵/值對添加到您的應用程序,然後在客戶端上獲取ParseConfig 。您獲得的每個ParseConfig實例始終是不可變的。將來當您從網絡中檢索新的ParseConfig時,它不會修改任何現有的ParseConfig實例,而是創建一個新實例,並通過getCurrentConfig()使它可用。

使用Firebase Remote Config,您可以為鍵/值對創建應用程序內默認值,您可以從Firebase控制台中覆蓋它們,並且可以使用規則和條件根據應用程序的用戶體驗提供不同的用戶群。 Firebase Remote Config實現了一個單例類,該類使鍵/值對可用於您的應用程序。最初,單例返回您在應用內定義的默認值。您可以隨時為您的應用方便地從服務器獲取一組新的值。成功獲取新集合後,您可以選擇何時激活它以使新值可用於應用程序。

建議的遷移策略

通過將解析配置的鍵/值對複製到Firebase控制台,然後部署使用Firebase Remote Config的應用程序的新版本,可以遷移到Firebase Remote Config。

如果要同時嘗試“解析配置”和“ Firebase遠程配置”,則可以部署使用兩個SDK的應用程序的新版本,直到有足夠的用戶從“僅解析”版本進行遷移。

代碼比較

解析

 ParseConfig.getInBackground(new ConfigCallback() {
    @Override
    public void done(ParseConfig config, ParseException e) {
        if (e == null) {
            Log.d("TAG", "Yay! Config was fetched from the server.");
        } else {
            Log.e("TAG", "Failed to fetch. Using Cached Config.");
            config = ParseConfig.getCurrentConfig();
        }

        // Get the message from config or fallback to default value
        String welcomeMessage = config.getString("welcomeMessage", "Welcome!");
    }
});
 

火力基地

 mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
// Set defaults from an XML resource file stored in res/xml
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);

mFirebaseRemoteConfig.fetch()
    .addOnSuccessListener(new OnSuccessListener<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            Log.d("TAG", "Yay! Config was fetched from the server.");
            // Once the config is successfully fetched it must be activated before newly fetched
            // values are returned.
            mFirebaseRemoteConfig.activateFetched();
        }
    })
    .addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            Log.e("TAG", "Failed to fetch. Using last fetched or default.");
        }
    })

// ...

// When this is called, the value of the latest fetched and activated config is returned;
// if there's none, the default value is returned.
String welcomeMessage = mFirebaseRemoteConfig.getString("welcomeMessage");