概述

承接上一篇RxJava2 源码解析(一),
本系列我们的目的:

知道源头(Observable)是如何将数据发送出去的。
    知道终点(Observer)是如何接收到数据的。
    何时将源头和终点关联起来的
    知道线程调度是怎么实现的
    知道操作符是怎么实现的

本篇计划讲解一下4,5.

RxJava最强大的莫过于它的线程调度 和 花式操作符。
map操作符

map是一个高频的操作符,我们首先拿他开刀。
例子如下,源头Observable发送的是String类型的数字,利用map转换成int型,最终在终点Observer接受到的也是int类型数据。:

final Observable<String> testCreateObservable = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> e) throws Exception {
                e.onNext("1");
                e.onComplete()
            }
        });

Observable<Integer> map = testCreateObservable.map(new Function<String, Integer>() {
                    @Override
                    public Integer apply(String s) throws Exception {
                        return Integer.parseInt(s);
                    }
                });
                map.subscribe(new Observer<Integer>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        Log.d(TAG, "onSubscribe() called with: d = [" + d + "]");
                    }

@Override
                    public void onNext(Integer value) {
                        Log.d(TAG, "onNext() called with: value = [" + value + "]");
                    }

@Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "onError() called with: e = [" + e + "]");
                    }

@Override
                    public void onComplete() {
                        Log.d(TAG, "onComplete() called");
                    }
                });

我们看一下map函数的源码:

public final <R> Observable<R> map(Function<? super T, ? extends R> mapper) {
        //判空略过
        ObjectHelper.requireNonNull(mapper, "mapper is null");
        //RxJavaPlugins.onAssembly()是hook 上文提到过
        return RxJavaPlugins.onAssembly(new ObservableMap<T, R>(this, mapper));
    }

RxJavaPlugins.onAssembly()是hook 上文提到过,所以我们只要看ObservableMap,它就是返回到我们手里的Observable:

public final class ObservableMap<T, U> extends AbstractObservableWithUpstream<T, U> {
    //将function变换函数类保存起来
    final Function<? super T, ? extends U> function;

public ObservableMap(ObservableSource<T> source, Function<? super T, ? extends U> function) {
        //super()将上游的Observable保存起来 ,用于subscribeActual()中用。
        super(source);
        this.function = function;
    }

@Override
    public void subscribeActual(Observer<? super U> t) {
        source.subscribe(new MapObserver<T, U>(t, function));
    }

它继承自AbstractObservableWithUpstream,该类继承自Observable,很简单,就是将上游的ObservableSource保存起来,做一次wrapper,所以它也算是装饰者模式的提现,如下:

abstract class AbstractObservableWithUpstream<T, U> extends Observable<U> implements HasUpstreamObservableSource<T> {
    //将上游的`ObservableSource`保存起来
    protected final ObservableSource<T> source;
    AbstractObservableWithUpstream(ObservableSource<T> source) {
        this.source = source;
    }
    @Override
    public final ObservableSource<T> source() {
        return source;
    }
}

关于ObservableSource,代表了一个标准的无背压的 源数据接口,可以被Observer消费(订阅),如下:

public interface ObservableSource<T> {
    void subscribe(Observer<? super T> observer);
}

所有的Observable都已经实现了它,所以我们可以认为Observable和ObservableSource在本文中是相等的:

public abstract class Observable<T> implements ObservableSource<T> {

所以我们得到的ObservableMap对象也很简单,就是将上游的Observable和变换函数类Function保存起来。
Function的定义超级简单,就是一个接口,给我一个T,还你一个R.

public interface Function<T, R> {
    R apply(T t) throws Exception;
}

本例写的是将String->int.

重头戏,subscribeActual()是订阅真正发生的地方,ObservableMap如下编写,就一句话,用MapObserver订阅上游Observable。:

@Override
    public void subscribeActual(Observer<? super U> t) {
    //用MapObserver订阅上游Observable。
        source.subscribe(new MapObserver<T, U>(t, function));
    }

MapObserver也是装饰者模式,对终点(下游)Observer修饰。

static final class MapObserver<T, U> extends BasicFuseableObserver<T, U> {
        final Function<? super T, ? extends U> mapper;
        MapObserver(Observer<? super U> actual, Function<? super T, ? extends U> mapper) {
            //super()将actual保存起来
            super(actual);
            //保存Function变量
            this.mapper = mapper;
        }
        @Override
        public void onNext(T t) {
            //done在onError 和 onComplete以后才会是true,默认这里是false,所以跳过
            if (done) {
                return;
            }
            //默认sourceMode是0,所以跳过
            if (sourceMode != NONE) {
                actual.onNext(null);
                return;
            }
            //下游Observer接受的值
            U v;
            //这一步执行变换,将上游传过来的T,利用Function转换成下游需要的U。
            try {
                v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value.");
            } catch (Throwable ex) {
                fail(ex);
                return;
            }
            //变换后传递给下游Observer
            actual.onNext(v);
        }

到此我们梳理一下流程:
订阅的过程,是从下游到上游依次订阅的。

即终点 Observer 订阅了 map 返回的ObservableMap。
    然后map的Observable(ObservableMap)在被订阅时,会订阅其内部保存上游Observable,用于订阅上游的Observer是一个装饰者(MapObserver),内部保存了下游(本例是终点)Observer,以便上游发送数据过来时,能传递给下游。
    以此类推,直到源头Observable被订阅,根据上节课内容,它开始向Observer发送数据。

数据传递的过程,当然是从上游push到下游的,

源头Observable传递数据给下游Observer(本例就是MapObserver)
    然后MapObserver接收到数据,对其变换操作后(实际的function在这一步执行),再调用内部保存的下游Observer的onNext()发送数据给下游
    以此类推,直到终点Observer。

线程调度subscribeOn

简化问题,代码如下:

Observable.create(new ObservableOnSubscribe<String>() {
                    @Override
                    public void subscribe(ObservableEmitter<String> e) throws Exception {
                        Log.d(TAG, "subscribe() called with: e = [" + e + "]" + Thread.currentThread());
                        e.onNext("1");
                        e.onComplete();
                    }
                    //只是在Observable和Observer之间增加了一句线程调度代码
                }).subscribeOn(Schedulers.io())
                        .subscribe(new Observer<String>() {
                            @Override
                            public void onSubscribe(Disposable d) {
                                Log.d(TAG, "onSubscribe() called with: d = [" + d + "]");
                            }
                            @Override
                            public void onNext(String value) {
                                Log.d(TAG, "onNext() called with: value = [" + value + "]");
                            }
                            @Override
                            public void onError(Throwable e) {
                                Log.d(TAG, "onError() called with: e = [" + e + "]");
                            }
                            @Override
                            public void onComplete() {
                                Log.d(TAG, "onComplete() called");
                            }
                        });

只是在Observable和Observer之间增加了一句线程调度代码:.subscribeOn(Schedulers.io()).
查看subscribeOn()源码:

public final Observable<T> subscribeOn(Scheduler scheduler) {
    //判空略过
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        //抛开Hook,重点还是ObservableSubscribeOn
        return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
    }

等等,怎么有种似曾相识的感觉,大家可以把文章向上翻,看看map()的源码。
和subscribeOn()的套路如出一辙,那么我们根据上面的结论,
先猜测ObservableSubscribeOn类也是一个包装类(装饰者),点进去查看:

public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
    //保存线程调度器
    final Scheduler scheduler;
    public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
        //map的源码中我们分析过,super()只是简单的保存ObservableSource
        super(source);
        this.scheduler = scheduler;
    }
    @Override
    public void subscribeActual(final Observer<? super T> s) {
        //1  创建一个包装Observer
        final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(s);
        //2  手动调用 下游(终点)Observer.onSubscribe()方法,所以onSubscribe()方法执行在 订阅处所在的线程
        s.onSubscribe(parent);
        //3 setDisposable()是为了将子线程的操作加入Disposable管理中
        parent.setDisposable(scheduler.scheduleDirect(new Runnable() {
            @Override
            public void run() {
            //4 此时已经运行在相应的Scheduler 的线程中
                source.subscribe(parent);
            }
        }));
    }

和map套路大体一致,ObservableSubscribeOn自身同样是个包装类,同样继承AbstractObservableWithUpstream。
创建了一个SubscribeOnObserver类,该类按照套路,应该也是实现了Observer、Disposable接口的包装类,让我们看一下:

static final class SubscribeOnObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable {
        //真正的下游(终点)观察者
        final Observer<? super T> actual;
        //用于保存上游的Disposable,以便在自身dispose时,连同上游一起dispose
        final AtomicReference<Disposable> s;

SubscribeOnObserver(Observer<? super T> actual) {
            this.actual = actual;
            this.s = new AtomicReference<Disposable>();
        }

@Override
        public void onSubscribe(Disposable s) {
            //onSubscribe()方法由上游调用,传入Disposable。在本类中赋值给this.s,加入管理。
            DisposableHelper.setOnce(this.s, s);
        }

//直接调用下游观察者的对应方法
        @Override
        public void onNext(T t) {
            actual.onNext(t);
        }
        @Override
        public void onError(Throwable t) {
            actual.onError(t);
        }
        @Override
        public void onComplete() {
            actual.onComplete();
        }

//取消订阅时,连同上游Disposable一起取消
        @Override
        public void dispose() {
            DisposableHelper.dispose(s);
            DisposableHelper.dispose(this);
        }

@Override
        public boolean isDisposed() {
            return DisposableHelper.isDisposed(get());
        }
        //这个方法在subscribeActual()中被手动调用,为了将Schedulers返回的Worker加入管理
        void setDisposable(Disposable d) {
            DisposableHelper.setOnce(this, d);
        }
    }

这两个类根据上一节的铺垫加上注释,其他都好理解,稍微不好理解的应该是下面两句代码:

//ObservableSubscribeOn类
        //3 setDisposable()是为了将子线程的操作加入Disposable管理中
        parent.setDisposable(scheduler.scheduleDirect(new Runnable() {
            @Override
            public void run() {
            //4 此时已经运行在相应的Scheduler 的线程中
                source.subscribe(parent);
            }
        }));

//SubscribeOnObserver类
        //这个方法在subscribeActual()中被手动调用,为了将Schedulers返回的Worker加入管理
        void setDisposable(Disposable d) {
            DisposableHelper.setOnce(this, d);
        }

其中scheduler.scheduleDirect(new Runnable()..)方法源码如下:

/**
     * Schedules the given task on this scheduler non-delayed execution.
     * .....
     */
    public Disposable scheduleDirect(Runnable run) {
        return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
    }

从注释和方法名我们可以看出,这个传入的Runnable会立刻执行。
再继续往里面看:

public Disposable scheduleDirect(Runnable run, long delay, TimeUnit unit) {
        //class Worker implements Disposable ,Worker本身是实现了Disposable  
        final Worker w = createWorker();
        //hook略过
        final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
        //开始在Worker的线程执行任务,
        w.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                //调用的是 run()不是 start()方法执行的线程的方法。
                    decoratedRun.run();
                } finally {
                //执行完毕会 dispose()
                    w.dispose();
                }
            }
        }, delay, unit);
        //返回Worker对象
        return w;
    }

createWorker()是一个抽象方法,由具体的Scheduler类实现,例如IoScheduler对应的Schedulers.io().

public abstract Worker createWorker();

初看源码,为了了解大致流程,不宜过入深入,先点到为止。
OK,现在我们总结一下scheduler.scheduleDirect(new Runnable()..)的重点:

传入的Runnable是立刻执行的。
    返回的Worker对象就是一个Disposable对象,
    Runnable执行时,是直接手动调用的 run(),而不是 start()方法.
    上一点应该是为了,能控制在run()结束后(包括异常终止),都会自动执行Worker.dispose().

而返回的Worker对象也会被parent.setDisposable(...)加入管理中,以便在手动dispose()时能取消线程里的工作。

我们总结一下subscribeOn(Schedulers.xxx())的过程:

返回一个ObservableSubscribeOn包装类对象
    上一步返回的对象被订阅时,回调该类中的subscribeActual()方法,在其中会立刻将线程切换到对应的Schedulers.xxx()线程。
    在切换后的线程中,执行source.subscribe(parent);,对上游(终点)Observable订阅
    上游(终点)Observable开始发送数据,根据RxJava2 源码解析(一),上游发送数据仅仅是调用下游观察者对应的onXXX()方法而已,所以此时操作是在切换后的线程中进行。

一点扩展,
大家可能看过一个结论:
subscribeOn(Schedulers.xxx())切换线程N次,总是以第一次为准,或者说离源Observable最近的那次为准,并且对其上面的代码生效(这一点对比的ObserveOn())。

为什么?
- 因为根据RxJava2 源码解析(一)中提到,订阅流程从下游往上游传递
- 在subscribeActual()里开启了Scheduler的工作,source.subscribe(parent);,从这一句开始切换了线程,所以在这之上的代码都是在切换后的线程里的了。
- 但如果连续切换,最上面的切换最晚执行,此时线程变成了最上面的subscribeOn(xxxx)指定的线程,
- 而数据push时,是从上游到下游的,所以会在离源头最近的那次subscribeOn(xxxx)的线程里push数据(onXXX())给下游。

可写如下代码验证:

Observable.create(new ObservableOnSubscribe<String>() {
                    @Override
                    public void subscribe(ObservableEmitter<String> e) throws Exception {
                        Log.d(TAG, "subscribe() called with: e = [" + e + "]" + Thread.currentThread());
                        e.onNext("1");
                        e.onComplete();
                    }
                }).subscribeOn(Schedulers.io())
                        .map(new Function<String, String>() {
                            @Override
                            public String apply(String s) throws Exception {
                                //依然是io线程
                                Log.d(TAG, "apply() called with: s = [" + s + "]" + Thread.currentThread());
                                return s;
                            }
                        })
                        .subscribeOn(Schedulers.computation())
                        .subscribe(new Observer<String>() {
                            @Override
                            public void onSubscribe(Disposable d) {
                                Log.d(TAG, "onSubscribe() called with: d = [" + d + "]");
                            }
                            @Override
                            public void onNext(String value) {
                                Log.d(TAG, "onNext() called with: value = [" + value + "]");
                            }
                            @Override
                            public void onError(Throwable e) {
                                Log.d(TAG, "onError() called with: e = [" + e + "]");
                            }
                            @Override
                            public void onComplete() {
                                Log.d(TAG, "onComplete() called");
                            }
                        });

线程调度observeOn

在上一节的基础上,增加一个observeOn(AndroidSchedulers.mainThread()),就完成了观察者线程的切换。

.subscribeOn(Schedulers.computation())
                        //在上一节的基础上,增加一个ObserveOn
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Observer<String>() {

继续看源码吧,我已经能猜出来了,hook+new XXXObservable();

public final Observable<T> observeOn(Scheduler scheduler) {
        return observeOn(scheduler, false, bufferSize());
    }

public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
        ....
        return RxJavaPlugins.onAssembly(new ObservableObserveOn<T>(this, scheduler, delayError, bufferSize));
    }

果然,查看ObservableObserveOn,:
高能预警,这部分的代码 有些略多,建议读者打开源码边看边读。

public final class ObservableObserveOn<T> extends AbstractObservableWithUpstream<T, T> {
    //本例是 AndroidSchedulers.mainThread()
    final Scheduler scheduler;
    //默认false
    final boolean delayError;
    //默认128
    final int bufferSize;
    public ObservableObserveOn(ObservableSource<T> source, Scheduler scheduler, boolean delayError, int bufferSize) {
        super(source);
        this.scheduler = scheduler;
        this.delayError = delayError;
        this.bufferSize = bufferSize;
    }

@Override
    protected void subscribeActual(Observer<? super T> observer) {
        // false
        if (scheduler instanceof TrampolineScheduler) {
            source.subscribe(observer);
        } else {
            //1 创建出一个 主线程的Worker
            Scheduler.Worker w = scheduler.createWorker();
            //2 订阅上游数据源,
            source.subscribe(new ObserveOnObserver<T>(observer, w, delayError, bufferSize));
        }
    }

本例中,就是两步:

创建一个AndroidSchedulers.mainThread()对应的Worker
    用ObserveOnObserver订阅上游数据源。这样当数据从上游push下来,会由ObserveOnObserver对应的onXXX()处理。

static final class ObserveOnObserver<T> extends BasicIntQueueDisposable<T>
    implements Observer<T>, Runnable {
        //下游的观察者
        final Observer<? super T> actual;
        //对应Scheduler里的Worker
        final Scheduler.Worker worker;
        //上游被观察者 push 过来的数据都存在这里
        SimpleQueue<T> queue;
        Disposable s;
        //如果onError了,保存对应的异常
        Throwable error;
        //是否完成
        volatile boolean done;
        //是否取消
        volatile boolean cancelled;
        // 代表同步发送 异步发送
        int sourceMode;
        ....
        @Override
        public void onSubscribe(Disposable s) {
            if (DisposableHelper.validate(this.s, s)) {
                this.s = s;
                //省略大量无关代码
                //创建一个queue 用于保存上游 onNext() push的数据
                queue = new SpscLinkedArrayQueue<T>(bufferSize);
                //回调下游观察者onSubscribe方法
                actual.onSubscribe(this);
            }
        }

@Override
        public void onNext(T t) {
            //1 执行过error / complete 会是true
            if (done) {
                return;
            }
            //2 如果数据源类型不是异步的, 默认不是
            if (sourceMode != QueueDisposable.ASYNC) {
                //3 将上游push过来的数据 加入 queue里
                queue.offer(t);
            }
            //4 开始进入对应Workder线程,在线程里 将queue里的t 取出 发送给下游Observer
            schedule();
        }

@Override
        public void onError(Throwable t) {
            //已经done 会 抛异常 和 上一篇文章里提到的一样
            if (done) {
                RxJavaPlugins.onError(t);
                return;
            }
            //给error存个值
            error = t;
            done = true;
            //开始调度
            schedule();
        }

@Override
        public void onComplete() {
        //已经done 会 返回  不会crash 和上一篇文章里提到的一样
            if (done) {
                return;
            }
            done = true;
            //开始调度
            schedule();
        }

void schedule() {
            if (getAndIncrement() == 0) {
                //该方法需要传入一个线程, 注意看本类实现了Runnable的接口,所以查看对应的run()方法
                worker.schedule(this);
            }
        }
        //从这里开始,这个方法已经是在Workder对应的线程里执行的了
        @Override
        public void run() {
            //默认是false
            if (outputFused) {
                drainFused();
            } else {
                //取出queue里的数据 发送
                drainNormal();
            }
        }

void drainNormal() {
            int missed = 1;

final SimpleQueue<T> q = queue;
            final Observer<? super T> a = actual;

for (;;) {
                // 1 如果已经 终止 或者queue空,则跳出函数,
                if (checkTerminated(done, q.isEmpty(), a)) {
                    return;
                }

for (;;) {
                    boolean d = done;
                    T v;

try {
                        //2 从queue里取出一个值
                        v = q.poll();
                    } catch (Throwable ex) {
                        //3 异常处理 并跳出函数
                        Exceptions.throwIfFatal(ex);
                        s.dispose();
                        q.clear();
                        a.onError(ex);
                        return;
                    }
                    boolean empty = v == null;
                    //4 再次检查 是否 终止  如果满足条件 跳出函数
                    if (checkTerminated(d, empty, a)) {
                        return;
                    }
                    //5 上游还没结束数据发送,但是这边处理的队列已经是空的,不会push给下游 Observer
                    if (empty) {
                        //仅仅是结束这次循环,不发送这个数据而已,并不会跳出函数
                        break;
                    }
                    //6 发送给下游了
                    a.onNext(v);
                }

//7 对不起这里我也不是很明白,大致猜测是用于 同步原子操作 如有人知道 烦请告知
                missed = addAndGet(-missed);
                if (missed == 0) {
                    break;
                }
            }
        }

//检查 是否 已经 结束(error complete), 是否没数据要发送了(empty 空),
        boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) {
            //如果已经disposed
            if (cancelled) {
                queue.clear();
                return true;
            }
            // 如果已经结束
            if (d) {
                Throwable e = error;
                //如果是延迟发送错误
                if (delayError) {
                    //如果空
                    if (empty) {
                        if (e != null) {
                            a.onError(e);
                        } else {
                            a.onComplete();
                        }
                        //停止worker(线程)
                        worker.dispose();
                        return true;
                    }
                } else {
                    //发送错误
                    if (e != null) {
                        queue.clear();
                        a.onError(e);
                        worker.dispose();
                        return true;
                    } else
                    //发送complete
                    if (empty) {
                        a.onComplete();
                        worker.dispose();
                        return true;
                    }
                }
            }
            return false;
        }
    }

核心处都加了注释,总结起来就是,

ObserveOnObserver实现了Observer和Runnable接口。
    在onNext()里,先不切换线程,将数据加入队列queue。然后开始切换线程,在另一线程中,从queue里取出数据,push给下游Observer
    onError() onComplete()除了和RxJava2 源码解析(一)提到的一样特性之外,也是将错误/完成信息先保存,切换线程后再发送。
    所以observeOn()影响的是其下游的代码,且多次调用仍然生效。
    因为其切换线程代码是在Observer里onXXX()做的,这是一个主动的push行为(影响下游)。
    关于多次调用生效问题。对比subscribeOn()切换线程是在subscribeActual()里做的,只是主动切换了上游的订阅线程,从而影响其发射数据时所在的线程。而直到真正发射数据之前,任何改变线程的行为,都会生效(影响发射数据的线程)。所以subscribeOn()只生效一次。observeOn()是一个主动的行为,并且切换线程后会立刻发送数据,所以会生效多次.

转载请标明出处:
    http://blog.csdn.net/zxt0601/article/details/61637439
    本文出自:【张旭童的博客】(http://blog.csdn.net/zxt0601)

总结

本文带大家走读分析了三个东西:

map操作符原理:

内部对上游Observable进行订阅
    内部订阅者接收到数据后,将数据转换,发送给下游Observer.
    操作符返回的Observable和其内部订阅者、是装饰者模式的体现。
    操作符数据变换的操作,也是发生在订阅后。

线程调度subscribeOn():

内部先切换线程,在切换后的线程中对上游Observable进行订阅,这样上游发送数据时就是处于被切换后的线程里了。
    也因此多次切换线程,最后一次切换(离源数据最近)的生效。
    内部订阅者接收到数据后,直接发送给下游Observer.
    引入内部订阅者是为了控制线程(dispose)
    线程切换发生在Observable中。

线程调度observeOn():

使用装饰的Observer对上游Observable进行订阅
    在Observer中onXXX()方法里,将待发送数据存入队列,同时请求切换线程处理真正push数据给下游。
    多次切换线程,都会对下游生效。

源码里那些实现了Runnable的类或者匿名内部类,最终并没有像往常那样被丢给Thread类执行。
而是先切换线程,再直接执行Runnable的run()方法。
这也加深了我对面向对象,对抽象、Runnable的理解,它就是一个简简单单的接口,里面就一个简简单单的run(),
我认为,之所以有Runnable,只是抽象出 一个可运行的任务的概念。
也许这句话很平淡,书上也会提到,各位大佬早就知道,但是如今我顺着RxJava2的源码这么走读了一遍,确真真切切的感受到了这些设计思想的美妙。
---------------------

https://blog.csdn.net/zxt0601/article/details/61637439

RxJava2 源码解析(二)的更多相关文章

  1. RxJava2源码解析(二)

    title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...

  2. Android进阶:五、RxJava2源码解析 2

    上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟 ...

  3. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  4. Sentinel源码解析二(Slot总览)

    写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...

  5. Android进阶:四、RxJava2 源码解析 1

    本文适合使用过Rxjava2或者了解Rxjava2的基本用法的同学阅读 一.Rxjava是什么 Rxjava在GitHub 主页上的自我介绍是 "a library for composin ...

  6. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

  7. jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究

    终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...

  8. Common.Logging源码解析二

    Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...

  9. erlang下lists模块sort(排序)方法源码解析(二)

    上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...

随机推荐

  1. Oracle 11g服务器安装详细步骤——图文教程(系统 windows server 2012 R2)

    Oracle 11g服务器安装的相关问题,下面小编就带大家一起来下载.安装. 方法/步骤 1 大家可以根据自己的操作系统是多少位(32位或64位)的,到官网下载相应的安装程序,如下图所示. 有一点需要 ...

  2. LeetCode(39):组合总和

    Medium! 题目描述: 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates ...

  3. Route pattern cannot reference variable name more than once

    在用 Laravel Backpack 写一个定制化的 CRUD 页面.例如,一个指定店铺所拥有的商品的 CRUD 页面. 起初路由我是这样写的 CRUD::resource('products-of ...

  4. MFC单文档

    一.创建并运行MFC单文档程序 1.创建单文档程序 这里使用的是VS2017.首先,打开VS2017,选择文件->新建->项目,然后选择Visual C++ -> MFC /ATL& ...

  5. jmeter正则表达式提取器多模块相互调用

    提取return的结果 (1)例: 创建账户和转账功能 注:以下为soap协议 添加账户1 创建正则表达式提取器(提取创建的结果) 点击导入接口文档URL地址和方框内方法 同上方法添加账户2 点击正则 ...

  6. python简单笔记

    Remarks:python中注意缩进(Tab键或者4个空格) print(输出) 格式:print(values) 字符串.数字.变量等都可以输出: 实例: print(1)->1 print ...

  7. python 全栈开发,Day66(web应用,http协议简介,web框架)

    一.web应用 web应用程序是一种可以通过Web访问的应用程序,程序的最大好处是用户很容易访问应用程序,用户只需要有浏览器即可,不需要再安装其他软件.应用程序有两种模式C/S.B/S.C/S是客户端 ...

  8. ***PHP基于H5的微信支付开发详解(CI框架)

    这次总结一下用户在微信内打开网页时,可以调用微信支付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能.当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现代码可 ...

  9. nexus私服常用设置

    https://www.jianshu.com/p/7a09915675d9 或许用得上. settings.xml <server> <!-- 服务器密码 --> <i ...

  10. django的FormView中,自定义初始化表单数据的曲折方法

    这个技巧,主要是用于表单初始化及回显. 也就是说,如果用户的数据库里有数据,则要将相应的数据显示在表单里, 如果用户的数据库里没有数据,才会生成一个空白的表单给用户, 这样才显得专业塞! 而我面对的尴 ...