Android网络编程(十)Retrofit2后篇[注解]

本文首发于微信公众号「刘望舒」

相关文章
Android网络编程系列

前言

在上一篇Android网络编程(九)Retrofit2前篇[基本使用]中我们了解了Retrofit的最基本的GET方式访问网络的写法以及请求参数的简单介绍。这一篇我们来详细的了解Retrofit的注解。

查看更多

分享到 评论

AsyncTask源码分析

本文首发于微信公众号「刘望舒」

查看更多

分享到 评论

设计模式(十一)策略模式

本文首发于微信公众号「刘望舒」

相关文章
设计模式系列

前言

当我们写代码时总会遇到一种情况就是我们会有很多的选择,由此衍生出很多的if…else,或者case。如果每个条件语句中包含了一个简单的逻辑,那还比较容易处理,如果在一个条件语句中又包含了多个条件语句就会使得代码变得臃肿,维护的成本也会加大,这显然违背了开放封闭原则。这一讲我们就来讲策略模式,来看看它是怎么解决如上所说的问题的。

查看更多

分享到 评论

Android网络编程(九)Retrofit2前篇[基本使用]

本文首发于微信公众号「刘望舒」

相关文章
Android网络编程系列

前言

Retrofit是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,而OkHttp现在已经得到Google官方认可,不了解OKHttp的请查看本系列的前作。

查看更多

分享到 评论

渣学历的我,是如何靠自己在北京买房的

前言

首先这不是一篇鸡汤,而是一个真实的经历,买房对于有钱人来说不值一提,对于我来说也没什么可称道的,但是我希望把自己的经历分享出来,给大家带来一些信心和鼓舞:普通出身也可以通过技术、眼光和机遇在北京等一线城市买房。

查看更多

分享到 评论

Android响应式编程(一)RxJava前篇[入门基础]

本文首发于公众号「刘望舒」

1.RxJava概述

ReactiveX与RxJava

在讲到RxJava之前我们首先要了解什么是ReactiveX,因为RxJava是ReactiveX的一种java实现。
ReactiveX是Reactive Extensions的缩写,一般简写为Rx,微软给的定义是,Rx是一个函数库,让开发者可以利用可观察序列和LINQ风格查询操作符来编写异步和基于事件的程序,开发者可以用Observables表示异步数据流,用LINQ操作符查询异步数据流, 用Schedulers参数化异步数据流的并发处理,Rx可以这样定义:Rx = Observables + LINQ + Schedulers。

为何要用RxJava

想到异步的操作我们会想到android的AsyncTask 和Handler,但是随着请求的数量越来越多,代码逻辑将会变得越来越复杂而RxJava却仍旧能保持清晰的逻辑。RxJava的原理就是创建一个Observable对象来干活,然后使用各种操作符建立起来的链式操作,就如同流水线一样把你想要处理的数据一步一步地加工成你想要的成品然后发射给Subscriber。

RxJava与观察者模式

RxJava的异步操作是通过扩展的观察者模式来实现的,不了解观察者模式的可以先看下 设计模式(五)观察者模式这篇文章Rxjava有四个基本的要素:Observable (被观察者)、 Observer (观察者)、 subscribe (订阅)、event(事件)。Observable (被观察者) 和 Observer (观察者)通过 subscribe() 方法实现订阅关系,Observable就可以在需要的时候来通知Observer。

2.RxJava基本用法

在使用RxJava前请现在Android Studio 配置gradle:

dependencies {
    ...
    compile 'io.reactivex:rxjava:1.1.6'
    compile 'io.reactivex:rxandroid:1.2.1'
}

其中RxAndroid是RxJava的一部分,在普通的RxJava基础上添加了几个有用的类,比如特殊的调度器,后文会提到。

RxJava的基本用法分为三个步骤,他们分别是:

创建Observer(观察者)

决定事件触发的时候将有怎样的行为

   Subscriber subscriber=new Subscriber<String>() {
    @Override
    public void onCompleted() {
        Log.i("wangshu","onCompleted");
    }

    @Override
    public void onError(Throwable e) {
        Log.i("wangshu","onError");
    }

    @Override
    public void onNext(String s) {
        Log.i("wangshu","onNext"+s);
    }

    @Override
    public void onStart() {
        Log.i("wangshu","onStart");
    }
};

其中onCompleted、onError和onNext是必须要实现的方法,他们的含义分别是:

  • onCompleted:事件队列完结,RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。当不会再有新的 onNext发出时,需要触发 onCompleted() 方法作为完成标志。
  • onError:事件队列异常,在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
  • onNext:普通的事件,将要处理的事件添加到事件队列中。
  • onStart:它会在事件还未发送之前被调用,可以用于做一些准备工作。例如数据的清零或重置,这是一个可选方法,默认情况下它的实现为空。

当然如果要实现简单的功能也可以用到Observer来创建观察者,Observer是一个接口,而上面用到Subscriber是在Observer基础上进行了扩展,在后文的Subscribe订阅过程中Observer也会先被转换为Subscriber来使用。

Observer<String> observer = new Observer<String>() {
    @Override
    public void onCompleted() {
        Log.i("wangshu", "onCompleted");
    }

    @Override
    public void onError(Throwable e) {
        Log.i("wangshu", "onError");
    }

    @Override
    public void onNext(String s) {
        Log.i("wangshu", "onNext" + s);
    }
};

创建 Observable(被观察者)

它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则:

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> subscriber) {
            subscriber.onNext("杨影枫");
            subscriber.onNext("月眉儿");
            subscriber.onCompleted();
        }
    });

通过调用subscriber的方法,不断的将事件添加到任务队列中,也可用just来实现:

Observable observable = Observable.just("杨影枫", "月眉儿");

上述的代码会依次调用onNext(“杨影枫”)、onNext(“月眉儿”)、onCompleted()。

Subscribe (订阅)

订阅比较简单:

observable.subscribe(subscriber);

或者也可以调用

observable.subscribe(observer);

运行代码查看log:

com.example.liuwangshu.moonrxjava I/wangshu: onStart
com.example.liuwangshu.moonrxjava I/wangshu: onNext杨影枫
com.example.liuwangshu.moonrxjava I/wangshu: onNext月眉儿
com.example.liuwangshu.moonrxjava I/wangshu: onCompleted

3.不完整定义回调

上文介绍了回调的接收主要是依赖subscribe(Observer) 和 subscribe(Subscriber),除此之外RxJava还提供了另一种回调方式,也就是不完整回调。再讲到不完整回调之前我们首先要了解Action,查看RxJava源码我们发现提供了一堆Action:

我们打开Action0来看看:

public interface Action0 extends Action {
    void call();
}

再打开Action1:

public interface Action1<T> extends Action {
    void call(T t);
}

最后看看Action9:

public interface Action9<T1, T2, T3, T4, T5, T6, T7, T8, T9> extends Action {
    void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9);
}

很明显Action后的数字代表回调的参数类型数量,上文订阅也就可以改写为下面的代码:

Action1<String> onNextAction = new Action1<String>() {
    @Override
    public void call(String s) {
        Log.i("wangshu", "onNext" + s);
    }
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
    @Override
    public void call(Throwable throwable) {

    }
};
Action0 onCompletedAction = new Action0() {
    @Override
    public void call() {
        Log.d("wangshu", "onCompleted");
    }
};
observable.subscribe(onNextAction,onErrorAction,onCompletedAction);

我们定义了onNextAction来处理onNext的回调,同理我们还定义了onErrorAction和onCompletedAction,最后我们把他传给subscribe方法。很显然这样写的灵活度很大一些,同时我们也可以只传一个或者两个Action:

observable.subscribe(onNextAction);
observable.subscribe(onNextAction,onErrorAction);

第一行只定义了onNextAction来处理onNext的回调,而第二行则定义了onNextAction处理onNext的回调,onErrorAction来处理onError的回调。

4.Scheduler

内置的Scheduler

方才我们所做的都是运行在主线程的,如果我们不指定线程,默认是在调用subscribe方法的线程上进行回调的,如果我们想切换线程就需要使用Scheduler。RxJava 已经内置了5个 Scheduler:

  • Schedulers.immediate():默认的,直接在当前线程运行,相当于不指定线程。
  • Schedulers.newThread():总是启用新线程,并在新线程执行操作。
  • Schedulers.io():I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。
  • Schedulers.computation():计算所使用的 Scheduler,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  • Schedulers.trampoline():当我们想在当前线程执行一个任务时,并不是立即时,可以用.trampoline()将它入队。这个调度器将会处理它的队列并且按序运行队列中每一个任务。

另外RxAndroid也提供了一个常用的Scheduler:

  • AndroidSchedulers.mainThread():RxAndroid库提供的Scheduler,它指定的操作在主线程中运行。

控制线程

subscribeOn() 和 observeOn() 两个方法来对线程进行控制。
subscribeOn()方法指定 subscribe() 这个方法所在的线程,即事件产生的线程。observeOn()方法指定 Subscriber 回调所运行在的线程,即事件消费的线程。

Action1<String> onNextAction = new Action1<String>() {
            @Override
            public void call(String s) {
                Log.i("wangshu", "onNext" + s);

            }
        };
Observable observable = Observable.just("杨影枫", "月眉儿"); 
            observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(onNextAction);

我们仍旧是用log打印出onNext事件所传递过来的字符串,只不过这一次事件的产生的线程是在io线程上,事件回调的线程则是在主线程。

###5.RxJava基础应用
好了,讲的不是很多,我们来举一个例子来消化上面的知识。RxJava+Retrofit访问网络是比较搭的,但是此前我的网络系列并没有介绍Retrofit,所以我们先准备用RxJava+OKHttp来访问网络,至于RxJava+Retrofit访问网络会在此系列的以后的章节做介绍。OKHttp的用法请详见Android网络编程(六)OkHttp3用法全解析这篇文章。
此前我们用OkHttp3访问网络是这样做的:

  private void postAsynHttp(int size) {
    mOkHttpClient=new OkHttpClient();
    RequestBody formBody = new FormBody.Builder()
            .add("size", size+"")
            .build();
    Request request = new Request.Builder()
            .url("http://api.1-blog.com/biz/bizserver/article/list.do")
            .post(formBody)
            .build();
    Call call = mOkHttpClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {

        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            String str = response.body().string();
            Log.i("wangshu", str);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
                }
            });
        }

    });
}

接下来我们进行改造,首先我们创建Observable(被观察者):

  private Observable<String> getObservable(final int size){
    Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(final Subscriber<? super String> subscriber) {
            mOkHttpClient=new OkHttpClient();
            RequestBody formBody = new FormBody.Builder()
                    .add("size",size+"")
                    .build();
            Request request = new Request.Builder()
                    .url("http://api.1-blog.com/biz/bizserver/article/list.do")
                    .post(formBody)
                    .build();
            Call call = mOkHttpClient.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    subscriber.onError(new Exception("error"));
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String str = response.body().string();
                    subscriber.onNext(str);
                    subscriber.onCompleted();
                }
            });
        }
    });
 return observable;
}

我们将根据Okhttp的回调(不在主线程)来定义事件的规则,调用subscriber.onNext来将请求返回的数据添加到事件队列中。接下来我们来实现观察者:

private void postAsynHttp(int size){   
getObservable(size).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<String>() {
           @Override
           public void onCompleted() {
               Log.i("wangshu", "onCompleted");
           }

           @Override
           public void onError(Throwable e) {
             Log.i("wangshu", e.getMessage());
           }

           @Override
           public void onNext(String s) {
               Log.i("wangshu", s);
               Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();
           }
       });
   }

我们将访问网络回调设置为主线程,所以Toast是能正常显示的。好了这一篇就讲到这里,关于RxJava的文章后期还会写,敬请期待。

参考资料
[译] ReactiveX 的理念和特点
RxJava快速入门
给 Android 开发者的 RxJava 详解
谜之RxJava (三)update 2 —— subscribeOn 和 observeOn 的区别
RxJava开发精要3-向响应式世界问好
详细解析RxAndroid的使用方式
RxJava学习笔记2:基于RxJava+okHttp的Rest Cas登录实现
RxJava(01-介绍与初体验)

分享到 评论

Android网络编程(八)源码解析OkHttp后篇[复用连接池]

本文首发于微信公众号「刘望舒」

相关文章
Android网络编程系列

1.引子

在了解OkHttp的复用连接池之前,我们首先要了解几个概念。

TCP三次握手

通常我们进行HTTP连接网络的时候我们会进行TCP的三次握手,然后传输数据,然后再释放连接。
VKJZTI.jpg

TCP三次握手的过程为:

  • 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
  • 第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

TCP四次分手

当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,断开连接就需要进行TCP四次分手:

  • 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment
    Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  • 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence
  • 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
  • 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

来看下面的图加强下理解:
VKJntP.jpg

keepalive connections

当然大量的连接每次连接关闭都要三次握手四次分手的很显然会造成性能低下,因此http有一种叫做keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而不需要再次握手。
VKJmkt.jpg

Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间)。

2.连接池(ConnectionPool)分析

引用计数

在okhttp中,在高层代码的调用中,使用了类似于引用计数的方式跟踪Socket流的调用,这里的计数对象是StreamAllocation,它被反复执行aquire与release操作,这两个函数其实是在改变RealConnection中的List<Reference<StreamAllocation>> 的大小。(StreamAllocation.java)

public void acquire(RealConnection connection) {
  connection.allocations.add(new WeakReference<>(this));
}
private void release(RealConnection connection) {
  for (int i = 0, size = connection.allocations.size(); i < size; i++) {
    Reference<StreamAllocation> reference = connection.allocations.get(i);
    if (reference.get() == this) {
      connection.allocations.remove(i);
      return;
    }
  }
  throw new IllegalStateException();
}

RealConnection是socket物理连接的包装,它里面维护了List<Reference<StreamAllocation>>的引用。List中StreamAllocation的数量也就是socket被引用的计数,如果计数为0的话,说明此连接没有被使用就是空闲的,需要通过下文的算法实现回收;如果计数不为0,则表示上层代码仍然引用,就不需要关闭连接。

主要变量

连接池的类位于okhttp3.ConnectionPool:

private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
     Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
     new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

 /** The maximum number of idle connections for each address. */
 //空闲的socket最大连接数
 private final int maxIdleConnections;
 //socket的keepAlive时间
 private final long keepAliveDurationNs;
 // 双向队列
 private final Deque<RealConnection> connections = new ArrayDeque<>();
 final RouteDatabase routeDatabase = new RouteDatabase();
 boolean cleanupRunning;

主要的变量有必要说明一下:

  • executor线程池,类似于CachedThreadPool,需要注意的是这种线程池的工作队列采用了没有容量的SynchronousQueue,不了解它的请查看Java并发编程(六)阻塞队列这篇文章。
  • Deque<RealConnection>,双向队列,双端队列同时具有队列和栈性质,经常在缓存中被使用,里面维护了RealConnection也就是socket物理连接的包装。
  • RouteDatabase,它用来记录连接失败的Route的黑名单,当连接失败的时候就会把失败的线路加进去。

    构造函数

    public ConnectionPool() {
    //默认空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟
      this(5, 5, TimeUnit.MINUTES);
    }
    public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
      this.maxIdleConnections = maxIdleConnections;
      this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
    
      // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
      if (keepAliveDuration <= 0) {
        throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
      }
    }
    

通过构造函数可以看出ConnectionPool默认的空闲的socket最大连接数为5个,socket的keepAlive时间为5分钟。

实例化

ConnectionPool实例化是在OkHttpClient实例化时进行的:

public OkHttpClient() {
  this(new Builder());
}

在OkHttpClient的构造函数中调用了new Builder():

public Builder() {
    dispatcher = new Dispatcher();
   ...省略
    connectionPool = new ConnectionPool();
   ...省略
  }

缓存操作

ConnectionPool提供对Deque<RealConnection>进行操作的方法分别为put、get、connectionBecameIdle和evictAll几个操作。分别对应放入连接、获取连接、移除连接和移除所有连接操作,这里我们举例put和get操作。

put操作

void put(RealConnection connection) {
  assert (Thread.holdsLock(this));
  if (!cleanupRunning) {
    cleanupRunning = true;
    executor.execute(cleanupRunnable);
  }
  connections.add(connection);
}

在添加到Deque<RealConnection>之前首先要清理空闲的线程,这个后面会讲到。

get操作

RealConnection get(Address address, StreamAllocation streamAllocation) {
  assert (Thread.holdsLock(this));
  for (RealConnection connection : connections) {
    if (connection.allocations.size() < connection.allocationLimit
        && address.equals(connection.route().address)
        && !connection.noNewStreams) {
      streamAllocation.acquire(connection);
      return connection;
    }
  }
  return null;
}

遍历connections缓存列表,当某个连接计数的次数小于限制的大小并且request的地址和缓存列表中此连接的地址完全匹配。则直接复用缓存列表中的connection作为request的连接。

自动回收连接

okhttp是根据StreamAllocation引用计数是否为0来实现自动回收连接的。我们在put操作前首先要调用executor.execute(cleanupRunnable)来清理闲置的线程。我们来看看cleanupRunnable到底做了什么:

private final Runnable cleanupRunnable = new Runnable() {
  @Override public void run() {
    while (true) {
      long waitNanos = cleanup(System.nanoTime());
      if (waitNanos == -1) return;
      if (waitNanos > 0) {
        long waitMillis = waitNanos / 1000000L;
        waitNanos -= (waitMillis * 1000000L);
        synchronized (ConnectionPool.this) {
          try {
            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
          } catch (InterruptedException ignored) {
          }
        }
      }
    }
  }
};

线程不断的调用cleanup来进行清理,并返回下次需要清理的间隔时间,然后调用wait进行等待以释放锁与时间片,当等待时间到了后,再次进行清理,并返回下次要清理的间隔时间,如此循环下去,接下来看看cleanup方法:

long cleanup(long now) {
  int inUseConnectionCount = 0;
  int idleConnectionCount = 0;
  RealConnection longestIdleConnection = null;
  long longestIdleDurationNs = Long.MIN_VALUE;

  // Find either a connection to evict, or the time that the next eviction is due.
  synchronized (this) {
  //遍历连接
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
      RealConnection connection = i.next();
      //查询此连接的StreamAllocation的引用数量,如果大于0则inUseConnectionCount数量加1,否则idleConnectionCount加1
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        inUseConnectionCount++;
        continue;
      }
      idleConnectionCount++;
      long idleDurationNs = now - connection.idleAtNanos;
      if (idleDurationNs > longestIdleDurationNs) {
        longestIdleDurationNs = idleDurationNs;
        longestIdleConnection = connection;
      }
    }
    //如果空闲连接keepAlive时间超过5分钟,或者空闲连接数超过5个,则从Deque中移除此连接
    if (longestIdleDurationNs >= this.keepAliveDurationNs
        || idleConnectionCount > this.maxIdleConnections) {
      // We've found a connection to evict. Remove it from the list, then close it below (outside
      // of the synchronized block).
      connections.remove(longestIdleConnection);
     //如果空闲连接大于0,则返回此连接即将到期的时间
    } else if (idleConnectionCount > 0) {
      // A connection will be ready to evict soon.
      return keepAliveDurationNs - longestIdleDurationNs;
      //如果没有空闲连接,并且活跃连接大于0则返回5分钟
    } else if (inUseConnectionCount > 0) {
      // All connections are in use. It'll be at least the keep alive duration 'til we run again.
      return keepAliveDurationNs;
    } else {
    //如果没有任何连接则跳出循环
      cleanupRunning = false;
      return -1;
    }
  }

  closeQuietly(longestIdleConnection.socket());

  // Cleanup again immediately.
  return 0;
}

cleanup所做的简单总结就是根据连接中的引用计数来计算空闲连接数和活跃连接数,然后标记出空闲的连接,如果空闲连接keepAlive时间超过5分钟,或者空闲连接数超过5个,则从Deque中移除此连接。接下来根据空闲连接或者活跃连接来返回下次需要清理的时间数:如果空闲连接大于0则返回此连接即将到期的时间,如果都是活跃连接并且大于0则返回默认的keepAlive时间5分钟,如果没有任何连接则跳出循环并返回-1。在上述代码中的第13行,通过pruneAndGetAllocationCount方法来判断连接是否闲置的,如果pruneAndGetAllocationCount方法返回值大于0则是空闲连接,否则就是活跃连接,让我们来看看pruneAndGetAllocationCount方法:

private int pruneAndGetAllocationCount(RealConnection connection, long now) {
  List<Reference<StreamAllocation>> references = connection.allocations;
  //遍历弱引用列表
  for (int i = 0; i < references.size(); ) {
    Reference<StreamAllocation> reference = references.get(i);
    //若StreamAllocation被使用则接着循环
    if (reference.get() != null) {
      i++;
      continue;
    }

    // We've discovered a leaked allocation. This is an application bug.
    Internal.logger.warning("A connection to " + connection.route().address().url()
        + " was leaked. Did you forget to close a response body?");
    //若StreamAllocation未被使用则移除引用
    references.remove(i);
    connection.noNewStreams = true;

    // If this was the last allocation, the connection is eligible for immediate eviction.
    //如果列表为空则说明此连接没有被引用了,则返回0,表示此连接是空闲连接
    if (references.isEmpty()) {
      connection.idleAtNanos = now - keepAliveDurationNs;
      return 0;
    }
  }
  //否则返回非0的数,表示此连接是活跃连接
  return references.size();
}

pruneAndGetAllocationCount方法首先遍历传进来的RealConnection的StreamAllocation列表,如果StreamAllocation被使用则接着遍历下一个StreamAllocation,如果StreamAllocation未被使用则从列表中移除。如果列表为空则说明此连接没有引用了,则返回0,表示此连接是空闲连接,否则就返回非0的数表示此连接是活跃连接。

总结

可以看出连接池复用的核心就是用Deque<RealConnection>来存储连接,通过put、get、connectionBecameIdle和evictAll几个操作来对Deque进行操作,另外通过判断连接中的计数对象StreamAllocation来进行自动回收连接。

参考资料
okhttp3源码
简析TCP的三次握手与四次分手
TCP三次握手过程
短连接、长连接与keep-alive
OkHttp3源码分析[复用连接池]
okhttp连接池复用机制

分享到 评论

设计模式(十)工厂方法模式

本文首发于微信公众号「刘望舒」

相关文章
设计模式系列

前言

在此前的设计模式(四)简单工厂模式中我们介绍了简单工厂模式,在这篇文章中我们来介绍下工厂方法模式,它同样是创建型设计模式,而且又有些类似,文章的末尾会介绍他们之间的不同。

查看更多

分享到 评论

Android架构(一)MVP全解析

本文首发于微信公众号「刘望舒」

前言

关于架构的文章,博主很早就想写了,虽说最近比较流行MVVM,但是MVP以及MVC也没有过时之说,最主要还是要根据业务来选择合适的架构。当然现在写MVP的文章很多,也有很多好的文章,但是大多数看完后还是一头雾水,用最少的文字表述清楚是我一贯的风格(这里小小的装逼一下),所以还是自己总结比较靠谱。

查看更多

分享到 评论

Android事件总线(二)EventBus3.0源码解析

本文首发于微信公众号「刘望舒」

前言

上一篇我们讲到了EventBus3.0的用法,这一篇我们来讲一下EventBus3.0的源码以及它的利与弊。

查看更多

分享到 评论

设计模式(九)模版方法模式

本文首发于微信公众号「刘望舒」

相关文章
设计模式系列

1.模版方法模式简介

模版方法模式介绍

在软件开发中,有时会遇到类似的情况,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用模板方法模式来应对这类情况。

模版方法模式定义

定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。

模版方法模式结构图

  • AbstractClass:抽象类,定义了一套算法。
  • ConcreteClass:具体实现类。

2.模版方法模式的简单实现

延续着上一篇设计模式(八)外观模式的写法,我们仍旧来举一个武侠的例子,原谅博主是一个武侠迷。

创建抽象类,定义算法框架

一个武侠要战斗的时候,也有一套固定的通用模式,那就是运行内功、开通经脉、准备武器和使用招式,我们把这些用代码表示就是:

public abstract class AbstractSwordsman {
  //该方法为final,防止算法框架被覆写
  public final void fighting(){
      //运行内功,抽象方法
      neigong();
      //调整经脉,具体方法
      meridian();
      //如果有武器则准备武器
      if(hasWeapons()) {
          weapons();
      }
      //使用招式
      moves();
      //钩子方法
      hook();
  }
    //空实现方法
    protected void hook(){}
    protected abstract void neigong();
    protected abstract void weapons();
    protected abstract void moves();
    protected void meridian(){
        System.out.println("开通正经与奇经");
    }

    /**
     * 是否有武器,默认是有武器的,钩子方法
     * @return
     */
    protected boolean hasWeapons(){
         return true;
    }
}

需要注意的是这个抽象类包含了三种类型的方法,分别是抽象方法、具体方法和钩子方法。抽象方法是交由子类去实现,具体方法则在父类实现了子类公共的方法实现,在上面的例子就是武侠开通经脉的方式都一样,所以就在具体方法中实现。钩子方法则分为两类,第一类是15行,它有一个空实现的方法,子类可以视情况来决定是否要覆盖它;第二类则是第9行,这类钩子方法的返回类型通常是bool类型的,一般用于对某个条件进行判断,如果条件满足则执行某一步骤,否则将不执行。

定义具体实现类

本文就拿张无忌、张三丰来作为例子:

public class ZhangWuJi extends AbstractSwordsman {

    @Override
    protected void neigong() {
        System.out.println("运行九阳神功");
    }

    @Override
    protected void weapons() {
    }

    @Override
    protected void moves() {
        System.out.println("使用招式乾坤大挪移");
    }

    @Override
    protected boolean hasWeapons() {
        return false;
    }
}

张无忌没有武器所以hasWeapons方法返回false,这样也不会走weapons方法了。

public class ZhangSanFeng extends AbstractSwordsman {

    @Override
    protected void neigong() {
        System.out.println("运行纯阳无极功");
    }

    @Override
    protected void weapons() {
        System.out.println("使用真武剑");
    }

    @Override
    protected void moves() {
        System.out.println("使用招式神门十三剑");
    }

    @Override
    protected void hook() {
        System.out.println("突然肚子不舒服,老夫先去趟厕所");
    }
}

最后张三丰突然肚子不舒服所以就实现了钩子方法hook。

客户端调用

public class Client {
    public static void main(String[] args) {
        ZhangWuJi zhangWuJi=new ZhangWuJi();
        zhangWuJi.fighting();
        ZhangSanFeng zhangSanFeng=new ZhangSanFeng();
        zhangSanFeng.fighting();
    }
}

运行结果:
运行九阳神功
开通正经与奇经
使用招式乾坤大挪移
运行纯阳无极功
开通正经与奇经
使用真武剑
使用招式神门十三剑
突然肚子不舒服,老夫先去趟厕所

4.模版方法模式的优缺点和使用场景

优点

  • 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
  • 子类实现算法的某些细节,有助于算法的扩展。

缺点

  • 每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。

使用场景

  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
  • 面对重要复杂的算法,可以把核心算法设计为模版方法,周边相关细节功能则有各个子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
分享到 评论

Android事件总线(一)EventBus3.0用法全解析

本文首发于微信公众号「刘望舒」

前言

EventBus是一款针对Android优化的发布/订阅事件总线。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,以及将发送者和接收者解耦。如果Activity和Activity进行交互还好说,如果Fragment和Fragment进行交互着实令人头疼,我们会使用广播来处理,但是使用广播稍显麻烦并且效率也不高,如果传递的数据是实体类需要序列化,那么很显然成本会有点高。今天我们就来学习下EventBus3.0的使用方法。

查看更多

分享到 评论

Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析

前言

我们要实现一个线程安全的队列有两种实现方式一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现,本节我们就来研究下ConcurrentLinkedQueue是如何保证线程安全的同时又能高效的操作的。

查看更多

分享到 评论

Android网络编程(七)源码解析OkHttp前篇[请求网络]

本文首发于微信公众号「刘望舒」

相关文章
Android网络编程系列

前言

学会了OkHttp3的用法后,我们当然有必要来了解下OkHttp3的源码,当然现在网上的文章很多,我仍旧希望我这一系列文章篇是最简洁易懂的。

查看更多

分享到 评论

Java并发编程(六)阻塞队列

前言

Android多线程(一)线程池这篇文章时,当我们要创建ThreadPoolExecutor的时候需要传进来一个类型为BlockingQueue的参数,它就是阻塞队列,在这一篇文章里我们会介绍阻塞队列的定义、种类、实现原理以及应用。

查看更多

分享到 评论

设计模式(八)外观模式

本文首发于微信公众号「刘望舒」

相关文章
设计模式系列

1.外观模式简介

外观模式介绍

当我们开发Android的时候,无论是做SDK还是封装API,我们大多都会用到外观模式,它通过一个外观类使得整个系统的结构只有一个统一的高层接口,这样能降低用户的使用成本。

外观模式定义

为系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得子系统更加容易使用。

外观模式结构图

  • Facade:外观类,知道哪些子系统类负责处理请求,将客户端的请求代理给适当的子系统对象。
  • Subsystem:子系统类,实现子系统的功能,处理外观类指派的任务,注意子系统类不含有外观类的引用。

2.外观模式的简单实现

在上一篇设计模式之装饰模式我们举了武侠的例子,这一篇我们还举武侠的例子,首先我们把武侠张无忌当作一个系统,他作为一个武侠,他本身分为三个系统分别是招式、内功和经脉。

子系统类(Subsystem)

我们知道张无忌的三个系统分别是招式、内功和经脉。那我们来创建它们:

/**
 * 子系统招式
 */
public class ZhaoShi {
    public void TaiJiQuan(){
        System.out.print("使用着招式太极拳");
    }
    public void QiShangQuan(){
        System.out.print("使用招式七伤拳");
    }
    public void ShengHuo(){
        System.out.print("使用招式圣火令");
    }
}


/**
 * 子系统内功
 */
public class NeiGong {
    public void JiuYang(){
        System.out.print("使用九阳神功");
    }
    public void QianKun(){
        System.out.print("使用乾坤大挪移");
    }
}
/**
 * 子系统经脉
 */
public class JingMai {
    public void jingmai(){
        System.out.print("开启经脉");
    }
}

张无忌有很多的武学和内功,怎么将他们搭配,并对外界隐藏呢,我们接下来看看外观类:

外观类(Facade)

这里的外观类就是张无忌,他负责将自己的招式、内功和经脉通过不同的情况合理的运用:

/**
 * 外观类张无忌
 */
public class ZhangWuJi {
    private JingMai jingMai;
    private ZhaoShi zhaoShi;
    private NeiGong neiGong;

    public ZhangWuJi(){
        jingMai=new JingMai();
        zhaoShi=new ZhaoShi();
        neiGong=new NeiGong();
    }
    /**
     * 使用乾坤大挪移
     */
    public void Qiankun(){
        jingMai.jingmai();//开启经脉
        neiGong.QianKun();//使用内功乾坤大挪移

    }
    /**
     * 使用七伤拳
     */
    public void QiShang(){
        jingMai.jingmai(); //开启经脉
        neiGong.JiuYang();//使用内功九阳神功
        zhaoShi.QiShangQuan();//使用招式七伤拳
    }
}

初始化外观类的同时将各个子系统类创建好。很明显张无忌很好的将自身的各个系统搭配好,如果使用七伤拳的话就需要开启经脉、使用内功九阳神功接下来使用招式七伤拳,如果不开经脉或者使用九阳神功的话那么七伤拳的威力会大打折扣。

客户端调用

public class Test {
    public static void main(String[] args){
        ZhangWuJi zhangWuJi=new ZhangWuJi();
        //张无忌使用乾坤大挪移
        zhangWuJi.Qiankun();
        //张无忌使用七伤拳
        zhangWuJi.QiShang();
    }
}

当张无忌使用乾坤大挪移或者七伤拳的时候,比武的对手显然不知道张无忌本身运用了什么,同时张无忌也不需要去重新计划使用七伤拳的时候需要怎么做,他已经早就计划好了。如果每次使用七伤拳或者乾坤大挪移时都要计划怎么做很显然会增加成本并贻误战机。另外张无忌也可以改变自己的内功、招式和经脉,这些都是对比武的对手有所隐藏的。
外观模式本身就是将子系统的逻辑和交互隐藏起来,为用户提供一个高层次的接口,使得系统更加易用,同时也隐藏了具体的实现,这样即使具体的子系统发生了变化,用户也不会感知到。

3.外观模式使用场景

  • 构建一个有层次结构的子系统时,使用外观模式定义子系统中每层的入口点,如果子系统之间是相互依赖的,则可以让他们通过外观接口进行通信,减少子系统之间的依赖关系。
  • 子系统往往会因为不断的重构演化而变得越来越复杂,大多数的模式使用时也会产生很多很小的类,这给外部调用他们的用户程序带来了使用的困难,我们可以使用外观类提供一个简单的接口,对外隐藏子系统的具体实现并隔离变化。
  • 当维护一个遗留的大型系统时,可能这个系统已经非常难以维护和拓展,但因为它含有重要的功能,新的需求必须依赖于它,则可以使用外观类,来为设计粗糙或者复杂的遗留代码提供一个简单的接口,让新系统和外观类交互,而外观类负责与遗留的代码进行交互。
分享到 评论