求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
面向 Android 应用程序的基于 Parse 云的服务
 

作者:C. Enrique Ortiz ,发布于2013-2-1,来源:IBM

 

Parse 移动 SDK 为 iOS、Android 和 Windows? 应用程序提供了基于云的 API 和服务。Parse SDK 还提供了 JavaScript 和 REST API。使用 Parse API,您可以极快地以最少工作量让您的移动应用程序支持云处理。集成了 Parse API 的移动应用程序可以轻松地在 Parse 云上存储数据对象和文件,发送并侦听推送通知,管理用户,处理地理位置数据,并使用 Twitter 和 Facebook 等社交媒体平台。对于需要扩展的移动应用程序,Parse SDK 可以提供所有灵活的云平台。

本文介绍了面向 Parse 用户、数据对象和文件的核心 Parse API 类。您将学习如何使用访问控制列表 (ACL),以及如何在数据对象上执行 CRUD 操作,还有如何在 Parse 云中存储和检索文件。示例均构建于 Parse SDK for Android 之上。

Parse 仪表板

Parse 仪表板可以帮助开发人员管理应用程序。该仪表板为 API、文件和推送的通知提供了一般指标和应用程序特定的使用指标。通过仪表板可管理应用程序的键和设置。该仪表板还提供了数据浏览器,使开发人员能够浏览(甚至编辑)所存储的 Parse 对象。数据浏览器对于调试非常有用。图 1 是 Parse 仪表板的屏幕截图:

图 1. Parse 仪表板

通过一个应用程序 ID 和客户端 ID 对应用程序进行身份验证。为了获得您的应用程序和客户端 ID,必须通过 Parse 仪表板注册您的应用程序。在您的应用程序上初始化 Parse 库时,会用到这些键。

Parse 数据对象

在 Parse 中,使用 “名称-值” 对的容器 ParseObject 表示数据。ParseObject 可以存储任何与 JSON 兼容的数据,如清单 1 所示:

清单 1. ParseObject 示例

ParseObject myParseObject = new ParseObject("MyObject"); // Class Name
myParseObject.put("name", "C. Enrique Ortiz");
myParseObject.put("twitterHandle", "eortiz");
myParseObject.put("followers", 123456); 

ParseObject 在实例化时被赋予一个 classname(类名称)。在 清单 1 中,类名称是 "MyObject"。类名称与关系数据库中的表名称相似,同一类的 Parse 对象相当于表中的行。

ParseObject 暴露的方法类似于 Java Map 类中的方法,如 put、get 和 remove,以及大量特定于 ParseObject 的其他方法。

ParseObject 名称键 必须是字母数字的,作为一个指导方针,请对名称键使用驼峰式大小写 (camel-casing) 格式。值可以是存储在 JSON 中的任何数据类型,也就是说,可以是数字、字符串、布尔值、数组、JSONObject.NULL、JSONObject 和 JSONArray。ParseObject 所支持的其他数据类型是 Java Date 和 byte[] 数组。ParseObject 还可以包含其他 ParseObject。

清单 2 显示了部分受支持的 ParseObject 值数据类型:

清单 2. ParseObject: 部分支持的值数据类型

// Byte Array
byte[] byteArray = {1, 2, 3, 4, 5};

// A date
Date d = new Date(); // java.util.Date

// A number
int number = 21;

// A String
String name = "Enrique";

// A JSONArray - any mix of JSONObjects, JSONArrays, Strings, Booleans, 
//   Integers, Longs, Doubles, null or NULL.
JSONArray jArray = new JSONArray();
jArray.put(number);
jArray.put(name);

// A JSONObject 
JSONObject jObject = new JSONObject();
try {
    jObject.put("number", number);
    jObject.put("name", name);
} catch (JSONException e) {
    e.printStackTrace();
}

// A ParseObject
ParseObject pObject = new ParseObject("MyObject"); // Class name
pObject.put("myByteArray", byteArray);
pObject.put("myDate", d);
pObject.put("myString", name);
pObject.put("myNumber", number);
pObject.put("myJsonArray", jArray);
pObject.put("myJsonObject", jObject);
pObject.put("myNull", JSONObject.NULL); 

清单 2 中的代码创建了一个 ParseObject,它在 Parse 云中被存储为一个对象。然后,许多同一类的 MyObject 被存储为 ParseObject 数据对象的行,可以保存、查询和更新,并能从 Parse 的云存储中删除。甚至可以在应用程序离线时保存数据,Parse 库将数据保存在本地,直到重新建立网络连接。

修改 ParseObject

如果您熟悉移动应用程序开发,那么您就会知道,网络操作等长时间操作一般都是在后台完成,或在一个工作线程上完成,而不是在主系统的 UI 线程上完成。这样可以避免主系统线程发生阻塞并影响用户界面的响应速度。稍后,在本文的后半部分,我会告诉您 Parse 如何为后台工作提供帮助,从而保存、删除和查找对象。现在,考虑下面的同步 remove() 方法,可以使用它从 Parse 对象删除一个键:

pObject.remove("myNumber"); // remove the field/key "myNumber" from pObject

在删除或添加字段后,或在更新当前字段后,您可以在云上保存(或更新)数据对象,只需调用其中一个 ParseObject 的 save...() 方法,我稍后会在本文中讨论它们。

Parse 用户、角色和 ACL

在我告诉您如何在 Parse 对象上执行 CRUD 操作之前,您应该了解一下 Parse 用户、角色和 ACL(访问控制列表)。这三个都是非常重要的概念,有助于保护您的应用程序的数据对象。

Parse 用户

名称为 ParseUser 的类代表一个用户,并为 Parse 应用程序提供用户帐户功能。每个 Parse 应用程序都有与之关联的 Parse 用户。一个 ParseUser 是一个 ParseObject,但具有更多的属性,如用户名、密码和电子邮件。您可以添加任何您认为合适的其他数据值。

用户可以注册为您应用的 Parse 用户,如清单 3 所示:

清单 3. ParseUser — 注册

ParseUser user = new ParseUser();
user.setUsername("eortiz");
user.setPassword("123456");
user.setEmail("eortiz@nospam.com");
user.put("userType", "Author"); // add another field

// Call the asynchronous background method to sign up 
user.signUpInBackground(new SignUpCallback() {
  public void done(ParseException e) {
    if (e == null) {
      // Successful. Allow access to app.
    } else {
      // Failed....
    }
  }
}); 

username 和 email 必须是惟一的。如果 username 或 email 已经被使用,那么注册调用将会失败。您应该有一个通知用户字段限制的机制,并提供一个重试的进程。

在注册之后,用户就可以登录您的应用,如清单 4 所示:

清单 4. ParseUser — 登录

ParseUser.logInInBackground("eortiz", "123456", new LogInCallback() {
  public void done(ParseUser user, ParseException e) {
    if (user != null) {
      // Successful. Allow access to app.
    } else {
      // Failed
    }
  }
}); 

您可以通过调用 ParseUser.save() 更新用户信息。 但是请注意,只有 ParseUser 的所有者可以修改其内容,数据对于其他任何人都是只读的。

Parse 会缓存当前登录的用户。您可以通过调用 ParseUser.currentUser() 查询当前用户。currentUser 方法使您能够快速访问当前用户信息,那么如果当前用户会话没有激活,您只需要根据提示输入验证。清单 5 显示了如何在 Parse 中检索当前用户:

清单 5. ParseUser — 获得当前用户

ParseUser currentUser = ParseUser.getCurrentUser();
if (currentUser != null) {
  // current user is valid
} else {
  // current user not valid, ask for credentials
} 

重置当前用户

在 Parse 中,您可以通过调用 ParseUser.logOut() 来重置当前用户,如清单 6 所示:

清单 6. ParseUser — 重置当前用户(登出)

ParseUser.logOut(); // static method

Parse ACL

ACL 是关联到数据对象的访问权限(或控制)列表。ParseACL 类允许您为给定的 ParseObject 定义权限。使用 ACL,您可以定义对您的应用程序数据对象的公共访问,并且可以(通过角色)限制对特定用户或用户组的访问。清单 7 演示了 Parse ACL 的用法:

清单 7. 使用 ParseACL 实现访问权限控制

// Define a Parse user
ParseUser user = new ParseUser();
user.setUsername(username);
:
:

// Define a read/write ACL
ParseACL rwACL = new ParseACL();
rwACL.setReadAccess(user, true); // allow user to do reads
rwACL.setWriteAccess(user, true); // allow user to do writes
:
:

// Define a Parse object and its ACL
ParseObject gameObject = new ParseObject("Game");
gameObject.setACL(rwACL); // allow user do read/writes on gameObject
gameObject.saveInBackground(); // save the ACL'ed object to the cloud
:
:

// You can define a public ACL that gives public access to the object
ParseACL publicACL = new ParseACL();
publicACL.setPublicReadAccess(true);
publicACL.setPublicWriteAccess(true);      
gameObject.setACL(publicACL); // allow public read/writes
gameObject.saveInBackground(); // save the ACL'ed object to the cloud 

您还可以为所有新创建的对象定义一个默认的 ACL。在清单 8 中,我已经将可读取和写入的默认 ACL 设置为 public,清单 7 中的 publicACL 定义。

清单 8. 设置默认的 ACL

// Set a default ACL for all newly created objects as public access
ParseACL.setDefaultACL(publicACL, true); 

虽然在这里没有演示,但我们也可以使用 ParseRole 类为用户组授予访问权限。

接下来,我们将查看如何将 Parse 数据对象保存到 Parse 云,并在 Parse 云中检索它们。

在云上的 Parse 数据对象

一旦创建并填充了 ParseObject,就可以在 Parse 云上保存。在 Parse 云上保存数据对象实际上是利用 Parse 的最简单操作之一,其复杂性通常与数据表示、编组、网络通信和传输等有关联,Parse 完全隐藏了此复杂性。您需要使用 helper 方法来将数据对象实例映射到 ParseObject,并映射回来,您需要决定是在您自己的线程上调度 Parse 保存操作,还是使用 save-in-the-background 方法。

ParseObject 提供两个保存方法: save() 和 saveInBackground()。 saveInBackground() 是建议的保存方法,因为它在自己的工作线程上运行保存操作。如果您选择使用同步的 save() 方法,需要注意的是,您需要在该方法自己的工作线程上调用该方法,以避免 UI 被阻塞。

清单 9 显示的代码在后台保存 Parse 数据对象:

清单 9. 在后台保存 ParseObject

// ParseObject
ParseObject pObject = new ParseObject("ExampleObject");
pObject.put("myNumber", number);
pObject.put("myString", name);
pObject.saveInBackground(); // asynchronous, no callback 

清单 10 显示的代码使用了一个回调,在后台保存 Parse 数据对象:

清单 10. 使用回调,在后台保存

pObject.saveInBackground(new SaveCallback () {
    @Override
    public void done(ParseException ex) {
        if (ex == null) {
            isSaved = true;
        } else {
            // Failed
            isSaved = false;
        }
    }
  }); 

save...() 方法的变形包括以下几种:

使用或不使用回调,saveAllinBackground() 保存 ParseObject。

saveAll(List<ParseObject> objects) 保存 ParseObjects 的列表。

saveAllinBackground(List<ParseObject> objects) 在后台保存 ParseObjects 的列表。

saveEventually() 让您能够在未来某个时点将数据对象保存到服务器;如果 Parse 云目前不可访问,则使用该方法。

一旦在云上已成功保存了 ParseObject ,就会为对象提供惟一的 Object-ID。此 Object-ID 非常重要,因为它惟一地标识了此 ParseObject 实例。例如,您可以使用 Object-ID 确定是否已在云上成功保存该对象,以便检索或刷新给定的 Parse 对象实例和删除特定的 ParseObject。

从云中检索数据对象

这一节将探讨查询和检索 Parse 云上的数据对象的方法。您可以通过 object-ID 查询一个 ParseObject,您也可以使用属性查询一个或多个 Parse 对象。如果您已经有一个 ParseObject,那么您可以通过从服务器提取最新的值,刷新或同步其内容。我们将在以下代码片段中探讨所有这些选项。

提取 ParseObjects

为了从 Parse 云提取数据对象,使用 ParseObject 方法 fetch() 或 fetchInBackground(),如清单 11 所示:

清单 11. 提取(无条件)

// ParseObject
ParseObject pObject = new ParseObject("ExampleObject");
:
:

// Fetch the parse object unconditionally
try {
    pObject.fetch();
} catch (ParseException e) {
    e.printStackTrace();
}

// Fetch the parse object unconditionally, with Callback
pObject.fetchInBackground(new GetCallback() {
    @Override
    public void done(ParseObject obj, ParseException ex) {
        if (ex == null) {
            // Success
        } else {
            // Failed
        }            
    }
}); 

您也可以仅在需要时提取数据,例如,提取一个相关 Parse 对象的 Parse 对象,如清单 12:

清单 12. 根据需要进行提取

ParseObject po = new ParseObject("ExampleObject");
:

ParseObject po2 = po.getParseObject("key");

// Fetch only if needed
try {
    po2.fetchIfNeeded();
} catch (ParseException e) {
    e.printStackTrace();
} 

另一个选项是在后台根据需要执行提取操作,并使用一个回调。为此,可以使用 Parse 对象的 fetchIfNeededInBackground(GetCallback callback) 方法。

在某些情况下,您需要无条件地或仅在需要时一次提取一个 Parse 对象集合。ParseObject 为此提供了一组静态方法,每个方法都将一个 Parse 对象的列表作为输入,然后返回一个 Parse 对象的列表:

fetchAll(List<ParseObject> objects)

fetchAllIfNeeded(List<ParseObject> objects)

fetchAllIfNeededInBackground(List<ParseObject> objects, FindCallback callback)

fetchAllInBackground(List<ParseObject> objects, FindCallback callback)

在云上查询数据对象

希望您现在已看到 Parse API 的强大,但是别着急,它还有更多功能!除了提取数据对象之外,Parse 还可以让您使用 object-ID 或属性来查询数据对象。要从 Parse 云查询一个数据对象,可以使用 ParseQuery。您可以使用 ParseQuery 进行基本的和复杂的数据查询,接收给定的对象或匹配对象的 List。

清单 13 显示了如何在一个后台线程中使用给定 object-ID 从服务器检索特定的 Parse 对象:

清单 13. 使用 ParseQuery 检索给定的 ParseObject

String myID = "12345";
ParseQuery query = new ParseQuery("Players");
query.getInBackground(myID, new GetCallback() {
    @Override
    public void done(ParseObject object, ParseException e) {
        if (object != null) {
            // Get object
        } else {
            // Not found
        }
    }            
}); 

请注意,query.getInBackground() 没有使用 ParseQuery 缓存。

清单 14 显示的查询检索 Players 类的所有数据对象。(没有提供约束。)

清单 14. 对给定类的所有 ParseObjects 使用 ParseQuery

ParseQuery query = new ParseQuery("Players");
query.findInBackground(new FindCallback() {
    @Override
    public void done(List<ParseObject> players, ParseException e) {
        if (players != null) {
            // Get list of players
        } else {
            // No players
        }
    }
}); 

在清单 15 中,我使用了一个查询约束来检索一个或多个匹配的 Parse 对象;在本例中,使用了一个活动的 Players:

清单 15. 使用 ParseQuery 检索匹配的 ParseObjects

ParseQuery query = new ParseQuery("Players");
query.whereEqualTo("status", "active");
query.findInBackground(new FindCallback() {
    @Override
    public void done(List<ParseObject> players, ParseException e) {
        if (players != null) {
            // Success - players contain active players 
        } else {
            // Failed
        }
    }
}); 

ParseQuery 还提供了一个方法来获得匹配对象的计数,不需要检索对象本身,这非常有用。清单 16 展示了如何获得活动玩家的计数:

清单 16. 使用 ParseQuery 对匹配的 ParseObjects 进行计数

ParseQuery query = new ParseQuery("Players");
query.whereEqualTo("status", "active"); //remove this line to count ALL
query.countInBackground(new CountCallback() {
    @Override
    public void done(int count, ParseException e) {
        if (e == null) {
            // Success, see count variable
        } else {
            // Failed
        }
    }
}); 

ParseQuery 方法和约束

ParseQuery 支持 20 多个不同的查询约束方法,以下是一些示例:

whereMatches(String key, String regex) 查找与所提供的正则表达式相匹配的字符串值。

whereStartsWith(String key, String prefix) 查找使用所提供的字符串开头的字符串值。

whereContains(String key, String substring) 查找包含所提供的字符串的值。

whereGreaterThan(String key, Object value) 查找大于所提供的值的值。

whereWithinKilometers(String key, ParseGeoPoint point, double maxDistance) 查找点值在给定点附近,并且在给定最大距离内的对象。

查询结果可以被排序,如清单 17 所示。为了对查询结果进行排序,可以调用其中一个 query orderBy...() 方法,指定作为排序依据的字段。

清单 17. 排序查询结果

query.orderByAscending("name"); 
query.orderByDescending("name");
For example:
ParseQuery query = new ParseQuery("Players");
query.whereEqualTo("status", "active");
query.orderByAscending("lastName"); // By lastname ascending order
query.findInBackground(new FindCallback() {
    @Override
  public void done(List<ParseObject> players, ParseException e) {
    if (players != null) {
      // Success - players contain active players
    } else {
      // Failed
                          } 
             }	
});

另外,需要注意的是,ParseQuery 结果被缓存。您可以根据应用程序的需求,采用不同的方式设置查询缓存策略。目前支持以下缓存策略:

IGNORE_CACHE:不使用缓存,这是默认的缓存策略。

CACHE_ONLY:只从缓存加载。如果没有已缓存的结果,将会有一个 ParseException。

NETWORK_ONLY:始终从网络加载,但将结果保存到缓存。

CACHE_ELSE_NETWORK:首先查找缓存。如果失败,则从网络加载。如果缓存与网络均不成功,结果将是 ParseException。

NETWORK_ELSE_CACHE:首先查找网络。如果失败,则从缓存加载。如果缓存与网络均不成功,结果将是 ParseException。

CACHE_THEN_NETWORK:首先查找缓存。如果失败,则从网络加载。请注意,FindCallback 实际上被调用了两次:第一次包括缓存的结果,然后包括网络结果。该策略只能与 findInBackground 异步使用。

设置缓存策略很简单。设置之前,首先要调用 find...() 方法,如清单 18 所示:

清单 18. 管理 CachePolicy

ParseQuery query = new ParseQuery("Players");
query.setCachePolicy(ParseQuery.CachePolicy.NETWORK_ELSE_CACHE);
query.findInBackground(new FindCallback() {
    @Override
    public void done(List<ParseObject> players, ParseException e) {
        if (e == null) {
            // Success - players contain active players 
        } else {
            // Failed
        }
    }
}); 

ParseQuery 类提供了查询存储在云中的数据对象所需的所有方法。有了 ParseQuery,就可以指定各种查询约束,对匹配的数据对象进行计数,设置限制,跳过数据对象,排序,清除缓存等。

删除数据对象

从 Parse 云删除数据对象也很简单。如果您已经有了对象实例,那么您就可以通过调用ParseObject 的 delete() 或 deleteInBackground() 方法,从云中删除现有的 ParseObject。这两个方法如清单 19 所示:

清单 19. 从云中删除 Parse 对象

parseObject.delete();
parseObject.deleteInBackground(); 

如您所料,delete() 是一个阻塞调用,这意味着您需要负责适当地在自己的工作线程上调用它。或者可以让 Parse 使用带有或不带有回调的 deleteInBackground() 方法来负责线程。

使用文件

至此,我们一直在使用 Parse 对象和 Parse 查询。我还向您介绍了 Parse 用户、ACL 和角色。最后,我将示范如何在 Parse 中使用文件的读、写和保存函数。

回想一下,您可以在 ParseObject 中存储原始 byte[] 数据,这对于小规模数剧来说是没问题的。但在存储较大的项目(如图像或文档)时,应改使用 Parse Files。

Parse 云中的文件使用 ParseFile 来表示,它提供获得文件名、URL、文件数据(假设数据可用)的方法。您也可以下载和上传文件,并访问其他一些辅助方法。

文件数据以 byte[] 语法表示。请注意,目前,给定的文件不得超过 10MB。在命名文件时,Parse 库需要避免潜在的名称冲突,提供文件扩展名可以帮助 Parse 处理文件的内容。

清单 20 演示了保存一个 JPG 文件:

清单 20. 保存一个 ParseFile

// Save image file
Drawable drawable = ...;
Bitmap bitmap = (Bitmap)((BitmapDrawable) drawable).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] data = stream.toByteArray();                
ParseFile imageFile = new ParseFile("image.jpg", data);
imageFile.saveInBackground(); 

清单 20 中最初的语句将位图转换成一个byte[]。然后,使用 ParseFile saveInBackground() 方法保存 byte[],这与将 ParseObject 保存在服务器上的方法类似。

一旦将文件保存到 Parse 上,必须将它与 ParseOject 关联(放入 ParseOject)。换句话说,Parse 文件不是真正的独立对象,并且为了以后检索和使用,必须将它关联到给定的 ParseObject 实例。这种局限可能会在未来版本的 Parse 中进行处理。清单 21 将图像文件关联到一个 Player Parse 对象:

清单 21. 关联 ParseFile 与 ParseObject

// Associate image with Parse object
ParseObject po = new ParseObject("Players");
po.put("name", "eortiz");
po.put("photo", imageFile);
po.saveInBackground(); 

我已将文件关联到数据对象,然后使用 saveInBackgroud() 方法将对象保存在服务器上,我在前面已讨论过这一点。

清单 22 显示了如何检索与数据对象有关联的文件:

清单 22. 检索 ParseFile

// Retrieving the file 
ParseFile imageFile2 = (ParseFile)po.get("photo");
imageFile2.getDataInBackground(new GetDataCallback() {
  public void done(byte[] data, ParseException e) {
    if (data != null) {
      // Success; data has the file
    } else {
      // Failed
    }
  }
}); 

从 Parse 对象收到 ParseFile 引用之后,我调用 getDataInBackground() 从服务器中检索 ParseFile。请注意,我使用了回调 GetDataCallback 来检索 Parse 文件,并没有使用 GetCallback,后者用于通过 ParseQuery 检索 Parse 对象。

结束语

Parse API 非常全面,包括访问移动服务的类,比如推送通知,使用地理数据,集成社交媒体平台等。在本文中,我通过介绍 Parse API 实现数据和文件的云存储,简单介绍了如何使用 Parse。我们还了解了如何在 Parse 云中存储和操纵 Parse 用户、数据对象、文件和 ACL,这是进一步探索云平台进行移动开发的良好基础。


 
分享到
 
 


专家视角看IT与架构
软件架构设计
面向服务体系架构和业务组件的思考
人人网移动开发架构
架构腐化之谜
谈平台即服务PaaS
更多...   
相关培训课程

云计算原理与应用
Windows Azure 云计算应用