Retrofit2.0学习

引言

    hello 大家好,没想到吧!我又回来了,回来了,来了,了(武器大师的话,我加了个特技,哈哈)。
    好了开始我们今天的主题吧,昨天有人问我,该用什么网络框架,怎么用,怎么保证内存不泄露?
    看到这个,我还是很激动的,毕竟是我写博客到现在第一个问我的问题。
    那好,我就拿我经常用的网络框架(Retrofit),慢慢欣赏吧(买好瓜子哈)。

OKHTTP

    总结一句话-->"略",哈哈,别打我,我还是个孩子。
    这个不是今天的重点,而且这个东西出来这么久了,大家估计都用了,所以我就不讲了,大家自己脑补吧(google)。

Retrofit版本介绍

    Retrofit是squareup公司完成的一个很吊的开源的网络框架,为什么这样说呢?,因为大家都说好啊。
    其实这里我也想略的,但是又觉得不好意思,所以这次我扔个连接给你们,接住哈!!!

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0915/3460.html

Retrofit2使用

    想必大家看了上面的文章,肯定不会再去使用retrofit1.X版本了,所以我直接就讲Retrofit2.X喽,好了,开始啦!!!
    首先创建一个新的工程(名字随便你们喽)打开app/build.gradle(感觉有点多余哈,哈哈),在dependencies节点下面添加
    (当然大家可以不按照我这个来)
    //compile 'com.squareup.retrofit2:retrofit:2.1.0'
    //retrofit2 转化器 gson
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    //引进Rxjava2.0
    compile 'io.reactivex.rxjava2:rxandroid:2.0.0'
    //log拦截器
    compile 'com.squareup.okhttp3:logging-interceptor:3.3.0'
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
注意:一定要保证添加的版本相同,要不然会有重复包或者冲突呦,直接按照我这个来得了哈,安全,绿色,多好啊。
这里呢大家可能看到了,我为什么把retrofit2.1.0注释掉了?
因为大部分我们都是使用Gson解析,我就直接引入了converter-gson。
这个就是可以直接把返回的数据通过gson解析成对象(都懂的)。
我们打开此包的pom文件(看下面),他已经引进了retrofit2.0版本,所以就不需要喽,以下同理哈(rxandroid- rxjava)

这里写图片描述
好了,大家可能也看到我也引进Rxjava2(rxandroid:2.0.0)了,这个留到最后。包引进之后呢,下面就可以撸代码了。
我们创建一个NetClient.Java,大家看名字就知道了,好了直接上代码

package com.lingchen.netretrofit.net;

import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  网络客户端
 */

public class NetClient {
    //暂时放在这里
    private static final String URL = "http://bobo.yimwing.com";

    /**
     * 获取Retrofit适配器。
     *
     * @return 网络适配器
     */
    public static Retrofit newRetrofit() {
        return new Retrofit.Builder().baseUrl(URL).client(getClient().build())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    /**
     * 获取 OkHttpClient
     *
     * @return OkHttpClient
     */
    public static OkHttpClient.Builder getClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(300, TimeUnit.SECONDS)
                .addInterceptor(new HttpLoggingInterceptor());//日志


    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
    嗯,比较简单的,注释也很清楚哈。现在我们来写一个接口(不要问为什么哈,因为retrofit就是这样规定的,嘻嘻)。
package com.lingchen.netretrofit.net;

/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  网络接口 Retrofit().create()
 */

public interface ComInterface {


}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
我们再来写一个类,来存放Retrofit实例。
/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  网络通用接口
 */

public class ComApi {

    public static ComApi comApi;
    private ComInterface mComInterface;

    public static ComApi getInstance() {
        synchronized (ComApi.class) {
            if (comApi == null) {
                comApi = new ComApi();
            }
        }
        return comApi;
    }

    private ComApi() {
        mComInterface = NetClient.newRetrofit().create(ComInterface.class);
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
好了这里大家可以看到了,此类为单例模式,而且含有retrofit创建出来的mComInterface(这就是retrofit的强大之后了)。
相比以前做过后台,使用过SpringMVC大家肯定会觉得很熟悉,貌似现在还看不出来,哈哈,别着急,现在还不是没有请求吗?)
    准备工作准备了,我们开始来写个请求吧,这是我从公司挑来的,好,我们看下接口
    http://bobo.yimwing.com/api/version/android_new
    当我们拿到这个接口的时候,我们就应该去浏览器看看喽

{“code”:1,”data”:{“versionName”:”1.3.0”,”versionCode”:4,”isQiangzhi”:1,”url”:”http:\/\/bobo-sql.oss-cn-beijing.aliyuncs.com\/app-bobo-release.apk”,”updateContent”:”重要版本更新!”}}
这个就是大家常见的json数据啦,我们使用AS一个插件(GsonFormat)具体安装大家可以google哈。我已经安装了,我直接用啦

这里写图片描述
很明了吧?可以看到code=1肯定就是成功了,data里面存放就是版本的内容了,我们直接点击ok(要预先创建一个新类哈)
这里写图片描述
这里我们就要说下了,从这里就可以看到,后台返回的固定格式是int code,Object data(其实应该还有String msg,只是这个接口没返回而已)这里我之所以用Object,是因为不可能所有的返回类型都是我们版本数据,所以我们就要开始提取了

package com.lingchen.netretrofit.net;

/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  数据响应模型
 */

public class ResBaseModel<T> {
    private static final int CODE_SUCCESS = 1;

    /**
     * data : {}
     * code : 1
     * msg : 操作成功
     */

    private int code;
    private String msg;
    protected T data;

    public String getMsg() {
        return msg;
    }

    public T getData() {
        return data;
    }

    public boolean isSuccess() {
        return CODE_SUCCESS == code;
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
package com.lingchen.netretrofit.net.model;

/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  版本信息
 */

public class VersionModel {

    /**
     * versionName : 1.3.0
     * versionCode : 4
     * isQiangzhi : 1
     * url : http://bobo-sql.oss-cn-beijing.aliyuncs.com/app-bobo-release.apk
     * updateContent : 重要版本更新!
     */

    private String versionName;
    private int versionCode;
    private int isQiangzhi;
    private String url;
    private String updateContent;

    public String getVersionName() {
        return versionName;
    }

    public void setVersionName(String versionName) {
        this.versionName = versionName;
    }

    public int getVersionCode() {
        return versionCode;
    }

    public void setVersionCode(int versionCode) {
        this.versionCode = versionCode;
    }

    public int getIsQiangzhi() {
        return isQiangzhi;
    }

    public void setIsQiangzhi(int isQiangzhi) {
        this.isQiangzhi = isQiangzhi;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUpdateContent() {
        return updateContent;
    }

    public void setUpdateContent(String updateContent) {
        this.updateContent = updateContent;
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

好了,我把我现在的架构发下
这里写图片描述
好了(大家肯定没看到CheckVErsionActivity,肯定没看到,谁会想到我事先做了测试,哈哈,也没办法,毕竟用了Rxjava2.0,上个月刚出来,太多坑哈)。
下面开始写我们的接口了,首先我们打开我们的ComInterface,在里面写下如下代码

    @GET("/api/version/android_new")
    Call<ResBaseModel<VersionModel>> checkVersion();
   
   
  • 1
  • 2
  • 1
  • 2
使用过SpringMVC的兄弟们,是不是感觉很亲近啊?哈哈,一开始我也是觉得很亲切,毕竟我还搞过半年的后台,好吧跑题了。
接下来我们在ComApi写上一个方法
/**
     * 检测版本
     *
     * @return 回调
     */
    public Call<ResBaseModel<VersionModel>> checkVersion() {
        return mComInterface.checkVersion();
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
好了,也没啥东西,这里可能对retrofit没有太多的讲解,不懂的兄弟可以自行去google哈(见谅哈)
我们现在写个activity来试试吧
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="start"
        android:text="请求" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:hint="请求结果" />

</LinearLayout>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  测试校验版本
 * http://bobo.yimwing.com/api/version/android_new
 */

public class CheckVersionActivity extends AppCompatActivity {
    private TextView resultTv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_check_version);
        resultTv = (TextView) findViewById(R.id.tv_result);
    }

    //开始请求
    public void start(View view) {
        Call<ResBaseModel<VersionModel>> call = ComApi.getInstance().checkVersion();
        call.enqueue(new Callback<ResBaseModel<VersionModel>>() {//异步请求
            @Override
            public void onResponse(Call<ResBaseModel<VersionModel>> call, final Response<ResBaseModel<VersionModel>> response) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (response.isSuccessful() && response.body().isSuccess()) {
                            resultTv.setText(response.body().getData().getUpdateContent());
                        } else {
                            resultTv.setText("失败");
                        }
                    }
                });
            }

            @Override
            public void onFailure(Call<ResBaseModel<VersionModel>> call, final Throwable t) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        resultTv.setText(t.getMessage());
                    }
                });
            }
        });
    }
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
开始运行吧

这里写图片描述
成功了。

RxJava2使用

        这里直接就给大家几篇文章,挺不错的,毕竟我也看了,哈哈。

http://gank.io/post/560e15be2dca930e00da1083
http://www.dieyidezui.com/qian-tan-rxjavayu-2-0de-xin-te-xing/
我也写了几个例子,也给大家看看哈

//Rxjava1.x
        Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> e) throws Exception {
                e.onNext("凌晨");
                e.onNext("是");
                e.onNext("大佬");
                e.onError(new Throwable("给大佬低头"));
            }
        }).onErrorResumeNext(Observable.<String>empty()).subscribe();

        //Rxjava2.x
        Flowable.create(new FlowableOnSubscribe<String>() {
            @Override
            public void subscribe(FlowableEmitter<String> e) throws Exception {
                e.onNext("凌晨");
                e.onNext("是");
                e.onNext("大佬");
                e.onError(new Throwable("给大佬低头"));
            }
        }, BackpressureStrategy.BUFFER).doOnNext(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.e(TAG, "accept() called with: s = [" + s + "]");
            }
        }).doOnError(new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                Log.e(TAG, "accept() called with: throwable = [" + throwable.getMessage() + "]");
            }
        }).onErrorResumeNext(Flowable.<String>empty()).subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                Log.e(TAG, "accept() called with: s = [" + s + "]");
            }
        });
        Flowable.just(R.mipmap.ic_launcher)
                .map(new Function<Integer, Bitmap>() {

                    @Override
                    public Bitmap apply(Integer integer) throws Exception {
                        return BitmapFactory.decodeResource(getResources(), integer);
                    }
                }).filter(new Predicate<Bitmap>() {
            @Override
            public boolean test(Bitmap bitmap) throws Exception {
                return bitmap != null;
            }
        }).doOnNext(new Consumer<Bitmap>() {
            @Override
            public void accept(Bitmap bitmap) throws Exception {
                Log.e(TAG, "accept() called with: bitmap = [" + bitmap.getWidth() + "]");
            }
        }).onErrorResumeNext(Flowable.<Bitmap>empty())
                .subscribe();
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
哈哈,就是今天自己去看了下,结合官方wiki,写了2个例子,变化还是蛮大的,好了,大家自己看吧。

Retrofit2+Rxjava2

    我直接上个代码了
//开始请求
    public void start(View view) {
        Flowable.create(new FlowableOnSubscribe<ResBaseModel<VersionModel>>() {
            @Override
            public void subscribe(final FlowableEmitter<ResBaseModel<VersionModel>> e) throws Exception {
                Call<ResBaseModel<VersionModel>> call = ComApi.getInstance().checkVersionCall();
                call.enqueue(new Callback<ResBaseModel<VersionModel>>() {//异步请求
                    @Override
                    public void onResponse(Call<ResBaseModel<VersionModel>> call, final Response<ResBaseModel<VersionModel>> response) {
                        if (!e.isCancelled()) {
                            e.onNext(response.body());
                        }
                        call.cancel();
                        e.onComplete();
                    }

                    @Override
                    public void onFailure(Call<ResBaseModel<VersionModel>> call, final Throwable t) {
                        if (!e.isCancelled()) {
                            e.onError(t);
                        }
                        call.cancel();
                        e.onComplete();
                    }
                });
            }
        }, BackpressureStrategy.BUFFER)
                .doOnNext(new Consumer<ResBaseModel<VersionModel>>() {
                    @Override
                    public void accept(ResBaseModel<VersionModel> versionModelResBaseModel) throws Exception {

                        if (versionModelResBaseModel.isSuccess()) {
                            resultTv.setText(versionModelResBaseModel.getData().getUpdateContent());
                        } else {
                            resultTv.setText("请求失败");
                        }
                    }
                }).subscribeOn(new IoScheduler())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnError(new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        resultTv.setText(throwable.getMessage());
                    }
                })
                .onErrorResumeNext(Flowable.<ResBaseModel<VersionModel>>empty())
                .subscribe();
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
好了,想必大家肯定看的很丑,其实也没办法,为啥呢?因为Rxjava2还没出来retrofit-adapter,大家敬请期待吧。我自己封装了下,给大家看下。
package com.lingchen.netretrofit.net;

import android.support.annotation.NonNull;

import org.reactivestreams.Publisher;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.FlowableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.internal.schedulers.IoScheduler;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  通用接口基类
 * 封装线程调度
 */

public class BaseComApi {


    /**
     * 自己封装的方法,来减少代码亮
     *
     * @param call retrofit的call
     * @param <T>  泛型
     * @return Flowable
     */
    public static <T> Flowable<T> create(@NonNull final Call<T> call) {
        return Flowable.create(new FlowableOnSubscribe<T>() {
            @Override
            public void subscribe(final FlowableEmitter<T> e) throws Exception {
                call.enqueue(new Callback<T>() {
                    @Override
                    public void onResponse(Call<T> call, Response<T> response) {
                        e.onNext(response.body());
                        e.onComplete();
                        call.cancel();
                    }

                    @Override
                    public void onFailure(Call<T> call, Throwable t) {
                        e.onError(t);
                        e.onComplete();
                        call.cancel();
                    }
                });
            }
        }, BackpressureStrategy.BUFFER);
    }

    /**
     * 后台线程执行同步,主线程执行异步操作
     * 并且拦截所有错误,不让app崩溃
     *
     * @param <T> 数据类型
     * @return Transformer
     */
    public static <T> FlowableTransformer<T, T> background() {
        return new FlowableTransformer<T, T>() {
            @Override
            public Publisher<T> apply(Flowable<T> upstream) {
                return upstream.subscribeOn(new IoScheduler())
                        .observeOn(AndroidSchedulers.mainThread())
                        .onErrorResumeNext(Flowable.<T>empty());
            }
        };
    }

}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

那我们再来看看我们的代码调用

 /**
     * 检测版本
     */
    public Flowable<ResBaseModel<VersionModel>> checkVersion() {
        return create(mComInterface.checkVersion())
                .compose(BaseComApi.<ResBaseModel<VersionModel>>background());
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
//开始请求
    public void start(View view) {
        ComApi.getInstance().checkVersion()
                .doOnNext(new Consumer<ResBaseModel<VersionModel>>() {
                    @Override
                    public void accept(ResBaseModel<VersionModel> versionModelResBaseModel) throws Exception {
                        if (versionModelResBaseModel.isSuccess()) {
                            resultTv.setText(versionModelResBaseModel.getData().getUpdateContent());
                        } else {
                            resultTv.setText("请求失败");
                        }
                    }
                })
                .doOnError(new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        resultTv.setText(throwable.getMessage());
                    }
                })
                .subscribe();
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
是不是代码少了好多?嘻嘻,如果使用lambda表达式,更简介(好希望squareup公司赶紧出retrofit2支持Rxjava2的adapter)

网络生命周期管理

    这个是老生长叹的问题了,用过rxjava1.x都知道调用subscribe之后会返回Subscription,然后利用

CompositeSubscription进行统一管理

    //防止RX 导致的内存泄漏
    protected CompositeSubscription subscription = new CompositeSubscription();
    //添加订阅 由 subscription 管理
    protected void add(Subscription sub) {
        if (this.subscription != null && sub != null) {
            this.subscription.add(sub);
        }
    }
    //移除订阅,并且取消订阅
    protected void remove(Subscription sub) {
        if (this.subscription != null && sub != null) {
            this.subscription.remove(sub);
        }
    }
    //移除所有订阅,并且取消订阅
    protected void remove() {
        if (this.subscription != null && !this.subscription.isUnsubscribed()) {
            this.subscription.unsubscribe();
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
    那到了Rxjava2.0又怎么管理呢?我们还是看下我们调用subscribe之后返回的是什么吧
    @BackpressureSupport(BackpressureKind.UNBOUNDED_IN)
    @SchedulerSupport(SchedulerSupport.NONE)
    public final Disposable subscribe() {
        return subscribe(Functions.emptyConsumer(), Functions.ERROR_CONSUMER,
                Functions.EMPTY_ACTION, FlowableInternalHelper.RequestMax.INSTANCE);
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
   /**
     * Represents a disposable resource.
     */
    public interface Disposable {
    /**
     * Dispose the resource, the operation should be idempotent.
     * 处理资源
     */
    void dispose();

    /**
     * Returns true if this resource has been disposed.
     * @return true if this resource has been disposed
     * 是否处理资源
     */
    boolean isDisposed();
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

/**
 * Common interface to add and remove disposables from a container.
 * @since 2.0
 */
public interface DisposableContainer {

    /**
     * Adds a disposable to this container or disposes it if the
     * container has been disposed.
     * @param d the disposable to add, not null
     * @return true if successful, false if this container has been disposed
     */
    boolean add(Disposable d);

    /**
     * Removes and disposes the given disposable if it is part of this
     * container.
     * @param d the disposable to remove and dispose, not null
     * @return true if the operation was successful
     */
    boolean remove(Disposable d);

    /**
     * Removes (but does not dispose) the given disposable if it is part of this
     * container.
     * @param d the disposable to remove, not null
     * @return true if the operation was successful
     */
    boolean delete(Disposable d);
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
看到这里,是不是都晓得了?对了DisposableContainer 是一个接口,那我们看看他们的子类
/**
 * A disposable container that can hold onto multiple other disposables.
 */
public final class ListCompositeDisposable implements Disposable, DisposableContainer {

    List<Disposable> resources;

    volatile boolean disposed;

    public ListCompositeDisposable() {
    }
   ***
 }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
/**
 * A disposable container that can hold onto multiple other disposables and
 * offers O(1) add and removal complexity.
 */
public final class CompositeDisposable implements Disposable, DisposableContainer {

    OpenHashSet<Disposable> resources;

    volatile boolean disposed;

    /**
     * Creates an empty CompositeDisposable.
     */
    public CompositeDisposable() {
    }
    ***
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
我贴出了关键的代码,2个子类使用了不同的数据结构存储,利弊,大家自己选择吧。
好了我们写个BaseActivity,这里我使用ListCompositeDisposable
/**
 * Author    lingchen
 * Email     838878458@qq.com
 * Time      2016/11/4
 * Function  所有界面基类
 */

public class BaseActivity extends AppCompatActivity {
    private ListCompositeDisposable listCompositeDisposable = new ListCompositeDisposable();


    protected void addDisposable(Disposable disposable) {
        if (disposable != null && !disposable.isDisposed()) {
            listCompositeDisposable.add(disposable);
        }
    }

    protected void reDisposable(Disposable disposable) {
        if (disposable != null) {
            listCompositeDisposable.remove(disposable);
        }
    }

    protected void clear() {
        if (!listCompositeDisposable.isDisposed()) {
            listCompositeDisposable.clear();
        }
    }

    @Override
    protected void onDestroy() {
        clear();
        super.onDestroy();
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
    我们修改下刚才的请求
//开始请求
    public void start(View view) {
        //交给父类管理生命周期
        addDisposable(ComApi.getInstance().checkVersion()
                .doOnNext(new Consumer<ResBaseModel<VersionModel>>() {
                    @Override
                    public void accept(ResBaseModel<VersionModel> versionModelResBaseModel) throws Exception {
                        if (versionModelResBaseModel.isSuccess()) {
                            resultTv.setText(versionModelResBaseModel.getData().getUpdateContent());
                        } else {
                            resultTv.setText("请求失败");
                        }
                    }
                })
                .doOnError(new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        resultTv.setText(throwable.getMessage());
                    }
                })
                .subscribe());
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
我擦,差点忘记重点,在封装call转成Flowable的时候没有加上判断,上代码,你们能看懂的
                         Log.e(TAG, "onResponse: ");
                        if (!e.isCancelled()) {
                            Log.e(TAG, "onResponse: no cancel");
                            e.onNext(response.body());
                            e.onComplete();
                        }
                        call.cancel();
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
onFailure我就不放了 一样的,那好,我们现在写个测试一下,我们在开启请求的时候写一个延迟调用父类的clear,代码如下
 //开始请求
    public void start(View view) {
        //交给父类管理生命周期
        addDisposable(ComApi.getInstance().checkVersion()
                .doOnNext(new Consumer<ResBaseModel<VersionModel>>() {
                    @Override
                    public void accept(ResBaseModel<VersionModel> versionModelResBaseModel) throws Exception {
                        if (versionModelResBaseModel.isSuccess()) {
                            resultTv.setText(versionModelResBaseModel.getData().getUpdateContent());
                        } else {
                            resultTv.setText("请求失败");
                        }
                    }
                })
                .doOnError(new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        resultTv.setText(throwable.getMessage());
                    }
                })
                .subscribe());
        //延迟0.1s取消
        resultTv.postDelayed(new Runnable() {
            @Override
            public void run() {
                clear();
            }
        }, 100);
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
好,我们运行下(这里就不妨图片了,因为大家都在吐槽图片看不清,而且不能放大)
11-04 18:22:45.304 21372-21372/com.lingchen.netretrofit E/BaseComApi: onResponse: 
   
   
  • 1
  • 1

大家看到了只有onResponse回调,那么意思走到 if (!e.isCancelled()),我们取消了,所以没有打印onResponse: no cancel,
我们现在把延迟去掉或者延迟时间长一点,在运行下

11-04 18:26:35.631 24805-24805/com.lingchen.netretrofit E/BaseComApi: onResponse: 
11-04 18:26:35.631 24805-24805/com.lingchen.netretrofit E/BaseComApi: onResponse: no cancel
   
   
  • 1
  • 2
  • 1
  • 2

哈哈,大家看懂了吧
到这里大家可能会问,那我重复点击发送,是什么样子呢?我们运行下

11-04 18:26:55.354 24805-24805/com.lingchen.netretrofit E/BaseComApi: onResponse: 
11-04 18:26:55.355 24805-24805/com.lingchen.netretrofit E/BaseComApi: onResponse: no cancel
11-04 18:26:55.472 24805-24805/com.lingchen.netretrofit E/BaseComApi: onResponse: 
11-04 18:26:55.472 24805-24805/com.lingchen.netretrofit E/BaseComApi: onResponse: no cancel
   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

可以发现,确实回来了2次,那我们现在想做的是,如果多次点击,那就取消前面发送的,这个东西呢我们只能自己去封装,去实现DisposableContainer 这个接口,大体的意思可以为发送请求加一个Tag标识符,每当我们add(Disposable )时候,我们就看看我们存储的Disposable有没有标识符是Tag的,如果有,那就直接取消吧,嗯,大体就是这个样子。

结束语

    好了,搞腾了一天了,终于搞定了。感谢大家对我前2个博客的支持,谢谢大家。
    Rxjava2.0刚出来,以后我会出个针对Rxjava2.0的文章。
    下篇文章可能会出retrofit相关的(上传/下载图片或者文件等)!!!
    拜拜喽(今天周五了哈)!!!!下面给出源码

https://github.com/CFlingchen/CSDN_NetRetrofit

感谢&完善

    感谢"yuke"对我这次封装的提的意见(以前公司的同事),我们来看看原来封装的代码:
/**
     * 自己封装的方法,来减少代码亮
     *
     * @param call retrofit的call
     * @param <T>  泛型
     * @return Flowable
     */
    public static <T> Flowable<T> create(@NonNull final Call<T> call) {
        return Flowable.create(new FlowableOnSubscribe<T>() {
            @Override
            public void subscribe(final FlowableEmitter<T> e) throws Exception {
                call.enqueue(new Callback<T>() {
                    @Override
                    public void onResponse(Call<T> call, Response<T> response) {
                        Log.e(TAG, "onResponse: ");
                        if (!e.isCancelled()) {
                            Log.e(TAG, "onResponse: no cancel");
                            e.onNext(response.body());
                            e.onComplete();
                        }
                    }

                    @Override
                    public void onFailure(Call<T> call, Throwable t) {
                        Log.e(TAG, "onFailure() called with: , t = [" + t + "]");
                        if (!e.isCancelled()) {
                            Log.e(TAG, "onResponse: no cancel");
                            e.onError(t);
                            e.onComplete();
                        }
                    }
                });
            }
        }, BackpressureStrategy.BUFFER);
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
第一个问题:我们这里使用的是 call.enqueue,这是异步的调用方法,很明显就没有跟Rxjava线程管理搭上边,所以我们修正下(只是把异步执行转成同步,把线程分配交给Rxjava)。
第二个问题:这个也不是问题吧,是个改善。大家可以看看上面的代码,我们不管你取消不取消当前的网络请求,我们都会去请求网络,只是在回调的时候我们再去判断是否取消了订阅。我们下面假设下面2种情况
    1.网络请求还没发送,我们就取消订阅了。(其实这种情况是不可能发生的,除非你在发送请求的线程做个sleep。这种情况我们就不考虑了,因为是我们自己封装的,也不会在发送请求之前还延迟,所以就不考虑这种情况了)。
    2.网络请求发送了,还没有回调,我们现在取消订阅。我们手动调用call.cancel();就会触发onFailure,异常是sokcet closed(异步,同步是 thread interrupted),那就跟我们以前的做法一样,都是在网络回调做处理,但是,如果我在取消订阅的时候,立马执行call.cancel(),带来的好处就是,会立马触发onFailure,不用等待网络请求回调了(毕竟都不知道会什么时候回调),所以这就体现我手动调用call.cancel()的好处。那好,我们现在上最终的代码:
/**
     * 自己封装的方法,来减少代码亮
     *
     * @param call retrofit的call
     * @param <T>  泛型
     * @return Flowable
     */
    public static <T> Flowable<T> create(@NonNull final Call<T> call) {
        return Flowable.create(new FlowableOnSubscribe<T>() {
            @Override
            public void subscribe(final FlowableEmitter<T> e) throws Exception {
                //设置取消监听
                e.setCancellable(new Cancellable() {
                    @Override
                    public void cancel() throws Exception {
                        Log.e(TAG, "cancel: ");
                        if (!call.isCanceled()) {
                            call.cancel();
                        }
                    }
                });
                //同步执行请求,把线程管理交给Rx
                try {
                    Response<T> response = call.execute();
                    Log.e(TAG, "onResponse: ");
                    if (!e.isCancelled()) {
                        e.onNext(response.body());
                        e.onComplete();
                    }
                } catch (Exception exception) {
                    Log.e(TAG, "exception with: exception = [" + exception.getMessage() + "]");
                    if (!e.isCancelled()) {
                        Log.e(TAG, "onResponse: no cancel");
                        e.onError(exception);
                        e.onComplete();
                    }
                }
            }
        }, BackpressureStrategy.BUFFER);
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
    好了,这里再次感谢"yuke",也希望大家多多提提意见,一起成长!!!代码已经同步到github。
    拜拜!!!
(function () {('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;var numbering = $('
    ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i
    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

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

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

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值