android 超简单的MVP+Retrofit+RxAndroid+模拟接口响应信息+随时切换BaseURL

写这篇博客目的是记录下自己写的网络请求框架,因为公司目前工作需要,需要一个可以动态变更BaseURL的请求框架(OEM厂商好几个),但是,后台还没写好(接口都没定义),所以我得自己模拟网络请求,所以还添加了拦截接口响应信息的拦截器。还有,这期间我参考了很多大神的博客:

比如动态切换BaseURL是:https://www.jianshu.com/p/2919bdb8d09a  非常感谢!!!

一、先说MVP:

1,项目结构图奉上~超级简单的

然后是各种base类:

首先BaseActivity:

主要是为了防止内存泄漏,在destory时释放资源

public abstract class SimpleBaseActivity<P extends IPresenter> extends Activity implements IView{
    protected P presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //去掉标题栏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(getLayoutId());
        presenter = bindPresenter();
        start();
    }
    //abstract类中带有abstract标记的方法必须由子类继承并重新,抽象就是将可变的东西让子类去实现
    public abstract int getLayoutId();
    // 绑定Presenter
    protected abstract P bindPresenter();

    //开始了,子类可以在这个方法初始化view或其他初始化工作
    public void start(){

    }

    @Override
    public Activity getSelfActivity() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(presenter != null)
            presenter.detachView();
    }

}

再看实现的接口IView,主要是当Presenter中需要获取上下文对象时,传递上下文对象,不会让Presenter直接持有

public interface IView {
    Activity getSelfActivity();
}

最后是BasePresenter类:实现的接口只有一个方法,实现这个接口主要方便Activity绑定Presenter

public interface IPresenter {
    void detachView();
}
public abstract class BasePresenter<V extends IView> implements IPresenter  {
    private WeakReference<V> mWeakActivity;
    //Disposable容器,收集Disposable,主要用于内存泄漏管理
    private CompositeDisposable mDisposables;

    public BasePresenter(V view){
        attachView(view);
    }

    /**
     * 绑定view
     * @param v
     */
    public void attachView(V v){
        mWeakActivity = new WeakReference<V>(v);
        mDisposables = new CompositeDisposable();
    }

    /**
     * 解绑view,就在BaseActivity中销毁activity时调用的
     */
    public void detachView(){
        if(null != mWeakActivity){
            mDisposables.clear();
            mDisposables = null;
            mWeakActivity.clear();
            mWeakActivity = null;
            System.gc();
        }
    }

    //获取View
    public V getView(){
        if(null != mWeakActivity){
            return mWeakActivity.get();
        }
        return  null;
    }

    //返回是否还绑定view
    protected boolean isViewAttach(){
        return null != mWeakActivity && null != mWeakActivity.get();
    }
    /**
     * @param disposable 添加Disposable到CompositeDisposable
     *                   通过解除disposable处理内存泄漏问题
     */
    protected boolean addDisposable(Disposable disposable) {
        if (isNullOrDisposed(disposable)) {
            return false;
        }
        return mDisposables.add(disposable);
    }
    /**
     * @param d 判断d是否为空或者dispose
     * @return true:一次任务未开始或者已结束
     */
    protected boolean isNullOrDisposed(Disposable d) {
        return d == null || d.isDisposed();
    }

    /**
     * @param d 判断d是否dispose
     * @return true:一次任务还未结束
     */
    protected boolean isNotDisposed(Disposable d) {
        return d != null && !d.isDisposed();
    }
}

Base类结束,很简单吧。

2,再看使用,先创建一个contract类,来约定MVP各层需要做的事儿~

public interface MainContract {
    interface Model{
        /**
         * Model层负责处理登陆的网络请求
         * @param name 需要传的参数
         * @param observer 在presenter层传来的,负责处理数据的逻辑
         */
        void login(String name, DisposableSingleObserver<NetResponse<UserInfo>> observer);
    }
    interface View extends IView{
        //获取参数,也就是Model层login方法需要的name
        String getParam();

        /**
         * 更新View,由Presenter处理数据后调用
         * @param userInfo
         */
        void updateView(UserInfo userInfo);
    }

    interface Presenter{
        //Model层和View层的桥梁,负责处理数据逻辑
        void login();
    }
}

接下来一气呵成看MVP各层

首先Activity层,也就是View层,主要用来更新View的:

/**
 * 继承SimpleBaseActivity<MainPresenter>,则bindPresenter则为MainPresenter类型(因为父类是泛型)
 * 实现MainContract约束文件中的View接口
 */
public class MainActivity extends SimpleBaseActivity<MainPresenter> implements MainContract.View {

    private TextView infoView;

    @Override
    public void start() {
        super.start();
        infoView = findViewById(R.id.info);
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_main;
    }

    //返回的是MainPresenter类型presenter,并绑定
    @Override
    protected MainPresenter bindPresenter() {
        return new MainPresenter((MainContract.View) getSelfActivity());
    }

    @Override
    public String getParam() {
        return "李白";
    }

    //更新View,不许考虑网络请求、数据处理等其他的事儿,只要更新好view就好
    @Override
    public void updateView(UserInfo userInfo) {
        infoView.setText(userInfo.toString());
    }

    //界面按钮的点击方法,在xml文件中定义的
    public void get(View view) {
        //调用presenter层的login方法,开始进行网络请求啦
        presenter.login();
    }
}

然后Model层,超级简单,网络请求之后再讲~:

public class MainModel implements MainContract.Model {

    @Override
    public void login(String s, DisposableSingleObserver<NetResponse<UserInfo>> observer) {
        //下面这行主要是用来修改baseURL的,如果不需要可以不加的
        //第一个参数表示要修改哪个网络请求的BaseURL,第二个参数表示要修改成什么样的URL
        RetrofitUrlManager.getInstance().putDomain(Api.URL_VALUE_SECOND, Api.BASEURL2);
        RetrofitHelper.getInstance()
                .getRetrofit()
                .create(Api.class)
                .login(s)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .subscribeWith(observer);

    }
}

最后看presenter层是如何把M层和V层连接在一起的:

public class MainPresenter extends BasePresenter<MainContract.View> implements MainContract.Presenter {
    //先生成一个Model对象
    private MainContract.Model model;
    public MainPresenter(MainContract.View view) {
        super(view);
        model = new MainModel();
    }
    @Override
    public void login() {
        //处理数据
        DisposableSingleObserver<NetResponse<UserInfo>> disposableSingleObserver = new DisposableSingleObserver<NetResponse<UserInfo>>(){

            @Override
            public void onSuccess(NetResponse<UserInfo> value) {
                //如果网络请求返回的状态码为1,则让View层更新View
                if (value.getStatus() == 1)
                    getView().updateView(value.getData());
            }

            @Override
            public void onError(Throwable e) {

            }
        };
        //防止内存泄漏,所以统一管理,在destory时销毁
        addDisposable(disposableSingleObserver);
        //看!调用View层的获取参数的方法,拿到返回值传给M层进行网络请求,这就是桥梁啊
        model.login(getView().getParam(),disposableSingleObserver);
    }
}

好MVP到现在就结束了!!!!!!接下来看网络请求吧~

二、Retrofit封装

1,先看下Api接口,这儿想补充下关于接口的一些小知识:

抽象abstract 是将不可变的东西封装到一起,将可变的东西让子类去实现,是可以定义变量、常量以及方法的实现的,比如上面的BasePresenter类就是抽象类。但接口可以看错是高层的抽象,接口不会定义变量,即使是子类实现了接口,也不能修改接口中的属性。对于属性值都是public static final,对于方法都是public abstract。

public interface Api {
    //成员变量,默认修饰符 public static final
    //成员方法,默认修饰符 public abstract
    String BASEURL = "https://api.apiopen.top";//测试api
    String BASEURL2 = "https://api.apiopen.second";//测试api,错误的

    //header中添加 URL_KEY,表示这个请求是需要替换BaseUrl的
    String URL_KEY = "URL_KEY";
    //header中的value值,用于区分需要替换的BaseUrl是哪一个
    String URL_VALUE_SECOND = "URL_VALUE_SECOND";
    //这儿添加Headers是为了修改BaseURL的,key用于识别是不是需要修改BaseURL,value用来识别需要修改哪个BaseURL
    //使用时只需要在网络请求前添加: RetrofitUrlManager.getInstance().putDomain(Api.URL_VALUE_SECOND,"https://new.address.com");
    @Headers({URL_KEY+":"+URL_VALUE_SECOND})
    @POST("login")
    Single<NetResponse<UserInfo>> login(@Query("name") String key);
}

2,再看RetrofitHelper类,主要用于管理网络请求,设置了下修改BaseURL,还设置了拦截响应数据的拦截器:

public class RetrofitHelper {
    private static volatile RetrofitHelper instance;
    private final Retrofit retrofit;
    private static final int READ_TIMEOUT = 12;//读取超时时间(秒)
    private static final int CONN_TIMEOUT = 12;//连接超时时间(秒)

    /**
     * 在生成OKHTTP的builder时,RetrofitUrlManager.getInstance()得到的是用于修改BaseURL的类的实例,with返回的是OkHttpClient.Builder
     * 如果不需要修改BaseURL,可以去掉RetrofitUrlManager.getInstance().with(),直接new OkHttpClient().newBuilder()即可
     */
    private RetrofitHelper() {
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(Api.BASEURL)
                .client(RetrofitUrlManager.getInstance()//设置更换BaseURL的实例,不需要就不要设置了
                        .with(new OkHttpClient().newBuilder())
                        .addInterceptor(loggingInterceptor)
                        .addInterceptor(new simulateResponse())//拦截响应的拦截器,不需要就不要加
                        .connectTimeout(CONN_TIMEOUT, TimeUnit.SECONDS)
                        .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                        .writeTimeout(10, TimeUnit.SECONDS)
                        .build())
                .build();
    }

    /**
     * 单例模式
     * @return
     */
    public static RetrofitHelper getInstance() {
        if (instance == null) {
            synchronized (RetrofitHelper.class) {
                if (instance == null) {
                    instance = new RetrofitHelper();
                }
            }
        }
        return instance;
    }

    public Retrofit getRetrofit() {
        return retrofit;
    }

    /**
     * 打印日志的拦截器
     */
    private HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
        @Override
        public void log(String message) {
            showLog("retrofitBack = " + message);
        }
    });

    /**
     * 用于模拟API响应数据的拦截器
     * 应用场景:后台还没写好接口,没法儿测?根本没后台,自己想做个展示类Demo。还有好多场景
     */
    private class simulateResponse implements Interceptor {

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            final HttpUrl url = request.url();//获取路径
            //判断是否需要拦截,如果URL是需要拦截的URL,则会根据关键字apiNname进行拦截
            String apiNname = NetTest.isExist(url.toString());
            if(null != apiNname){
                //返回需要拦截的请求要进行模拟的数据
                String responseContent = NetTest.getVisualResponseByApi(apiNname);
                return new Response.Builder()
                        .code(200)
                        .message("模拟响应")
                        .body(ResponseBody.create(MediaType.parse("UTF-8"),responseContent))
                        .request(request)
                        .protocol(HTTP_1_1)
                        .build();
            }
            return chain.proceed(request);
        }
    }
    private void showLog(String msg) {
        Log.i(getClass().getName(), msg);
    }
}

3,再看拦截响应的拦截器的工具类:

public class NetTest {
    private static HashMap<String, String> apiResponse = new HashMap<>();

    /**
     * 判断这个URL是不是需要拦截,如果需要拦截就返回interceptApi,即apiResponse的key值
     * @param apiName 需要拦截的URL
     * @return
     */
    public static String isExist(String apiName) {
        //初始化apiResponse,添加键值对
        init();
        for (String interceptApi : apiResponse.keySet()) {
            if (apiName.contains(interceptApi)) {
                return interceptApi;
            }
        }
        return null;
    }

    /**
     * 根据键值对获取需要替换的响应值
     * @param apiName
     * @return
     */
    public static String getVisualResponseByApi(String apiName) {
        return apiResponse.get(apiName);
    }

    public static void init() {
        if (apiResponse.size() > 0) {
            return;
        }
        apiResponse.put("register", "{\"status\":1,\"msg\":\"调用成功\",\"data\":{\"userId\":\"20191118\",\"userType\":0,\"userBirthYear\":1994,\"userName\":\"王二小\",\"userStature\":\"185cm\",\"userWeight\":\"50kg\"}}");
        apiResponse.put("login", "{\"status\":1,\"msg\":\"调用成功\",\"data\":{\"userId\":\"20191118\",\"userType\":0,\"userBirthYear\":1994,\"userName\":\"王二小\",\"userStature\":\"185cm\",\"userWeight\":\"50kg\"}}");
    }
}

4,最后是改变BaseURL的了,不需要更改BaseURL的童鞋可以到此止步了。

先看用于更换BaseURL的实体类

public class DefaultUrlParser implements UrlParser{
    private Cache<String,String> mCache;
    @Override
    public void init(RetrofitUrlManager retrofitUrlManager) {
        //根据LRU规则,初始化一个用于管理诸多BaseURL的列表
        this.mCache = new CacheLRU<>(20);
    }

    /**
     * 主要操作,用来替换BaseURL
     * @param domainUrl 用于替换的 URL 地址
     * @param oldUrl  旧 URL 地址
     * @return
     */
    @Override
    public HttpUrl parseUrl(HttpUrl domainUrl, HttpUrl oldUrl) {
        if(null == domainUrl)
            return oldUrl;
        HttpUrl.Builder builder = oldUrl.newBuilder();
        if(TextUtils.isEmpty(mCache.getValue(getKey(domainUrl,oldUrl)))){
            for(int i = 0;i<oldUrl.pathSize();i++){
                builder.removePathSegment(0);
            }
            List<String> newPathSegments = new ArrayList<>();
            newPathSegments.addAll(domainUrl.encodedPathSegments());
            newPathSegments.addAll(oldUrl.encodedPathSegments());

            for(String PathSegment : newPathSegments) {
                builder.addEncodedPathSegment(PathSegment);
            }
        }else {
            builder.encodedPath(mCache.getValue(getKey(domainUrl,oldUrl)));
        }
        HttpUrl httpUrl = builder
                .scheme(domainUrl.scheme())
                .host(domainUrl.host())
                .port(domainUrl.port())
                .build();
        if(TextUtils.isEmpty(mCache.getValue(getKey(domainUrl,oldUrl))))
            mCache.put(getKey(domainUrl,oldUrl),httpUrl.encodedPath());

        return httpUrl;

    }

    private String getKey(HttpUrl domainUrl,HttpUrl oldUrl){
        return domainUrl.encodedPath() + oldUrl.encodedPath();
    }
}

最后是修改BaseURL的工具类:

public class RetrofitUrlManager {
    private static final String TAG = "RetrofitUrlManager";
    private static final String GLOBAL_DOMAIN_NAME = "URL_VALUE_DEFAULT";

    private final Map<String, HttpUrl> mDomainNameHub = new HashMap<>();
    private final Interceptor mInterceptor;
    private final List<onUrlChangeListener> mListeners = new ArrayList<>();
    private UrlParser mUrlParser;

    private RetrofitUrlManager() {
        UrlParser urlParser = new DefaultUrlParser();
        urlParser.init(this);
        setUrlParser(urlParser);
        this.mInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                return chain.proceed(processRequest(chain.request()));
            }
        };
    }

    //单例模式
    private static class RetrofitUrlManagerHolder {
        private static final RetrofitUrlManager INSTANCE = new RetrofitUrlManager();
    }

    public static final RetrofitUrlManager getInstance() {
        return RetrofitUrlManagerHolder.INSTANCE;
    }

    //添加拦截器
    public OkHttpClient.Builder with(OkHttpClient.Builder builder) {
        checkNotNull(builder, "builder cannot be null");
        return builder.addInterceptor(mInterceptor);
    }

    //对request进行修改
    public Request processRequest(Request request) {
        if (request == null) return request;

        Request.Builder newBuilder = request.newBuilder();
        //检测header中是否存在需要更换BaseUrl的key值,如果存在则返回对应的value值
        String domainName = obtainDomainNameFromHeaders(request);

        HttpUrl httpUrl;

        Object[] listeners = listenersToArray();

        // 如果有 header,获取 header 中 domainName 所映射的 url,若没有,则检查全局的 BaseUrl,未找到则为null
        if (!TextUtils.isEmpty(domainName)) {
            notifyListener(request, domainName, listeners);
            httpUrl = fetchDomain(domainName);
            newBuilder.removeHeader(Api.URL_KEY);
        } else {
            notifyListener(request, GLOBAL_DOMAIN_NAME, listeners);
            httpUrl = getGlobalDomain();
        }

        if (null != httpUrl) {
            HttpUrl newUrl = mUrlParser.parseUrl(httpUrl, request.url());
                Log.d(RetrofitUrlManager.TAG, "The new url is { " + newUrl.toString() + " }, old url is { " + request.url().toString() + " }");

            if (listeners != null) {
                for (int i = 0; i < listeners.length; i++) {
                    ((onUrlChangeListener) listeners[i]).onUrlChanged(newUrl, request.url()); // 通知监听器此 Url 的 BaseUrl 已被切换
                }
            }

            return newBuilder
                    .url(newUrl)
                    .build();
        }

        return newBuilder.build();

    }

    /**
     * 通知所有监听器的  onUrlChangeListener#onUrlChangeBefore(HttpUrl, String)方法
     * @param request    {@link Request}
     * @param domainName 域名的别名
     * @param listeners  监听器列表
     */
    private void notifyListener(Request request, String domainName, Object[] listeners) {
        if (listeners != null) {
            for (int i = 0; i < listeners.length; i++) {
                ((onUrlChangeListener) listeners[i]).onUrlChangeBefore(request.url(), domainName);
            }
        }
    }

    /**
     * 全局动态替换 BaseUrl, 优先级: Header中配置的 BaseUrl > 全局配置的 BaseUrl
     * 除了作为备用的 BaseUrl, 当您项目中只有一个 BaseUrl, 但需要动态切换
     * 这种方式不用在每个接口方法上加入 Header, 就能实现动态切换 BaseUrl
     *
     * @param globalDomain 全局 BaseUrl
     */
    public void setGlobalDomain(String globalDomain) {
        checkNotNull(globalDomain, "globalDomain cannot be null");
        synchronized (mDomainNameHub) {
            mDomainNameHub.put(GLOBAL_DOMAIN_NAME, checkUrl(globalDomain));
        }
    }
    private HttpUrl checkUrl(String url) {
        HttpUrl parseUrl = HttpUrl.parse(url);
        if (null == parseUrl) {
            throw new RuntimeException("You've configured an invalid url : "+url);
        } else {
            return parseUrl;
        }
    }

    /**
     * 获取全局 BaseUrl
     */
    public synchronized HttpUrl getGlobalDomain() {
        return mDomainNameHub.get(GLOBAL_DOMAIN_NAME);
    }

    /**
     * 移除全局 BaseUrl
     */
    public void removeGlobalDomain() {
        synchronized (mDomainNameHub) {
            mDomainNameHub.remove(GLOBAL_DOMAIN_NAME);
        }
    }

    /**
     * 存放 Domain(BaseUrl) 的映射关系
     *
     * @param domainName
     * @param domainUrl
     */
    public void putDomain(String domainName, String domainUrl) {
        checkNotNull(domainName, "domainName cannot be null");
        checkNotNull(domainUrl, "domainUrl cannot be null");
        synchronized (mDomainNameHub) {
            mDomainNameHub.put(domainName, checkUrl(domainUrl));
        }
    }

    /**
     * 取出对应 {@code domainName} 的 Url(BaseUrl)
     *
     * @param domainName
     * @return
     */
    public synchronized HttpUrl fetchDomain(String domainName) {
        checkNotNull(domainName, "domainName cannot be null");
        return mDomainNameHub.get(domainName);
    }

    /**
     * 移除某个 {@code domainName}
     *
     * @param domainName {@code domainName}
     */
    public void removeDomain(String domainName) {
        checkNotNull(domainName, "domainName cannot be null");
        synchronized (mDomainNameHub) {
            mDomainNameHub.remove(domainName);
        }
    }

    /**
     * 清理所有 Domain(BaseUrl)
     */
    public void clearAllDomain() {
        mDomainNameHub.clear();
    }

    /**
     * 存放 Domain(BaseUrl) 的容器中是否存在这个 {@code domainName}
     *
     * @param domainName {@code domainName}
     * @return {@code true} 为存在, {@code false} 为不存在
     */
    public synchronized boolean haveDomain(String domainName) {
        return mDomainNameHub.containsKey(domainName);
    }

    /**
     * 存放 Domain(BaseUrl) 的容器, 当前的大小
     *
     * @return 容量大小
     */
    public synchronized int domainSize() {
        return mDomainNameHub.size();
    }

    /**
     * 可自行实现 {@link UrlParser} 动态切换 Url 解析策略
     *
     * @param parser {@link UrlParser}
     */
    public void setUrlParser(UrlParser parser) {
        checkNotNull(parser, "parser cannot be null");
        this.mUrlParser = parser;
    }

    /**
     * 注册监听器(当 Url 的 BaseUrl 被切换时会被回调的监听器)
     *
     * @param listener 监听器列表
     */
    public void registerUrlChangeListener(onUrlChangeListener listener) {
        checkNotNull(listener, "listener cannot be null");
        synchronized (mListeners) {
            mListeners.add(listener);
        }
    }

    /**
     * 注销监听器(当 Url 的 BaseUrl 被切换时会被回调的监听器)
     *
     * @param listener 监听器列表
     */
    public void unregisterUrlChangeListener(onUrlChangeListener listener) {
        checkNotNull(listener, "listener cannot be null");
        synchronized (mListeners) {
            mListeners.remove(listener);
        }
    }

    private Object[] listenersToArray() {
        Object[] listeners = null;
        synchronized (mListeners) {
            if (mListeners.size() > 0) {
                listeners = mListeners.toArray();
            }
        }
        return listeners;
    }

   //检测header中是否存在需要更换BaseUrl的key值
    private String obtainDomainNameFromHeaders(Request request) {
        //查询header中是否包含Api.URL_KEY,如果包含则表示这个request需要替换BaseUrl
        List<String> headers = request.headers(Api.URL_KEY);
        if (headers == null || headers.size() == 0)
            return null;
        //header中应该只有一个Api.URL_KEY
        if (headers.size() > 1)
            throw new IllegalArgumentException("Only one Domain-Name in the headers");
        //返回header中对应Api.URL_KEY的value值
        return request.header(Api.URL_KEY);
    }
    private <T> T checkNotNull(final T reference, final Object errorMessage) {
        if (reference == null) {
            throw new NullPointerException(String.valueOf(errorMessage));
        }
        return reference;
    }
}

到此,就结束了。

项目地址:https://github.com/androidGL/RetrofitChangeUrlMVP

 

    
 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值