@Override
public void subscribeActual(final Observer<? super T> observer) {
final SubscribeOnObserver parent = new SubscribeOnObserver(observer);
observer.onSubscribe(parent);
parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
ObservableSubscribeOn
的subscribeActual
方法中流程和之前的也很类似,这次是先创建了一个SubscribeOnObserver
对象,将观察者对象传入,接着同样先调用了observer.onSubscribe
方法,然后将传入的SubscribeOnObserver
封装入了一个SubscribeTask
对象中,接着调用了scheduler.scheduleDirect
方法再将返回结果得到的Disposable
设置到SubscribeOnObserver
中。下面一个方法一个方法看。首先是创建SubscribeTask
。
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver parent;
SubscribeTask(SubscribeOnObserver parent) {
this.parent = parent;
}
@Override
public void run() {
source.subscribe(parent);
}
}
SubscribeTask
是ObservableSubscribeOn
的内部类,其实现很简单就是实现了一个Runnable
接口,构造方法中传入了SubscribeOnObserver
对象,在其run
方法中调用了ObservableSubscribeOn
中的成员变量source
的subscribe
方法。这个source
是在创建ObservableSubscribeOn
时传入的,根据前面的代码可以找到是在subscribeOn
方法中创建的对象并且这个source
对应传入的是当前这个Observable
对象即通过Observable.create
获得的被观察者对象,其实现之前看过是一个ObservableCreate
所以这里就和之前一样又会走到了其父类Observable
的subscribe
方法中,继而调用ObservableCreate
的subscribeActual
方法,之后最终会调用到观察者的对应onNext
等方法,不过此时的观察者不直接是在使用时创建传入的Observer
,而是之前看到的SubscribeOnObserver
类型,不过其中的onNext
等方法还是调用了在使用时创建传入的Observer
的对应方法。
static final class SubscribeOnObserver extends AtomicReference implements Observer, Disposable {
private static final long serialVersionUID = 8094547886072529208L;
final Observer<? super T> downstream;
final AtomicReference upstream;
SubscribeOnObserver(Observer<? super T> downstream) {
this.downstream = downstream;
this.upstream = new AtomicReference();
}
@Override
public void onNext(T t) {
downstream.onNext(t);
}
@Override
public void onError(Throwable t) {
downstream.onError(t);
}
@Override
public void onComplete() {
downstream.onComplete();
}
…
}
下面接着看到scheduleDirect
这个方法,在创建好SubscribeTask
之后调用了scheduleDirect
方法。这里的scheduler
就是subscribeOn
中传入的,对应开始例子中的Schedulers.newThread
。
public static Scheduler newThread() {
return RxJavaPlugins.onNewThreadScheduler(NEW_THREAD);
}
// 静态成员变量NEW_THREAD
static final Scheduler NEW_THREAD;
NEW_THREAD = RxJavaPlugins.initNewThreadScheduler(new NewThreadTask());
进入Schedulers.newThread
一步步跟踪,看到newThread
方法返回静态成员变量中的NEW_THREAD
,而NEW_THREAD
又是通过NewThreadTask
创建。
static final class NewThreadTask implements Callable {
@Override
public Scheduler call() throws Exception {
return NewThreadHolder.DEFAULT;
}
}
static final Scheduler DEFAULT = new NewThreadScheduler();
继续跟踪查看发现NewThreadTask
实际是实现了Callable
接口,其call
方法中返回了静态内部类中的NewThreadHolder.DEFAULT
。这个DEFAULT
的实现类型为NewThreadScheduler
。至此终于找到了我们传入的Scheduler
的真正实现类。于是继续看其scheduleDirect
方法。
public Disposable scheduleDirect(@NonNull Runnable run) {
return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
}
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
final Worker w = createWorker();
final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
DisposeTask task = new DisposeTask(decoratedRun, w);
w.schedule(task, delay, unit);
return task;
}
scheduleDirect
方法是在其父类中实现的,看到其中进而调用了同名重载方法,方法中首先是调用createWorker
方法创建一个Worker
。这个方法的实现就是在NewThreadScheduler
中了。
public Worker createWorker() {
return new NewThreadWorker(threadFactory);
}
createWorker
方法中只做了一件事就是创建返回了一个NewThreadWorker
。
public class NewThreadWorker extends Scheduler.Worker implements Disposable {
private final ScheduledExecutorService executor;
volatile boolean disposed;
public NewThreadWorker(ThreadFactory threadFactory) {
executor = SchedulerPoolFactory.create(threadFactory);
}
…
}
NewThreadWorker
中看到创建了一个线程池,再回到scheduleDirect
方法,创建完Worker
后将传入的Runnable
即SubscribeTask
进行一个装饰得到新的Runnable
对象。接着将Worker
和新的Runnable
封装到一个DisposeTask
对象中。
static final class DisposeTask implements Disposable, Runnable, SchedulerRunnableIntrospection {
@NonNull
final Runnable decoratedRun;
@NonNull
final Worker w;
@Nullable
Thread runner;
DisposeTask(@NonNull Runnable decoratedRun, @NonNull Worker w) {
this.decoratedRun = decoratedRun;
this.w = w;
}
@Override
public void run() {
runner = Thread.currentThread();
try {
decoratedRun.run();
} finally {
dispose();
runner = null;
}
}
…
}
DisposeTask
同样实现了Runnable
接口,在run
方法中调用了从构造传入的decoratedRun
的run
方法执行任务。回到最后一步,调用Worker
的schedule
方法,这里就对应的NewThreadWorker
的schedule
方法。
public Disposable schedule(@NonNull final Runnable action, long delayTime, @NonNull TimeUnit unit) {
if (disposed) {
return EmptyDisposable.INSTANCE;
}
return scheduleActual(action, delayTime, unit, null);
}
schedule
方法中又进一步调用了其scheduleActual
方法
public ScheduledRunnable scheduleActual(final Runnable run, long delayTime, @NonNull TimeUnit unit, @Nullable DisposableContainer parent) {
Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, parent);
if (parent != null) {
if (!parent.add(sr)) {
return sr;
}
}
Future<?> f;
try {
if (delayTime <= 0) {
f = executor.submit((Callable)sr);
} else {
f = executor.schedule((Callable)sr, delayTime, unit);
}
sr.setFuture(f);
} catch (RejectedExecutionException ex) {
if (parent != null) {
parent.remove(sr);
}
RxJavaPlugins.onError(ex);
}
return sr;
}
scheduleActual
方法里看到又将decoratedRun
和DisposableContainer
封装成ScheduledRunnable
最后将这个ScheduledRunnable
交给构造函数中创建的线程池去运行,最终就会执行到前面看过的SubscribeTask
中的run
方法完成订阅逻辑,调用观察者的onNext
等方法。到这里就看出最终的source.subscribe
是会通过线程池切换到子线程中去执行了。
通过查看subscribeOn
方法源码可以发现,方法里实际上是在前一个创建的ObservableCreate
外面包了一层,把它包成一个ObservableSubscribeOn
对象,同样的原先的Observer
也被包了一层包成一个SubscribeOnObserver
对象,而线程切换的工作是由Scheduler
完成的。
4.2 observeOn
接着再来看看切换回主线程的方法observeOn
,还是先修改使用代码,查看运行日志。
Observable observable2 = observable0.observeOn(AndroidSchedulers.mainThread());
Log.d(getClass().getName(), Thread.currentThread().getName() + " observable2:"+observable2.getClass().getName());
observable2.subscribe(observer);
运行日志:
接着还是进入来看源码。
public final Observable observeOn(Scheduler scheduler) {
return observeOn(scheduler, false, bufferSize());
}
public final Observable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
ObjectHelper.requireNonNull(scheduler, “scheduler is null”);
ObjectHelper.verifyPositive(bufferSize, “bufferSize”);
return RxJavaPlugins.onAssembly(new ObservableObserveOn(this, scheduler, delayError, bufferSize));
}
这里看到observeOn
方法里调用了重载方法,方法中还是同一个套路,不过这里创建的又是另一个对象ObservableObserveOn
了。根据前面的经验这里就又是将前一个Observable
传递到ObservableObserveOn
中的成员变量source
上,这里看到就是构造函数中的第一个参数。接着还是会调用subscribe
与观察者建立订阅关系进而会执行到ObservableObserveOn
对象的subscribeActual
方法。
@Override
protected void subscribeActual(Observer<? super T> observer) {
if (scheduler instanceof TrampolineScheduler) {
source.subscribe(observer);
} else {
Scheduler.Worker w = scheduler.createWorker();
source.subscribe(new ObserveOnObserver(observer, w, delayError, bufferSize));
}
}
subscribeActual
方法中判断了scheduler
的类型,这里的scheduler
就是由AndroidSchedulers.mainThread()
传入的,于是先来看一下这个方法。
public static Scheduler mainThread() {
return RxAndroidPlugins.onMainThreadScheduler(MAIN_THREAD);
}
private static final Scheduler MAIN_THREAD = RxAndroidPlugins.initMainThreadScheduler(
new Callable() {
@Override public Scheduler call() throws Exception {
return MainHolder.DEFAULT;
}
});
private static final class MainHolder {
static final Scheduler DEFAULT = new HandlerScheduler(new Handler(Looper.getMainLooper()), false);
}
从mainThread
开始看,发现代码调用逻辑和之前的Schedulers.newThread
方法类似,最终会返回一个HandlerScheduler
而这个Scheduler
中的Handler
则是主线程的Handler
,看到这里就能猜想到了,后面观察者的对应方法一定是由这个Handler
来切换到主线程执行的。回到subscribeActual
方法。
@Override
protected void subscribeActual(Observer<? super T> observer) {
if (scheduler instanceof TrampolineScheduler) {
source.subscribe(observer);
} else {
Scheduler.Worker w = scheduler.createWorker();
source.subscribe(new ObserveOnObserver(observer, w, delayError, bufferSize));
}
}
这里判断完类型会走else
中的方法首先还是会调用HandlerScheduler
的createWorker
方法创建一个Worker
。
@Override
public Worker createWorker() {
return new HandlerWorker(handler, async);
}
这里是个HandlerWorker
其中具体方法后面再看。接着上面创建完Worker
后同样还是一样调用source.subscribe
创建了一个ObserveOnObserver
对象传入。这里的source
就还是之前的ObservableCreate
,所以这里还是会调用ObservableCreate
中的subscribeActual
方法。
protected void subscribeActual(Observer<? super T> observer) {
CreateEmitter parent = new CreateEmitter(observer);
observer.onSubscribe(parent);
try {
source.subscribe(parent);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
parent.onError(ex);
}
}
ObservableCreate
中的subscribeActual
方法中的逻辑之前看过,不过此时传入的observer
仍然不再是在使用时创建的观察者对象了,而是传过来的ObserveOnObserver
对象,此时创建的CreateEmitter
中的observer
也就是这个ObserveOnObserver
对象。和之前逻辑一样,接着就会调用observer
的onNext
等方法,此时调用的即是ObserveOnObserver
中的onNext
等方法。所以进入ObserveOnObserver
查看。
@Override
public void onNext(T t) {
if (done) {
return;
}
if (sourceMode != QueueDisposable.ASYNC) {
queue.offer(t);
}
schedule();
}
@Override
public void onComplete() {
if (done) {
return;
}
done = true;
schedule();
}
void schedule() {
if (getAndIncrement() == 0) {
worker.schedule(this);
}
}
查看ObserveOnObserver
中的代码会发现onNext
方法中先将传入的参数放入了一个队列,然后无论是onNext
还是onComplete
方法最后都调用了schedule
方法,进而再进入查看,发现schedule
方法中又调用了worker.schedule
方法。这里的worker
就是之前创建的HandlerWorker
,这时再来看它的schedule
方法。
public Disposable schedule(@NonNull Runnable run) {
return schedule(run, 0L, TimeUnit.NANOSECONDS);
}
单个参数schedule
方法是在其父类中的,而这个方法中又调用另一个三个参数的schedule
方法,这个方法父类中是抽象方法所以实现就在子类HandlerWorker
里了。
@Override
@SuppressLint(“NewApi”) // Async will only be true when the API is available to call.
public Disposable schedule(Runnable run, long delay, TimeUnit unit) {
if (run == null) throw new NullPointerException(“run == null”);
if (unit == null) throw new NullPointerException(“unit == null”);
// 判断是否取消订阅
if (disposed) {
return Disposables.disposed();
}
run = RxJavaPlugins.onSchedule(run);
// 创建ScheduledRunnable
ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);
// 创建消息,并将主线程Handler和ScheduledRunnable
Message message = Message.obtain(handler, scheduled);
message.obj = this;
// 判断设置异步消息
if (async) {
message.setAsynchronous(true);
}
// 发送消息执行callback
handler.sendMessageDelayed(message, unit.toMillis(delay));
// 检查是否取消订阅
if (disposed) {
handler.removeCallbacks(scheduled);
return Disposables.disposed();
}
return scheduled;
}
在子类的这个方法里在做了取消订阅的判断后将方法传入的Runnable
和Handler
又封装到一个ScheduledRunnable
对象中。接着创建了一个Message
并将ScheduledRunnable
放入Message
,最后调用handler.sendMessageDelayed
方法通过这个主线程的Handler
执行这个ScheduledRunnable
。
最后来追溯下ScheduledRunnable
到底执行了什么,不过猜也知道最后一定调用到观察者中的对应方法。
private static final class ScheduledRunnable implements Runnable, Disposable {
private final Handler handler;
private final Runnable delegate;
private volatile boolean disposed; // Tracked solely for isDisposed().
ScheduledRunnable(Handler handler, Runnable delegate) {
this.handler = handler;
this.delegate = delegate;
}
@Override
public void run() {
try {
delegate.run();
} catch (Throwable t) {
RxJavaPlugins.onError(t);
}
}
…
}
ScheduledRunnable
中的run
方法很简单就是调用了构造中传入的Runnable
的run
方法。而根据之前看过得创建ScheduledRunnable
时传入的Runnable
又是从scheduleDirect
方法中传入的,而scheduleDirect
方法中的Runnable
又是从worker.schedule(this)
方法时传入的,根据上下文代码发现这个this
指代的是ObserveOnObserver
对象,于是进一步进入它的run
方法查看。
static final class ObserveOnObserver extends BasicIntQueueDisposable
implements Observer, Runnable {
…
ObserveOnObserver(Observer<? super T> actual, Scheduler.Worker worker, boolean delayError, int bufferSize) {
this.downstream = actual;
this.worker = worker;
this.delayError = delayError;
this.bufferSize = bufferSize;
}
…
@Override
public void run() {
if (outputFused) {
drainFused();
} else {
drainNormal();
}
}
…
}
可以看到run
方法中判断了outputFused
的真假,然后分别调用了drainFused
和drainNormal
方法。这里的outputFused
是与RxJava2
中的背压处理相关暂时先不管,根据方法名也能知道正常调用会执行drainNormal
方法,于是直接来看drainNormal
方法。
void drainNormal() {
int missed = 1;
// 存放onNext传入的事件对象队列
final SimpleQueue q = queue;
// 传入的观察者对象
final Observer<? super T> a = downstream;
// 循环check事件是否完成或者发生错误
for (;😉 {
if (checkTerminated(done, q.isEmpty(), a)) {
return;
}
for (;😉 {
boolean d = done;
T v;
try {
// 从队列中取出发送事件传入的对象
v = q.poll();
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
disposed = true;
upstream.dispose();
q.clear();
a.onError(ex);
worker.dispose();
return;
}
boolean empty = v == null;
// 再次判断是否完成或者发生错误
if (checkTerminated(d, empty, a)) {
return;
}
// 判断队列中取出的发送事件传入的对象v是否为空
if (empty) {
break;
}
// 执行观察者对象的onNext方法
a.onNext(v);
}
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
drainNormal
方法中先通过checkTerminated
方法校验发送事件是否完成或者发生异常,接着从队列中取出事件对象,再次判断是否完成或者发生错误和取出的对象是否为空,没有问题的话就会执行观察者的onNext
方法。而发送完成和出现异常的方法则是在checkTerminated
方法处理。
boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) {
if (disposed) {
queue.clear();
return true;
}
if (d) {
Throwable e = error;
if (delayError) {
if (empty) {
disposed = true;
if (e != null) {
a.onError(e);
} else {
a.onComplete();
}
worker.dispose();
return true;
}
} else {
if (e != null) {
disposed = true;
queue.clear();
a.onError(e);
worker.dispose();
return true;
} else
if (empty) {
disposed = true;
a.onComplete();
worker.dispose();
return true;
}
}
}
return false;
}
在checkTerminated
方法里根据delayError
判断是否设置了超时的错误,接着再根据获得的错误e
是否为空再决定调用的是观察者的onError()
方法还是onComplete
方法。至此observeOn
切换线程的流程也梳理结束。
5. map操作符
RxJava
中有很多功能强大的操作符,通过使用这些操作符,可以很容易的解决代码编写时遇到的一些复杂繁琐的问题。这里就用map
操作符来作为一个例子,来看看操作符是怎样工作的。首先还是来了解map
操作符的使用方法和作用。
final Observable observable = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
Log.d(getClass().getName(), Thread.currentThread().getName() + " ObservableOnSubscribe subscribe");
emitter.onNext(“5”);
emitter.onComplete();
}
});
Observer observer = new Observer() {
@Override
public void onSubscribe(Disposable d) {
Log.d(getClass().getName(), Thread.currentThread().getName() + " onSubscribe");
}
@Override
public void onNext(Integer i) {
Log.d(getClass().getName(), Thread.currentThread().getName() + " onNext "+i);
}
@Override
public void onError(Throwable e) {
Log.d(getClass().getName(), Thread.currentThread().getName() + " onError");
}
@Override
public void onComplete() {
Log.d(getClass().getName(), Thread.currentThread().getName() + " onComplete");
}
};
Observable mapObservable = observable.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) throws Exception {
return Integer.parseInt(s);
}
});
Log.d(getClass().getName(), Thread.currentThread().getName() + " mapObservable:"+mapObservable.getClass().getName());
mapObservable.subscribe(observer);
运行日志:
map
操作符作用是可以将被观察者发送事件的数据类型转换成其他的数据类型。它的使用方法很简单,例如上面这个例子就将一开始发送的String
类型转换成观察者接收到的Integer
类型。下面开始看map
方法的源码。
public final Observable map(Function<? super T, ? extends R> mapper) {
ObjectHelper.requireNonNull(mapper, “mapper is null”);
return RxJavaPlugins.onAssembly(new ObservableMap<T, R>(this, mapper));
}
看到map
方法中依旧还是同样的套路,通过RxJavaPlugins.onAssembly
方法返回一个被观察者对象,只不过这次构建传入的类型又是另一个ObservableMap
类型的对象。订阅的流程前面已经看过了,这里和之前的一样最终会走到ObservableMap
的subscribeActual
方法,所以直接来看这个方法。
@Override
public void subscribeActual(Observer<? super U> t) {
source.subscribe(new MapObserver<T, U>(t, function));
}
ObservableMap
的subscribeActual
方法里看到很熟悉还是会调用source.subscribe
方法,只是这里传入的Observer
对象是一个MapObserver
对象。接下来的逻辑又和之前一样,根据之前的经验source.subscribe
方法最终会调用Observer
的onNext
方法,所以接下来直接来看MapObserver
的onNext
方法。
public void onNext(T t) {
if (done) {
return;
}
if (sourceMode != NONE) {
downstream.onNext(null);
return;
}
U v;
try {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
总而言之,成功是留给准备好的人的。无论是参加什么面试,都要做好充足的准备,注意好面试的礼仪和穿着,向面试官表现出自己的热忱与真诚就好。即使最后没有过关,也要做好经验的总结,为下一次面试做好充足准备。
这里我为大家准备了一些我在面试后整理的面试专题资料,除了面试题,还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家,希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
毕竟不管遇到什么样的面试官,去面试首先最主要的就是自己的实力,只要实力够硬,技术够强,就不怕面试拿不到offer!
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-8gMDxyOd-1712442970828)]
[外链图片转存中…(img-PH5PWMPx-1712442970828)]
[外链图片转存中…(img-ge5fHZT0-1712442970829)]
[外链图片转存中…(img-kzyP8vin-1712442970829)]
[外链图片转存中…(img-c303xazT-1712442970829)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
总而言之,成功是留给准备好的人的。无论是参加什么面试,都要做好充足的准备,注意好面试的礼仪和穿着,向面试官表现出自己的热忱与真诚就好。即使最后没有过关,也要做好经验的总结,为下一次面试做好充足准备。
这里我为大家准备了一些我在面试后整理的面试专题资料,除了面试题,还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家,希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
毕竟不管遇到什么样的面试官,去面试首先最主要的就是自己的实力,只要实力够硬,技术够强,就不怕面试拿不到offer!
[外链图片转存中…(img-uUR7K0Yx-1712442970830)]
[外链图片转存中…(img-plRpNoqz-1712442970830)]
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!
[外链图片转存中…(img-BOKzdMof-1712442970830)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!