像小白一样学习mvp+retrofit2

-1-什么是retrofit2

Retrofit是一个不错的网络请求库

A type-safe REST client for Android and Java

好吧,说人话就是:一个用于网络请求的库。跟我们用到的OKhttp、Vellory、AysncHttp是一样一样的作用。

-2-废话

此demo采用mvp+retrofit2的形式展示,如果不熟悉mvp的同学请参考

像小白一样学习MVP

有的好事者肯定就问了,现在主流不是mvp+retrofit2+rxjava吗,怎么你只介绍前面2个的结合呢。是的我就只介绍mvp+retrofit2原因是什么不怕告诉你,因为看了rxjava能大概知道原理和作用,但是能直接上手的案例真的不多,也因为本人天资愚钝所以还是打算一步一步来比较好。

如果有人同学想学习rxjava,请参考扔物线这位大神的给 Android 开发者的 RxJava 详解文章,说实话我至少看了3遍,已经了解一些了,下一步就会上手写demo,也相信自己能搞定。

-3-实战

1 项目包结构

项目包结构

熟悉mvp的同学都知道model、presenter、view就不做介绍了。同时我自己新建了一个retrofit包名负责retrofit的所有功能。

当然此结构由优化的地方,因demo,所以不做过度累述。

这是一个请求通过网络获取网络天气信息的demo。请求连接为

http://api.avatardata.cn/Weather/Query?key=xxxxxxx&cityname=长沙

先看效果图


运行前
运行中
运行后

2 添加依赖

在使用之前,你必须先导入必要的jar包,以androidStudio为例:

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

注意:本demo使用retrofit2,而不是retrofit1.x

3 mvp中的M

/**
 * 描述:mvp中的m
 * 作者:dc on 2016/11/15 14:48
 * 邮箱:597210600@qq.com
 */
public interface WeatherModel {
    /**  异步加载数据  **/
    void loadWeather(String key , String cityId);

    /**  同步加载数据   **/
    void synLoadWeather(String key, String cityName);

    /**  设置监听器  **/
    void setWeatherListener(WeatherModelImp.LoadWeatherInfoListener listener);
}

里面有3个方法,一个异步加载数据、一个同步加载数据。一个监听器方法(作用是通过interface返回请求结果,如果用上Rxjava就不需要这么麻烦了)。

然后新建一个类实现该接口

import android.content.Context;
import android.util.Log;

import com.souv.rxjava.Constan;
import com.souv.rxjava.bean.WeatherInfoBean;
import com.souv.rxjava.retrofit.LoadWeatherServer;

import java.io.IOException;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * 描述:mvp中的m实现接口
 * 作者:dc on 2016/11/15 14:48
 * 邮箱:597210600@qq.com
 */
public class WeatherModelImp implements WeatherModel {
private static final String TAG = WeatherModelImp.class.getSimpleName();

/**  对象定义  **/
private Context mContext = null;
private LoadWeatherInfoListener loadWeatherInfoListener = null;

public WeatherModelImp(Context context){
    mContext = context;
}

@Override
public void loadWeather(String key, String cityId) {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Constan.WHETHER_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    LoadWeatherServer apiService = retrofit.create(LoadWeatherServer.class);
    Call<WeatherInfoBean> call = apiService.loadRetrofitWeatherInfo(key,cityId);
    call.enqueue(new Callback<WeatherInfoBean>() {
        @Override
        public void onResponse(Call<WeatherInfoBean> call, Response<WeatherInfoBean> response) {
            String result = "";
            try{
                String errorResult = response.errorBody().string();
            }catch (Exception e){
                e.printStackTrace();
            }
            WeatherInfoBean weatherBean = response.body();
            Log.e(TAG, "请求天气返回结果:" + weatherBean.getResult().getRealtime().getWeather().getTemperature());
            loadWeatherInfoListener.loadWeatherSuccess(weatherBean);
        }

        @Override
        public void onFailure(Call<WeatherInfoBean> call, Throwable t) {
            //请求失败
            t.printStackTrace();
            loadWeatherInfoListener.loadWeatherFial();
        }
    });
}

@Override
public void synLoadWeather(String key, String cityId) {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Constan.WHETHER_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    LoadWeatherServer apiService = retrofit.create(LoadWeatherServer.class);
    Call<WeatherInfoBean> call = apiService.loadRetrofitWeatherInfo(key, cityId);

    try {
        Response<WeatherInfoBean> responseBody = call.execute();
        WeatherInfoBean weatherBean = responseBody.body();
        Log.e(TAG, "请求天气返回结果:" + weatherBean.getResult().getRealtime().getWeather().getTemperature());
        loadWeatherInfoListener.synLoadWeatherSuccess(weatherBean);
    } catch (IOException e) {
        e.printStackTrace();
        loadWeatherInfoListener.synLoadWeatherFail();
    }
}

@Override
public void setWeatherListener(LoadWeatherInfoListener listener) {
    loadWeatherInfoListener = listener;
}

public interface LoadWeatherInfoListener{
    void loadWeatherSuccess(WeatherInfoBean bean);  //异步加载数据成功

    void loadWeatherFial(); //异步加载数据失败

    void synLoadWeatherSuccess(WeatherInfoBean bean);  //同步加载数据成功

    void synLoadWeatherFail();   //同步加载数据失败
}

我们仔细看下loadWeather方法和synLoadWeather方法。分别是异步请求和同步请求

Retrofit2使用

异步请求

Call<WeatherInfoBean> call = apiService.loadRetrofitWeatherInfo(key,cityId);
    call.enqueue(new Callback<WeatherInfoBean>() {
        @Override
        public void onResponse(Call<WeatherInfoBean> call, Response<WeatherInfoBean> response) {
            
        }

        @Override
        public void onFailure(Call<WeatherInfoBean> call, Throwable t) {
            //请求失败
            t.printStackTrace();
        }

以上代码发起了一个在后台线程的请求并从response 的response.body()方法中获取一个结果对象。注意这里的onResponse和onFailure方法是在主线程中调用的。

我建议你使用enqueue,它最符合 Android OS的习惯。

同步请求

Call<WeatherInfoBean> call = apiService.loadRetrofitWeatherInfo(key, cityId);

    try {
        Response<WeatherInfoBean> responseBody = call.execute();
        WeatherInfoBean weatherBean = responseBody.body();
       
    } catch (IOException e) {
        e.printStackTrace();
    }

以上的代码会阻塞线程,因此你不能在安卓的主线程中调用,不然会面临NetworkOnMainThreadException。如果你想调用execute方法,请在后台线程执行。

然后我们来看下retrofit的URL定义方式。

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

/**
 * 描述:retrofit
 * 作者:dc on 2016/11/15 15:16
 * 邮箱:597210600@qq.com
 */
public interface LoadWeatherServer {

//http://api.avatardata.cn/
//http://api.avatardata.cn/Weather/Query?key=75bfe88f27a34311a41591291b7191ce&cityname=%E9%95%BF%E6%B2%99
    @GET("Weather/Query?")
    Call<WeatherInfoBean> loadRetrofitWeatherInfo(@Query("key") String key,@Query("cityname") String cityName);
}

......

//引用
Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Constan.WHETHER_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

......

//URL
public static final String WHETHER_URL = "http://api.avatardata.cn/";

因retrofit重新定义了新的URL形式,所以URL形式有不同的表示。

下面引用几个案例

URL案例-1

URL案例-2

URL案例-3

显然我喜欢第二个案例

对于 Retrofit 2.0中新的URL定义方式,这里是我的建议:

  • Base URL: 总是以 /结尾

  • @Url: 不要以 / 开头

如:

     @GET("Weather/Query?")
    Call<WeatherInfoBean> loadRetrofitWeatherInfo(@Query("key") String key,@Query("cityname") String cityName);


//引用
Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(http://api.avatardata.cn/)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

Retrofit请求

LoadWeatherServer apiService = retrofit.create(LoadWeatherServer.class);
    Call<WeatherInfoBean> call = apiService.loadRetrofitWeatherInfo(key,cityId);
    call.enqueue(new Callback<WeatherInfoBean>() {
        @Override
        public void onResponse(Call<WeatherInfoBean> call, Response<WeatherInfoBean> response) {
            String result = "";
            try{
                String errorResult = response.errorBody().string();
            }catch (Exception e){
                e.printStackTrace();
            }
            WeatherInfoBean weatherBean = response.body();
            Log.e(TAG, "请求天气返回结果:" + weatherBean.getResult().getRealtime().getWeather().getTemperature());
            loadWeatherInfoListener.loadWeatherSuccess(weatherBean);
        }

        @Override
        public void onFailure(Call<WeatherInfoBean> call, Throwable t) {
            //请求失败
            t.printStackTrace();
            loadWeatherInfoListener.loadWeatherFial();
        }
    });

前面说到enqueue是异步请求,其中Response是请求响应的结果体。

在Retrofit 2.0中,不管 response 是否能被解析。onResponse总是会被调用。但是在结果不能被解析的情况下,response.body()会返回null。别忘了处理这种情况。

如果response存在什么问题,比如404什么的,onResponse也会被调用。你可以从response.errorBody().string()中获取错误信息的主体。

好了,然后我们在来看

MVP中的V

/**
 * 描述:MVP中的V
 * 作者:dc on 2016/11/15 16:02
 * 邮箱:597210600@qq.com
 */
public interface WeatherView {

void showWeather(String weather);  //异步显示天气信息

void showWeatherTemp(String weatherTemp); //异步显示天气温度信息

void loadWeatherFail(); //异步加载天气信息失败

void startLoadWeatherInfo();  //开始加载天气信息

void synShowWeather(String weather);  //同步显示天气信息

void synShowWeatherTemp(String weatherTemp);//同步显示天气温度信息

void synoadWeatherFail();   //同步加载天气信息失败
}

也没什么好解释的,只要是提供接口给MVP中的P,让其调用。接口主要是处理控件事件。因为上面提到过retrofit异步加载是在主线程中的,同步加载时开启了一个新线程的所以我在不同的请求方式下也分了不同的显示方法。这之后我们会在MVP中的P看到

MVP中的P

/**
 * 描述:
 * 作者:dc on 2016/11/15 16:02
 * 邮箱:597210600@qq.com
 */
public class WeatherPresenter implements WeatherModelImp.LoadWeatherInfoListener{
private static final String TAG = WeatherPresenter.class.getSimpleName();

private WeatherModelImp weatherModelImp = null;
private WeatherView weatherView = null;
private Handler handler = new Handler();

public WeatherPresenter(Context context , WeatherView view){
    weatherModelImp = new WeatherModelImp(context);
    weatherView = view;
    weatherModelImp.setWeatherListener(this);
}

/**
 * 异步获取天气数据
 * @param key
 * @param cityName
 */
public void loadWeatherInfo(String key ,String cityName){
    weatherView.startLoadWeatherInfo();
    weatherModelImp.loadWeather(key, cityName);

}

/**
 * 同步获取天气数据
 * @param key
 * @param cityName
 */
public void synLoadWeatherInfo(String key ,String cityName){
    weatherView.startLoadWeatherInfo();
    weatherModelImp.synLoadWeather(key, cityName);
}

@Override
public void loadWeatherSuccess(WeatherInfoBean bean) {
    if(null != bean){
        //显示当前温度
        String temperature = bean.getResult().getRealtime().getWeather().getTemperature();
        String info = bean.getResult().getRealtime().getWeather().getInfo();

        weatherView.showWeather(info);
        weatherView.showWeatherTemp(temperature);
    }
}

@Override
public void loadWeatherFial() {
    weatherView.loadWeatherFail();
}

@Override
public void synLoadWeatherSuccess(WeatherInfoBean bean) {
    //显示当前温度
    final String temperature = bean.getResult().getRealtime().getWeather().getTemperature();
    final String info = bean.getResult().getRealtime().getWeather().getInfo();
    //同步请求,启动的新线程,所以在现实界面的时候需要在主线程中
    handler.post(new Runnable() {
        @Override
        public void run() {
            weatherView.synShowWeather(info);
            weatherView.synShowWeatherTemp(temperature);
        }
    });
}
@Override
public void synLoadWeatherFail() {
    //同步请求,启动的新线程,所以在现实界面的时候需要在主线程中
    handler.post(new Runnable() {
        @Override
        public void run() {
            weatherView.synoadWeatherFail();
        }
    });
}
}

也不过大解释,MVP中的P主要是M和V的桥接。这里面引用了接口的方式来返回请求天气结果。个人觉得还是过于麻烦,所以说Rxjava还是很有必要学习的

MainActivity

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.souv.rxjava.presenter.WeatherPresenter;
import com.souv.rxjava.view.WeatherView;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity implements WeatherView {
private static final String TAG = MainActivity.class.getSimpleName();
@Bind(R.id.main_weather_tv)
TextView mainWeatherTv;
@Bind(R.id.main_weather_temp_tv)
TextView mainWeatherTempTv;
@Bind(R.id.main_weather_getweather_btn)
Button mainWeatherGetweatherBtn;
@Bind(R.id.main_weather_syn_getweather_btn)
Button mainWeatherSynGetweatherBtn;

/**
 * 对象定义
 **/
private WeatherPresenter weatherPresenter = null;
private WaitDialog waitDialog = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);

    init();

}

private void init() {
    waitDialog = new WaitDialog(MainActivity.this, android.R.style.Theme_Translucent_NoTitleBar);
    waitDialog.setCancelable(true);
    weatherPresenter = new WeatherPresenter(MainActivity.this, this);
}

@OnClick(R.id.main_weather_getweather_btn)
public void loadWeather() {
    weatherPresenter.loadWeatherInfo("75bfe88f27a34311a41591291b7191ce", "长沙");
}

@OnClick(R.id.main_weather_syn_getweather_btn)
public void synLoadWeather() {
    //以上的代码会阻塞线程,因此你不能在安卓的主线程中调用,不然会面临NetworkOnMainThreadException。如果你想调用execute方法,请在后台线程执行。
    new Thread(new Runnable() {
        @Override
        public void run() {
            weatherPresenter.synLoadWeatherInfo("75bfe88f27a34311a41591291b7191ce", "长沙");
        }
    }).start();

}

@Override
protected void onDestroy() {
    super.onDestroy();
}

@Override
public void showWeather(String weather) {
    mainWeatherTv.setText(weather);
}

@Override
public void showWeatherTemp(String weatherTemp) {
    waitDialog.cancel();
    waitDialog.dismiss();
    mainWeatherTempTv.setText(weatherTemp + "℃");
}

@Override
public void loadWeatherFail() {
    Toast.makeText(MainActivity.this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
}

@Override
public void startLoadWeatherInfo() {
    waitDialog.setWaitText("开始请求天气信息");
    waitDialog.show();
}

@Override
public void synShowWeather(String weather) {
    mainWeatherTv.setText(weather);
}

@Override
public void synShowWeatherTemp(String weatherTemp) {
    waitDialog.cancel();
    waitDialog.dismiss();
    mainWeatherTempTv.setText(weatherTemp + "℃");
}

@Override
public void synoadWeatherFail() {
    Toast.makeText(MainActivity.this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
}
}

也没什么好解释的,主要是extends MVP中的V,用来实现视图的操作。同时使用到MVP中的P的实例来调用P中的方法。

总结

Retrofit2还有很多知识需要学习,上面的demo只是冰山一角,但是却能清晰的知道他的使用方式,如何使用。只要是入了门那么之后的深入我相信是不会很难的。因为学习了Retrofit所以我觉得RXjava真的很有必要,也很适合跟Retrofit一起使用。如果可以你也一定要学习新的知识。


慢慢努力做好身边所有的事

求知若饥,虚心若愚

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

推荐阅读更多精彩内容