对于App开发来说,网络接口访问占很大一部分比重,现在也有许多第三方的网络框架,比如afinal、xutils、volley、okHttp等。这些框架简化了网络访问的复杂度,用比较简单的方法实现http接口访问。
如果只是简单地使用这些框架,大部分逻辑实现写在调用接口的Activity中,那么随着项目越来越复杂,接口越来越多,甚至有可能对接多个后台,不同后台返回json格式都不统一,这时繁多的接口散落在各个Activity中就会很不好管理。(虽然现在都在推广Restful的规范,也有非常好用的retrofit框架,但现实中由于各种不可抗力,我们需要去对接一些不那么restful的api,甚至还有WebService soap协议)
那么如何去有效地管理app中所有用到的接口,将网络接口逻辑与Activity逻辑分离,就是这个PPAPI所解决的问题了,和retrofit一样,是基于okHttp框架实现的网络访问。目前框架发布0.1版本,持续优化中。里面参考了 Hongyang 的博客有关okHttp的使用和https的支持。
Android OkHttp完全解析 是时候来了解OkHttp了
Android Https相关完全解析 当OkHttp遇到Https
导入PPAPI到项目中
首先我们在项目中导入PPAPI库,源码Github地址:https://github.com/lawlight/ppapi。
在gradle中添加jitpack库:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
添加依赖
dependencies {
compile 'com.github.lawlight:ppapi:0.1.2'
}
使用继承自PPAPI的接口类来访问接口
主要使用到有两个类:PPAPI和PPAPIListener。
我们先来看一段代码,在一个Activity中访问一个图书查询列表的接口,接口有一个入参keyword,返回图书列表的JSON数组:
GetBooksAPI api = new GetBooksAPI(context);
api.keyword = "三国";
api.doGet(null);
对,就这三行代码,就可以实现一个通过关键字来获取图书列表接口的功能,看起来就像是访问一个java bean的实体类一样。但是现在没有处理回调,这个doGet方法中的参数类型是监听器PPAPIListener:
final GetBooksAPI api = new GetBooksAPI(context);
api.keyword = "三国";
api.doGet(new PPAPIListener() {
@Override
public void onStart() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
}
@Override
public void onSuccess(String result) {
for(Book book : api.books){
textView.append(book.name + book.author);
}
}
@Override
public void onFail(int errCode, String errMessage) {
Toast.makeText(context, errMessage, Toast.LENGTH_SHORT).show();
}
});
这个PPAPIListener中有4个回调方法:onStart、onFinish、onSuccess、onFail。回调是在UI线程执行,所以在对应的方法里面写上要执行的UI命令即可,这里是简单控制一个ProgressBar的显示隐藏,将接口获取的内容输出到TextView中。
可以看到,给入参api.keyword赋值,然后在onSuccess中取出接口解析好的List<Book>类型的api.books。
Book类是个简单的bean类:
public class Book {
public String id;
public String name;
public String author;
}
那么我们接下来看一下这个GetBooksAPI类里面是什么:
public class GetBooksAPI extends BasicAPI {
public String keyword;
List<Book> books = new ArrayList<>();
public GetBooksAPI(Context context) {
super(context);
setCacheTime(10);
}
@Override
protected void putInputs() throws Exception {
super.putInputs();
putParam("keyword", keyword);
}
@Override
protected void parseOutput(String output) throws Exception {
books.clear();
JSONArray array = new JSONArray(output);
for(int i = 0 ; i< array.length(); i++){
JSONObject o = array.getJSONObject(i);
Book book = new Book();
book.name = o.optString("name");
book.author = o.optString("author");
book.price = o.optString("price");
books.add(book);
}
}
@Override
public String getUrl() {
return "/api/books";
}
}
这个类继承自一个BasicAPI的类,一会再说,看到类内部除了定义了public的入参keyword和出参books以外,重写了几个父类的方法。
putInputs方法中可以看到,我们调用了一个putParam方法,将public对象keyword以“keyword”的key值put到了请求参数中。因为我们是get请求,所以这里也可以手动添加一个url encode:
改成putParam("keyword", URLEncoder.encode(keyword));
也就是说我们可以在putInputs方法中,来做一些处理以适配接口的需要,比如url encode、base64、接口key值的适配(有些接口提供的入参名的可读性不怎么好,在这里可以做一些转换的处理)而对于调用他的Activity,我们只需要提供一个简单的keyword变量即可。
接下来的parseOutput方法中,参数中的output就是接口返回的json数据,在这里我做了一个手动的解析,是为了更好地展示出数据的内容,在实际使用时可以通过如gson、fastJSON等json库直接实现json转java对象的序列化操作。当然,有的时候,接口返回的json对象可能会很复杂,无法自动序列化,那我们就可以在这个重写的方法中进行解析了。这里解析了返回的json列表,将数据保存到了public List<Book>对象books中,这样在Activity中就无需关心接口返回的数据格式,而直接针对这个books对象操作即可。虽然也可以在onSuccess方法中解析output,但是极不推荐,把复杂的解析过程封装在API类中,对外只展示出解析好的对象,有利于降低与Activity的耦合度。
另外在这个类的构造方法中,填写了一个setCacheTime(10),可以对此接口数据进行10秒的数据缓存。这个功能只针对GET接口有效。
最后这个getUrl顾名思义就是这个接口的url了,可以看到这个url只是后半段。
现在我们就有一些问题:
- URL前半段是在哪里定义?
- 返回的json一般会有一个外层通用的格式,比如返回码,错误信息等,这里的解析写在哪里?
- 如果我有一个全局的参数,要求项目中每个接口访问时都携带,需要怎么做?
定义一个通用的父接口类
记得前面这个类继承自BasicAPI吗?我们看看这个类:
public abstract class BasicAPI extends PPAPI {
public DemoBasicAPI(Context context) {
super(context);
}
@Override
public String getHostname() {
if(BuildConfig.DEBUG){
return "http://www.google.com/debug";
}else{
return "http://www.google.com/release";
}
}
@Override
protected void putInputs() throws Exception {
putParam("token", "123456");
}
@Override
protected String filterOutput(String output) throws Exception {
JSONObject jsonObject = new JSONObject(output);
if(jsonObject.getInt("code") == 200){
return jsonObject.getString("data");
}else{
onFail(jsonObject.getInt("code"), jsonObject.getString("message"));
return null;
}
}
}
这个类就直接继承自PPAPI类了,是一个抽象类,这个类的作用是对这个项目的所有接口进行一些通用逻辑的处理,作为这整个项目所有接口的父类来使用。重写的方法中:
getHostname方法就是设置接口域名,这里随便填写了两个地址。一般可以根据app的debug或release版本做一些正式测试库切换的逻辑。接口调用的时候完整的url其实就是这里的hostname拼接上getUrl()返回的内容了。
putInput和GetBooksAPI类中的putInput一样,这里作为项目父类,put了一个全局的token参数,而在GetBooksAPI中调用了super.putInputs(),所以所有的接口访问时,就都会携带这个全局参数。
接下来的filterOutput方法,会在parseOutput之前调用,这里判断了一下json中的code为200代表访问成功,然后把json中的data字段返回,这样子类的parseOuput中的output入参就是纯数据部分的json,无需再去解析code==200的逻辑了,如果访问有问题,调用了onFail方法,会中断接口流程,调用listener的onFail方法。
在parseOutput中也可以随时调用onFail方法来中断接口的访问。
以上我们介绍了GET类型的接口,接下来我们再来看一个POST类型的接口,更新book的接口,调用时的代码是这样的:
UpdateBookAPI updateBookAPI = new UpdateBookAPI(context);
updateBookAPI.bookId = "12";
updateBookAPI.author = "罗贯中";
updateBookAPI.name = "三国演义";
updateBookAPI.doPost(new PPAPIListener() {
@Override
public void onStart() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
}
@Override
public void onSuccess(String result) {
Toast.makeText(context, "操作成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onFail(int errCode, String errMessage) {
Toast.makeText(context, errMessage, Toast.LENGTH_SHORT).show();
}
});
可以看到除了doGet改为了doPost外,其他的基本没有什么差别,除了doGet和doPost外,还可以使用doPut和doDelete来进行http请求。
看看这个类的实现:
public class UpdateBookAPI extends BasicAPI {
public String bookId;
public String name;
public String author;
public UpdateBookAPI(Context context) {
super(context);
}
@Override
protected void putInputs() throws Exception {
super.putInputs();
putParam("name", name);
putParam("author", author);
putHeader("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
}
@Override
public String getUrl() {
return "/api/books/"+bookId;
}
@Override
protected void parseOutput(String output) throws Exception {
//什么也不用做了~~
}
}
基本和GetBooksAPI是一样的,不过注意这里这个id的入参实际上是拼到了url中,但是对于Activity,不需要去关注每个参数应该是body还是url query了,这些逻辑都交给了API类来做。
另外还添加了一个Authorization的header实现验证,这里可以用putHeader方法来设置http的header,默认情况下,PPAPI会传默认的User-Agent,传输一些app的名称、版本号等基本信息。
我们还发现这里没有在parseOutput中做任何事情,因为这种POST的提交型接口,返回了通用的json外层数据,已经在Basic的filterOutput方法中处理过了,如果出现问题,会在上一步直接调用onFail,所以在子类的这里我们就不需要做任何处理了,很爽。
另外注意一下,putInputs、filterOutput、parseOutput方法,都有throws Exception,这里如果抛出异常,PPAPI会调用onFail。如果不希望由PPAPI处理异常,就在这里try catch吧。
接下来,我们就可以把项目中所有的接口都定义为继承自BasicAPI的类,每一个接口都对应一个java类,可以像使用java bean一样去使用这些接口类,可以有效地对接口进行统一管理、复用和项目分工。如果app需要对接多个不同返回结构的后台,那么只需多定义几个BasicAPI即可。
其他支持
以上就是PPAPI的基本用法了,除此之外,PPAPI还支持:
- 测试模式,setTest(true)之后,请求接口时会不进行网络访问,直接调用parseOutput方法,并模拟500ms的访问时间。可以在后台接口还没提供时,自己填充一些测试数据来做离线测试。
- 缓存,setCacheTime(int time)可以指定这个接口数据的缓存时间,在缓存时间内再次调用会先读取缓存的数据来优化网络访问。配合clearCache()方法清除缓存使用。缓存只对GET方式请求的接口有效。
- https支持
- 文件上传支持,在putParams中,如果value是一个File对象的话,会切换至multipart form形式的请求来进行文件上传。
网友评论