刷机又把数据刷没了,网络时代,数据还是和服务器同步好了。
从 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/”博客,向博主学习。