主流第三方库的使用方法
Android开发中必用到许多第三方开源库,开源库设计好,能大量减少工作量,但也要酌情使用。
文本主要写主流第三方库的简单配置和使用方法。
Butter knife
加入到项目的方法
Gradle项目
implementation 'com.jakewharton:butterknife:7.0.1'
另外,还需要下面两个配置:
//支持lint warning 检查机制
lintOptions {
disable 'InvalidPackage'
}
//为什么加入这个呢?防止冲突,比如我同时用了dagger-compiler就会报错,说下面这个`Processor`重复了
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
这样加入了还没有完,我们还要在Proguard中加入下面这些代码(为什么呢?Butterknife的使用和生成的一些类都是动态的,而ProGuard这样的工具可能判定这些类没有被使用而移除他们,所以要在他的配置文件下面做下面的配置):
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
最简单的用法
最简单的肯定是自动关联View了,以前我们都是样板式的代码:
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=(Button)findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Btn Clicked", Toast.LENGTH_SHORT).show();
}
});
}
后来有了ButterKnife就简单了,如下:
class ExampleActivity extends Activity {
@Bind(R.id.title) Button btn;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
// TODO Use fields...
}
}
没有一堆的 findViewById 和强制转换是不是清爽多了呢? 而且,即使这些代码,都可以通过 Android Studio 的插件 Android ButterKnife Zelezny 帮你完成了。
ButterKnife不是通过反射实现了,而是在编译的时候生成的代码,和我们自己写的其实原理一样,比如上面的Button的绑定的实现就类似下面的方法:
public void bind(ExampleActivity activity) {
activity.btn = (android.widget.Button) activity.findViewById(2130968578);
}
@Bind注解对于绑定的成员变量有没有要求呢?其实是有的,如果你试着将它的限定符改为private,在编译的时候就会报错如下:
Error:(21, 20) 错误: @Bind fields must not be private or static. ...
也就是你的成员变量不能是private 或者static修饰了。
更多的绑定
绑定资源
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
// ...
}
绑定其他的资源类似,应该不需要一一列举了吧 不过有时候资源不需要搞成成员变量吧?自己选择吧
非Activity的绑定
比如说 Fragment 中(得到 View ,然后 bind() 方法传入这个 View 的实例):
public class FancyFragment extends Fragment {
@Bind(R.id.button1) Button button1;
@Bind(R.id.button2) Button button2;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
}
在Adapter中的用法
经常会在Adapter中使用ViewHolder,其实你也可以在ViewHolder中使用ButterKnife: 在ViewHolder的构造函数中调用bind,然后成员变量同样的使用@Bind注解。其实这几种绑定原理都一样,就是我们前面编译后的代码那样的方式。
public class MyAdapter extends BaseAdapter {
@Override public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view != null) {
holder = (ViewHolder) view.getTag();
} else {
view = inflater.inflate(R.layout.whatever, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}
holder.name.setText("John Doe");
// etc...
return view;
}
static class ViewHolder {
@Bind(R.id.title) TextView name;
@Bind(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
ButterKnife.bind()还有其他的一些API。
View List批量操作
如果我们有一系列的View放到了一个List里面,就可以进行批量操作了:批量绑定,批量设置属性等。
//批量绑定
@Bind({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
//批量设置
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
其中,DIABLE,ENABLED是我们定义的两个对象,一个是Action,负责执行操作,一个是Setter,负责将值设置为第三个参数:
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
@Override public void apply(View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
@Override public void set(View view, Boolean value, int index) {
view.setEnabled(value);
}
};
也就是第一个apply将所有的nameViews中的View对象设置为disabled,第二个apply将所有的对象的enable属性设置为false(第三个参数)
另外,我们可以直接对属性进行设置,而不需要编写Action和Setter,例如:
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
绑定监听器
ButterKnife还有一个比较常用的功能就是类似@OnClick等的绑定监听器的方法,Android中需要大量的监听器监听用户的操作。示例如下:
@OnClick(R.id.submit)
public void submit(View view) {
// TODO submit data to server...
}
如果不需要绑定的对象,不写也可以:
@OnClick(R.id.submit)
public void submit() {
// TODO submit data to server...
}
而且,这里可以直接将绑定的实例转换成实际的对象:
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
另外,多个View也可以绑定到一个处理方法上:
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}
如果是自定义的View,可以直接绑定到他自己的处理方法上而不需要指定ID
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}
这样,用户点击FancyButton时就会触发该方法。
绑定重置
我们可能需要在Fragment销毁的时候将绑定的View全部设置为null,ButterKnife提供了一个unbind方法自动执行这个操作。
public class FancyFragment extends Fragment {
@Bind(R.id.button1) Button button1;
@Bind(R.id.button2) Button button2;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
@Override public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
}
可选的绑定
如果你给一个绑定添加了一个@Nullable注解,即使对应的资源id不存在也不会报错。有时候我们不能保证这个id一定存在,或者有特殊的需求的时候可以使用
@Nullable @Bind(R.id.might_not_be_there) TextView mightNotBeThere;
@Nullable @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
// TODO ...
}
EventBus
EventBus 是一个 Android 端优化的 publish/subscribe 消息总线 ,简化了应用程序内各组件间、组件与后台线程间的通信。比如请求网络,等网络返回时通过 Handler 或 Broadcast 通知UI,两个 Fragment 之间需要通过Listener 通信,回调很多时会形成回调地狱,这些需求都可以通过 EventBus 实现。
优点
- 简化组件间的通信 (1).对发送和接受事件解耦 (2).可以在Activity,Fragment,和后台线程间执行 (3).避免了复杂的和容易出错的依赖和生命周期问题
- 让你的代码更简洁
- 更快
- 更轻量(jar包小于50K)
- 实践证明已经有一亿多的APP中集成了EventBus
- 拥有先进的功能比如线程分发,用户优先级等等
为什么要使用EventBus
- 简化子线程和主线程之间或两个子线程之间的消息传递 通常我们都会在一个线程中做一些耗时操作得到数据之后需要发送到另一个线程去处理数据(这个线程可以是主线程去更新UI,或子线程继续处理接下来的任务)。这些通信都需要用到 Handler 。我们需要定义Handler ,并重写 handleMessage 方法去处理数据,在使用处定义 Message 对象携带数据并发送到指定的 Handler中。
- **代替 BroadcastReceiver / Intent ** 不同于安卓的BroadcastReceiver/Intent,EventBus 就是普通的java类,它提供很简单易用的API调用。EventBus 适用于更多的场景,并不需要你麻烦的设置 Intent ,设置携带数据,然后从Intent中取出数据,或者定义广播和广播接受者。同时,EventBus的开销会低,对于输出传送方和接收方没有那么高的耦合。
- 简化数据传输 比如:(1)在一个Activity中横向同时加载两个Fragment,左边的ragment是列表,右边的Fragment是详情展示。这时候涉及到Activity和Fragment,两个Fragment之间的通信(2)当使用startService启动一个Service时,你需要和Service相互通信时(3)有些场景需要使用接口处理问题时(4)在应用程序运行时可能有多个界面,多处都需要使用到一个数据时,这个数据只是需要临时存储,并不需要落地时。
加入到项目的方法
添加依赖库:
implementation ‘de.greenrobot:eventbus:3.0.0-beta1’
基本用法
定义一个事件
需要定义的这个事件其实就是一个普通的Java Object(POJO),并没有特殊的要求
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
注册
举个例子,你需要在一个 activity 中注册 eventbus 事件,然后定义接收方法,这和 Android 的广播机制很像,你需要首先注册广播,然后需要编写内部类,实现接收广播,然后操作UI,在 EventBus 中,你同样需要这么做。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
订阅者
类似广播,但是有别于2.4版本,你不必再去约定OnEvent方法开头了:
@Subscribe(threadMode = ThreadMode.MainThread)
public void helloEventBus(String message){
mText.setText(message);
}
该操作很简单,定义了一个hello方法,需要传入 String 参数,在其中操作UI操作,注意: 我们添加了注解 @Subscribe ,其含义为订阅者,在其内传入了 threadMode ,我们定义为ThreadMode.MainThread ,其含义为该方法在UI线程完成,这样你就不要担心抛出异常啦。
发布者
既然你在某个地方订阅了内容,当然就会在某个地方发布消息。举个例子,你的这个activity需要http请求,而http请求你肯定是在异步线程中操作,其返回结果后,你可以这么写:
String json="";
EventBus.getDefault().post(json);
线程分发和线程模型
Event 可以为你处理线程:事件可以被发布到与发布线程不同的线程中。 一般用法是处理UI,在安卓中UI的处理需要在主线程中完成,其他处理比如网络请求,耗时操作不能在主线程中处理。EventBus 帮助你处理这些任务并且同步到UI线程(不用深入研究线程转换,使用 AsyncTask 等等) 在EvenyBus中,在将要调用事件处理方法 onEvent 时可以使用一个ThreadMode去定义这个方法调用的线程。ThreadMode有四种:
-
PostThread,
-
MainThread,
-
BackgroundThread,
-
Async.
PostThread:订阅者将会被调用在与发布线程同样的线程中。这是默认的,事件的分发意味着最小的开销,因为这种模式避免了线程切换。对于简单任务来说这是被推荐的用法。使用这个 mode 需要快速返回结果,避免锁住主线程,因为他可能在主线程执行。
// Called in the same thread (default)
public void onEvent(MessageEvent event) {
log(event.message);
}
**MainThread **:订阅者将被回调在安卓的主线程中。如果发布线程是主线程那事件的处理会马上被执行。同样使用这个 mode 需要快速返回结果,避免锁住主线程。
// Called in Android UI's main thread
public void onEventMainThread(MessageEvent event) {
textField.setText(event.message);
}
**BackgroundThread ** :订阅者将会被回调在子线程中。如果发布线程不是主线程,事件处理会马上被执行在发布线程中。如果发布线程是主线程,EventBus 会使用一个单独的子线程顺序处理事件。虽然是子线程,但是也需要尽快返回结果,避免锁住线程。
// Called in the background thread
public void onEventBackgroundThread (MessageEvent event){
saveToDisk(event.message);
}
Async:事件处理方法会在一个单独的线程中调用。这个线程永远独立与发布线程和主线程。如果需要处理耗时任务时事件处理方法应该使用这个 mode 。比如网络请求。避免在短时间内引发大量的长时间运行的异步任务EventBus使用线程池有效的控制线程数并重用线程
// Called in a separate thread
public void onEventAsync(MessageEvent event){
backend.send(event.message);
}
EventBus 负责在适当的线程中调用 onEvent 方法取决于方法的后缀。
取消事件分发
可以在订阅者的事件处理方法(onEvent)中调用 cancelEventDelivery(Object event) 这个方法去取消事件的分发,任何进一步的事件分发都会被取消,后续的订阅者不会再收到此类事件。
// Called in the same thread (default)
public void onEvent(MessageEvent event){
// Process the event
...
EventBus.getDefault().cancelEventDelivery(event) ;
}
注意:事件通常被高优先级的订阅者取消。取消只能在 ThreadMode 为 PostThread 时,也就是 onEvent 方法中取消。
粘性的事件
一些 event 携带的消息是在 event 被发布后。有这些场景:你需要通过一个 event 去做初始化,或如果你有传感器或一些本地数据并且你想要保存最近的值。你可以使用粘性事件去代替你自己的缓存。EventBus 会把最后一个确定类型的粘性事件保存到内存中。粘性事件可以被订阅者接收或者根据事件类型去查询获取。因此不需要特定的逻辑去验证可用数据。
EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
这段代码之后,一个新的Activity启动了,可以使用registerSticky去注册EventBus,这个注册操作会马上获得之前的发布的事件,在onEvent方法中执行。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().registerSticky(this);
}
public void onEventMainThread(MessageEvent event) {
textField.setText(event.message);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
// 也可以根据确定的类型去获取一个粘性事件
// EventBus.getDefault().getStickyEvent(Class<?> eventType)
注意:可以使用 removeStickyEvent 方法移除之前发布的粘性事件。可以通过事件对象或者事件的类去移除。也可以去构建一个自定义的事件。但是要记住一个类型的事件只有一个会被保存
RxJava
为什么要学 RxJava?
提升开发效率,降低维护成本一直是开发团队永恒不变的宗旨。而就目前的情况,Android 的网络库基本被 Retrofit + OkHttp 一统天下了,而配合上响应式编程 RxJava 可谓如鱼得水。Kotlin 其中有个非常好的优点就是简洁,支持函数式编程,而 rxjava 也是函数式编程的写法。
什么是响应式编程
响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它可以被观测,被过滤,被操作,或者为新的消费者与另外一条流合并为一条新的流。
响应式编程的一个关键概念是事件。事件可以被等待,可以触发过程,也可以触发其它事件。事件是唯一的以合适的方式将我们的现实世界映射到我们的软件中:如果屋里太热了我们就打开一扇窗户。同样的,当我们的天气app从服务端获取到新的天气数据后,我们需要更新app上展示天气信息的UI;汽车上的车道偏移系统探测到车辆偏移了正常路线就会提醒驾驶者纠正,就是是响应事件。
今天,响应式编程最通用的一个场景是UI:我们的移动App必须做出对网络调用、用户触摸输入和系统弹框的响应。在这个世界上,软件之所以是事件驱动并响应的是因为现实生活也是如此。
接口变化
RxJava 2.x 拥有了新的特性,其依赖于4个基础接口,它们分别是
- Publisher
- Subscriber
- Subscription
- Processor
其中最核心的莫过于 Publisher 和 Subscriber。Publisher 可以发出一系列的事件,而 Subscriber 负责和处理这些事件。其中用的比较多的自然是 Publisher 的 Flowable,它支持背压。关于背压给个简洁的定义就是:
背压是指在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略。
简而言之,背压是流速控制的一种策略。有兴趣的可以看一下官方对于背压的讲解。 可以明显地发现,RxJava 2.x 最大的改动就是对于 backpressure 的处理,为此将原来的 Observable 拆分成了新的 Observable 和 Flowable,同时其他相关部分也同时进行了拆分。
观察者模式
大家可能都知道, RxJava 以观察者模式为骨架,在 2.0 中依旧如此。
不过此次更新中,出现了两种观察者模式:
- Observable ( 被观察者 ) / Observer ( 观察者 )
- Flowable (被观察者)/ Subscriber (观察者)
在 RxJava 2.x 中,Observable 用于订阅 Observer,不再支持背压(1.x 中可以使用背压策略),而 Flowable 用于订阅 Subscriber , 是支持背压(Backpressure)的。
Observable
在 RxJava 1.x 中,我们最熟悉的莫过于 Observable 这个类了,此外,由于没有了Subscriber的踪影,我们创建观察者时需使用 Observer。而 Observer 也不是我们熟悉的那个 Observer,又出现了一个 Disposable 参数带你装逼带你飞。
第一步:初始化 Observable
第二步:初始化 Observer
第三步:建立订阅关系
Observable.create(new ObservableOnSubscribe<Integer>() { // 第一步:初始化Observable
@Override
public void subscribe(@NonNull ObservableEmitter<Integer> e) throws Exception {
Log.e(TAG, "Observable emit 1" + "\n");
e.onNext(1);
Log.e(TAG, "Observable emit 2" + "\n");
e.onNext(2);
Log.e(TAG, "Observable emit 3" + "\n");
e.onNext(3);
e.onComplete();
Log.e(TAG, "Observable emit 4" + "\n" );
e.onNext(4);
}
}).subscribe(new Observer<Integer>() { // 第三步:订阅
// 第二步:初始化Observer
private int i;
private Disposable mDisposable;
@Override
public void onSubscribe(@NonNull Disposable d) {
mDisposable = d;
}
@Override
public void onNext(@NonNull Integer integer) {
i++;
if (i == 2) {
// 在RxJava 2.x 中,新增的Disposable可以做到切断的操作,让Observer观察者不再接收上游事件
mDisposable.dispose();
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.e(TAG, "onError : value : " + e.getMessage() + "\n" );
}
@Override
public void onComplete() {
Log.e(TAG, "onComplete" + "\n" );
}
});
首先,创建 Observable 时,回调的是 ObservableEmitter ,字面意思即发射器,并且直接 throws Exception。其次,在创建的 Observer 中,也多了一个回调方法:onSubscribe,传递参数为Disposable,Disposable 相当于 RxJava 1.x 中的 Subscription, 用于解除订阅。可以看到示例代码中,在 i 自增到 2 的时候,订阅关系被切断。
当然,我们的 RxJava 2.x 也为我们保留了简化订阅方法,我们可以根据需求,进行相应的简化订阅,只不过传入对象改为了 Consumer。 Consumer 即消费者,用于接收单个值,BiConsumer 则是接收两个值,Function 用于变换对象,Predicate 用于判断。这些接口命名大多参照了 Java 8 。
线程调度
关于线程切换这点,RxJava 1.x 和 RxJava 2.x 的实现思路是一样的。
subScribeOn 同 RxJava 1.x 一样,subscribeOn 用于指定 subscribe() 时所发生的线程,从源码角度可以看出,内部线程调度是通过 ObservableSubscribeOn来实现的。
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> subscribeOn(Scheduler scheduler) {
ObjectHelper.requireNonNull(scheduler, "scheduler is null");
return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}
ObservableSubscribeOn 的核心源码在 subscribeActual 方法中,通过代理的方式使用 SubscribeOnObserver 包装 Observer 后,设置 Disposable 来将 subscribe 切换到 Scheduler 线程中。
observeOn observeOn 方法用于指定下游 Observer 回调发生的线程。
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
ObjectHelper.requireNonNull(scheduler, "scheduler is null");
ObjectHelper.verifyPositive(bufferSize, "bufferSize");
return RxJavaPlugins.onAssembly(new ObservableObserveOn<T>(this, scheduler, delayError, bufferSize));
}
RxJava 内置的线程调度器的确可以让我们的线程切换得心应手,但其中也有些需要注意的地方。
简单地说,subscribeOn() 指定的就是发射事件的线程,observerOn 指定的就是订阅者接收事件的线程。 多次指定发射事件的线程只有第一次指定的有效,也就是说多次调用 subscribeOn() 只有第一次的有效,其余的会被忽略。 但多次指定订阅者接收线程是可以的,也就是说每调用一次 observerOn(),下游的线程就会切换一次。
RxJava 中,已经内置了很多线程选项供我们选择,例如有:
-
Schedulers.io() 代表 io 操作的线程, 通常用于网络,读写文件等io密集型的操作;
-
Schedulers.computation() 代表CPU计算密集型的操作, 例如需要大量计算的操作;
-
Schedulers.newThread() 代表一个常规的新线程;
-
AndroidSchedulers.mainThread() 代表 Android 的主线程
这些内置的 Scheduler 已经足够满足我们开发的需求,因此我们应该使用内置的这些选项,而 RxJava 内部使用的是线程池来维护这些线程,所以效率也比较高。
操作符
map
map 操作符可以将一个 Observable 对象通过某种关系转换为另一个 Observable 对象。在 2.x 中和 1.x 中作用几乎一致,不同点在于:2.x 将 1.x 中的 Func1 和 Func2 改为了 Function 和 BiFunction。
采用 map 操作符进行网络数据解析
想必大家都知道,很多时候我们在使用 RxJava 的时候总是和 Retrofit 进行结合使用,这里我们就采用 OkHttp3 进行演示,配合 map,doOnNext ,线程切换进行简单的网络请求: 1)通过 Observable.create() 方法,调用 OkHttp 网络请求; 2)通过 map 操作符集合 gson,将 Response 转换为 bean 类; 3)通过 doOnNext() 方法,解析 bean 中的数据,并进行数据库存储等操作; 4)调度线程,在子线程中进行耗时操作任务,在主线程中更新 UI ; 5)通过 subscribe(),根据请求成功或者失败来更新 UI 。
Observable.create(new ObservableOnSubscribe<Response>() {
@Override
public void subscribe(@NonNull ObservableEmitter<Response> e) throws Exception {
Builder builder = new Builder()
.url("https://wushiqian.github.io/")
.get();
Request request = builder.build();
Call call = new OkHttpClient().newCall(request);
Response response = call.execute();
e.onNext(response);
}
}).map(new Function<Response, MobileAddress>() {
@Override
public MobileAddress apply(@NonNull Response response) throws Exception {
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body != null) {
Log.e(TAG, "map:转换前:" + response.body());
return new Gson().fromJson(body.string(), MobileAddress.class);
}
}
return null;
}
}).observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Consumer<MobileAddress>() {
@Override
public void accept(@NonNull MobileAddress s) throws Exception {
Log.e(TAG, "doOnNext: 保存成功:" + s.toString() + "\n");
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<MobileAddress>() {
@Override
public void accept(@NonNull MobileAddress data) throws Exception {
Log.e(TAG, "成功:" + data.toString() + "\n");
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "失败:" + throwable.getMessage() + "\n");
}
});
concat
concat 可以做到不交错的发射两个甚至多个 Observable 的发射事件,并且只有前一个 Observable 终止(onComplete) 后才会订阅下一个 Observable。采用 concat 操作符先读取缓存再通过网络请求获取数据。
想必在实际应用中,很多时候(对数据操作不敏感时)都需要我们先读取缓存的数据,如果缓存没有数据,再通过网络请求获取,随后在主线程更新我们的UI。 利用 concat 的必须调用 onComplete 后才能订阅下一个 Observable 的特性,我们就可以先读取缓存数据,倘若获取到的缓存数据不是我们想要的,再调用 onComplete() 以执行获取网络数据的 Observable,如果缓存数据能应我们所需,则直接调用 onNext(),防止过度的网络请求,浪费用户的流量。
有时候我们的缓存可能还会分为 memory 和 disk ,实际上都差不多,无非是多写点 Observable ,然后通过 concat 合并即可。
flatMap 实现多个网络请求依次依赖
想必这种情况也在实际情况中比比皆是,例如用户注册成功后需要自动登录,我们只需要先通过注册接口注册用户信息,注册成功后马上调用登录接口进行自动登录即可。 我们的 flatMap 恰好解决了这种应用场景,flatMap 操作符可以将一个发射数据的 Observable 变换为多个 Observables ,然后将它们发射的数据合并后放到一个单独的 Observable ,利用这个特性,我们很轻松地达到了我们的需求。
Rx2AndroidNetworking.get("http://www.tngou.net/api/food/list")
.addQueryParameter("rows", 1 + "")
.build()
.getObjectObservable(FoodList.class) // 发起获取食品列表的请求,并解析到FootList
.subscribeOn(Schedulers.io()) // 在io线程进行网络请求
.observeOn(AndroidSchedulers.mainThread()) // 在主线程处理获取食品列表的请求结果
.doOnNext(new Consumer<FoodList>() {
@Override
public void accept(@NonNull FoodList foodList) throws Exception {
// 先根据获取食品列表的响应结果做一些操作
Log.e(TAG, "accept: doOnNext :" + foodList.toString());
mRxOperatorsText.append("accept: doOnNext :" + foodList.toString()+"\n");
}
})
.observeOn(Schedulers.io()) // 回到 io 线程去处理获取食品详情的请求
.flatMap(new Function<FoodList, ObservableSource<FoodDetail>>() {
@Override
public ObservableSource<FoodDetail> apply(@NonNull FoodList foodList) throws Exception {
if (foodList != null && foodList.getTngou() != null && foodList.getTngou().size() > 0) {
return Rx2AndroidNetworking.post("http://www.tngou.net/api/food/show")
.addBodyParameter("id", foodList.getTngou().get(0).getId() + "")
.build()
.getObjectObservable(FoodDetail.class);
}
return null;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<FoodDetail>() {
@Override
public void accept(@NonNull FoodDetail foodDetail) throws Exception {
Log.e(TAG, "accept: success :" + foodDetail.toString());
mRxOperatorsText.append("accept: success :" + foodDetail.toString()+"\n");
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "accept: error :" + throwable.getMessage());
mRxOperatorsText.append("accept: error :" + throwable.getMessage()+"\n");
}
});
善用 zip 操作符,实现多个接口数据共同更新 UI
在实际应用中,我们极有可能会在一个页面显示的数据来源于多个接口,这时候我们的 zip 操作符为我们排忧解难。
zip 操作符可以将多个 Observable 的数据结合为一个数据源再发射出去。
Observable<MobileAddress> observable1 = Rx2AndroidNetworking.get("http://api.avatardata.cn/MobilePlace/LookUp?key=ec47b85086be4dc8b5d941f5abd37a4e&mobileNumber=13021671512")
.build()
.getObjectObservable(MobileAddress.class);
Observable<CategoryResult> observable2 = Network.getGankApi()
.getCategoryData("Android",1,1);
Observable.zip(observable1, observable2, new BiFunction<MobileAddress, CategoryResult, String>() {
@Override
public String apply(@NonNull MobileAddress mobileAddress, @NonNull CategoryResult categoryResult) throws Exception {
return "合并后的数据为:手机归属地:"+mobileAddress.getResult().getMobilearea()+"人名:"+categoryResult.results.get(0).who;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(@NonNull String s) throws Exception {
Log.e(TAG, "accept: 成功:" + s+"\n");
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
Log.e(TAG, "accept: 失败:" + throwable+"\n");
}
});
采用 interval 操作符实现心跳间隔任务
想必即时通讯等需要轮训的任务在如今的 APP 中已是很常见,而 RxJava 2.x 的 interval 操作符可谓完美地解决了我们的疑惑。
这里就简单的意思一下轮训。
private Disposable mDisposable;
@Override
protected void doSomething() {
mDisposable = Flowable.interval(1, TimeUnit.SECONDS)
.doOnNext(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
Log.e(TAG, "accept: doOnNext : "+aLong );
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
Log.e(TAG, "accept: 设置文本 :"+aLong );
mRxOperatorsText.append("accept: 设置文本 :"+aLong +"\n");
}
});
}
/**
* 销毁时停止心跳
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mDisposable != null){
mDisposable.dispose();
}
}
Okhttp3
HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:
- 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
- 连接池减少请求延时
- 透明的GZIP压缩减少响应数据的大小
- 缓存响应内容,避免一些完全重复的请求
当网络出现问题的时候 OkHttp 依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP,OkHttp使用现代TLS技术(SNI, ALPN)初始化新的连接,当握手失败时会回退到TLS 1.0。
OkHttp的使用是非常简单的. 它的请求/响应 API 使用构造器模式builders来设计,它支持阻塞式的同步请求和带回调的异步请求。
引入:
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
异步GET请求
- new OkHttpClient;
- 构造Request对象;
- 通过前两步中的对象构建Call对象;
- 通过Call#enqueue(Callback) 方法来提交异步请求;
String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.get()//默认就是GET请求,可以不写
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: ");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "onResponse: " + response.body().string());
}
});
异步发起的请求会被加入到 Dispatcher 中的 runningAsyncCalls 双端队列中通过线程池来执行。
同步GET请求
前面几个步骤和异步方式一样,只是最后一布是通过 Call#execute() 来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行,否则有可能引起ANR异常,Android3.0 以后已经不允许在主线程访问网络。
String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.build();
final Call call = okHttpClient.newCall(request);
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
Log.d(TAG, "run: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
POST方式
这种方式与前面的区别就是在构造Request对象时,需要多构造一个RequestBody对象,用它来携带我们要提交的数据。在构造 RequestBody 需要指定MediaType,用于描述请求/响应 body 的内容类型,关于 MediaType 的更多信息可以查看 RFC 2045,RequstBody的几种构造方式:
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
String requestBody = "I am Jdqm.";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(mediaType, requestBody))
.build();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message());
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
Log.d(TAG, headers.name(i) + ":" + headers.value(i));
}
Log.d(TAG, "onResponse: " + response.body().string());
}
});
//响应内容
http/1.1 200 OK
Date:Sat, 10 Mar 2018 05:23:20 GMT
Content-Type:text/html;charset=utf-8
Content-Length:18
Server:GitHub.com
Status:200 OK
X-RateLimit-Limit:60
X-RateLimit-Remaining:52
X-RateLimit-Reset:1520661052
X-CommonMarker-Version:0.17.4
Access-Control-Expose-Headers:ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin:*
Content-Security-Policy:default-src 'none'
Strict-Transport-Security:max-age=31536000; includeSubdomains; preload
X-Content-Type-Options:nosniff
X-Frame-Options:deny
X-XSS-Protection:1; mode=block
X-Runtime-rack:0.019668
Vary:Accept-Encoding
X-GitHub-Request-Id:1474:20A83:5CC0B6:7A7C1B:5AA36BC8
onResponse: <p>I am Jdqm.</p>
提交流
RequestBody requestBody = new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MediaType.parse("text/x-markdown; charset=utf-8");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("I am Jdqm.");
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message());
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
Log.d(TAG, headers.name(i) + ":" + headers.value(i));
}
Log.d(TAG, "onResponse: " + response.body().string());
}
});
提交文件
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
OkHttpClient okHttpClient = new OkHttpClient();
File file = new File("test.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(mediaType, file))
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message());
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
Log.d(TAG, headers.name(i) + ":" + headers.value(i));
}
Log.d(TAG, "onResponse: " + response.body().string());
}
});
提交表单
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message());
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
Log.d(TAG, headers.name(i) + ":" + headers.value(i));
}
Log.d(TAG, "onResponse: " + response.body().string());
}
});
提交表单时,使用 RequestBody 的实现类FormBody来描述请求体,它可以携带一些经过编码的 key-value 请求体,键值对存储在下面两个集合中:
private final List<String> encodedNames;
private final List<String> encodedValues;
提交分块请求
MultipartBody 可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如它的 Content-Disposition 。如果 Content-Length 和 Content-Type 可用的话,他们会被自动添加到请求头中。
private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
private void postMultipartBody() {
OkHttpClient client = new OkHttpClient();
// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
MultipartBody body = new MultipartBody.Builder("AaB03x")
.setType(MultipartBody.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(body)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
}
拦截器-interceptor
OkHttp 采用了一种非常典型的面向接口编程,将对 Http 请求的编码及解码等功能抽象成了接口,再通过不同的实现类来实现将相同的 Request 对象编码为 HTTP1 及 HTTP2 的格式的数据,将 HTTP1 及 HTTP2 格式的数据解码为相同格式的 Response 对象。通过这样的一种面向接口的设计,大大地提高了 OkHttp 的可扩展性,可以通过实现接口的形式对更多的应用层进行支持。
OkHttp自带有几个拦截器,使Http 请求功能更完善。
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor
OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor 分为两类:
-
全局的 interceptor,该类 interceptor 在整个拦截器链中最早被调用,通过 OkHttpClient.Builder#addInterceptor(Interceptor) 传入;
-
非网页请求的 interceptor ,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的 interceptor 被保存在 List
interceptors 集合中,按照添加顺序来逐个调用,具体可参考 RealCall#getResponseWithInterceptorChain() 方法。通过 OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 传入; 举一个例子,假如有这样一个需求,我要监控App通过 OkHttp 发出的所有原始请求,以及整个请求所耗费的时间,针对这样的需求就可以使用第一类全局的 interceptor 在拦截器链头去做。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody body = response.body();
if (body != null) {
Log.d(TAG, "onResponse: " + response.body().string());
body.close();
}
}
});
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.nanoTime();
Log.d(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long endTime = System.nanoTime();
Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (endTime - startTime) / 1e6d, response.headers()));
return response;
}
}
针对这个请求,打印出来的结果
Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
Received response for https://publicobject.com/helloworld.txt in 1265.9ms
Server: nginx/1.10.0 (Ubuntu)
Date: Wed, 28 Mar 2018 08:19:48 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes
注意到一点是这个请求做了重定向,原始的 request url 是 http://www.publicobject.com/helloworld.tx,而响应的 request url 是 https://publicobject.com/helloworld.txt,这说明一定发生了重定向,但是做了几次重定向其实我们这里是不知道的,要知道这些的话,可以使用 addNetworkInterceptor()去做。更多的关于 interceptor的使用以及它们各自的优缺点,请移步OkHttp官方说明文档。
其他
推荐让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行你的所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient 实例,显然就是一种资源的浪费。当然,也可以使用如下的方式来创建一个新的 OkHttpClient 实例,它们共享连接池、线程池和配置信息。
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
Response response = eagerClient.newCall(request).execute();
每一个Call(其实现是RealCall)只能执行一次,否则会报异常,具体参见 RealCall#execute()
Retrofit
- 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
- 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装
- App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
- 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析
使用 Retrofit 的步骤共有7个:
- 步骤1:添加Retrofit库的依赖
- 步骤2:创建 接收服务器返回数据 的类
- 步骤3:创建 用于描述网络请求 的接口
- 步骤4:创建 Retrofit 实例
- 步骤5:创建 网络请求接口实例 并 配置网络请求参数
- 步骤6:发送网络请求(异步 / 同步),封装了数据转换、线程切换的操作
- 步骤7:处理服务器返回的数据
1. 添加Retrofit库的依赖
- 在Gradle加入Retrofit库的依赖
由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖 build.gradle
dependencies {
compile 'com.squareup.retrofit2:retrofit:2.0.2'
// Retrofit库
compile 'com.squareup.okhttp3:okhttp:3.1.2'
// Okhttp库
}
- 添加网络权限 AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
2. 创建接收服务器返回数据的类
Reception.java
public class Reception {
...
// 根据返回数据的格式和数据解析方式(Json、XML等)定义
}
3. 创建用于描述网络请求的接口
Retrofit将Http请求抽象成Java接口:采用注解描述网络请求参数和配置网络请求参数
-
用动态代理动态将该接口的注解“翻译”成一个Http请求,最后再执行Http请求
-
注:接口中的每个方法的参数都需要使用注解标注,否则会报错
```java
public interface GetRequest_Interface {
@GET("openapi.do?key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<Translation> getCall();
// @GET注解的作用:采用Get方法发送网络请求
// getCall() = 接收网络请求数据的方法
// 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
// 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
}
下面详细介绍Retrofit网络请求接口的注解类型。
注解说明 第一类:网络请求方法
详细说明: a. @GET、@POST、@PUT、@DELETE、@HEAD 以上方法分别对应 HTTP中的网络请求方式
public interface GetRequest_Interface {
@GET("openapi.do?&key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<Translation> getCall();
// @GET注解的作用:采用Get方法发送网络请求
// getCall() = 接收网络请求数据的方法
// 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
}
此处特意说明URL的组成:Retrofit把网络请求的URL分成了两部分设置:
// 第1部分:在网络请求接口的注解设置
@GET("openapi.do?&key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<Translation> getCall();
// 第2部分:在创建Retrofit实例时通过.baseUrl()设置
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址
.addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
.build();
// 从上面看出:一个请求的URL可以通过替换块和请求方法的参数来进行动态的URL更新。
// 替换块是由 被{}包裹起来的字符串构成
// 即:Retrofit支持动态改变网络请求根目录
网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置(下面称 “path“ )
建议采用第三种方式来配置,并尽量使用同一种路径形式。
b. @HTTP
作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解的作用 及 更多功能拓展 具体使用:通过属性method、path、hasBody进行设置
public interface GetRequest_Interface {
/**
* method:网络请求的方法(区分大小写)
* path:网络请求地址路径
* hasBody:是否有请求体
*/
@HTTP(method = "GET", path = "blog/{id}", hasBody = false)
Call<ResponseBody> getCall(@Path("id") int id);
// {id} 表示是一个变量
// method 的值 retrofit 不会做处理,所以要自行保证准确
}
第二类:标记
a. @FormUrlEncoded
作用:表示发送form-encoded的数据 每个键值对需要用@Filed 来注解键名,随后的对象需要提供值。
b. @Multipart
作用:表示发送form-encoded的数据(适用于 有文件 上传的场景) 每个键值对需要用@Part 来注解键名,随后的对象需要提供值。
具体使用如下: GetRequest_Interface
public interface GetRequest_Interface {
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
}
// 具体使用
GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
// @FormUrlEncoded
Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
// @Multipart
RequestBody name = RequestBody.create(textType, "Carson");
RequestBody age = RequestBody.create(textType, "24");
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
第三类:网络请求参数
详细说明 a. @Header & @Headers
作用:添加请求头 &添加不固定的请求头 具体使用如下:
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
b. @Body
作用:以 Post方式 传递 自定义数据类型 给服务器 特别注意:如果提交的是一个Map,那么作用相当于 @Field 不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");
c. @Field & @FieldMap
作用:发送 Post请求 时提交请求的表单字段 具体使用:与 @FormUrlEncoded 注解配合使用
public interface GetRequest_Interface {
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* Map的key作为表单的键
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
}
// 具体使用
// @Field
Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
// @FieldMap
// 实现的效果与上面相同,但要传入Map
Map<String, Object> map = new HashMap<>();
map.put("username", "Carson");
map.put("age", 24);
Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
d. @Part & @PartMap
作用:发送 Post请求 时提交请求的表单字段 与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景
具体使用:与 @Multipart 注解配合使用
public interface GetRequest_Interface {
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
* 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}
// 具体使用
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "Carson");
RequestBody age = RequestBody.create(textType, "24");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");
// @Part
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
ResponseBodyPrinter.printResponseBody(call3);
// @PartMap
// 实现和上面同样的效果
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
//fileUpload2Args.put("file", file);
Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
ResponseBodyPrinter.printResponseBody(call4);
}
e. @Query和@QueryMap
作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value) 如:url = http://www.println.net/?cate=android,其中,Query = cate
具体使用:配置时只需要在接口方法中增加一个参数即可:
@GET("/")
Call<String> cate(@Query("cate") String cate);
}
// 其使用方式同 @Field与@FieldMap,这里不作过多描述
f. @Path
作用:URL地址的缺省值 具体使用:
public interface GetRequest_Interface {
@GET("users/{user}/repos")
Call<ResponseBody> getBlog(@Path("user") String user );
// 访问的API是:https://api.github.com/users/{user}/repos
// 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
}
g. @Url
作用:直接传入一个请求的 URL变量 用于URL设置 具体使用:
public interface GetRequest_Interface {
@GET
Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 当有URL注解时,@GET传入的URL就可以省略
// 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
}
创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址
.addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台
.build();
a. 关于数据解析器(Converter)
- Retrofit支持多种数据解析方式
- 使用时需要在Gradle添加依赖 数据解析器 Gradle依赖 Gson com.squareup.retrofit2:converter-gson:2.0.2 Jackson com.squareup.retrofit2:converter-jackson:2.0.2
b. 关于网络请求适配器(CallAdapter) Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava 使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加 Retrofit 提供的 CallAdapter
使用时需要在Gradle添加依赖: 网络请求适配器 Gradle依赖 guava com.squareup.retrofit2:adapter-guava:2.0.2 Java8 com.squareup.retrofit2:adapter-java8:2.0.2 rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2
创建网络请求接口实例
// 创建 网络请求接口 的实例
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
//对 发送请求 进行封装
Call<Reception> call = request.getCall();
发送网络请求(异步 / 同步)
//发送网络请求(异步)
call.enqueue(new Callback<Translation>() {
//请求成功时回调
@Override
public void onResponse(Call<Translation> call, Response<Translation> response) {
//请求处理,输出结果
response.body().show();
}
//请求失败时候的回调
@Override
public void onFailure(Call<Translation> call, Throwable throwable) {
System.out.println("连接失败");
}
});
// 发送网络请求(同步)
Response<Reception> response = call.execute();
处理返回数据
通过response类的 body()对返回的数据进行处理
//发送网络请求(异步)
call.enqueue(new Callback<Translation>() {
//请求成功时回调
@Override
public void onResponse(Call<Translation> call, Response<Translation> response) {
// 对返回数据进行处理
response.body().show();
}
//请求失败时候的回调
@Override
public void onFailure(Call<Translation> call, Throwable throwable) {
System.out.println("连接失败");
}
});
// 发送网络请求(同步)
Response<Reception> response = call.execute();
// 对返回数据进行处理
response.body().show();