Android下使用googleapi-client-java操作google calendar

刷机又把数据刷没了,网络时代,数据还是和服务器同步好了。

从 http://code.google.com/p/google-api-java-client 下载了库和sample,因为此库还在更新中,文档不是很全,自己摸索了一遍,总结在下面。

访问google服务的流程:
1.使用用户名、密码获取授权字符串。
2.使用授权字符串以及其他的参数发送http请求。
3.解析响应,并处理结果。

在Android平台上第一步可以通过AccountManager服务来实现,只要有绑定的Gmail账户,就无须再次输入密码。
第二步以及第三步可以利用Google封装好的库来实现,有两个官方库可用,一个是gdata-client-java,一个是googleapi-client-java,后者是新出的库,具体该采用哪个库可以参见其Wiki根据自己的情况选择。
此处是新项目,无需考虑旧代码,所以选择了后者。

具体的流程:
1.创建一个处理HTTP发送接收的对象,通过GoogleTransport类(库提供)的静态函数create()来创建:
Java代码
mTransport = GoogleTransport.create(); 

HTTP发送数据需要HTTP Header,GoogleTransport对象内已经自己生成了默认的Header,但是有些成员需要设置数据。
1.1 数据交换协议版本号
Java代码
headers.gdataVersion = "2"; //Google data version 

1.2 应用名,ap可以设置为自己的唯一字符串即可
Java代码
headers.setApplicationName(getString(R.string.app_name)); 

1.3 创建Parser用来把HTTP传送的数据转换为对象。
Java代码
AtomParser parser = new AtomParser();   //parse data from http data  
parser.namespaceDictionary = Namespace.DICTIONARY;  
mTransport.addParser(parser); 

其中AtomParser类是库提供的,但是parser的DICTIONARY要用户定义,用来将缩写转换到url。一般可直接使用从example中复制的:
Java代码
public class Namespace {   
    public static final XmlNamespaceDictionary DICTIONARY = new XmlNamespaceDictionary();   
    static {   
    Map<String, String> map = DICTIONARY.namespaceAliasToUriMap;   
    map.put("", "http://www.w3.org/2005/Atom");   
    map.put("atom", "http://www.w3.org/2005/Atom");   
    map.put("batch", "http://schemas.google.com/gdata/batch");   
    map.put("gAcl", "http://schemas.google.com/acl/2007");   
    map.put("gCal", "http://schemas.google.com/gCal/2005");   
    map.put("gd", "http://schemas.google.com/g/2005");   
    map.put("georss", "http://www.georss.org/georss");   
    map.put("gml", "http://www.opengis.net/gml");   
    map.put("openSearch", "http://a9.com/-/spec/opensearch/1.1/");   
    map.put("xml", "http://www.w3.org/XML/1998/namespace");   
    }  

2.获取授权字符串,大部分操作都需要在HTTP请求中包含此字符串,这个字符串是和用户名相关的。
2.1获取账户对象:
在Android中可以使用AccountManager来获取用户列表,然后由用户选择一个。
Java代码
final AccountManager manager = AccountManager.get(this);    //获取AccountManger实例   
final Account[] accounts = manager.getAccountsByType("com.google"); //获取所有的google账户对象  

Account对象的name成员保存了帐户名字符串,可以将这些名称形成一个列表显示到对话框中供用户选择。
当用户选择后,就可使用Account对象作为参数获取授权字符串了。
2.2获取授权字符串:
Java代码
Bundle bundle = manager.getAuthToken(account, "cl", true, null, null).getResult();  

通过AccountManager来获取授权字符串,account是账户对象,"cl"是google约定的获取calendar服务授权时的类型。
因为此步操作要进行网络通信,可能会阻塞,所以强制要求启动一个线程来进行,如果在UI线程调用会有异常出来。
另外函数返回后,结果中未必就有授权字符串,有可能会返回需要与用户交互的Intent,比如询问用户是否允许获取授权,代码要进行处理。
Java代码
if (bundle.containsKey(AccountManager.KEY_INTENT)) {   
    //需要和用户交互。使用此intent启动activity。   
    Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);   
    int flags = intent.getFlags();   
    flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;    //清除新任务标志   
    intent.setFlags(flags);   
    startActivityForResult(intent, REQUEST_AUTHENTICATE);   
} else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {   
    //得到了key,执行login   
    authenticatedClientLogin(bundle.getString(AccountManager.KEY_AUTHTOKEN));   
}  

此外还要处理异常,当授权过期时,那么还要通过
manager.invalidateAuthToken("com.google", this.mAuthToken);来清除cache中的授权字符串。

3.与服务器进行数据交互:
交互是通过HTTP请求及响应来进行;数据被通过xml被打包成Atom协议(http://www.ietf.org/rfc/rfc4287.txt)格式交互。
按照Atom协议格式:
     Google的日历数据是以feed为根节点打包返回,每个feed又可以包含多个entry,每个entry是一个日历。
     同样日历中的事件也是以feed为根节点打包返回,每个feed包含多个entry,每个entry就是日历中的一个事件。
     库函数对交互过程进行了封装。包括:将函数请求转换成HTTP请求以及将返回的XML数据解析到对象。
     为了能够将XML数据解析到对象,需要定义一个数据模型,这里面包含日历feed,日历entry,事件feed,事件entry等对象定义。
     而这个对象是由用户定义的,那么库函数怎么知道将XML数据解析后保存到哪个数据成员呢?
     跟踪了代码简单总结其机制大概为:
     XML中的数据每个成员都有名字,比如<entry><link>等,按照这个名字通过Java的反射机制来查找对象中的成员名字,如果名字相符,那么就保存到此成员变量。
     而成员的名字如何传递给JVM呢?则是通过了Java annotation机制来实现的,在函数库中定义了一个annotation,名字为@key,
Java代码
@Key("content")   
public String var;  

@Key("content")
public String var;

表示将变量var命名为content,库函数中可以获取到此附加信息,那么就会将XML中contentn成员的值解析出来保存到var中。
至于解析时如何将字符串转换成所需要的值,库函数会判断var的类型,来调用合适的生成函数。
3.1Feed基类:
   根据ATOM约定,必然包含:
  <id>、<title>、<updated>
  一般还会包含多个<link>,所以Feed基类的成员定义如下:
Java代码
@Key("id")   
public String id;   
@Key("title")   
public String title;   
@Key("updated")   
public String updated;   
@Key("link")   
public List<Link> links;  

@Key("id")
public String id;
@Key("title")
public String title;
@Key("updated")
public String updated;
@Key("link")
public List<Link> links;

如果不关注id、title、update等信息,可以不定义这些成员。
Link也是自定义的一个类,用来存储<link>节点的信息,其成员定义如下:
Java代码
@Key("@href")   
public String href;   
@Key("@rel")   
public String rel;  

@Key("@href")
public String href;
@Key("@rel")
public String rel;

在Feed中还会包含<entry>,对于Calendar feed每个entry就是一个日历,对于Event feed每个entry就是一个事件。
由于两类entry的成员不一样,所以此处并没有包含entry成员,而是在扩展的Feed类中定义。
3.2Entry基类:
  必然包含:<id>、<title>、<updated>
  可能包含最多一个<content>;
  可能包含最多一个<summary>
  一般还会包含多个<link>;
  所以Entry基类的成员定义如下:
 
Java代码
@Key  
public String summary;   
@Key  
public String content;   
@Key  
public String id;   
@Key  
public String title;   
@Key  
public String updated;     
@Key("link")   
public List<Link> links;     

  @Key
  public String summary;
  @Key
  public String content;
  @Key
  public String id;
  @Key
  public String title;
  @Key
  public String updated;  
  @Key("link")
  public List<Link> links;	


3.3CalendarFeed类:
Java代码
@Key("entry")   
public List<CalendarEntry> calendars = new ArrayList<CalendarEntry>();  

@Key("entry")
public List<CalendarEntry> calendars = new ArrayList<CalendarEntry>();

扩展出一个保存entry的列表,列表中的每项是一个entry。
3.4EventFeed类:
Java代码
@Key("entry")   
public List<EventEntry> events = new ArrayList<EventEntry>();  

@Key("entry")
public List<EventEntry> events = new ArrayList<EventEntry>();

3.5CalendarEntry类:
  只扩展了一些操作接口。
3.6EvnentEntry类:
Java代码
@Key("gd:when")   
public When when;  

@Key("gd:when")
public When when;

扩展了一个时间属性。

4.获取日历列表并显示:
原理:向https://www.google.com/calendar/feeds/allcalendars/full发送GET请求然后解析响应。
发送时牵扯到HTTP Header生成,重定向以及把数据解析成为Java对象等操作,这些库函数都提供了接口可用。
在googleapi-client-api的例子中提供了一个model,此model对库的接口针对calendar又进行了一次封装,可以直接复制到代码中使用。
4.1 CalendarUrl类:继承自GoogleUrl,扩展了一些获取url的函数。
CalendarUrl url = CalendarUrl.forAllCalendarsFeed(); //构造获取feed的url串。如果要删除日历,那么这儿要换成forOwnCalendarsFeed(),否则删除时会返回http 500错误。
4.2 CalendarFeed类:定义了一个成员变量,是calendar的列表,通过Java Annotation,库函数可以通过反射机制取到这个变量,并将解析后的CalendarEntry填充到此列表中来。
CalendarFeed feed = CalendarFeed.executeGet(mTransport, url); //从网络获取数据并解析成feed对象。
4.3 在列表中显示:
calendarNames = new String[numCalendars]; 
for (int i = 0; i < numCalendars; i++) { 
calendarNames[i] = calendars.get(i).title; //更新数组 
} 
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, calendarNames)); //更新列表。 

5.获取某个日历的事件列表并显示:
向日历所保存的事件url发送get请求。
CalendarEntry calendar = mCalendars.get(position); 
CalendarUrl url = new CalendarUrl(calendar.getEventFeedLink()); //获得event url 
EventFeed efeed = EventFeed.executeGet(mTransport, url); //发送请求,从网络获得event feed 

在EventFeed中保存了所有entry列表,可以通过EvnentFeed.getEntries()来获得。

6.日历操作:
6.1 添加日历:
原理:向https://www.google.com/calendar/feeds/owncalendars/full发送数据。
获取目标url:
CalendarUrl url = CalendarUrl.forOwnCalendarsFeed(); 

创建新的日历对象:
CalendarEntry ncalendar = new CalendarEntry(); 

设置标题:
ncalendar.title = "Calendar " + new DateTime(new Date()); 

执行插入操作:
ncalendar.executeInsert(mTransport, url); 

6.2 删除日历:
原理:向calendar entry的编辑url发送HTTP DELETE请求。这个编辑url是服务器返回并被解析后保存在calendar对象中的。
获取要删除的日历对象,执行删除操作。
calendar.executeDelete(mTransport);
6.3 修改日历:
原理:向calendar entry的编辑url发送HTTP PATCH请求。
从原日历clone一个对象,然后执行patch函数。
CalendarEntry patchedCalendar = calendar.clone(); 
//modify 
patchedCalendar.title = calendar.title + " UPDATED " + new DateTime(new Date()); 
//更新到网络 
patchedCalendar.executePatchRelativeToOriginal(mTransport, calendar); 

6.4 添加事件:
原理:向所属的calendar的event feed url发送数据。
event = new EventEntry(); 
event.title = "title"; 
When when = new When(); 
when.startTime = new DateTime(new Data()); 
event.when = when; 
CalendarUrl url = new CalendarUrl(calendar.getEventFeedLink()); 
event.executeInsert(mTransport, url); 

6.5 删除事件:
原理:发送DELETE请求。
event.executeDelete(mTransport);
需要注意的是执行delete时我收到的结果是403,始终不能成功删除。
以“google api client delete calendar event forbidden"搜索发现此问题别人也有遇到,查阅资料:
http://code.google.com/apis/gdata/docs/2.0/basics.html
在Deleting an entry一节提到:
To do an unconditional delete, set the following HTTP header:If-Match: *
所以需要修改executeDelete函数,补充一行:request.headers.ifMatch = "*";
正常应该是ifMatch=etag,etag的作用参见Updating an entry一节:
You also have to specify the original entry's ETag, to ensure that you don't overwrite anyone else's changes.
以上理解未必正确,如有指教可直接回复。
6.6 修改事件:
event.executePatchRelativeToOriginal(mTransport, event); 

同样也要修改一下executePatchRelativeToOriginal函数,增加:
request.headers.ifMatch = "*"; 

对于删除Calendar以及修改Calendar这一行加不加没影响。

仓促成文,附件的例子也有一些冗余代码未修改,日后再加工。

本文来自“http://suyuening.iteye.com/”博客,向博主学习。

你可能感兴趣的:(java,android,网络协议,Google,网络应用)