Retrofit知道这些就差不多了

引言

最近项目没什么进度,一直在学习IOS跟复习PHP。Android这边也没怎么搞了。不过这东西可不能拉下啊。二话不说赶紧上Github学习下新的框架。就看到了两个比较火的RXJavaRetrofit。于是就开始啃一下Retrofit文档。文档可谓是短小精悍。。。一开始硬是没看懂。下面就把自己看懂的部分说一下吧.

Retrofit到底是个啥

Retrofit是一套RESTful架构的Android(Java)客户端实现,基于注解,提供JSON to POJO(Plain Ordinary Java Object,简单Java对象),POJO to JSON,网络请求(POST,GET,PUT,DELETE等)封装。
从别人那里偷来的定义,看清来应该已经足够清晰了吧?至于什么是RESTful?不要问我,因为我对这个概念也比较模糊。。。

没时间解释了,赶紧上车

我们这里跟官方文档不一样,我们先获取最简单的网络数据吧。就是获取String类型的数据。
然后我们再去结合Gson去直接把返回的数据转成模型

环境

  • Android Studio 2.1.1
  • OSX 10.11.6

第一步:导入库

compile 'com.squareup.retrofit2:retrofit:2.1.0'

没啥好说的

第二部:编写服务接口

刚开始看这种做法有点奇葩,可能是本人的见识不足,勿喷。。。。不过后来发现这样写是真的屌。。自己慢慢去体会。

/**
 * Created by August on 16/7/29.
 * 获取Github首页的HTML源码
 */
public interface GithubService {
    @GET("/")
    Call<String> getGitHubIndexSource();
}

上面只是获取HTML源码,别想太多。。。。

  • @GET("/") :使用GET方法,到时候请求某网站的/路径下的网页。获取啥就得到啥。一般根目录都是首页的。至于一般获取数据使用GET,传输数据使用POST这个就没什么异议了吧?
  • Call<String>:最终数据类型使用String

第三部:直接调用

Retrofit retrofit = new Retrofit
        .Builder()
        .baseUrl("https://github.com/")
        .addConverterFactory(new Converter.Factory() {
            @Override
            public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
                return new Converter<ResponseBody, String>() {
                    @Override
                    public String convert(ResponseBody value) throws IOException {
                        return value.string();
                    }
                };
            }
        })
        .build();
GithubService githubService = retrofit.create(GithubService.class);
Call<String> indexSource = githubService.getGitHubIndexSource();
indexSource.enqueue(new Callback<String>() {
    @Override
    public void onResponse(Call<String> call, Response<String> response) {
        Log.i("TAG", response.body());
    }

    @Override
    public void onFailure(Call<String> call, Throwable t) {
        Log.i("TAG", t.getMessage());
    }
});

我们成功就可以打印出结果了。看到这里你可能会想暴揍一顿笔者,然后头也不回地离开。。。我第一次也有这样的想法,不过你得耐心看下去,真的。。。因为此处我们使用了最基础的Converter所以代码写起来有点多。但是作为程序员要懂得封装。待会我们就可以使用GsonConverter编写出简介的代码。

  • baseUrl:这里才真正给出了请求的地址,这里的地址会和GithubService中的GET中的参数/进行拼接。
  • addConverterFactory:添加转换器,这里我们使用了原生的Converter.Factory抽象类结合Converter。我们把返回的二进制流(姑且认为你们都知道数据是以二进制数据流传输的。。。)转换成ResponseBody然后转换成String其实我们一般只需要做ResponseBodyString或者其他模型的转换就可以了。因为Android开发一般访问都是要的是数据,所以二进制流的就甭管了。。。
  • 然后我们就通过retrofit.create去实现GithubService接口并返回。内部具体怎样就不要问我,因为我也没(kan)去(bu)看(dong)它的源码。
  • githubService.getGitHubIndexSource:构造出来了一个Call对象咯,这跟很多网络库没啥区别。
  • indexSource.enqueue:把Call加入异步请求队列。然后通过回调获取结果。

嗯?整个过程就大概像我上面那样说的。。。

除了Get还有什么呢?

我们都知道除了Get的请求方法,常见的还有Post(携带字段数据)Multipart(携带文件数据与字段数据等)

Post

@POST(URLConstant.SEND_LOGIN_CODE)
@FormUrlEncoded
Call<BaseResponseEntity> sendCode(@Field("phone") String phone);

Post中我们使用Field来指定参数字段

Multipart

@Multipart
@POST(URLConstant.COMPLETE_DATA)
Call<BaseObjResponseEntity<UserEntity>> completeData(@Part("user_token") RequestBody userToken,
                                                              @Part MultipartBody.Part icon,
                                                              @Part("nick_name") RequestBody nickName);

这里的尽管userToken跟nickName都是字符串类型,但是在Multipart中需要包装一下.重要的方法就是RequestBody.create(),其中包含多个重载的函数.大家可以使用下面的工具类来生成对应参数

/**
 * Created by August on 2017/2/10.
 * 文件上传帮助类
 */

public class MultiPartHelper {

    /**
     * 构造字段信息参数
     *
     * @param content
     * @return
     */
    public static RequestBody create(String content) {
        return RequestBody.create(MediaType.parse("multipart/form-data"), content);
    }


    /**
     * 构造单个文件上传
     *
     * @param key
     * @param file
     * @return
     */
    public static MultipartBody.Part create(String key, File file) {
        return MultipartBody.Part.createFormData(key, file.getName(), create(file));
    }

    public static RequestBody create(File file) {
        return RequestBody.create(MediaType.parse("multipart/form-data"), file);
    }

    public static MultipartBody.Part create(String key, String filePath) {
        return create(key, new File(filePath));
    }

    /**
     * 构造多个文件上传参数
     *
     * @param fileList
     * @return
     */
    public static List<MultipartBody.Part> create(List<String> fileList) {
        List<MultipartBody.Part> partList = new ArrayList<>();
        for (String filename : fileList) {
            File file = new File(filename);
            RequestBody imageBody = create(file);
            partList.add(MultipartBody.Part.createFormData(file.getName(), file.getName(), imageBody));
        }
        return partList;
    }

}

一起装逼一起飞

上面使用原生的CallBack我们已经受够了。。。一坨坨的代码如翔一样挥之不去。。。我们结合别人封装的GsonConverter来写一个从请求模型的转换吧
我们使用一个天气的API来获取数据吧。自己注册开发者身份

第一步:还是导入库(这是新的库。。。)

compile 'com.squareup.retrofit2:converter-gson:2.1.0'

因为我们前面选择的是Retrofit2.1.0的库。所以我们选择的converter-gson的版本也要对应上,我在学习的过程中就遇到过版本不对上的话就出错了。可能版本之间差异还是比较大的。还有让你添加库,你丫的别把Retrofit2.1.0给删掉。。。

第二步:编写模型

我们可以找到我们需要获取的数据的返回格式

获取城市列表返回数据

{
    errNum: 0,
    errMsg: "success",
    retData: [
        {
            province_cn: "北京",  //省
            district_cn: "北京",  //市
            name_cn: "朝阳",    //区、县 
            name_en: "chaoyang",  //城市拼音
            area_id: "101010300"  //城市代码
        },
        {
            province_cn: "辽宁",
            district_cn: "朝阳",
            name_cn: "朝阳",
            name_en: "chaoyang",
            area_id: "101071201"
        }
    ]
}

获取指定天气信息返回数据

{
errNum: 0,
errMsg: "success",
retData: {
   city: "北京", //城市
   pinyin: "beijing", //城市拼音
   citycode: "101010100",  //城市编码   
   date: "15-02-11", //日期
   time: "11:00", //发布时间
   postCode: "100000", //邮编
   longitude: 116.391, //经度
   latitude: 39.904, //维度
   altitude: "33", //海拔 
   weather: "晴",  //天气情况
   temp: "10", //气温
   l_tmp: "-4", //最低气温
   h_tmp: "10", //最高气温
   WD: "无持续风向",  //风向
   WS: "微风(<10m/h)", //风力
   sunrise: "07:12", //日出时间
   sunset: "17:44" //日落时间
  }    
}

从上面我们可以看出,我们需要建立4个模型。

  • CityListResult:包装获取城市列表返回数据整个JSON
  • CityInfo:包装CityListResult中的retData中的每一个城市信息
  • WeatherResult:包装获取指定天气信息返回数据整个JSON
  • Weather:包装WeatherResult中的每一个retData

所以我们的模型代码就像下面那样

CityInfo模型

public class CityInfo {
    @SerializedName("province_cn")
    private String provinceCN;
    @SerializedName("district_cn")
    private String districtCN;
    @SerializedName("name_cn")
    private String nameCN;
    @SerializedName("name_en")
    private String nameEN;
    @SerializedName("area_id")
    private String areaID;

    public String getProvinceCN() {
        return provinceCN;
    }

    public void setProvinceCN(String provinceCN) {
        this.provinceCN = provinceCN;
    }

    public String getDistrictCN() {
        return districtCN;
    }

    public void setDistrictCN(String districtCN) {
        this.districtCN = districtCN;
    }

    public String getNameCN() {
        return nameCN;
    }

    public void setNameCN(String nameCN) {
        this.nameCN = nameCN;
    }

    public String getNameEN() {
        return nameEN;
    }

    public void setNameEN(String nameEN) {
        this.nameEN = nameEN;
    }

    public String getAreaID() {
        return areaID;
    }

    public void setAreaID(String areaID) {
        this.areaID = areaID;
    }
}

CityListResult模型

/**
 * Created by August on 16/7/29.
 * 城市查询结果模型
 */
public class CityListResult {
    @SerializedName("errNum")
    private int errNum;
    @SerializedName("errMsg")
    private String errMsg;
    @SerializedName("retData")
    private List<CityInfo> retData;

    public int getErrNum() {
        return errNum;
    }

    public void setErrNum(int errNum) {
        this.errNum = errNum;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    public List<CityInfo> getRetData() {
        return retData;
    }

    public void setRetData(List<CityInfo> retData) {
        this.retData = retData;
    }
}

Weather模型


/**
 * Created by August on 16/7/29.
 * 指定天气结果模型
 */
package demo.august1996.top.retrofitnewssample.model;

import com.google.gson.annotations.SerializedName;

/**
 * Created by August on 16/7/29.
 * 指定天气结果模型
 */
public class Weather {
    @SerializedName("city")
    private String city;
    @SerializedName("pinyin")
    private String pinyin;
    @SerializedName("citycode")
    private long cityCode;
    @SerializedName("date")
    private String date;
    @SerializedName("time")
    private String time;
    @SerializedName("postCode")
    private long postCode;
    @SerializedName("longitude")
    private double longitude;
    @SerializedName("latitude")
    private double latitude;
    @SerializedName("altitude")
    private double altitude;
    @SerializedName("weather")
    private String weather;
    @SerializedName("temp")
    private int temp;
    @SerializedName("l_tmp")
    private int l_tmp;
    @SerializedName("h_tmp")
    private int h_tmp;
    @SerializedName("WD")
    private String WD;
    @SerializedName("WS")
    private String WS;
    @SerializedName("sunrise")
    private String sunrise;
    @SerializedName("sunset")
    private String sunset;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getPinyin() {
        return pinyin;
    }

    public void setPinyin(String pinyin) {
        this.pinyin = pinyin;
    }

    public long getCityCode() {
        return cityCode;
    }

    public void setCityCode(long cityCode) {
        this.cityCode = cityCode;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public long getPostCode() {
        return postCode;
    }

    public void setPostCode(long postCode) {
        this.postCode = postCode;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getAltitude() {
        return altitude;
    }

    public void setAltitude(double altitude) {
        this.altitude = altitude;
    }

    public String getWeather() {
        return weather;
    }

    public void setWeather(String weather) {
        this.weather = weather;
    }

    public int getTemp() {
        return temp;
    }

    public void setTemp(int temp) {
        this.temp = temp;
    }

    public int getL_tmp() {
        return l_tmp;
    }

    public void setL_tmp(int l_tmp) {
        this.l_tmp = l_tmp;
    }

    public int getH_tmp() {
        return h_tmp;
    }

    public void setH_tmp(int h_tmp) {
        this.h_tmp = h_tmp;
    }

    public String getWD() {
        return WD;
    }

    public void setWD(String WD) {
        this.WD = WD;
    }

    public String getWS() {
        return WS;
    }

    public void setWS(String WS) {
        this.WS = WS;
    }

    public String getSunrise() {
        return sunrise;
    }

    public void setSunrise(String sunrise) {
        this.sunrise = sunrise;
    }

    public String getSunset() {
        return sunset;
    }

    public void setSunset(String sunset) {
        this.sunset = sunset;
    }
}

WeatherResult模型


/**
 * Created by August on 16/7/29.
 * 获取特定城市返回结果模型
 */
public class WeatherResult {
    @SerializedName("errNum")
    private int errNum;
    @SerializedName("errMsg")
    private String errMsg;
    @SerializedName("retData")
    private Weather retData;

    public int getErrNum() {
        return errNum;
    }

    public void setErrNum(int errNum) {
        this.errNum = errNum;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    public Weather getRetData() {
        return retData;
    }

    public void setRetData(Weather retData) {
        this.retData = retData;
    }
}

前面的例子中使用的是String类型的结果,这次使用的是特定模型的结果

  • @SerializedName注解:里面的值就是对应字段JSON字段

编写服务接口

/**
 * Created by August on 16/7/28.
 * 获取天气相关的服务
 */
public interface WeatherService {
    @Headers("apikey:ffe27ca85324881f8414e8c1ab28xxxx")
    @GET("/apistore/weatherservice/citylist")
    Call<CityListResult> getCityList(@Query("cityname") String cityName);

    @Headers("apikey:ffe27ca85324881f8414e8c1ab28xxxx")
    @GET("/apistore/weatherservice/cityid")
    Call<WeatherResult> getWeather(@Query("cityid") String cityID);
}

上面第一个就是获取城市列表与指定城市天气结果的定义

  • @Headers:根据API接口要求,在header中添加apikey大家务必换成自己的apikey
  • @Query("cityname") String cityName:到时候调用时传入一个参数cityNameRetrofit会把cityName自动映射到GET请求的cityname字段。
  • 补充:
    • 如果请求是POST请求就把上面的@GET换成@POST,同时把@Query换成@Field即可。
    • 因为有些请求参数不是写在请求参数里面的,而是修改链接的。例如Github的https://api.github.com/users/august1996/repos就是通过请求路径的这时候我们可以写成:
@GET("https://api.github.com/users/{user}/repos")
Call<String> getGitHub(@Path("user") String user);

调用走起!

我们通过一个简单的ListView去展示城市列表,然后点击城市显示相应的天气信息。


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ListView mListView;
    private CityListResult mResult;
    private ArrayList<String> mDatas = new ArrayList<>();
    private ArrayAdapter<String> mAdapter;
    private WeatherService mWeatherService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.getBtn).setOnClickListener(this);
        mWeatherService = new Retrofit.Builder()
                .baseUrl("http://apis.baidu.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build().create(WeatherService.class);
        mListView = (ListView) findViewById(R.id.mListView);
        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, final int i, long l) {

                mWeatherService.getWeather(String.valueOf(mResult.getRetData().get(i).getAreaID()))
                        .enqueue(new Callback<WeatherResult>() {
                    @Override
                    public void onResponse(Call<WeatherResult> call, final Response<WeatherResult> response) {
                        MainActivity.this.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if (response.body().getErrNum() == 0) {

                                    Toast.makeText(MainActivity.this, response.body().getRetData().getWeather(), Toast.LENGTH_SHORT).show();

                                } else {
                                    Toast.makeText(MainActivity.this, response.body().getErrMsg(), Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
                    }

                    @Override
                    public void onFailure(Call<WeatherResult> call, Throwable t) {
                        t.printStackTrace();
                    }
                });
            }
        });
    }

    @Override
    public void onClick(View view) {
        mWeatherService.getCityList("广东").enqueue(new Callback<CityListResult>() {
            @Override
            public void onResponse(Call<CityListResult> call, final Response<CityListResult> response) {
                mResult = response.body();
                if (mResult.getErrNum() == 0) {
                    mDatas.clear();
                    for (int i = 0; i < mResult.getRetData().size(); i++) {
                        mDatas.add(mResult.getRetData().get(i).getNameCN());
                    }
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mAdapter.notifyDataSetChanged();
                        }
                    });
                } else {
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, response.body().getErrMsg(), Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }

            @Override
            public void onFailure(Call<CityListResult> call, Throwable t) {
                t.printStackTrace();
            }
        });
    }
}

看到这么恶心的代码。。。把我自己也吓到了,其实并不多【尴尬死了】。主要的就是

获取服务

mWeatherService = new Retrofit.Builder()
        .baseUrl("http://apis.baidu.com")
        .addConverterFactory(GsonConverterFactory.create())
        .build().create(WeatherService.class);
mListView = (ListView) findViewById(R.id.mListVie

创建请求

mWeatherService.getWeather(String.valueOf(mResult.getRetData().get(i).getAreaID())).enqueue(this);

结果解析

response.body();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容