Android 音视频学习:使用三种不同的方式绘制图片

本系列是个人 Android 音视频学习总结,这是第一篇,主要学习内容是:

在 Android 平台上绘制一张图片,使用三种不同的 API,ImageView、SurfaceView、自定义 View。

ImageView 绘制图片

这种方式较为普遍简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ImageViewActivity extends BaseActivity {

private ImageView mImageView;

@Override
protected View getContentView() {
mImageView = new ImageView(this);
return mImageView;
}

@Override
protected int getTitleResId() {
return R.string.image_view;
}

@Override
protected void initView() {
super.initView();
// 绘制图片
mImageView.setImageBitmap(FileUtil.getDrawImageBitmap(this));
}
}

SurfaceView 绘制图片

SurfaceView 是 View 的一个子类,特点在于其实现了双缓冲技术,适用于频繁刷新页面的场景。

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
public class SurfaceViewActivity extends BaseActivity {

private SurfaceView mSurfaceView;

@Override
protected int getTitleResId() {
return R.string.surface_view;
}

@Override
protected View getContentView() {
mSurfaceView = new SurfaceView(this);
return mSurfaceView;
}

@Override
protected void initView() {
super.initView();
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (holder == null) {
return;
}

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);

Canvas canvas = holder.lockCanvas();
// 绘制图片
canvas.drawBitmap(FileUtil.getDrawImageBitmap(SurfaceViewActivity.this), 0, 0, paint);
holder.unlockCanvasAndPost(canvas);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}
});
}
}

自定义 View 绘制图片

还可以通过自定义 View 绘制图片。

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
public class CustomView extends View {

private Paint mPaint;
private Bitmap mBitmap;

public CustomView(Context context) {
this(context, null);
}

public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mBitmap = FileUtil.getDrawImageBitmap(getContext());

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
}
}
}

具体源码看这里:AndroidMultiMediaLearning,下一篇总结使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件。

如何让百度收录 GitHub Pages 个人博客

很多程序员朋友都有在 GitHub Pages 上搭建自己的个人博客,对于个人博客,没有被搜索引擎收录的话,别人基本是是看不到的,再好的技术文无法被分享也是白搭。

基于 GitHub Pages 的个人博客, Google 收录非常及时全面。然而,到目前为止,GitHub 还是拒绝百度爬虫的访问,直接返回 403。

官方给出原因是,百度爬虫爬得太狠,影响了 Github Pages 服务的正常使用。这就导致了,但凡在 Github Pages 搭建的个人博客,都无法被百度收录。

现有的解决办法

1、使用 coding.net 建立镜像网站

我之前使用过 coding.net,在本地 repo 的配置文件中同时添加 GitHub 和 coding.net 远程 repo 地址,发布时,两边都会部署到,加上域名智能解析,对于国内的请求,转发到 Coding Page 即可。

但是通过 coding.net 访问个人主页时会先出现跳转页面,导致百度无法正确爬取。

2、利用 CDN

这个没试过,理论上来说,百度在第一次爬取时,CDN 上必须要已经有相应页面的缓存,否则,爬取的请求会被转发到 GitHub 源站,GitHub 还是会拒绝。

3、使用 Nginx 反向代理

Nginx 做反向代理,直接代理百度爬虫,去 GitHub Pages 请求,然后将结果返回给百度爬虫。

这种方式可行,只不过,这些方法都需要一定的定制能力,对于个人开发者,还得买一台 VPS 或者云服务器。

可靠、免费还简单的方法

Guillermo Rauch 大神创业搞了一个静态站 hosting 服务 zeit.co,可以通过 GitHub Hooks 实现自动部署,zeit 提供 存储 + CDN + DNS 一套完整的服务。

我给个人网站配置完成后,去百度站长试了一下,发现抓取成功了,sitemap 也提交成功了,坐等百度收录。

1

下面我把配置的步骤记录下来,给有需要的朋友一个参考。

zeit 网站主要就三个步骤:

  • Github 账户登陆 zeit.io,授予 zeit repo 的 read 权限;

  • 导入 GitHub 博客 repo;

  • 稍等片刻,部署成功。

项目名中的 . 自动替换成 -,生成了一个类似于 xxxx.now.sh 的链接,点击可以访问你的博客主页,这时候静态资源已经部署到 zeit 的边缘 CDN 节点上了,下次你 GitHub 项目的任何更新会触发 zeit 项目更新。

接下来的就是切换域名,通过智能 DNS 将国内流量切过去。通过 zeit.io 提供的 DNS 解析服务配置自己的域名,然后在百度站长里配置信息。

在 Domains 下为项目添加你的个人域名。

我添加后出现以下配置错误,原因我的域名权威 dns 是 dnspod。

一种解决方式是将直接使用 zeit 提供的 nameserver 智能 DNS,另一种方式,就是保留 dnspod 作为权威 dns 服务器,但是要添加一条 ANAME 记录。

有两张配置方式,一种是改 nameserver,我用的是这种,权威dns服务器改成左边那些,我看到你还是用的dnspod来解析的。另一种方式,就是保留dnspod作为权威dns服务器,但是要添加一条ANAME记录。

我使用的是第一种方式,直接在阿里云替换了 DNS 服务器,直接用 zeit 提供的 nameserver 智能 DNS。

回到 zeit,刷新下,正常是这样,这里是给你签发 https 证书,免费的。

过一会儿应该就好了。

看一下 DNS 解析地址,说明 zeit 域名已经配置成功了。

最后就是在百度站长里面添加个人域名了。这里注意选择 https 协议,因为 zeit 默认都是 https 了。

网站验证我采用的是文件验证,下载验证文件放在你博客本地 repo 的 source 目录下,部署到 GitHub,当然也会及时更新到 zeit。然后完成验证就好了,试一下链接诊断,看能不能正常抓取,失败的话,看看抓取的 ip 地址是不是还是之前的缓存,等待一段时间重新抓取下,时间取决于 dns 的 ttl。

zeit.co 官网上看,台湾和香港都有 CDN 节点,免费账户可以有 20G/月,个人博客应该是够用了。

配置还是很简单的,赶紧试试吧,有问题欢迎交流。

Retrofit 源码分析

前面的文章我们分析了 OkHttp 的核心源码,而 Retrofit 与 OkHttp 的结合使用,也是目前主流的方式,这篇文章主要分析下目前 Android 最优秀的网络封装框架 Retrofit。

在分析 Retrofit 源码之前,先看下 Retrofit 的简单使用。

基本使用

一般情况,Retrofit 的使用流程按照以下三步:

1、将 HTTP API 定义成接口形式

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

2、构建 Retrofit 实例,生成 GitHubService 接口的实现。

1
2
3
4
5
6
// Retrofit 构建过程
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

GitHubService service = retrofit.create(GitHubService.class);

3、发起网络请求,可以做同步或异步请求。

1
2
3
Call<List<Repo>> repos = service.listRepos("octocat");

call.execute() 或者 call.enqueue()

这里,Retrofit 用注解标识不同的网络请求类型,极大的简化了 OkHttp 的使用方式。

这篇文章主要关注的几个问题:

  • Retrofit 实例是如何创建的,它初始化了哪些东西?

  • GitHubService 实例是如何创建的,这些注解是如何映射到每种网络请求的 ?

  • 网络请求的流程是怎样的?

Retrofit 创建过程

1
2
3
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

这里可以看出,Retrofit 实例是使用建造者模式通过 Builder 类进行创建。

建造者模式简言之:将一个复杂对象的构建与表示分离,使得用户在不知道对象的创建细节情况下可以直接创建复杂的对象。

Retrofit

Retrofit 包含 7 个成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final class Retrofit {
// 网络请求配置对象,存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();

// 网络请求器的工厂:生产网络请求器
final okhttp3.Call.Factory callFactory;

// 网络请求的 URL 地址
final HttpUrl baseUrl;

// 数据转换器工厂的集合
final List<Converter.Factory> converterFactories;

// 网络请求适配器工厂的集合
final List<CallAdapter.Factory> callAdapterFactories;

// 回调方法执行器
final @Nullable Executor callbackExecutor;

// 是否缓存创建的 ServiceMethod
final boolean validateEagerly;
}

再看 Retrofit 构造函数,除了 serviceMethodCache, 其他成员变量都在这里进行赋值。

1
2
3
4
5
6
7
8
9
10
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
@Nullable Executor callbackExecutor, boolean validateEagerly) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
this.callbackExecutor = callbackExecutor;
this.validateEagerly = validateEagerly;
}

Retrofit.Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static final class Builder {
private final Platform platform; // 平台
private @Nullable okhttp3.Call.Factory callFactory; // 网络请求工厂,默认使用OkHttpCall(工厂方法模式)
private @Nullable HttpUrl baseUrl; // 网络请求URL地址
private final List<Converter.Factory> converterFactories = new ArrayList<>(); // 数据转换器工厂的集合
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(); // 网络请求适配器工厂的集合
private @Nullable Executor callbackExecutor; // 回调方法执行器
private boolean validateEagerly;

Builder(Platform platform) {
this.platform = platform;
}

public Builder() {
this(Platform.get());
}
...
}

这里主要关注 Platform,在 Builder 构造函数中调用了 Platform.get() ,然后赋值给自己的 platform 变量,我们来看看 Platform 类。

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
class Platform {
private static final Platform PLATFORM = findPlatform();

static Platform get() {
return PLATFORM;
}

private static Platform findPlatform() {
try {
// 判断是否是 Android 平台
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
// Java 平台
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
}

Platform.get() 方法会调用 findPlatform() 方法,这里主要是判断是 Android 平台还是 Java 平台,如果是 Android 平台会返回一个 Android 对象。

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
static class Android extends Platform {
@IgnoreJRERequirement // Guarded by API check.
@Override boolean isDefaultMethod(Method method) {
if (Build.VERSION.SDK_INT < 24) {
return false;
}
return method.isDefault();
}

@Override public Executor defaultCallbackExecutor() {
return new MainThreadExecutor();
}

@Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
@Nullable Executor callbackExecutor) {
if (callbackExecutor == null) throw new AssertionError();
DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
return Build.VERSION.SDK_INT >= 24
? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
: singletonList(executorFactory);
}

@Override int defaultCallAdapterFactoriesSize() {
return Build.VERSION.SDK_INT >= 24 ? 2 : 1;
}

@Override List<? extends Converter.Factory> defaultConverterFactories() {
return Build.VERSION.SDK_INT >= 24
? singletonList(OptionalConverterFactory.INSTANCE)
: Collections.<Converter.Factory>emptyList();
}

@Override int defaultConverterFactoriesSize() {
return Build.VERSION.SDK_INT >= 24 ? 1 : 0;
}

static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());

@Override public void execute(Runnable r) {
handler.post(r);
}
}
}

关注三个重要的方法:

  • defaultCallbackExecutor: 返回默认的 Executor 对象,正是 Retrofit 的成员变量回调执行器,它的内部采用 Handler 负责子线程到主线程的切换工作。

  • defaultCallAdapterFactories:返回的是默认的 CallAdpter.Factory 的集合,也就是 Retrofit 的成员变量网络请求适配器工厂集合,如果是 Android 7.0 以上或者 Java 8,使用并发包中的 CompletableFuture 保证了回调的同步。

  • defaultConverterFactories:返回的是默认的 Converter.Factory 的集合,也就是 Retrofit 的成员变量数据转换器工厂集合。

build 过程

接着看一下 Builder.build() 方法。

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
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}

okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
// 默认使用 OkHttp
callFactory = new OkHttpClient();
}

Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
// 默认的 callbackExecutor
callbackExecutor = platform.defaultCallbackExecutor();
}

// 添加你配置的 CallAdapter.Factory 到 List,然后把 Platform 默认的 defaultCallAdapterFactories 添加到 List
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

// 添加 BuiltInConverters 和手动配置的 Converter.Factory 到 List,然后把 Platform 默认的 defaultConverterFactories 添加到 List
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories = new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());

// 返回一个 Retrofit 对象
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}

至此,Retrofit 的创建流程就完成了,它的成员变量的值如下:

  • serviceMethodService:暂时为空的 ConcurrentHashMap

  • callFactory:默认OkHttpClient 对象

  • baseUrl:根据配置的 baseUrl,构建 HttpUrl 对象

  • callAdapterFactories:配置的和默认的网络请求适配器工厂集合

  • converterFactories:配置的和默认的数据转换器工厂集合

  • callbackExecutor:MainThreadExecutor 对象

  • validateEagerly:默认 false

创建网络请求接口实例

接着来看 GitHubService 实例是如何创建的。

1
GitHubService service = retrofit.create(GitHubService.class);

Service 创建

retrofit.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
28
29
public <T> T create(final Class<T> service) {

// 对参数 service 进行校验, service 必须是一个接口,而且没有继承别的接口
Utils.validateServiceInterface(service);
// 判断是否需要提前验证
if (validateEagerly) {
eagerlyValidateMethods(service);
}
// 利用动态代理技术,自动生成 Service 接口的实现类,将 Service 接口方法中的参数交给 InvocationHandler 处理
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];

@Override public @Nullable Object invoke(Object proxy, Method method,
@Nullable Object[] args) throws Throwable {
// Object 类的方法直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 如果是对应平台本身类就有的方法,直接调用
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// 否则通过 loadServiceMethod 方法获取到对应 ServiceMethod 并 invoke
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}

retrofit.create() 返回的是代理对象 Proxy,并转换为 T 类型,即 GitHubService。这里利用了动态代理技术,自动生成 Service 接口的实现类,将 Service 接口方法中的参数交给 InvocationHandler 处理。

对于 Object 类本身独有以及对应平台本身存在的方法,就直接调用,否则通过 loadServiceMethod() 对 Service 接口中对应的 method 进行解析处理,之后对其调用 invoke() 方法。

可以看出,Retrofit 不是在创建 Service 接口实例时就立即对所有接口中的方法进行注解解析,而是采用了在方法被调用时才进行注解的解析,也就是懒加载。

validateEagerly 的作用

我们看看 validateEagerly 这个变量,看看它控制着什么。validateEagerly 为 true 会进入 eagerlyValidateMethods() 方法。

1
2
3
4
5
6
7
8
private void eagerlyValidateMethods(Class<?> service) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
loadServiceMethod(method);
}
}
}

这里循环取出接口中的 Method,调用 loadServiceMethod() loadServiceMethod() 先从 serviceMethodCache 获取 Method 对应的 ServiceMethod,如果有直接返回,否则对 Method 进行解析得到一个 ServiceMethod 对象,存入缓存中。

所以 validateEagerly 变量是用于判断是否需要提前验证解析的,默认为 false,如果在 Retrofit 创建时设置为 true,会对 Service 接口中所有方法进行提前解析处理。

ServiceMethod 创建过程

loadServiceMethod() 方法的具体实现如下,这里采用了 Double Check 的方式尝试从 serviceMethodCache 中获取 ServiceMethod 对象,如果获取不到则通过 ServiceMethod.parseAnnotations() 方法对该 method 的注解进行处理并将得到的 ServiceMethod 对象加入缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ServiceMethod<?> loadServiceMethod(Method method) {
// 1. 先从缓存中获取,如果有则直接返回
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
// 2. 这里又获取一次,原因是网络请求一般是多线程环境下,ServiceMethod 可能创建完成了
result = serviceMethodCache.get(method);
if (result == null) {
// 3. 解析方法注解,创建 ServiceMethod
result = ServiceMethod.parseAnnotations(this, method);
// 存入缓存
serviceMethodCache.put(method, result);
}
}
return result;
}

我们详细看一下 ServiceMethod 创建过程。 ServiceMethod.parseAnnotations() 方法具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 通过 RequestFactory 解析注解配置(工厂模式、内部使用了建造者模式)
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
// HttpServiceMethod 解析注解的方法
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

1、通过 RequestFactory 解析注解配置

通过工厂模式和建造者模式创建 RequestFactory,解析封装注解配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}

Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
// 获取网络请求接口方法里的注解
this.methodAnnotations = method.getAnnotations();
// 获取网络请求接口方法里的参数类型
this.parameterTypes = method.getGenericParameterTypes();
// 获取网络请求接口方法里的注解内容
this.parameterAnnotationsArray = method.getParameterAnnotations();
}

2、ServiceMethod 的创建

ServiceMethod 的创建在 HttpServiceMethod 的 parseAnnotations() 方法中。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;

Annotation[] annotations = method.getAnnotations();
Type adapterType;
// 如果方法是 kotlin 中的 suspend 方法
if (isKotlinSuspendFunction) {
// 获取 Continuation 的范型参数,它就是 suspend 方法的返回值类型
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
// 如果 Continuation 的范型参数是 Response,则说明它需要的是 Response,那么将 continuationWantsResponse 置为 true;
if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
// Unwrap the actual body type from Response<T>.
responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
continuationWantsResponse = true;
} else {
// TODO figure out if type is nullable or not
// Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
// Find the entry for method
// Determine if return type is nullable or not
}

adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
// 否则获取方法返回值的范型参数,即为请求需要的返回值的类型
adapterType = method.getGenericReturnType();
}

// 根据网络请求接口方法的返回值和注解类型
// 从 Retrofit 对象中获取对于的网络请求适配器
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);

// 得到响应类型
Type responseType = callAdapter.responseType();
if (responseType == okhttp3.Response.class) {
throw methodError(method, "'"
+ getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
if (responseType == Response.class) {
throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// TODO support Unit for Kotlin?
if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
throw methodError(method, "HEAD method must use Void as response type.");
}

// 根据网络请求接口方法的返回值和注解类型从Retrofit对象中获取对应的数据转换器
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);

okhttp3.Call.Factory callFactory = retrofit.callFactory;
if (!isKotlinSuspendFunction) {
// 不是 suspend 方法的话则直接创建并返回一个 CallAdapted 对象
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);
}
}

HttpServiceMethod.parseAnnotations() 的主要作用就是获取 CallAdapter 以及 Converter 对象,并构建对应 HttpServiceMethod

  • CallAdapter :根据网络接口方法的返回值类型来选择具体要用哪种 CallAdapterFactory,然后获取具体的 CallAdapter。
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
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
try {
//noinspection unchecked
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}

public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}

public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {

int start = callAdapterFactories.indexOf(skipPast) + 1;

// 遍历 CallAdapter.Factory 集合寻找合适的工厂(该工厂集合在第一步构造 Retrofit 对象时进行添加)
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
...
}
  • 获取 Converter:根据网络请求接口方法的返回值和注解类型从 Retrofit 对象中获取对应的数据转换器,和创建 CallAdapter 基本一致,遍历 Converter.Factory 集合并寻找具体的 Converter。
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
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
Retrofit retrofit, Method method, Type responseType) {
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.responseBodyConverter(responseType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create converter for %s", responseType);
}
}

public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {

int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
...
}
  • 构建 HttpServiceMethod:根据是否是 kotlin suspend 方法分别返回不同类型的 HttpServiceMethod。如果不是 suspend 方法的话则直接创建并返回一个 CallAdapted 对象,否则根据 suspend 方法需要的是 Response 还是具体的类型,分别返回 SuspendForResponse 和 SuspendForBody 对象。

ServiceMethod.invoke()

ServiceMethod 是一个抽象类,invoke() 是一个抽象方法,具体实现在子类中。

1
2
3
abstract class ServiceMethod<T> {
abstract @Nullable T invoke(Object[] args);
}

它的子类是 HttpServiceMethod,HttpServiceMethod 的 invoke() 方法中,首先构造一个 OkHttpCall,然后通过 adapt() 方法实现对 Call 的转换。

1
2
3
4
5
6
7
8
9
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}

protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
}

adapt() 是一个 抽象方法,所以具体实现在 HttpServiceMethod 的子类中。

HttpServiceMethod 有三个子类,非协程的情况是 CallAdapted,另外两个子类则是在使用协程的情况下为了配合协程的 SuspendForResponse 以及 SuspendForBody 类。

  • CallAdapted:通过传递进来的 CallAdapter 对 Call 进行转换。

  • SuspendForResponse:首先根据传递进来的 Call 构造了一个参数为 Response 的 Continuation 对象然后通过 Kotlin 实现的 awaitResponse() 方法将 call 的 enqueue 异步回调过程封装成 一个 suspend 的函数。

  • SuspendForBody:SuspendForBody 则是根据传递进来的 Call 构造了一个 Continuation 对象然后通过 Kotlin 实现的 await()awaitNullable() 方法将 call 的 enqueue 异步回调过程封装为了一个 suspend 的函数。

发起网络请求

创建 Call

1
Call<List<Repo>> repos = service.listRepos("octocat");

从前面的分析了解到,Service 对象是动态代理对象,当调用 listRepos() 方法时会调用到 InvocationHandler的 invoke() 方法,得到最终的 Call 对象。

如果没有传入 CallAdapter 的话,默认情况返回的 Call 是 OkHttpCall 对象,它实现了 Call 接口。

同步请求

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
@Override
public Response<T> execute() throws IOException {
okhttp3.Call call;

synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;

if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else if (creationFailure instanceof RuntimeException) {
throw (RuntimeException) creationFailure;
} else {
throw (Error) creationFailure;
}
}

call = rawCall;
if (call == null) {
try {
// 1. 创建 OkHttp 的 Call 对象
call = rawCall = createRawCall();
} catch (IOException | RuntimeException | Error e) {
throwIfFatal(e); // Do not assign a fatal error to creationFailure.
creationFailure = e;
throw e;
}
}
}

if (canceled) {
call.cancel();
}

// 2. 执行请求并解析返回结果
return parseResponse(call.execute());
}

很简单,主要就是创建 OkHttp 的 Call 对象,调用 Call 的 execute 方法,对 Response 进行解析返回。

异步请求

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Override
public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null");

okhttp3.Call call;
Throwable failure;

synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;

call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
// 1. 创建 OkHttp 的 Call 对象
call = rawCall = createRawCall();
} catch (Throwable t) {
throwIfFatal(t);
failure = creationFailure = t;
}
}
}

if (failure != null) {
callback.onFailure(this, failure);
return;
}

if (canceled) {
call.cancel();
}

// 2. 调用 Call 的异步执行方法
call.enqueue(new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
// 3. 解析返回结果
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}

try {
// 4. 执行回调
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great
}
}

@Override
public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}

private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great
}
}
});
}

也很简单,主要就是创建 OkHttp 的 Call 对象,调用 Call 的 enqueue 方法,解析返回结果,执行回调。

总结

至此,Retrofit 的源码基本上就看完了,虽然还有很多细节没有提及,但 Retrofit 的整体流程很清晰了。

Retrofit 本质上是一个 RESTful 的 Http 网络请求框架的封装,通过大量的设计模式封装了 OkHttp,使得更加简单易用。它内部主要是用动态代理的方式,动态将网络请求接口的注解解析成 HTTP 请求,最后执行请求的过程。

建议将 Retrofit 的源码下载下来,使用 IDEA 可以直接打开阅读。我这边已经将源码下载下来,进行了注释说明,有需要的可以直接从 Android open framework analysis 查看。

参考

1、Android开源框架源码鉴赏:Retrofit

2、Android:手把手带你 深入读懂 Retrofit 2.0 源码

3、带你一步步剖析Retrofit 源码解析:一款基于 OkHttp 实现的网络请求框架

我的 2019 年个人总结

虽然 2020 年已经过去一周时间,但还是想记录一下我的 2019 年个人总结,复盘一下自己过去一年的工作、学习和生活状况。

工作方面

年初面了几家心仪的公司,但没有得到想要的结果,认识到自身实力一般,而且外界的环境也不是很好,也就还在现在的公司继续干着。

2018 年部门效益很差,那年年底还裁掉了一些同事。为了生存下来,2019 年整个团队像是在打一场战役,这一年大家都挺忙的,忙碌的时候加班比较严重,所幸团队成员的积极性还算高。

虽然如王兴说的 「2019 可能会是过去 10 年最差的一年,但却是未来 10 年最好的一年」,但庆幸的是 2019 年部门的业绩应该是超额完成了,这里面有每一个员工的付出和努力。

毕竟只有企业活得好,员工才能得到应得的回报。企业的成败,每一个员工都有责任,在一个企业里,应该尽职尽责做好自己的工作。

2020 年,不管自己在哪个企业,都希望自己保持主动、积极、尽职尽责。

技术学习方面

主要涉及了 Android 音视频知识, Android 性能优化,Android 源码分析以及 Android 面试系列等等。

其实对这一年的技术成长不是很满意。很多东西从一开始没有做好计划,导致学习的深度和效率不高,技术提升也并不显著。

所以最近几天,我会好好思考下,今年的技术学习路线到底是什么,做一份详尽的学习计划。

这一年在个人博客、公众号以及知乎等平台写了几十篇文章,数量还是太少了。

在互联网平台,做消费者的同时做一个生产者,比只做消费者的收获要大很多。因此我也通过写文章的方式分享一些自己的技术总结、个人见识、生活经验等等,也有帮助到一些朋友,解决了他们的问题,这是一件很棒的事情。只要能帮助到一个朋友,就说明了我的文章是有价值的。

2020 年我也会争取多多分享,目前确定的会长期持续更新的有 Android 面试系列文章和每周分享周刊。

技术之外,这一年主要是在微信阅读上面读书,大概读了三十几本书。相比比从前不看书的我,这个提升还是很大的。2020 年,继续保持。

生活方面

这一年可能是这一生极为重要的一个年份了。因为今年完成了结婚、生娃两件大事。和妻子结束了 9 年的恋爱马拉松,步入婚姻的殿堂,2019 年 12 月 27 日,喜提佩奇,平安喜乐,辛苦妻子。

这一年,身份角色的转变,伴随而来的是爱与责任。2020 年,多一点时间陪伴亲人。

以上,算是 2019 年的一点总结。

其实一年的时间非常快。如果没有目标,没有预先做好计划,按计划执行,一年的时间很容易被荒废,到年底会发现这一年啥事没做成。

2020 年,有一些目标和想法,没必要轻易说出来,关键还是看行动吧。

2019 年,感谢大家的关注,2020 年祝福每一位朋友,万事顺意,平安健康。

每周分享第 6 期

这里记录过去一周,我看到的值得分享的内容。

本周刊开源(GitHub: zywudev/weekly),欢迎投稿,或者推荐好玩的东西。

titu

(题图:Payson Wick)

资源

1、Python最佳实践指南

《Python 开发最佳实践指南》,中文版开源电子书,翻译自英语原版。

2、微软 REST API 设计指南

微软官方出品的 REST API 指导规范。

3、Web 开发者 2020 年学习指南

GitHub 上有一位开发者根据 Udemy 的热门课程,整理了一份 Web 开发者 2020 年学习指南。其中包含常用的 Web 开发工具、设计软件、主流框架、基础知识、后端 & DevOps 技术堆栈等分类。

工具

1、Code Search

Android 官方最近为 AOSP 引入了代码搜索工具。

体验了下,挺好用的,可以在任意开源分支上切换,还支持交叉引用查找,感觉可以替代掉第三方的网站了。

2、网红脸生成器

一个网红脸生成器项目(非真实人脸)这是一个用 StyleGAN 训练出的网红脸生成器。

作者还开源了其他几个人脸生成器。

64_examples

3、Crater

一款免费开源的 Web 与移动端发票应用:Crater。

可跟踪记录日常费用支出情况,并生成专业发票与消费报告。界面设计清新而简洁,适用于自由职业者或小型企业。

文摘

1、《孩子王》阿城

我的父亲是世界中力气最大的人。他在队里扛麻袋,别人都比不过他。我的父亲又是世界中吃饭最多的人。家里的饭,都是母亲让他吃饱。这很对,因为父亲要做工,每月拿钱来养活一家人。但是父亲说:“我没有王福力气大。因为王福在识字。”父亲是一个不能讲话的人,但我懂他的意思。队上有人欺负他,我明白。所以我要好好学文化,替他说话。父亲很辛苦,今天他病了,后来慢慢爬起来,还要去干活,不愿失去一天的钱。我要上学,现在还替不了他。早上出的白太阳,父亲在山上走,走进白太阳里去。我想,父亲有力气啦。

言论

1、

最后十年

在我 20 来岁的时候,我觉得青春只剩最后十年了

在我 30 来岁的时候,我觉得职场只剩最后十年了

在我 40 来岁的时候,我觉得人生只剩最后十年了

在我 50 来岁的时候,我觉得精力只剩最后十年了

在我 60 来岁的时候,我觉得生命只剩最后十年了

是的,我们人生只有最后十年……

—— hao chen

2、

我们的人生是由命运和因果报应两条法则互相交织而成的。这两者互相干涉,比如当命运非常恶劣时,做一点好事,并不会出现好的结果,因为仅有的一点善行为强势的厄运所淹没。同样,当好运非常旺盛时,稍稍做点坏事,也不会马上出现恶因招恶果的情形。

– 稻盛和夫

3、

任何傻瓜都能写计算机能理解的代码,优秀的程序员编写人类能够理解的代码

– Martin Fowler

每周分享第 5 期

这里记录过去一周,我看到的值得分享的内容。

titu

(题图:Duy Hoang

文章

1、兽爷丨大象踩了他一脚

大公司欺负员工真的是轻而易举,就像大象踩蚂蚁一样。

2、华为做过的恶

收集华为作过的恶,记录这些不应该被遗忘的历史。

工具

1、Snipaste

Snipaste 是一个简单但强大的截图工具,也可以让你将截图贴回到屏幕上!下载并打开 Snipaste,按下 F1 来开始截图,再按 F3,截图就在桌面置顶显示了。就这么简单!

资源

1、Best-websites-a-programmer-should-visit

学习计算机软件,一些很有用的网站。

言论

1、

原中央网信办副主任彭波对华为事件的评论

  • 水可以载舟,也可以覆舟。舆论可以把你追捧成“民族英雄”,也可以把你踩在脚下,而且很可能是同一批人。 应该看到“捧”你和“踩”你,都是“爱”你——踩你时,绝大部分网友是希望你更好,希望一个伟大的公司更加伟大,一个心目中偶像级的企业更加完美。 要善待这种爱心。 网上最恐怖的情形,不是沸反盈天、骂声一片,而是鸦雀无声、万马齐喑。

  • 此事标志着在新的社会主要矛盾背景下,人们对于公平正义的追求更加强烈。人们用更加严苛的眼光看待企业,看待社会,涉及“公平正义”的问题,往往触动全社会最敏感的神经。

  • 不管是谁,永远要敬畏互联网,敬畏网民,敬畏舆论;不管是多大的企业,千万不要傲慢,不要任性。

  • 千万不要把事情随便上纲上线,不要把一般的舆情事件政治化,尽管可能有“境内外阶级敌人”的破坏。

  • 网上舆论的治理如同治水,正确的方法是疏堵结合,以疏为主。有时候处理不当带来的次生舆情,危害大于原始舆情。

  • 同情“弱者”,不符合法治精神,“强者”也有合法的权益,真理不一定掌握在“弱者”手上,但同情“弱者”符合舆情规律。

  • 机构(包括政府机关和企业)处理事情的思维逻辑是“法理情”,先考虑是否合法,再考虑是否有理,最后考虑民众情感上能否接受;而民众处理事情的逻辑是“情理法”,先考虑感情上能否接受。

  • 重大舆情,要由公司公共关系部门统筹处理,实务部门和法务部门在需要时才出现;实务部门和法务部门不能主导舆情处置。

  • 永远留有后手,不要轻易把自己置于悬崖边上。 只有公开透明,才能赢得公平公正。世界上没有不明真相的群众,只有掌握真相又没有说明真相的机构。在一片谴责之声出现时,机构仍不说明真相,这里一定有“难言之隐”,大家要有耐心,让子弹再飞一会儿。拒绝恶意猜测,拒绝恶意炒作。

2、

拉里佩奇的管理法则

  • 不要推诿:自己去做任何能让产品研发进展更快的事情。

  • 如果你不能为产品增值,那么你就不要成为碍事的人。让那些真正做事的人相互讨论,你去做其他事情吧。

  • 不要官僚主义。

  • 创意比年龄更重要。提出想法的人很初级不意味着TA不应该获得尊重和合作。

  • 最糟糕的事情就是只会说「不,不行。」,而不提出切实可行的改进措施来。

3、

关于251,看到一个妙评:大象踩了一脚蚂蚁,没踩死,对蚂蚁说,你可以踩我一脚。

图片

互联网现状

1、

photo1

2、

photo2

3、

photo3

Android 面试题(8):抽象类和接口的区别?

抽象类和接口的区别

1、抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法;

接口在 Java 长达 20 多年的时间中,都只能拥有抽象方法,直到 JDK1.8 才能拥有实现的方法(还必须用default关键字修饰)

2、抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;

3、接口中不能含有构造器、静态代码块以及静态方法,而抽象类可以有构造器、静态代码块和静态方法;

4、一个类只能继承一个抽象类,而一个类却可以实现多个接口;

5、抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法。

换个角度思考,抽象类和接口的区别在于设计目的不同。

接口的设计目的,是对类的行为进行约束,强制不同的类具有相同的行为,它只约束行为的有无,不对如何实现进行限制。

抽象类的设计目的,是代码复用。当不同的类具有相同的行为,且其中一部分行为的实现方式一致时,可以让这些类都派生于一个抽象类。

为什么接口中不能定义变量?

如果接口可以定义变量,但是接口中的方法又都是抽象的,在接口中无法通过行为来修改属性。有的人会说了,没有关系,可以通过实现接口的对象的行为来修改接口中的属性。这当然没有问题,但是考虑这样的情况。如果接口 A 中有一个public 访问权限的静态变量 a。按照 Java 的语义,我们可以不通过实现接口的对象来访问变量 a,通过 A.a = xxx; 就可以改变接口中的变量 a 的值了。正如抽象类中是可以这样做的,那么实现接口 A 的所有对象也都会自动拥有这一改变后的 a 的值了,也就是说一个地方改变了 a,所有这些对象中 a 的值也都跟着变了。这和抽象类有什么区别呢,怎么体现接口更高的抽象级别呢,怎么体现接口提供的统一的协议呢,那还要接口这种抽象来做什么呢?所以接口中不能出现变量,如果有变量,就和接口提供的统一的抽象这种思想是抵触的。所以接口中的属性必然是常量,只能读不能改,这样才能为实现接口的对象提供一个统一的属性。

通俗的讲,你认为是要变化的东西,就放在你自己的实现中,不能放在接口中去,接口只是对一类事物的属性和行为更高层次的抽象。对修改关闭,对扩展(不同的实现 implements)开放,接口是对开闭原则的一种体现。

参考:Java中接口里定义的成员变量

每周分享第 4 期

这里记录过去一周,我看到的值得分享的内容。

titu

(题图: Martin Schmidli

文章

1、和死神赛跑:趁父亲还在世,我想用人工智能留住他

《连线》杂志的一篇文章《我用 AI 机器人留住去世的父亲》,作者在得知父亲肺癌晚期后,录下了自己与父亲的对话。后来利用这些对话资料,建造了一个人工智能对话机器人。父亲去世以后,跟机器人对话,机器人说出父亲会说的话。

有网友把这篇文章翻译成了中文,读完很是感动。

dadbot-opener

2、网易裁员事件引发的思考:5点建议,越早懂,越能保护自己

这一周,一篇《网易裁员,让保安把身患绝症的我赶出公司。》的文章获得广泛关注。 最后双方也达成了和解。

作为普通群众,我们应该从这件事中有所获得,我们应该学到的也许不是公关技巧,而是被很多人忽略的《劳动合同法》的基本知识。

3、深度调查:人贩子“梅姨”身后嗜血的“寻人灰产”

寻亲背后一直有一条灰色产业链,只是你不知道。希望这些吃人血馒头的恶人能被绳之以法。

另外推荐大家了解下公安部失踪儿童消息紧急发布平台,也叫着 「团圆系统」。

工具

1、Carbon

一个生成代码图片的网站,很多好看的样式。

carbon

2、codelf

变量命名神器,写代码不知道变量怎么命名就去这里查,命令多来自与 GitHub 爬虫,具备参考价值。

codelf

3、Saladict

聚合词典专业划词翻译,浏览器插件必备。sala

4、Sourcetrail

一个免费开源、跨平台的可视化源码探索项目。

特地为它写了一篇文章:开源免费的源码阅读神器 Sourcetrail

sourcetrail

5、英语轻松读

英语轻松读是针对已经有一定英语阅读能力的人群推出的阅读辅助工具。在看新闻的同时学习英语。

目前 ios App 已经上架,Android 正在开发中,稍等几天即可。

learn_english

资源

1、English Learning Blog by EngFluent – EngFluent

这个博客分享学习英语的经验和方法,听说读写都有。

言论和笑话

1、

在计算机科学中只有两件难事:缓存失效和命名 。

– Phil Karlton

2、

未来的世界,我们每人都必须要用一个政府发的手机,收集每个人的数据,且每天都要用手机的人脸识别进行微笑打卡,手机每天推送的文章都会有是否已读标识,如果没读,就会打电话催你,读了就要回复写心得,Al会自动判分,一天要挣够积分,挣不够分就是价值观有问题,要去补习班参加集体学习……

– Hao Chen

3、

我们这个币圈,不容易啊,真的不容易啊,大家这种有梦想,怀揣着一夜暴富的心理来做币,遇到这种情况真是,真是特别糟糕,我们一定要就是(哽咽),大家一定要(抽泣),同心协力(破声),好吗?

– 币圈维权群语音

开源免费的源码阅读神器 Sourcetrail

阅读源码的工具很多,今天给大家推荐一款别具一格的源码阅读神器。

它就是 Sourcetrail,一个免费开源、跨平台的可视化源码探索项目。

concept

直接看效果图:

step10

上面是我阅读 okhttp 源码的一个界面,不同于其他代码编辑器的导航栏,左侧使用图形直观地表示了调用上游和下游,类成员列表等等细节,使得理解源代码的结构变得很容易。

目前支持 C、C++、Python 和 Java 语言,同时提供了相关 SDK 用于拓展支持其它语言,相信在未来会提供更多语言的支持。

官网地址:

https://www.sourcetrail.com/

目前已经开源:

https://github.com/CoatiSoftware/Sourcetrail

支持 Windows、macOS、Linux 三个平台,下载地址:

https://github.com/CoatiSoftware/Sourcetrail/releases

下面简单介绍下如何使用 sourcetrail 阅读源码,以 okhttp 源码为例:

首先打开界面如下,点击左侧的 New Project 创建项目

step1

选择预先下载好的 okhttp 源码文件夹

step2

点击 Add Source Group,选择 Empty Java Source Group:

step3

点击 Next 到下面这个界面, 依次点击步骤 1、2 指定索引文件:

step4

点击 Next 后出现下面的界面,点击 Create 按钮:

step5

选择 All Files, 点击 Start 按钮开始索引,等待一段时间。

step6

step7

step8

出现 Error 是正常的,因为源码中有很多文件找不到,不影响阅读源码。

索引完成后点击 OK,就可以开始源码阅读了。

step9

step10

项目中的符号都可以点击,支持来回跳转,还支持模糊搜索。左侧源码结构非常清晰,对于熟悉陌生代码结构非常有用。

还可以与其他编辑器链接,比如: Atom, Clion, Eclipse, Emacs, IntelliJ IDEA 等等。

还有更多功能可以参见官方文档:

https://www.sourcetrail.com/documentation/

一般不特地推荐,一推荐必是利器。赶紧上手体验下。

Android 面试题(7):你对单例模式了解多少?

不管是在开发还是面试过程中,单例模式出现的频率都非常的高。但很多人对单例模式一知半解,单例模式的写法非常多,不同写法的区别很大,这篇文章的目的是带你深入学习一下单例模式。

什么是单例模式

单例模式是一种对象创建型模式,用来编写一个类,在整个应用系统中只能有该类的一个实例对象。

UML 结构图:

singleton

单例模式的三要点:

1、某个类只能有一个实例

2、必须自行创建这个实例

3、必须自行向整个系统提供这个实例

单例模式的特点

优点:

使用单例模式可以减少内存的开销,避免了对象实例的频繁创建和销毁。

缺点:

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

  • 单例类的职责过重,在一定程度上违背了”单一职责原则”。

单例模式的各种写法

1、饿汉式单例类

饿汉式比较简单,对象在类加载时就实例化,因此称为饿汉式单例类。

1
2
3
4
5
6
7
8
9
10
public class Singleton {  

private static Singleton instance = new Singleton();

private Singleton (){}

public static Singleton getInstance() {
return instance;
}
}

优点:线程安全,没有加锁,执行效率高。

缺点:未使用单例类时,造成内存浪费。

2、懒汉式单例类

在需要使用单例类时在创建实例对象,所有称为懒汉式单例类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {

private static Singleton instance;

private Singleton (){}

public static Singleton getInstance() {

if (instance == null) {
instance = new Singleton();
}

return instance;
}

}

优点:节约内存

缺点:线程不安全,多线程可能会创建多个实例

3、同步方法的懒汉式单例类

getInstance() 方法前增加关键字 synchronized。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {

private static Singleton instance;

private Singleton (){}

public static synchronized Singleton getInstance() {

if (instance == null) {
instance = new Singleton();
}

return instance;
}

}

优点:解决了线程安全问题

缺点:除了第一次调用 getInstance()需要同步,后面的调用造成不必要的同步开销,不建议用这种模式。

4、 双重检查锁定

这种写法是在 getInstance() 方法中进行两次判空,缩小同步锁的粒度。

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

private volatile static Singleton singleton;

private Singleton (){}

public static Singleton getInstance() {

if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}

return singleton;
}

}

第二个判空是必要的,原因如下:

假如某一瞬间线程 A 和线程 B 都在调用getInstance() 方法,此时 instance 对象为 null,均能通过 “ instance == null ” 的判断。线程 A 进入 synchronized 锁定的代码中执行实例创建代码,线程 B 处于排队等待状态。当 A 执行完毕创建了实例后,线程 B 进入 synchronized 代码,此时 B 并不知道实例已经创建,将创建新的实例。

因此需要在 synchronized 锁定代码中再进行一次判空处理,这种方式称为双重检查锁定。实例变量需要加 volatile 关键字保证易变可见性,JDK1.5 起才可用。

优点:一定程度上解决了资源的消耗和多余的加锁同步,线程安全等问题。

缺点:由于 volatile 关键字会屏蔽 Java 虚拟机所做的一些代码优化,可能会导致系统运行效率降低。

5、静态内部类单例模式

同样利用了 JVM 类加载机制来保证初始化实例对象时只有一个线程,静态内部类 SingletonHolder 类只有第一次调用 getInstance() 方法时,才会装载从而实例化对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {

private static class SingletonHolder {

private static final Singleton INSTANCE = new Singleton();
}

private Singleton (){}

public static final Singleton getInstance() {

return SingletonHolder.INSTANCE;
}

}

优点:解决了线程安全问题,延迟加载、节省内存,性能优于上述几种实现方式。

6、枚举方式

定义一个枚举的元素,就代表 Singleton 实例。

1
2
3
4
5
6
7
8
9
10
11
12
public enum Singleton {

INSTANCE;

/*
**调用方式:Singleton.INSTANCE.doSomethingMethod();
*/
public void doSomethingMethod() {

}

}

优点:线程安全,防止反序列化或者反射攻击时实例重新创建。

如何避免 Java 反射破坏单例模式

上面 6 种 Java 单例模式实现方式除枚举方式外,其他几种依然可以通过相关反射方法,改变其权限,创建多个实例,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {

public static void main(String args[]) {

Singleton singleton = Singleton.getInstance();

 try {

Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singletonnew = constructor.newInstance();
System.out.println(singleton == singletonnew); // 输出结果:false

} catch (Exception e) {

}
}
}

如何避免 Java 反射破坏单例模式呢?可以在构造函数里面进行判断:如果当前已有实例,通过抛出异常来阻止反射创建对象。

1
2
3
4
5
6
private Singleton() {

if (null != Singleton.singleton) {
throw new RuntimeException();
}
}

如何避免 Java 反序列化破坏单例模式

除了反射,反序列化也可能破坏单例模式,反序列化会通过反射调用无参数的构造方法创建一个新的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("tempFile"))) {
oos.writeObject(Singleton.getInstance());
Singleton newInstance = (Singleton) ois.readObject();
System.out.println(newInstance == Singleton.getInstance()); // 输出 false
} catch (Exception ex) {
ex.printStackTrace();
}
}

}

解决方法是只要在 Singleton 类中定义 readResolve() 方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton implements Serializable {

private volatile static Singleton singleton;

private Singleton (){}

public static Singleton getInstance() {

if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}

return singleton;
}

private Object readResolve() {
return singleton;
}
}