美文网首页我爱编程
android PPAPI(基于okHttp的网络请求框架)

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

作者: df661d1e16ba | 来源:发表于2018-04-12 13:42 被阅读172次

    对于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形式的请求来进行文件上传。

    相关文章

      网友评论

        本文标题:android PPAPI(基于okHttp的网络请求框架)

        本文链接:https://www.haomeiwen.com/subject/okqtkftx.html