0%

fresco介绍

说到android图片加载框架,我们都熟悉glide,Picasso.fresco等,由于我们公司使用了react-native作为了主要的跨平台开发框架,其中自然而然的集成了facebookfresco。所以,在这里主要分析一下fresco是怎么加载图片的.

fresco github仓库

fresco官网

在使用方面fresco是其中最复杂的,但是它的优势也是比较明显的,主要是在以下这几个方面:

  • 图片的渐进式呈现。

  • 支持动图加载:加载Gif、WebP动图,每一帧都是一张很大的Bitmap,每个动画都有很多帧。Fresco能管理好每一帧并管理好你的内存。

  • 丰富的图片处理:缩放、圆角、透明、高斯模糊等处理。

  • 在5.0以下系统,Bitmap缓存位于`ashmem,这样Bitmap对象的创建和释放将不会引发GC,更少的GC会使你的App运行得更加流畅。

  • 良好的代码设计,代码可扩展性非常好。

fresco加载图片流程

我们知道图片加载框架,为了加快下次图片的加载速度,一般是有其缓存机制的,而fresco的缓存机制也和大多数图片加载库一样,有着memory cahce&disk cache,但是fresco缓存策略比较复杂,分为:bitmap缓存,未解码图片的内存缓存,磁盘缓存。fresco有着三级缓存。

注解:所谓图片是否解码,指的是图片是以bitmap的形式存在,还是二进制流的形式存在。未解码表示是二进制流的形式,解码表示将二进制流转为bitmap对象。

简易的加载流程如下,

因为在具体的图片加载逻辑中,设计到各种缓存,decode,裁剪图片等操作,流程是非常复杂的,下面是正常图片加载Producer和Consumer的流程。

关于这块的逻辑,fresco设计的非常巧妙,使用装饰器和责任链模式混用的方式,使得代码结构变得非常容易扩展,而且想要自己定义图片的加载顺序,只需要按照相应的顺序,嵌套new一个producer sequence就可以了。我们目前请求图片就是用的getDecodedImageProducerSequence这个sequence.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
ImageRequest imageRequest,
Object callerContext,
ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
@Nullable RequestListener requestListener,
@Nullable String uiComponentId) {
try {
Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
return submitFetchRequest(
producerSequence,
imageRequest,
lowestPermittedRequestLevelOnSubmit,
callerContext,
requestListener,
uiComponentId);
} catch (Exception exception) {
return DataSources.immediateFailedDataSource(exception);
}
}

fresco有个producerSequenceFactory有许多不同的producer sequence,方便我们根据不同的场景选择不同的sequence。

关于producer和consumer的设计,我使用ts写了一个简单的实现,可以一块预览一下。

bitmapMemoryCache && encode Image Cache

bitmapMemoryCacheEncodedMemoryCache是使用的相同的数据结构,MemoryCache来存储的。主要区别是bitmapMemoryCache缓存的是CloseableImage,而EncodedMemoryCache缓存的是PooledByteBuffer字节流。

LRUMap: Least Recently Used Map,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。 也常见于软件开发中,用于处理一些缓存策略。fresco中使用LinkedHashMap实现LRU算法,LinkedHashMapjdk中,一个具有双链表的Map,是一个有序的HashMap,这里的有序指的是元素的插入顺序。

disk cache

fresco磁盘缓存是分为SMALL,DEFAULT两种,这两种策略的都是用BufferedDiskCache做的缓存,因此,直接分析这个类就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public void put(final CacheKey key, EncodedImage encodedImage) {
try {
if (FrescoSystrace.isTracing()) {
FrescoSystrace.beginSection("BufferedDiskCache#put");
}
Preconditions.checkNotNull(key);
Preconditions.checkArgument(EncodedImage.isValid(encodedImage));

// Store encodedImage in staging area
mStagingArea.put(key, encodedImage);

// Write to disk cache. This will be executed on background thread, so increment the ref
// count. When this write completes (with success/failure), then we will bump down the
// ref count again.
final EncodedImage finalEncodedImage = EncodedImage.cloneOrNull(encodedImage);
try {
final Object token = FrescoInstrumenter.onBeforeSubmitWork("BufferedDiskCache_putAsync");
// 开启异步线程
mWriteExecutor.execute(
new Runnable() {
@Override
public void run() {
final Object currentToken = FrescoInstrumenter.onBeginWork(token, null);
try {
// 将缓存写入到disk中
writeToDiskCache(key, finalEncodedImage);
} catch (Throwable th) {
FrescoInstrumenter.markFailure(token, th);
throw th;
} finally {
mStagingArea.remove(key, finalEncodedImage);
EncodedImage.closeSafely(finalEncodedImage);
FrescoInstrumenter.onEndWork(currentToken);
}
}
});
} catch (Exception exception) {
// We failed to enqueue cache write. Log failure and decrement ref count
// TODO: 3697790
FLog.w(TAG, exception, "Failed to schedule disk-cache write for %s", key.getUriString());
mStagingArea.remove(key, encodedImage);
EncodedImage.closeSafely(finalEncodedImage);
}
} finally {
if (FrescoSystrace.isTracing()) {
FrescoSystrace.endSection();
}
}
}

首先是按照[cacheKey,EncodeImage]的方式,将其缓存在Map中,接下来会开启一个线程,用来异步存储encodeImage到disk中。在这里通过图片的url,经过SHA1加密和base64转码,之后得到一个resourceId,主要用来生成存储文件的文件名,文件名格式为:/data/user/0/package/cache/image_cache/v2.ols100.1/95/mtQL41H7RDPU2uZo1zMCo5fto-Q.4178151137210219191.tmp

图片加载网络请求

fresco关于图片的网络请求,抽象出来一个NetworkFetcher接口来处理,因此,开发者可以通过继承NetworkFetcher,使用自己熟悉的网络请求库来封装。fresco内部已经有了volley,okhttp,httpUrlConnection三种网络库的实现。

ReactImageView是如何加载的?

1.1qrn是如何加载的?

首先我们先分析一下qrn是如何加载的?我们打开一个rn页面,一般是通过scheme的形式打开,scheme的类似:qunariphone://react/open?hybridId=f_home_rn&pageName=Home&initProps=${encodeURIComponent ( JSON.stringify ({"param":{"cityName":"xx”,"bd_source":"xx”}}))},经过native代码桥接的QRCTJumpHandleManagermodule配合动态路由,跳转到QReactNativeActivity页面,再调用QReactHelper#doCreate开始,创建rn环境。

qrn对于ReactInstanceManager的缓存处理

创建rn环境:

1.2 Image标签的渲染

以上react-native环境是初始化完成,接下来分析一下,用react编写的Image是如何渲染到页面上的。

一个简单的列子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from 'react';
import {
AppRegistry,
View,
Image
} from 'react-native';

export default class App extends Component {
render() {
return (
<View>
<Image style={{ height: 50, width: 50 }} source={{ uri: '' }}/>
</View>
);
}
}


AppRegistry.registerComponent('app', () => App);

AppResgistry注册完组件之后,Native端调用AppRegistryrunApplication开始渲染组件。

写过原生android的同学都知道,android一般用布局方式是LinearLayout,RealativeLayout,FrameLayout等,并没有所谓的flex布局,facebook使用c++实现了一套flex布局,即Yoga.

在js端,facebook实现了一套虚拟的dom树结构,可以在ReactNativeRender-prod.js查看其实现方式,在ReactNativeRender中,react-native将所有的view操作都抽象为了UI操作,对应的是native sideUIOpeation,比如:创建view,更新view,测量view。对应的是CreateViewOperation,UpdateViewExtraDataMeasureOperation.

大致流程如下图所示:

React Component在native端View的映射:

native和js端双边通信简易图:

参考文档

https://juejin.cn/post/6844903559280984071

ReactNative Native层的渲染流程

fresco 源码

https://yanbober.blog.csdn.net/article/details/53157456

https://litslink.com/blog/new-react-native-architecture

C++如何调用java

React Native渲染流程浅析

ReactNative源码篇:渲染原理

网站

超轻量级的http server,帮助理解http的流程

tinyhttpd

httpbin,一个http的测试服务器

httpbin

okhttp android 轻量级的网络 请求库

okhhtp

书籍

网络是怎么连接的?

图解http

计算机网络自顶向下方法

问题

ajax跨域问题

openwrt

责任链模式概念

首先先说说什么是责任链模式

在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

维基百科上对于责任链模式的描述wikipedia

责任链模式最基本的就是多个分支判断形成的链,怎么说呢,先看看下面的一段伪代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

private String check(Object condition){

String result = "";

if(checkA(condition)){
return "A符合";
}

if(checkB(condition)){
return "B符合";
}

if(checkC(condition)){
return "c符合"
}
return result;
}

上面这个代码就是最简单的责任链模式的呈现,但是你可能会说,你tm在逗我,我看到的责任链模式可不张这样.

大兄弟,你别着急,你听我说,你看到的平常的代码中的责任链模式,是将上述代码中的checkA(),checkB()…等抽象成接口,使用面向对象的组合和继承等一系列手法,将代码进行解藕实现的.其实最基本的逻辑就是以上的写法,也是我们在日常中经常写的形式.

为什么要用责任链模式呢?随着逻辑的越来越多,以上的写法会写一大堆if else判断,使得代码非常冗余,而且在更改逻辑时,也非常容易出错.因此,我们可以用责任链模式使代码变的优雅,使代码之间的耦合降低.

责任链模式的UML图:

从上面可以看出职责链包含三个角色:

Handler: 抽象处理者。定义了一个处理请求的方法。所有的处理者都必须实现该抽象类。
ConcreteHandler: 具体处理者。处理它所负责的请求,同时也可以访问它的后继者。如果它能够处理该请求则处理,否则将请求传递到它的后继者。
Client: 客户类。

责任链模式在android中的应用

  1. View的事件传递机制.

以下是一个viewgroup的事件分发的核心伪代码.更多讲解请看**android view事件分发**

1
2
3
4
5
6
7
8
9
10
11

public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else{
consume = child.dispatchTouchEvent(event);
}
return consume;
}

以下是android view事件机制的流程图,

android view 事件机制

  1. okhttp的拦截器

我们都知道网络的OSI模型,一个网络通信的形成,都是从应用层进行使用http,https,ftp等应用层的协议进行编码,组织成响应的数据格式;然后在往下传递到传输层组成成报文,数据包的形式;再往下添加ip,传到网络层;在通过一系列的路由转换到达另一台服务器,服务器在一层一层的解码获取响应的数据.

网络模块的参考书,推荐:图解TCP/IP,网络是怎样连接的,计算机网络自定向下方法

可见一个网络的数据包操作也是一个链式的,通过每一层对request的处理传递到下一层,获取到response后在一层层的向上传递进行解析response.

接下来咱们分析一下okhttp拦截器部分的源码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}

从上述代码可以看到interceptors是将所有的拦截器加到一个list中,然后顺序的调用RealInterceptorChain中的process来进行相应的处理.

对okhttp源码的分析,请看这里,传送们

如果不理解,我们可以看看,他的接口是怎么定义的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface Interceptor {
Response intercept(Chain chain) throws IOException;

interface Chain {
Request request();

Response proceed(Request request) throws IOException;

/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();

Call call();

int connectTimeoutMillis();

Chain withConnectTimeout(int timeout, TimeUnit unit);

int readTimeoutMillis();

Chain withReadTimeout(int timeout, TimeUnit unit);

int writeTimeoutMillis();

Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}

拦截器的类图关系:

下面是interceptor的调用关系:

推荐绘制流程图工具:plantUML,官网

责任链模式的demo练习

责任链模式代码练习:
练习场

对于okhttp的拦截器的代码简化:
okhttp拦截器简化

责任链模式在项目中的使用

直接看项目中重构的代码部分

责任链模式与装饰者模式的区别

装饰者模式在java io得到了充分应用,接下来看看装饰者模式的uml图:

责任链模式装饰者模式在形式上非常相似,但是实际上是执行上是有不同的.

责任链模式是如果你不满足条件则传递给下一个类去执行.
而,装饰者模式是执行自身,并且在执行传递的下一个类.

装饰者模式demo

当你的ubuntu同时安装了python2和python3时,你想切换python的版本(由于某些库只支持python2),你需要下面这些操作来切换。

表示将不同版本的python关联到不同的id上.

1
2
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 100
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 150

如果想切换不同的python版本,可以输入一下命令。

1
sudo update-alternatives --config python

就会出现如下界面:

点击回车来确认选择。

输入以下命令查看切换后的python版本。

1
$ python --version

从这以后就可以随意切换python版本了。

adb基本语法

$ adb [-d|-e|-s ]

参数含义:
|参数|含义|
|——|——|
|-d|指定当前唯一通过 USB 连接的 Android 设备为命令目标|
|-e|指定当前唯一运行的模拟器为命令目标|
|-s|指定相应 serialNumber 号的设备/模拟器为命令目标|

实例:

1
adb -s cf264b8f shell wm size

无线连接(需要借助 USB 线)

操作步骤:

  1. 将 Android 设备与要运行 adb 的电脑连接到同一个局域网,比如连到同一个 WiFi。

  2. 将设备与电脑通过 USB 线连接。

应确保连接成功(可运行 adb devices 看是否能列出该设备)。

  1. 让设备在 5555 端口监听 TCP/IP 连接:

    1
    adb tcpip 5555
  2. 断开 USB 连接。

  3. 找到设备的 IP 地址。

一般能在「设置」-「关于手机」-「状态信息」-「IP地址」找到,也可以使用下文里 查看设备信息 - IP 地址 一节里的方法用 adb 命令来查看。

  1. 通过 IP 地址连接设备。

    1
    adb connect <device-ip-address>

    这里的 就是上一步中找到的设备 IP 地址。

  2. 确认连接状态。

    1
    adb devices

    如果能看到

    1
    <device-ip-address>:5555 device

    说明连接成功。

断开无线连接

命令:

1
adb disconnect <device-ip-address>

应用管理

查看应用列表的基本命令格式是

1
adb shell pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]

参数:
|参数|显示列表|
|——|——|
|无|所有应用|
|-f|显示应用关联的 apk 文件|
|-d|只显示 disabled 的应用|
|-e|只显示 enabled 的应用|
|-s|只显示系统应用|
|-3|只显示第三方应用|
|-i|显示应用的 installer|
|-u|包含已卸载应用|
||包名包含 字符串|

参考

awesome-adb

git log只显示commitId

$ git log –pretty=oneline

版本回退

在git中HEAD是当前版本,HEAD^表示回退到上一个版本,HEAD^^表示回退到上上一个版本,HEAD~100表示回退100个版本

回退到上一个版本

$ git reset –hard HEAD^

查看你在git中每一次的commit记录

$ git reflog

撤销修改

如果你在一个分支上的某个文件修改了一些代码(没有执行add命令),你想回退到原先的代码的样子,可以使用checkout命令,具体使用如下:

$ git checkout – file

file为你修改的文件名

如果你修改了代码,然后又执行了add命令,想要回退到之前的版本,就使用只能reset命令了.

$ git reset –hard HEAD^

删除文件

git删除文件,file为删除的文件.

$ git rm file

生成sshkey

生成ssh-key命令

$ ssh-keygen -t rsa -C “youremail@example.com

注意:如果有多个git服务器,在生成ssh-key时可在冒号后,输入别名,图片如下:

分支

创建并切换到该dev分支

$ git checkout -b dev

相当于执行了

$ git branch dev

$ git checkout dev

查看本地分支

$ git branch

查看所有分支(本地及远程分支)

$ git branch -a

将dev分支合并到master分支

$ git checkout master (先切换到master分支)

$ git merge dev (合并dev分支)

删除一个合并过的分支

$ git branch -d dev

删除一个没有合并过的分支

$ git branch -D dev

拉取远程分支到本地

git checkout origin/remoteName -b localName

存储现场代码

当代码写的一半时,又要需要添加新的分支处理bug,则需要将当前的代码保存,命令如下:

$ git stash

查看保存的代码的list

$ git stash list

恢复工作现场

$ git stash apply

因为恢复后,stash保存的代码并不会删除,因此调用以下方法进行删除:

$ git stash drop

也可以调用pop,使得在恢复现场的同时将stash保存的代码删除:

$ git stash pop

远程协作

基于远程分支的新建分支到本地

  1. $ git checkout -b <origin/remote_branch_name>

  2. $ git pull origin :

本地分支关联远程分支

$ git branch –set-upstream-to=<origin/remote_branch_name>

标签管理

tag就是方便的查看每次提交的版本

新建标签tag,将本次commit的tag设为v1.0

$ git tag

显示所有tag

$ git tag

将指定的commitId与tag绑定

$ git tag

查看标签信息

$ git show

打tag的时候,同时增加说明

$ git tag -a -m “this is describe”

删除标签

$ git tag -d

一次性推送全部尚未推送到远程的本地标签:

$ git push origin –tags

pandoc是一个用来转换文档的命令行工具,人们俗称文档转换的”瑞士军刀”.

官方文档:https://pandoc.org/
github项目:https://github.com/jgm/pandoc

但是要将markdown转为pdf,只使用pandoc是不行的,还需要Latex,Latex是写papper的人们用来表示数学符号的一个工具.

ubuntu安装Latex

1
$ sudo apt-get install texlive-full 

安装完之后你就可以将markdown转为pdf,但是只能转换英文的markdown,想要转换中文的markdown还需要进行一些操作.

查看中文字体库

使用linux命令查看中文字体库,

1
$ fc-list :lang=zh

中文markdown转pdf的方式

  1. 直接命令行

    1
    $ pandoc  srs.md -o srs.pdf --latex-engine=xelatex -V mainfont='WenQuanYi Micro Hei Mono'

    mainfont之后是你中文字体库的一个字体名称

  2. 在markdown文件中加入一段代码

1
2
3
---
mainfont: WenQuanYi Micro Hei Mono
---

再使用以下命令就可以了:

1
pandoc --latex-engine=xelatex test.md -o test1.pdf

参考

https://github.com/jgm/pandoc/wiki/Pandoc-with-Chinese

Retrofit这个框架字如其面:重构,即对OKHttp的重构.是使用注解+动态代理的方式是开发者的开发工作变的更加方便,其次是为了开发者在调用RESTful风格的后台接口更加方便.

我们这里先回忆一下Retrofit的使用,首先我们需要定一个接口(这个接口不能在继承其他接口了),为什么一会分析源码是讲解.好,我现在来编写这个接口.

1
2
3
4
5
public interface Api(){
// 这里是url的相对路径
@GET('/v1/{p}/test')
Call<String> test(@Path("p")String person,@Query String a);
}

恩,很好,我们定义好了一个Retrofit想要的接口的形式,接下来我们开始写Retrofit的设置及调用代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public NetHelper {
public static Api get(){
Retrofit retrofit = Retrofit.Builder()
// 设置你的host的url
.baseUrl("http://www.baidu.com/")
// 配置okhttp
.client(new OkHttpClient())
// 设置解析数据的工厂
.addConverterFactory(GsonConverterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Call<String> call = api.test("lisi","test");
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {

}

@Override
public void onFailure(Call<String> call, Throwable t) {

}
});
}
}

好,以上就是使用Retrofit完成了一个异步网络请求.接下来我们分析分析Retrofit的源码实现.

Builder

首先是Retrofit的静态内部类Builder,是用构造者的模式配置了一系列的东西.主要看几个方法:baseUrl,addConverterFactory,addConverterFactory,client.

  1. 设置host的url
1
2
3
4
5
6
7
8
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (httpUrl == null) {
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl);
}
  1. 配置response解析工厂
1
2
3
4
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}

可以看到这是将新的解析工厂加入到一个list中,当发生网络请求获取到Response之后,会遍历解析list的挨个取解析,因此可以添加多个解析工厂.

  1. 配置返回的Call的类型的工厂
1
2
3
4
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
adapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
  1. 配置OKHttpClient
1
2
3
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}

create

接下来我们分析这个Retrofit库中非常重要的一个方法create,这个方法内部实现是用了动态代理.动态代理的好处是什么?那我们先想一想如果没有动态代理,只使用静态代理,如果有许许多多的类需要写代理类,那重复代码和工作量是不是很大,动态代理就是要来简化工作量的.怎么简化工作量?因为,动态代理实际上是使用的反射,通过反射获取要代理的类的内部方法,使用method.invoke(obj,args);的方式来调用它,因此只要传递进来类名我们便可以调用它的方法实现,不用再写多个代理类,从而简化了工作量.好,我们分析完了动态代理原理,我们看看create的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
// 动态代理,使用Proxy的一个静态方法newProxyInstance,需要实现InvocationHandler接口
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();

@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
// 如果是method是Object的方法,直接正常调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}

我们看到InvaocationHandler回调方法中,有个loadServiceMethod方法,这是个很重要的方法,我在看看它的实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ServiceMethod<?, ?> loadServiceMethod(Method method) {
// double lock check
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;

synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}

OSI七层模型

层数 layer 作用 实例
7 应用层(application) 提供为应用软件而设的界面,以设置与另一应用软件之间的通信。 例如:http,https,ftp,telnet,ssh,smtp,pop3等协议
6 表示层(presentation) 把数据转换为能与接收者的系统格式兼容并适合传输的格式。
5 会话层(session) 负责在数据传输中设置和维护电脑网络中两台电脑之间的通信连接。
4 传输层(transport) 把传输表头(TH)加至数据以形成数据包。传输表头包含了所使用的协议等发送信息。 TCP,UDP
3 网络层(network) 决定数据的路径选择和转寄,将网络表头(NH)加至数据包,以形成分组。网络表头包含了网络数据。 ip
2 数据链路层(data link) 负责网络寻址、错误侦测和改错。当表头和表尾被加至数据包时,会形成帧。数据链表头(DLH)是包含了物理地址和错误侦测及改错的方法。数据链表尾(DLT)是一串指示数据包末端的字符串。例如以太网、无线局域网(Wi-Fi)和通用分组无线服务(GPRS)等。
1 物理层(physical) 在局部局域网上传送数据框(frame),它负责管理电脑通信设备和网络媒体之间的互通。