鸿蒙开发之网络框架搭建,MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony2

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 鸿蒙开发之网络框架搭建,MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony
3.1.6、使用时写法

地址可以写成常量

ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)
3.2、态改变retrofit.create(cls)的接口cls,组件化思想很有必要
3.2.1、当我们搭建组件化后,立马会想到每个组件用一个接口类,或者搭建组件化时,每个模块用一个接口类,这种需求肯定会存在,看如何来封装(其中包含文件下载拦截器拦截逻辑,和添加请求头等逻辑,可参考,可忽略)
public class ApiRetrofit {
    private static Retrofit retrofit;
    private Gson gson;
    private static final int DEFAULT_TIMEOUT = 135;
    private static List<Retrofit> mRetrofitList = new ArrayList<>();
    public static String mBaseUrl = BaseContent.getBaseUrl();
    private static BaseView mBaseView = null;
    private static volatile Type mType = Type.BASE;
    public enum Type {
        FILE,
        BASE,
        BASE_URL,
    }
    public Type getType() {
        return mType;
    }
    public static void setType(Type type) {
        mType = type;
    }
    /**
     * 文件处理
     *
     * @param httpClientBuilder
     */
    public void initFileClient(OkHttpClient.Builder httpClientBuilder) {
        /**
         * 处理文件下载进度展示所需
         */
        httpClientBuilder.addNetworkInterceptor(new ProgressInterceptor());
    }
    /**
     * 默认所需
     *
     * @param httpClientBuilder
     */
    public void initDefaultClient(OkHttpClient.Builder httpClientBuilder) {
        /**
         * 处理一些识别识别不了 ipv6手机,如小米  实现方案  将ipv6与ipv4置换位置,首先用ipv4解析
         */
//        httpClientBuilder.dns(new ApiDns());
        /**
         * 添加cookie管理
         * 方法1:第三方框架
         */
        PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(),
                new SharedPrefsCookiePersistor(app));
        httpClientBuilder.cookieJar(cookieJar);
        /**
         * 添加cookie管理
         * 方法2:手动封装cookie管理
         */
//        httpClientBuilder.cookieJar(new CookieManger(BaseApp.getContent()));
        /**
         * 添加日志拦截 实现方式1     上下俩种二者选其一即可
         */
//        httpClientBuilder.addInterceptor(new JournalInterceptor());
        /**
         * 添加日志拦截 实现方式2     上下俩种二者选其一即可
         */
        HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLogger());
        logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        httpClientBuilder.addInterceptor(logInterceptor);
        /**
         * 添加请求头
         */
//        httpClientBuilder.addInterceptor(new HeadUrlInterceptor());
        /**
         * 忽略证书
         */
//        httpClientBuilder.hostnameVerifier(new AllowAllHostnameVerifier());
    }
    public ApiRetrofit() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        httpClientBuilder
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);//错误重联
        switch (getType()) {
            case FILE:
                initFileClient(httpClientBuilder);
                break;
            case BASE:
            case BASE_URL:
                initDefaultClient(httpClientBuilder);
                break;
        }
            retrofit = new Retrofit.Builder()
                    .baseUrl(mBaseUrl)
                    .addConverterFactory(GsonConverterFactory.create(buildGson()))
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(httpClientBuilder.build())
                    .build();
        mRetrofitList.add(retrofit);
    }
    /**
     * 增加后台返回""和"null"的处理,如果后台返回格式正常
     * 1.int=>0
     * 2.double=>0.00
     * 3.long=>0L
     * 4.String=>""
     *
     * @return
     */
    public Gson buildGson() {
        if (gson == null) {
            gson = new GsonBuilder()
                    .registerTypeAdapter(Integer.class, new IntegerDefaultAdapter())
                    .registerTypeAdapter(int.class, new IntegerDefaultAdapter())
                    .registerTypeAdapter(Double.class, new DoubleDefaultAdapter())
                    .registerTypeAdapter(double.class, new DoubleDefaultAdapter())
                    .registerTypeAdapter(Long.class, new LongDefaultAdapter())
                    .registerTypeAdapter(long.class, new LongDefaultAdapter())
                    .registerTypeAdapter(String.class, new StringNullAdapter())
                    .create();
        }
        return gson;
    }
    private static <T> T create(Class<T> cls, String baseUrl) {
        mBaseUrl = baseUrl;
        if (retrofit == null) {
            new ApiRetrofit();
        } else {
            initRetrofit();
        }
        T t = retrofit.create(cls);
        return t;
    }
    private static void initRetrofit() {
        int mIndex = -1;
        for (int i = 0; i < mRetrofitList.size(); i++) {
            if (mBaseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
                mIndex = i;
                break;
            }
        }
        //新的baseUrl
        if (mIndex == -1) {
            synchronized (Object.class) {
                new ApiRetrofit();
            }
        } else {
            //已经创建过的baseUrl
            retrofit = mRetrofitList.get(mIndex);
        }
    }
    /**
     * 默认使用方式
     *
     * @return
     */
    public static <T> T getInstance(Class<T> cls) {
        setType(Type.BASE);
        mBaseView = null;
        return create(cls, BaseContent.getBaseUrl());
    }
    /**
     * 文件下载使用方式
     *
     * @param baseView
     * @return
     */
    public static <T> T getFileInstance(Class<T> cls, BaseView baseView) {
        setType(Type.FILE);
        mBaseView = baseView;
        return create(cls, BaseContent.getBaseUrl() + "file/");
    }
    /**
     * 动态改变baseUrl使用方式
     *
     * @param baseUrl
     * @return
     */
    public static <T> T getBaseUrlInstance(Class<T> cls, String baseUrl) {
        setType(Type.BASE_URL);
        mBaseView = null;
        return create(cls, baseUrl);
    }
  }
3.2.2、使用时写法
ApiRetrofit.getBaseUrlInstance(LiveApiServer.class, "http://www.baidu.com/").getCeShi(params)
ApiRetrofit.getInstance(LiveApiServer.class).getCeShi(params)

四、Retrofit,Gson解析,请求返回的类型不统一,假如double返回的是null

现实开发中,往往会遇到后台返回数据格式不规范情况,比如前端字段原本定义为int类型,而数据返回为空,如果用Gson解析会导致解析失败,比如字段定义为double类型,而返回的格式为字符串null,导致解析失败等等(只在后台返回数据格式不规范情况下出现,如果后台返回格式规范并不用考虑此问题)

1、 实现目标

1、格式化数据不规范【格式化int类型数据】

2、格式化数据不规范【格式化Long类型数据】

3、格式化数据不规范【格式化Double类型数据】

4、格式化数据不规范【格式化String类型数据】

5、格式化数据不规范【格式化Null类型数据】

2、 添加格式化工具方法到Gson解析中
if (gson == null) {
            gson = new GsonBuilder()
                    .registerTypeAdapter(Integer.class, new IntegerDefaultAdapter())
                    .registerTypeAdapter(int.class, new IntegerDefaultAdapter())
                    .registerTypeAdapter(Double.class, new DoubleDefaultAdapter())
                    .registerTypeAdapter(double.class, new DoubleDefaultAdapter())
                    .registerTypeAdapter(Long.class, new LongDefaultAdapter())
                    .registerTypeAdapter(long.class, new LongDefaultAdapter())
                    .registerTypeAdapter(String.class, new StringNullAdapter())
                    .create();
        }
        return gson;
    }
 public ApiRetrofit() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
        httpClientBuilder
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);//错误重联
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_SERVER_URL)
                .addConverterFactory(GsonConverterFactory.create(buildGson()))//添加json转换框架buildGson()根据需求添加
                //支持RxJava2
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClientBuilder.build())
                .build();
        apiServer = retrofit.create(ApiServer.class);
    }
3、 对double类型处理,返回“”,或“null”,动态更改为默认值0.00,新建DoubleDefaultAdapter类
public class DoubleDefault0Adapter implements JsonSerializer<Double>, JsonDeserializer<Double> {
    @Override
    public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        try {
            if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为double类型,如果后台返回""或者null,则返回0.00
                return 0.00;
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsDouble();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }
    @Override
    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}
4、 对int类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类
public class IntegerDefaultAdapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
    @Override
    public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
        try {
            if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为int类型,如果后台返回""或者null,则返回0
                return 0;
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsInt();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }
    @Override
    public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}
5、 对Long类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类
public class LongDefault0Adapter implements JsonSerializer<Long>, JsonDeserializer<Long> {
    @Override
    public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
        try {
            if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为long类型,如果后台返回""或者null,则返回0
                return 0l;
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsLong();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }
    @Override
    public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}
5、 重点说一下String类型

根据上边其他类型处理代码可以看出,String也就是把上述类中代码改成String就可以了,答案是可以的,如下,处理的内容为如果服务器返回字符串类型null,我们将其格式化成“”,空类型,但是我们为什么不直接写,请往下看

public class StringDefaultConverter implements JsonSerializer<String>, JsonDeserializer<String> {
    @Override
    public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        try {
            if (json.getAsString().equals("null")) {
                return "";
            }
        } catch (Exception ignore) {
        }
        try {
            return json.getAsJsonPrimitive().getAsString();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }
    @Override
    public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src);
    }
}

但是有种比较常见的不规范数据返回,为null,不是字符串的"null",是这个null,如果返回null,会进入到上边这个类吗,经过测试,返回null的直接跳过,所以出现了个问题,null到底是什么类型?

通过读源码可知,我们可以自定义TypeAdapter,将其放入facotries中,并且gson在解析json时使用对应的TypeAdapter来的,而我们手动添加的TypeAdapter会优先于预设的TypeAdapter被使用。

于是乎找到了一种其他方法来解决这个问题

新建个类来集成TypeAdapter,这样就便优先于预设的TypeAdapter

public class StringNullAdapter extends TypeAdapter<String> {
    @Override
    public String read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
            reader.nextNull();
            return "";//原先是返回Null,这里改为返回空字符串
        }
        String jsonStr = reader.nextString();
        if(jsonStr.equals("null")) {
            return "";
        }else {
            return jsonStr;
        }
    }
    @Override
    public void write(JsonWriter writer, String value) throws IOException {
        if (value == null) {
            writer.nullValue();
            return;
        }
        writer.value(value);
    }
}

定义的类型为String,这样为null的情况会都归这个类来处理,但是String的所有情况也会走里边的方法,所以为了同样的类型不执行俩遍,Stringnull都在此类处理,只处理一遍就可以了, 处理所有情况为返回null,或字符串"null",格式化为"" 空

五、Retrofit实现cookie自动化管理

对应文章解析

在现实开发中,我们可能会遇到这样的需求,需要保持长登陆状态,登陆失效为服务器判断,在我们不想往接口添加任何参数处理时,我们便想到cookie

最终实现效果为:登录成功后将将服务器返回的cookie保存到本地(每次接口请求成功,更新本地保存Cookie值,目的让本地的cookie值一直为最新的),下次请求接口时将本地最新cookie带上,用来告诉哪个用户与服务器之间的交互

1、 第一种实现方方法(第三方库实现Cookie自动化管理)

(1)依赖第三方库

implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'

(2)创建OkHttpClient时添加cookieJar

PersistentCookieJar cookieJar = new PersistentCookieJar(new  SetCookieCache(), new SharedPrefsCookiePersistor(context));
  OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .addInterceptor(new LoginInterceptor())
                .cookieJar(cookieJar)// 设置封装好的cookieJar
                .build();
2、 第二种实现方方法(涉及到相关三个类)

(1)创建CookieManger类

public class CookieManger implements CookieJar {
    private static Context mContext;
    private static PersistentCookieStore cookieStore;
    public CookieManger(Context context) {
        mContext = context;
        if (cookieStore == null) {
            cookieStore = new PersistentCookieStore(mContext);
        }
    }
    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        if (cookies != null && cookies.size() > 0) {
            for (Cookie item : cookies) {
                cookieStore.add(url, item);
                if (item.name() != null && !TextUtils.isEmpty(item.name()) &&
                        item.value() != null && !TextUtils.isEmpty(item.value())) {
                    /*保存cookie到sp地方  可能会用到 */
//                    PrefUtils.setString(mContext, "cookie_name", item.name());
//                    PrefUtils.setString(mContext, "cookie_value", item.value());
                }
            }
        }
    }
    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url);
        for (int i = 0; i < cookies.size(); i++) {
            Log.e("", "拿出来的cookies name()==" + cookies.get(i).name());
            Log.e("", "拿出来的cookies value()==" + cookies.get(i).value());
        }
        return cookies;
    }
}

(2)创建OkHttpCookies类

public class OkHttpCookies  implements Serializable {
    private transient final Cookie cookies;
    private transient Cookie clientCookies;
    public OkHttpCookies(Cookie cookies) {
        this.cookies = cookies;
    }
    public Cookie getCookies() {
        Cookie bestCookies = cookies;
        if (clientCookies != null) {
            bestCookies = clientCookies;
        }
        return bestCookies;
    }
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(cookies.name());
        out.writeObject(cookies.value());
        out.writeLong(cookies.expiresAt());
        out.writeObject(cookies.domain());
        out.writeObject(cookies.path());
        out.writeBoolean(cookies.secure());
        out.writeBoolean(cookies.httpOnly());
        out.writeBoolean(cookies.hostOnly());
        out.writeBoolean(cookies.persistent());
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        String name = (String) in.readObject();
        String value = (String) in.readObject();
        long expiresAt = in.readLong();
        String domain = (String) in.readObject();
        String path = (String) in.readObject();
        boolean secure = in.readBoolean();
        boolean httpOnly = in.readBoolean();
        boolean hostOnly = in.readBoolean();
        boolean persistent = in.readBoolean();
        Cookie.Builder builder = new Cookie.Builder();
        builder = builder.name(name);
        builder = builder.value(value);
        builder = builder.expiresAt(expiresAt);
        builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
        builder = builder.path(path);
        builder = secure ? builder.secure() : builder;
        builder = httpOnly ? builder.httpOnly() : builder;
        clientCookies =builder.build();
    }
}

(3)创建PersistentCookieStore类

public class PersistentCookieStore {
    private static final String LOG_TAG = "PersistentCookieStore";
    private static final String COOKIE_PREFS = "Cookies_Prefs";
    private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;
    private final SharedPreferences cookiePrefs;
    public PersistentCookieStore(Context context) {
        cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
        cookies = new HashMap<>();
        //将持久化的cookies缓存到内存中 即map cookies
        Map<String, ?> prefsMap = cookiePrefs.getAll();
        for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
            String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
            for (String name : cookieNames) {
                String encodedCookie = cookiePrefs.getString(name, null);
                if (encodedCookie != null) {
                    Cookie decodedCookie = decodeCookie(encodedCookie);
                    if (decodedCookie != null) {
                        if (!cookies.containsKey(entry.getKey())) {
                            cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
                        }
                        cookies.get(entry.getKey()).put(name, decodedCookie);
                    }
                }
            }
        }
    }
    protected String getCookieToken(Cookie cookie) {
        return cookie.name() + "@" + cookie.domain();
    }
    public void add(HttpUrl url, Cookie cookie) {
        String name = getCookieToken(cookie);
        //将cookies缓存到内存中 如果缓存过期 就重置此cookie
        if (!cookie.persistent()) {
            if (!cookies.containsKey(url.host())) {
                cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
            }
            cookies.get(url.host()).put(name, cookie);
        } else {
            if (cookies.containsKey(url.host())) {
                cookies.get(url.host()).remove(name);
            }
        }
        //讲cookies持久化到本地
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
        prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
        prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie)));
        prefsWriter.apply();
    }
    public List<Cookie> get(HttpUrl url) {
        ArrayList<Cookie> ret = new ArrayList<>();
        if (cookies.containsKey(url.host())) {
            ret.addAll(cookies.get(url.host()).values());
        }
        return ret;
    }
    public boolean removeAll() {
        SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
        prefsWriter.clear();
        prefsWriter.apply();
        cookies.clear();
        return true;
    }
    public boolean remove(HttpUrl url, Cookie cookie) {
        String name = getCookieToken(cookie);
        if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {
            cookies.get(url.host()).remove(name);
            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            if (cookiePrefs.contains(name)) {
                prefsWriter.remove(name);
            }
            prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
            prefsWriter.apply();
            return true;
        } else {
            return false;
        }
    }
    public List<Cookie> getCookies() {
        ArrayList<Cookie> ret = new ArrayList<>();
        for (String key : cookies.keySet()) {
            ret.addAll(cookies.get(key).values());
        }
        return ret;
    }
    /**
     * cookies 序列化成 string
     *
     * @param cookie 要序列化的cookie
     * @return 序列化之后的string
     */
    protected String encodeCookie(OkHttpCookies cookie) {
        if (cookie == null) {
            return null;
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(os);
            outputStream.writeObject(cookie);
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in encodeCookie", e);
            return null;
        }
        return byteArrayToHexString(os.toByteArray());
    }
    /**
     * 将字符串反序列化成cookies
     *
     * @param cookieString cookies string
     * @return cookie object
     */
    protected Cookie decodeCookie(String cookieString) {
        byte[] bytes = hexStringToByteArray(cookieString);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Cookie cookie = null;
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            cookie = ((OkHttpCookies) objectInputStream.readObject()).getCookies();
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in decodeCookie", e);
        } catch (ClassNotFoundException e) {
            Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
        }
        return cookie;
    }
    /**
     * 二进制数组转十六进制字符串
     *
     * @param bytes byte array to be converted
     * @return string containing hex values
     */
    protected String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte element : bytes) {
            int v = element & 0xff;
            if (v < 16) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(v));
        }
        return sb.toString().toUpperCase(Locale.US);
    }
    /**
     * 十六进制字符串转二进制数组
     *
     * @param hexString string of hex-encoded values
     * @return decoded byte array
     */
    protected byte[] hexStringToByteArray(String hexString) {
        int len = hexString.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }
}

(4)创建OkHttpClient时添加cookieJar

OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .addInterceptor(new LoginInterceptor())
                .cookieJar(new CookieManger (context))// 设置封装好的cookieJar
                .build();

六、接口成功失败路由判断,处理格式异常情况,如code=1成功,data={},code=100,data=null

* 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题
         * 假如正常情况 返回data为集合
         * code:1
         * msg:获取成功
         * data[ 。。。]
         *
         * 当异常情况下,返回data:{}或者data:""
         * code:0
         * msg:获取失败
         * data:{}或者data:""
         *
         * 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,
         * 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式
         *
         * 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,
         * 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!

当我们处理后台返回数据时,我们会将成功需要的数据提取出来,失败的只提示一下msg,所以通过判断code来区分状态,一般情况下我们可以在onNext()中判断,如下

@Override
    public void onNext(BaseModel<T> o) {
        T t = o.getData();
        try {
           /* if (t!=null){
                L.e("返回数据="+o.toString());
            }else {
                L.e("返回数据=null");
            }*/
            if (view != null) {
                view.hideLoading();
            }
            if (o.getErrcode() == mSuccessCode) {
                onSuccessResult(t, o.getMsg(), o.getErrcode());
            } else {
                view.onErrorResult(o);
            }
        } catch (Exception e) {
            e.printStackTrace();
            onError(e.toString());
        }
    }

假如code=1是成功,获取成功值从onSuccessResult中拿,失败值只要code,msg从回调中onErrorResult拿,

返回的数据规范情况是没有问题的,但是,如果数据不规范,data原本需要{},但是返回了null,或者''",这样GOSN解析立马报异常,所以我们需要向,当我们执行到OnNext方法中,此时已经执行了Gson解析代码,所以我们是否可以将判断提前到Gson解析时候判断呢? 请看第二种方法

2、 第二种判断方法,Gson解析期间判断

如果想通过Gson解析期间判断,这样必然会设计到Gson源码如果走向,我们通过更改源码来自定义操作,通过阅读源码我们会发现解析数据会涉及到三个类,GsonConverterFactory,GsonRequestBodyConverter,GsonResponseBodyConverter这三个类,我们需要重写这个三个类,阅读代码会返现主要执行解析代码在GsonResponseBodyConverter中,所以我们的目标便是这里。

思路:Gosn解析数据时,如果出现服务器下发非正常标识,此刻我们已判断服务器返回数据不是我们需要展示的,那我们解析到这一步已不用再向下解析,可以通过抛异常来释放当前任务代码如下

@Override
    public T convert(ResponseBody value) throws IOException {
        String response = value.string();
        BaseResult re = gson.fromJson(response, BaseResult.class);
        //关注的重点,自定义响应码中非0的情况,一律抛出ApiException异常。
        //这样,我们就成功的将该异常交给onError()去处理了。
        if (re.getCode() != BaseContent.basecode) {
            value.close();
            throw new ApiException(re.getCode(), re.getMessage());
        }
        MediaType mediaType = value.contentType();
        Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
        ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes());
        InputStreamReader reader = new InputStreamReader(bis, charset);
        JsonReader jsonReader = gson.newJsonReader(reader);
        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }

异常已成功抛出,那异常信息到哪里了呢?答案是到Rxjava的OnError中,异常我们抛的是自定义实体类ApiException,内含code,message,那我们到Rxjava中OnError获取到异常信息 e,e instanceof ApiException通过分析异常是否为我们自定义实体类来判断下一步如何操作,此方法为路由的第二种判断,示例如下

@Override
    public void onError(Throwable e) {
        if (mView != null) mView.hideLoading();
        if (e instanceof HttpException) {
            onErrorResult(new BaseModel<>(BAD_NETWORK, "网络超时"));
        } else if (e instanceof ConnectException ||
                e instanceof UnknownHostException) {
            onErrorResult(new BaseModel<>(CONNECT_ERROR, "连接错误"));
        } else if (e instanceof InterruptedIOException) {        //  连接超时
            onErrorResult(new BaseModel<>(CONNECT_TIMEOUT, "连接超时"));
        } else if (e instanceof JsonParseException
                || e instanceof ParseException) {
            onErrorResult(new BaseModel<>(PARSE_ERROR, "数据解析失败"));
        } else if (e instanceof ApiException) {
            /***************************************************************
             * 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题
             * 假如正常情况 返回data为集合
             * code:1
             * msg:获取成功
             * data[ 。。。]
             *
             * 当异常情况下,返回data:{}或者data:""
             * code:0
             * msg:获取失败
             * data:{}或者data:""
             *
             * 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,
             * 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式
             *
             * 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,
             * 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!
             **************************************************************/
            ApiException apiException = (ApiException) e;
            onErrorResult(new BaseModel<>(apiException.getErrorCode(), apiException.getMessage()));
        } else {
            if (e != null) {
                onErrorResult(new BaseModel<>(CONNECT_N, e.toString()));
            } else {
                onErrorResult(new BaseModel<>(CONNECT_N, "未知错误"));
            }
        }
    }
    private void onSuccessResult(BaseModel<T> o) {
        onSuccess(o);
    }
    private void onErrorResult(BaseModel<T> o) {
        if (mView != null) mView.onErrorState(o, mType);
    }
    public abstract void onSuccess(BaseModel<T> o);

七、Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等

相关参考跳转此链接

八、后记

如使用中遇到问题,后记中进行回答讲解

相关文章
|
9天前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
56 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
7天前
|
存储 JavaScript 开发者
探索鸿蒙新世界:ArkUI框架实战指南,解锁HarmonyOS应用UI设计的无限可能!
【10月更文挑战第19天】ArkUI框架是华为鸿蒙系统中用于开发用户界面的核心工具,支持ArkTS和eTS两种开发语言。本文介绍了ArkUI的基本概念、组件使用、布局管理和状态管理,通过示例代码帮助开发者轻松构建美观、高效的跨设备UI。
30 3
|
7天前
|
机器学习/深度学习 人工智能
类人神经网络再进一步!DeepMind最新50页论文提出AligNet框架:用层次化视觉概念对齐人类
【10月更文挑战第18天】这篇论文提出了一种名为AligNet的框架,旨在通过将人类知识注入神经网络来解决其与人类认知的不匹配问题。AligNet通过训练教师模型模仿人类判断,并将人类化的结构和知识转移至预训练的视觉模型中,从而提高模型在多种任务上的泛化能力和稳健性。实验结果表明,人类对齐的模型在相似性任务和出分布情况下表现更佳。
19 3
|
5天前
|
存储 JavaScript 关系型数据库
鸿蒙开发:实现全局异常捕获和异常查看
如何灵活的拿到错误信息后,执行我们想要的逻辑,也是自研的一个诉求,比如全局监听到异常后,重启应用,或者上传到自己的服务器,或者可以在应用内查看等等,实现一个全局异常捕获,确实有很多的有用之处。
鸿蒙开发:实现全局异常捕获和异常查看
|
5天前
|
前端开发 API
鸿蒙开发:走进stateStyles多态样式
stateStyles为多态样式,可以依据组件的内部状态的不同,快速设置不同样式,比如背景颜色,颜色、大小等等常见的通用属性,此种行为,很类似于css中的伪类,但语法稍有不同
鸿蒙开发:走进stateStyles多态样式
|
4天前
|
开发框架 JavaScript 前端开发
HarmonyOS UI开发:掌握ArkUI(包括Java UI和JS UI)进行界面开发
【10月更文挑战第22天】随着科技发展,操作系统呈现多元化趋势。华为推出的HarmonyOS以其全场景、多设备特性备受关注。本文介绍HarmonyOS的UI开发框架ArkUI,探讨Java UI和JS UI两种开发方式。Java UI适合复杂界面开发,性能较高;JS UI适合快速开发简单界面,跨平台性好。掌握ArkUI可高效打造符合用户需求的界面。
27 8
|
1天前
|
安全 测试技术 数据安全/隐私保护
猫头虎分享:鸿蒙生态带给开发者的全新机遇!轻松实现按需加载与多端适配,开发效率翻倍
猫头虎分享:鸿蒙生态带来的全新机遇!华为在原生鸿蒙之夜发布会上,推出了全新的鸿蒙系统和焕新升级的应用市场。此次升级在用户体验和隐私保护方面实现了重大突破,提供了自动化检测前移、按需加载和多端适配等服务,帮助开发者提高开发效率和应用质量。
30 5
|
1天前
|
存储 缓存 Dart
Flutter&鸿蒙next 封装 Dio 网络请求详解:登录身份验证与免登录缓存
本文详细介绍了如何在 Flutter 中使用 Dio 封装网络请求,实现用户登录身份验证及免登录缓存功能。首先在 `pubspec.yaml` 中添加 Dio 和 `shared_preferences` 依赖,然后创建 `NetworkService` 类封装 Dio 的功能,包括请求拦截、响应拦截、Token 存储和登录请求。最后,通过一个登录界面示例展示了如何在实际应用中使用 `NetworkService` 进行身份验证。希望本文能帮助你在 Flutter 中更好地处理网络请求和用户认证。
108 1
|
4天前
|
安全 测试技术 数据安全/隐私保护
|
7天前
|
JavaScript API 开发者
掌握ArkTS,打造HarmonyOS应用新视界:从“Hello World”到状态管理,揭秘鸿蒙UI开发的高效秘诀
【10月更文挑战第19天】ArkTS(ArkUI TypeScript)是华为鸿蒙系统中用于开发用户界面的声明式编程语言,结合了TypeScript和HarmonyOS的UI框架。本文介绍ArkTS的基本语法,包括组件结构、模板和脚本部分,并通过“Hello World”和计数器示例展示其使用方法。
20 1