將剖析 Android 應用程式遷移至 Firebase

如果您是剖析使用者,且想尋找替代後端 服務解決方案 (Firebase) 或許是 Android 應用程式的最佳選擇。

本指南說明如何將特定服務整合至您的應用程式。適用對象 基本 Firebase 設定操作說明,請參閱 Android 設定 指南。

Google Analytics

Google Analytics 是一款免費的應用程式成效評估解決方案,可針對應用程式的使用情況和 像是使用者參與度Analytics 已整合各項 Firebase 功能,可提供無限制 使用 Firebase SDK 定義最多 500 種不同事件的報表。

詳情請參閱 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);

Google Analytics

// 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 Realtime Database

Firebase Realtime Database 是 NoSQL 雲端託管資料庫,資料會以 JSON 格式 即時同步到每個連線的用戶端

詳情請參閱 Firebase Realtime Database 說明文件

與剖析資料的差異

物件

在「剖析」中,您會儲存包含鍵/值組合的 ParseObject 或子類別 資料。資料為無結構定義,也就是說,您不需要指定鍵/值 都存在於各個 ParseObject

所有 Firebase Realtime Database 資料都會儲存為 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();
Firebase
// 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 Realtime Database 中,關係是使用平面資料結構更好地表示, 將資料分成不同的路徑,以便在不同的呼叫中有效率地下載。

以下範例說明如何在 網誌應用程式及其作者。

剖析
// 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();
Firebase
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"
    }
    ...
  }
}
敬上 詳情請參閱 建立資料庫結構 指南。

讀取資料

在剖析中,您可以使用特定剖析物件的 ID 讀取資料,或 使用 ParseQuery 執行查詢。

在 Firebase 中,您可以將非同步事件監聽器附加至資料庫參照,藉此擷取資料。 監聽器的初始狀態就會觸發一次,並在資料變更時再次觸發。 因此您不必新增任何程式碼即可判斷資料是否變更。

以下範例說明如何根據 "Objects" 一節所示的範例。

剖析
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());
        }
    }
});
Firebase
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 Realtime Database 經過最佳化調整,能以毫秒為單位在所有連線間同步處理資料 而產生的資料結構與剖析核心資料不同。也就是說, 遷移的第一步是考量資料需要進行哪些變更,包括:

  • Parse 物件應如何對應至 Firebase 資料
  • 如果有父項/子項關係 如何劃分不同路徑的資料 能有效率地在不同呼叫中下載

遷移資料

決定好在 Firebase 中建構資料的方式後,接著就必須規劃要如何處理 期間,應用程式需要寫入兩個資料庫。可用的選項包括:

背景同步處理

在這個情境下,應用程式有兩個版本:使用 Parse 的舊版應用程式和新版本 以及使用 Firebase 的版本兩個資料庫之間的同步作業是由剖析 Cloud Code 處理 (剖析) 傳送至 Firebase),並使用程式碼來監聽 Firebase 中的變更,並將這些變更同步至 Parse。 開始使用新版本前,您必須:

  • 將現有的剖析資料轉換為新的 Firebase 結構,然後寫入 Firebase Realtime Database
  • 編寫剖析 Cloud Code 函式,使用 Firebase REST API 寫入 舊用戶端在剖析資料中所做的變更 Firebase Realtime Database
  • 編寫及部署程式碼,監聽 Firebase 中的變更,然後同步至剖析 資料庫

這個案例可確保您明確區分新舊程式碼,並讓用戶端保持簡單。 這種情況的挑戰,在於如何處理初始匯出中的大型資料集 雙向同步不會產生無限遞迴。

重複書寫

在這個情境中,您要使用 Firebase 和 Parse 編寫新版應用程式,並同時使用 Firebase 和 Parse 剖析 Cloud Code,將舊用戶端所做的變更從「剖析資料」同步到 Firebase Realtime Database。等到足夠人數從僅剖析版本的應用程式改用新平台後, 會從雙重寫入版本中移除剖析程式碼。

這種情況不需要任何伺服器端程式碼。缺點是資料不會 不會遷移,而應用程式大小會因為同時使用兩個 SDK 而增加。

Firebase Authentication

Firebase Authentication 可以使用密碼和熱門聯合識別資訊提供者來驗證使用者 例如 Google、Facebook 和 Twitter並提供 UI 程式庫 在應用程式中導入並維持完整的驗證體驗, 和所有平台。

詳情請參閱 Firebase Authentication 說明文件

剖析驗證的差異

Parse 提供名為 ParseUser 的特殊使用者類別,可自動處理 管理使用者帳戶所需的功能ParseUserParseObject,這表示「剖析資料」會提供使用者資料,且可使用 就像其他 ParseObject 一樣。

FirebaseUser 具有一組固定的基本屬性,包括專屬 ID、主要電子郵件地址。 名稱和相片網址—儲存在個別專案的使用者資料庫中;這些屬性只要: 使用者。您無法直接將其他屬性新增至 FirebaseUser 物件; 您可以改為將額外屬性儲存在 Firebase Realtime Database 中。

以下範例說明如何註冊使用者,並新增其他電話號碼欄位。

剖析
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
        }
    }
});
Firebase
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 指令

首先,從剖析控制台或自行託管的使用者資料庫匯出 資料庫例如,從剖析控制台匯出的 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使用剖析使用者的 objectId 做為 您 localId 的 Firebase 使用者。此外,Base64 會將 剖析中的 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 Database 使用資料遷移一節所述的策略。如果遷移 帳戶 (使用帳戶遷移一節所述流程), Firebase 帳戶有相同的 Parse 帳戶的 ID,方便您遷移及重現 使用者 ID 所建立的任何關係

Firebase Cloud Messaging

Firebase Cloud Messaging」(FCM) 是跨平台的訊息傳遞解決方案,提供可靠的服務 免費傳送訊息和通知。通知編輯器是免付費服務 是針對行動應用程式開發人員,開放指定使用者通知的 Firebase Cloud Messaging

詳情請參閱 Firebase Cloud Messaging 說明文件

剖析推播通知的差異

裝置上安裝的每個 Parse 應用程式都會在註冊通知時存取 Installation 物件,用來儲存目標通知所需的所有資料。 InstallationParseUser 的子類別,表示您可以 用於 Installation 例項的任何額外資料。

通知編輯器會根據應用程式、應用程式版本和裝置等資訊,提供預先定義的使用者區隔 語言。您可以使用 Google Analytics 事件和屬性建立更複雜的使用者區隔 建立目標對象查看目標對象 說明指南。這些指定目標資訊不會顯示在 Firebase Realtime Database 中。

建議遷移策略

遷移裝置權杖

在本文撰寫期間,Parse Android SDK 使用舊版的 FCM 註冊符記,與通知編輯器提供的功能不相容。

在應用程式中新增 FCM SDK 即可取得新的權杖;不過 這可能會使 Parse SDK 用來接收通知的權杖失效。 如果不想這麼做,可以將 Parse SDK 設定為同時使用兩種剖析的傳送者 ID 和傳送者 ID這樣就不會讓 Parse SDK 使用的權杖失效。 但請注意,當 Parse 關閉專案時,這個解決方法就會停止運作。

將頻道遷移至 FCM 個主題

如果您使用剖析管道來傳送通知,可以遷移至 FCM 主題,這樣就能 相同的發布者/訂閱者模式如要處理從 Parse 轉換為 FCM 的轉換程序,您可以編寫新版本 應用程式使用 Parse SDK 取消訂閱剖析頻道,FCM SDK 訂閱 對應的 FCM 主題。這個版本的應用程式應停止透過以下裝置接收通知: ,請將下列項目從應用程式的資訊清單移除:

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

舉例來說,如果您訂閱了「巨人」topic,您可以進行如下操作:

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

您可以運用這項策略,將訊息同時傳送至 Parse 管道和相應的 FCM 主題,支援新舊版本的使用者。完成遷移的使用者人數不足時 只有剖析版本的應用程式可以停用該版本,然後只使用 FCM 傳送訊息。

詳情請參閱 FCM 主題文件

Firebase Remote Config

Firebase Remote Config 是一項雲端服務,可讓您變更應用程式的行為和外觀 不必要求使用者下載應用程式更新。使用遠端設定時 控制應用程式行為和外觀的預設值。您之後可以使用 透過 Firebase 控制台,針對所有應用程式使用者或特定使用者族群覆寫應用程式內預設值。

如果您要在遷移作業中進行測試,Firebase Remote Config 就能派上用場 不同解決方案,以動態方式將更多客戶轉移至其他供應商。例如: 如果您的應用程式版本同時使用 Firebase 和 Parse 資料,您可以使用 來決定從 Firebase 讀取哪些用戶端,並逐漸提高百分比。

如要進一步瞭解Firebase Remote Config,請參閱 Remote Config 簡介

與剖析設定的差異

您可以透過剖析設定,透過剖析設定資訊主頁將鍵/值組合新增至應用程式, 在用戶端上擷取 ParseConfig。您產生的每 ParseConfig 個執行個體 get 一律無法變更日後從ParseConfig 網路,因此不會修改任何現有的 ParseConfig 執行個體,而只會修改 新建一個,並透過 getCurrentConfig() 提供。

有了 Firebase Remote Config,您就能為鍵/值組合建立應用程式內預設值,以便覆寫 ,您可以運用規則和條件,針對應用程式的 區分不同客群的使用者體驗Firebase Remote Config 會實作 可讓應用程式使用鍵/值組合的單例模式類別。單例模式一開始 您在應用程式內定義的預設值您可以隨時從伺服器擷取一組新的值 對應用程式的重要性成功擷取新資料集後,您就能選擇何時啟用 即可將新的值提供給應用程式。

建議遷移策略

只要複製 Parse 設定的鍵/值組合,即可移至 Firebase Remote Config ,然後部署使用 Firebase Remote Config 的新版應用程式。

如要同時測試剖析設定和 Firebase Remote Config,您可以 新版應用程式使用兩種 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!");
    }
});

Firebase

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