我爱编程

android PPAPI(基于okHttp的网络请求框架)

2018-04-12  本文已影响172人  df661d1e16ba

对于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只是后半段。
现在我们就有一些问题:

定义一个通用的父接口类

记得前面这个类继承自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还支持:

上一篇 下一篇

猜你喜欢

热点阅读