2013年3月23日星期六

【Android Training - 15】云同步的实现 [Lesson 1 - 使用App Engine进行同步]

 

 http://blog.csdn.net/kesenhoo/article/details/7487026

Syncing with App Engine [使用App Engine进行同步]

  • 写一个能够同步到云端的app是具有挑战性的。那存在许多细节需要处理,例如服务端 身份验证,客户端身份验证,分享数据的模块,还有API。简化这些操作的一个方法是使用Google Plugin for Eclipse,这个插件帮你垂直整合处理了那些Android系统与App Engine程序交互的操作。这一课会介绍如何创建那样一个项目。
  • [关于App Engine,请参考:https://developers.google.com/appengine/docs/whatisgoogleappengine?hl=zh-CN]
  • 下面会介绍:
    • 建立Android与App engine apps能够交互的程序。

    • 利用Cloud to Device Messaging(C2DM)的优势,这样app就不需要使用轮询机制去更新。[关于C2DM的详情请参考前面课程与http://code.google.com/intl/zh-CN/android/c2dm/)]
  • 这一课仅仅专注于本地开发测试使用,并不涉及程序发布(例如,发布你的App Engine,发布你的Android程序到Market),程序发布等知识点会在其他课程里涉及到。

Prepare Your Environment [准备你的开发环境]

如果你想继续下面的课程步骤,你必须依据下面所述搭建好你的开发环境:

Create Your Projects [创建你的项目]

  • 在你安装完Google Plugin for Eclipse之后,请注意在创建一个新的Eclipse项目的时候会存在一种新的Android项目选项:App Engine Connected Android Project(在Google项目分类下)。安装向导会提示你输入账户验证信息,这个账户就是之前提到的你在C2DM上注册的时候使用的。(请注意不要输错了账户类型)
  • 一 旦你创建好之后,你会在workspace看见两个有2个项目:一个Android程序与一个App Engine程序。好吧!那两个程序具备了需要实现的所有功能,安装向导创建了sample程序,它可以允许你使用AccountManager来验证 Android设备与App Engine的交互。为了便于后续测试,请做下面的操作:
  • 请确保你存在2.2以上的AVD,右击Eclipse中的Android项目,选择Debug As>Local App Engine Connected Android Application。这样程序就能够测试C2DM的功能(Google Play是这一类程序的典型代表).它也启动了一个App Engine的local instance,里面包含了你的程序。

Create the Data Layer [创建数据层]

  • 因为上面已经创建了一个完整功能的sample程序。下面应该学习开始修改那些代码来创建你自己的程序。
  • 首先,创建数据模块,它定义了在App Engine与Android app之间共享的数据。打开App Engine项目的文件夹,定位到 (yourApp)-AppEngine > src > (yourapp) > server。创建一个新的类,它包含了那些你想要存储到云端的数据。下面是一段sample code:
  1. package com.cloudtasks.server;  
  2.   
  3. import javax.persistence.*;  
  4.   
  5. @Entity  
  6. public class Task {  
  7.   
  8.     private String emailAddress;  
  9.     private String name;  
  10.     private String userId;  
  11.     private String note;  
  12.   
  13.     @Id  
  14.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  15.     private Long id;  
  16.   
  17.     public Task() {  
  18.     }  
  19.   
  20.     public String getEmailAddress() {  
  21.         return this.emailAddress;  
  22.     }  
  23.   
  24.     public Long getId() {  
  25.         return this.id;  
  26.     }  
  27.     ...  
  28. }  
  • 请注意注释的使用:EntityId GeneratedValue 都是来自Java Persistence API. 这些注释都是必备的, Entity需要注释在类声明的上面, 这表明这个类在你的数据层代表了一个Entity.Id 与GeneratedValue分别表明了寻找这个类的id与这个id是如何形成的(在上面的示例中GenerationType.IDENTITY意味这是由DB生成的). 你可以参考Using JPA with App Engine来查看更多关于资料。
  • 一旦你完成了所有的数据实体类的创建, 你需要创建Android与App Engine程序之间交互的方法. 这种交互的方法可以通过创建一个Remote Procedure Call (RPC)服务来开启。通常的是,这包含了许多一成不变的单调的代码. 幸运的是, 有一种简单的办法来完成这个操作! 在你的App Engine源代码文件夹下右击,选择New > Other ,再选择Google > RPC Service. 这个时候会出现向导, 陈列出所有你在上一步骤创建的实例,它是通过在源代码文件夹中去查找具有@Entity的方法来实现的. 这样出来的代码非常整洁,之后点击Finish,向导会创建一个Service类,它包含了Create, Retrieve, Update and Delete (CRUD)实例的操作.

Create the Persistence Layer [创建持久层]

  • 持久层是一个你的程序数据能够存放long-term的地方。为了编写你的持久层,你有一些选择,这取决于你想存储哪些类型的数据. 一些由Google管理的选择包含在Google Storage for Developers与App Engine's built-in Datastore. 下面是一个sample code,使用DataStore的代码.
  • 在你的com.cloudtasks.server下创建一个类用来处理持久层的输入与输出. 为了访问这些数据,使用PersistenceManager. 你可以使用在com.google.android.c2dm.server.PMF下的PMF类生成这个类的的一个实例,然后使用它来执行基本的CRUD操作:
  1. /** 
  2. * Remove this object from the data store. 
  3. */  
  4. public void delete(Long id) {  
  5.     PersistenceManager pm = PMF.get().getPersistenceManager();  
  6.     try {  
  7.         Task item = pm.getObjectById(Task.class, id);  
  8.         pm.deletePersistent(item);  
  9.     } finally {  
  10.         pm.close();  
  11.     }  
  12. }  
  • 你也可以使用Query对象从你的Datastore来retrieve数据
  1. public Task find(Long id) {  
  2.     if (id == null) {  
  3.         return null;  
  4.     }  
  5.   
  6.     PersistenceManager pm = PMF.get().getPersistenceManager();  
  7.     try {  
  8.         Query query = pm.newQuery("select from " + Task.class.getName()  
  9.         + " where id==" + id.toString() + " && emailAddress=='" + getUserEmail() + "'");  
  10.         List list = (List) query.execute();  
  11.         return list.size() == 0 ? null : list.get(0);  
  12.     } catch (RuntimeException e) {  
  13.         System.out.println(e);  
  14.         throw e;  
  15.     } finally {  
  16.         pm.close();  
  17.     }  
  18. }  
  • 一个好的例子,帮你encapsulate了持久层,请参考Cloud Tasks app里面的DataStore类.

Query and Update from the Android App [从Android App查询与更新]

  • 为了保持与App Engine程序的同步,你的Android程序需要知道下面两件事情:从云端拉取数据与发送数据到云端。大部分这类操作已经由上面提到的插件生成了,但是你需要自己编写UI来呈现那些操作.
  • 插件生成的sample code显示了一些重要的特征:
    • 首先,我们需要删除样本里面的Activity.java中的setHelloWorldScreenContent()的方法,替换的是与实际程序有关的代码。
    • 其次,所有交互的操作都是包在AsyncTask中来完成的,这样不会因为网络操作而卡到UI thread.
    • 最后,它给出了一个简单的模板演示如何访问云端的数据, 使用RequestFactory 来操作,它由Eclipse plugin提供支持。
  • 关于实例,如果你的云端数据模型包含了一个叫做Task的对象,这个对象会在你生成RPC layer的时候自动为你创建的一个TaskRequest的类, 还有一个TaskProxy来代表单独的Task. 在下面的代码中演示了请求一个所有task的列表:
  1. public void fetchTasks (Long id) {  
  2.   // Request is wrapped in an AsyncTask to avoid making a network request  
  3.   // on the UI thread.  
  4.     new AsyncTask>() {  
  5.         @Override  
  6.         protected List doInBackground(Long... arguments) {  
  7.             final List list = new ArrayList();  
  8.             MyRequestFactory factory = Util.getRequestFactory(mContext,  
  9.             MyRequestFactory.class);  
  10.             TaskRequest taskRequest = factory.taskNinjaRequest();  
  11.   
  12.             if (arguments.length == 0 || arguments[0] == -1) {  
  13.                 factory.taskRequest().queryTasks().fire(new Receiver>() {  
  14.                     @Override  
  15.                     public void onSuccess(List arg0) {  
  16.                       list.addAll(arg0);  
  17.                     }  
  18.                 });  
  19.             } else {  
  20.                 newTask = true;  
  21.                 factory.taskRequest().readTask(arguments[0]).fire(new Receiver() {  
  22.                     @Override  
  23.                     public void onSuccess(TaskProxy arg0) {  
  24.                       list.add(arg0);  
  25.                     }  
  26.                 });  
  27.             }  
  28.         return list;  
  29.     }  
  30.   
  31.     @Override  
  32.     protected void onPostExecute(List result) {  
  33.         TaskNinjaActivity.this.dump(result);  
  34.     }  
  35.   
  36.     }.execute(id);  
  37. }  
  38. ...  
  39.   
  40. public void dump (List tasks) {  
  41.     for (TaskProxy task : tasks) {  
  42.         Log.i("Task output", task.getName() + "\n" + task.getNote());  
  43.     }  
  44. }  
  • 为了创建一个新的任务并发送到云端,需要创建一个新的请求对象并使用它来创建一个proxy对象。然后proxy对象执行它的更新方法。重申,这些操作应该放在AsyncTask里面去执行,避免网络操作卡到UI Thread。下面是sample code:
  1. new AsyncTask() {  
  2.     @Override  
  3.     protected Void doInBackground(Void... arg0) {  
  4.         MyRequestFactory factory = (MyRequestFactory)  
  5.                 Util.getRequestFactory(TasksActivity.this,  
  6.                 MyRequestFactory.class);  
  7.         TaskRequest request = factory.taskRequest();  
  8.   
  9.         // Create your local proxy object, populate it  
  10.         TaskProxy task = request.create(TaskProxy.class);  
  11.         task.setName(taskName);  
  12.         task.setNote(taskDetails);  
  13.         task.setDueDate(dueDate);  
  14.   
  15.         // To the cloud!  
  16.         request.updateTask(task).fire();  
  17.         return null;  
  18.     }  
  19. }.execute();  

Configure the C2DM Server-Side [确认C2DM服务器端]

  • 为了设置C2DM的消息能够被发送到你的Android设备,回到你的App Engine代码处,打开生成RPC层的时候创建的Service类. 如果你的项目名是Foo, 这个类的名字就叫做FooService. 为每一个方法都添加一些代码,允许做adding, deleting, or updating数据的操作,这样C2DM message才能发送到用户的设备上. 下面是一段sample code:
  1. public static Task updateTask(Task task) {  
  2.     task.setEmailAddress(DataStore.getUserEmail());  
  3.     task = db.update(task);  
  4.     DataStore.sendC2DMUpdate(TaskChange.UPDATE + TaskChange.SEPARATOR + task.getId());  
  5.     return task;  
  6. }  
  7.   
  8. // Helper method.  Given a String, send it to the current user's device via C2DM.  
  9. public static void sendC2DMUpdate(String message) {  
  10.     UserService userService = UserServiceFactory.getUserService();  
  11.     User user = userService.getCurrentUser();  
  12.     ServletContext context = RequestFactoryServlet.getThreadLocalRequest().getSession().getServletContext();  
  13.     SendMessage.sendMessage(context, user.getEmail(), message);  
  14. }  
  • 在下面的示例中,一个帮助类TaskChange,创建了一些常量. 这样一个帮助类能够使得App Engine与Android App直接的交互更简单。
  1. public class TaskChange {  
  2.     public static String UPDATE = "Update";  
  3.     public static String DELETE = "Delete";  
  4.     public static String SEPARATOR = ":";  
  5. }  

Configure the C2DM Client-Side [确认C2DM的客户端]

  • 为了定义Android程序在接受到C2DM的消息的行为,打开C2DMReceiver类, 找到onMessage() 方法. 根据接受到的消息类型进行修改这个方法.
  1. //In your C2DMReceiver class  
  2.   
  3. public void notifyListener(Intent intent) {  
  4.     if (listener != null) {  
  5.         Bundle extras = intent.getExtras();  
  6.         if (extras != null) {  
  7.             String message = (String) extras.get("message");  
  8.             String[] messages = message.split(Pattern.quote(TaskChange.SEPARATOR));  
  9.             listener.onTaskUpdated(messages[0], Long.parseLong(messages[1]));  
  10.         }  
  11.     }  
  12. }  
  1. // Elsewhere in your code, wherever it makes sense to perform local updates  
  2. public void onTasksUpdated(String messageType, Long id) {  
  3.     if (messageType.equals(TaskChange.DELETE)) {  
  4.         // Delete this task from your local data store  
  5.         ...  
  6.     } else {  
  7.         // Call that monstrous Asynctask defined earlier.  
  8.         fetchTasks(id);  
  9.     }  
  10. }  
  • 一旦C2DM消息触发了本地进行更新,那么说明已经设置成功。
学习自:http://developer.android.com/training/cloudsync/aesync.html,请多指教,谢谢!
转载请注明出处:http://blog.csdn.net/kesenhoo,谢谢!

没有评论:

发表评论