自定义Gradle-Plugin 插件详解

自定义Gradle-Plugin 插件

官方文档给出了详细的实现步骤,笔者 将参考官方文档:通过自定义插件实现lint文件输出,本文按照以下三个方面进行讲解:

  • 插件基础介绍
  • 三种插件的打包方式
  • 实例Demo

插件基础介绍

官方介绍:A Gradle plugin packages up reusable pieces of build logic, which can be used across many different projects and builds. Gradle allows you to implement your own plugins, so you can reuse your build logic, and share it with others.

You can implement a Gradle plugin in any language you like, provided the implementation ends up compiled as bytecode. In our examples, we are going to use Groovy as the implementation language. Groovy, Java or Kotlin are all good choices as the language to use to implement a plugin, as the Gradle API has been designed to work well with these languages. In general, a plugin implemented using Java or Kotlin, which are statically typed, will perform better than the same plugin implemented using Groovy.

大体意思:插件打包了可重用的构建逻辑,可以适用不同的项目和构建。

Gradle 提供了很多官方插件,用于支持Java、Groovy等工程的构建和打包。同时也提供了自定义插件机制,让每个人都可以通过插件来实现特定的构建逻辑,并可以把这些逻辑打包起来,分享给其他人。

您可以实现一个Gradle插件用你喜欢任何语言,提供了实现最终编译成字节码。官方例子中,使用Groovy实现语言。Groovy、Java或Kotlin都是不错的选择。深入理解可以参考上一篇文章:Android中Gradle深入理解

Gradle自定义插件三种方式:

There are several places where you can put the source for the plugin.

Build script

You can include the source for the plugin directly in the build script. This has the benefit that the plugin is automatically compiled and included in the classpath of the build script without you having to do anything. However, the plugin is not visible outside the build script, and so you cannot reuse the plugin outside the build script it is defined in.

buildSrc project

You can put the source for the plugin in the rootProjectDir/buildSrc/src/main/groovy directory. Gradle will take care of compiling and testing the plugin and making it available on the classpath of the build script. The plugin is visible to every build script used by the build. However, it is not visible outside the build, and so you cannot reuse the plugin outside the build it is defined in.

See Organizing Gradle Projects for more details about the buildSrc project.

Standalone project

You can create a separate project for your plugin. This project produces and publishes a JAR which you can then use in multiple builds and share with others. Generally, this JAR might include some plugins, or bundle several related task classes into a single library. Or some combination of the two.

大体意思总结为下3点:

1.build.gradle脚本中直接使用。这种方式就是直接在Android Studio app moudle的build.gradle 中进行插件的编写,优点就是不用再上传插件到maven或者其它地方,项目就可以直接使用;缺点也是很明显,就是只能在自己的项目中使用,不能复用,这个不是我们今天要说的。

buildSrc中使用。这种方式需要在项目中新建一个moudle命名为buildSrc,这个目录就用来存放自定义插件。然后在src/main中建立两个目录,一个就是存放代码的groovy目录,一个是存放自定义插件名称的resources目录。这种定义方式也是只能在我们项目中进行使用,不好复用。

3.独立Module中使用。这种方式就是完全独立开发一个Module,可以随便用。

Build Script

把插件写在build.gradle 文件中,一般用于简单的逻辑,只在改build.gradle 文件中可见,这里直接贴出Demo代码:

1
2
3
4
5
6
7
8
9
10
11
/**
* 插件入口类
*/
class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
//do something
}
}
apply plugin: TestPlugin

buildSrc

Use buildSrc to abstract imperative logic

Complex build logic is usually a good candidate for being encapsulated either as custom task or binary plugin. Custom task and plugin implementations should not live in the build script. It is very convenient to use buildSrc for that purpose as long as the code does not need to be shared among multiple, independent projects.
有兴趣可参考:buildSrc介绍

总得来说:只对该项目中可见,适用于逻辑较为复杂,但又不需要外部可见的插件

独立项目

先来一个整体代码感受:
image

  1. 在Android Studio中新建 Java Library module uploader(moduleName 不重要,根据实际情况定义)
  1. 修改项目文件夹
  • [x] 移除java文件夹,因为在这个项目中用不到java代码
  • [x] 添加Groovy文件夹,主要的代码文件放在这里
  • [x] 添加resource文件夹,存放用于标识gradle插件的meta-data
  1. 修改build.gradle 文件
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
apply plugin: 'groovy'
dependencies{
//gradle sdk
compile gradleApi()
//groovy sdk
compile localGroovy()
}
//以上配置比较固定
//以下内容主要用来上传插件
apply plugin: 'maven'
repositories {
mavenCentral()
}
group = 'com.charles.plugin'
version = '1.0.0'
uploadArchives{
repositories {
mavenDeployer{
repository(url: uri('./../repo'))
}
}
}
  1. 创建Groovy脚本

接下来,在groovy目录下,创建一个Groovy类(与Java类似,可以带包名,但Groovy类以.grovvy结尾,所以groovy文件的创建是new->file->custom.groovy)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//插件是一个类 继承Plugin
class LintPlugin implements Plugin<Project> {
//重载 void apply(Project project)方法,这个方法将会传入使用这个插件的 project 的实例,这是一个重要的 context。
@Override
void apply(Project project) {
/*project.task('lintOutputsTask') {
doLast {
println("lint outputs task start...")
}
}*/
project.afterEvaluate {
project.android.lintOptions.xmlOutput=new File(project.buildDir,"lintResult.xml")
}
project.tasks.create('cleanTest',CleanTestTask)
}
}
  1. 创建resources
    resources目录是标识整个插件的目录,也是后面apply plugin的内容。

image

1
implementation-class=com.charles.plugin.LintPlugin
  1. 在主项目中使用插件
    在主项目的build.gradle文件中,通过apply指令来加载自定义的插件,脚本如下所示:
1
apply plugin: 'com.charles.plugin'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
buildscript {
ext.kotlin_version = '1.2.30'
repositories {
google()
jcenter()
maven {
url uri('./../repo')
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.charles.plugin:lintplugin:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

对比:
在buildSrc中创建自定义Gradle插件只能在当前项目中使用,因此,对于具有普遍性的插件来说,通常是建立一个独立的Module来创建自定义Gradle插件。

区别在于:

  • 不需要进行model的名称的写死,也就是你可以随意的命名
  • buildSrc会自动的编译和加入到classpath中,这里我们需要手动依赖
  • 需要上传到maven仓库中

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Android进阶–动态代理是基于什么原理?

最近感悟:对自己负责、对工作负责

问题:

  • 对Java平台的理解?
  • Java是解释执行,这句话正确吗?

首先在这里我们就基于HotSpot虚拟机进行分析(因为作者对HotSpot理解有点模糊,强化一下)

维基百科:
Java HotSpot Virtual Machine
开发者 甲骨文公司 (前升阳公司)
HotSpot Group
HotSpot的正式发布名称为”Java HotSpot Performance Engine”,是Java虚拟机的一个实现,包含了服务器版桌面应用程序版,现时由Oracle维护并发布。它利用JIT及自适应优化技术(自动查找性能热点并进行动态优化,这也是HotSpot名字的由来)来提高性能。

两种执行方式
  • 解释执行(运行时、解释字节码并执行)
1
强制使用该模式:-Xint
  • 编译执行(将字节码编译为机器码执行,这个编译过程发生在运行期,称为JIT编译)
    1
    强制使用该模式 -Xcomp
两种编译模式对比:
  • client(即C1):只做少量性能开销比高的优化,占用内存少,适用于桌面程序
  • server(即C2):进行了大量优化,占用内存多,适用于服务端程序。会收集大量的运行时信息。

总结:

解释器:

  • 程序启动速度比编译快。
  • 节省内存(不需要编译,所以不需要放置编译后的机器码)。

JIT编译器:

  • 时间长了,对于“热点代码”的执行会快。
注意
  • 之所以使用JIT而不是在编译器直接编译成机器码,除了上面解释器的原因以外,还有为了在运行期获取数据,有目的的进行编译。

回答问题1:

宏观角度:

Java平台通过虚拟机屏幕了操作系统的底层细节,使得开发者不需要关心不同操作系统之间的差异。Java平台已经形成了一个生态系统,在这个生态系统中有诸多的研究领域:

  1. 虚拟机、编译技术的优化(例如:GC优化、JIT、AOT等):对效率的追求是人类的天性之一。
  2. Java语言本身的变化。
  3. 大数据处理。
  4. 客户端开发(例如:Android平台)
微观角度:
  1. Java语言本身、JDK中所提供的核心类库和相关工具:
    • 面向对象(封装、继承、多态)
    • 跨平台(JVM运行.class文件)
    • 语言(泛型、Lambda)
    • 类库(集合、并发、网络、IO/NIO)
    • JRE(Java运行环境、JVM、类库)
    • JDK (JDK开发工具、包括JRE、javac、诊断工具)
  2. Java虚拟机以及其他包含的GC。

回答问题2:

回答片面,不正确!

  • Java源文件经过javac编译成字节码文件(.class文件)
  • .class文件经过JVM解释执行或者编译执行

    • 解析:.class文件经过JVM内嵌的解析器解析执行。
    • 编译:存在JIT编译器(Just In Time Compile 即使编译器)把经常运行的代码作为“热点代码”编译成本地相关的机器代码,并且做各种层次的优化。
    • AOT编译器:Java9提供的直接将所有的代码编译成机器码执行。

上面答案分析相信也会随着知识面的不断拓宽而不断的进行优化修改

参考:https://www.cnblogs.com/java-zhao/p/5203144.html
,极客时间APP第1讲|谈谈你对Java平台的理解

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Android–进阶对Java平台的理解?Java是解释执行吗?

最近感悟:对自己负责、对工作负责

问题:

  • 对Java平台的理解?
  • Java是解释执行,这句话正确吗?

首先在这里我们就基于HotSpot虚拟机进行分析(因为作者对HotSpot理解有点模糊,强化一下)

维基百科:
Java HotSpot Virtual Machine
开发者 甲骨文公司 (前升阳公司)
HotSpot Group
HotSpot的正式发布名称为”Java HotSpot Performance Engine”,是Java虚拟机的一个实现,包含了服务器版桌面应用程序版,现时由Oracle维护并发布。它利用JIT及自适应优化技术(自动查找性能热点并进行动态优化,这也是HotSpot名字的由来)来提高性能。

两种执行方式
  • 解释执行(运行时、解释字节码并执行)
1
强制使用该模式:-Xint
  • 编译执行(将字节码编译为机器码执行,这个编译过程发生在运行期,称为JIT编译)
    1
    强制使用该模式 -Xcomp
两种编译模式对比:
  • client(即C1):只做少量性能开销比高的优化,占用内存少,适用于桌面程序
  • server(即C2):进行了大量优化,占用内存多,适用于服务端程序。会收集大量的运行时信息。

总结:

解释器:

  • 程序启动速度比编译快。
  • 节省内存(不需要编译,所以不需要放置编译后的机器码)。

JIT编译器:

  • 时间长了,对于“热点代码”的执行会快。
注意
  • 之所以使用JIT而不是在编译器直接编译成机器码,除了上面解释器的原因以外,还有为了在运行期获取数据,有目的的进行编译。

回答问题1:

宏观角度:

Java平台通过虚拟机屏幕了操作系统的底层细节,使得开发者不需要关心不同操作系统之间的差异。Java平台已经形成了一个生态系统,在这个生态系统中有诸多的研究领域:

  1. 虚拟机、编译技术的优化(例如:GC优化、JIT、AOT等):对效率的追求是人类的天性之一。
  2. Java语言本身的变化。
  3. 大数据处理。
  4. 客户端开发(例如:Android平台)
微观角度:
  1. Java语言本身、JDK中所提供的核心类库和相关工具:
    • 面向对象(封装、继承、多态)
    • 跨平台(JVM运行.class文件)
    • 语言(泛型、Lambda)
    • 类库(集合、并发、网络、IO/NIO)
    • JRE(Java运行环境、JVM、类库)
    • JDK (JDK开发工具、包括JRE、javac、诊断工具)
  2. Java虚拟机以及其他包含的GC。

回答问题2:

回答片面,不正确!

  • Java源文件经过javac编译成字节码文件(.class文件)
  • .class文件经过JVM解释执行或者编译执行

    • 解析:.class文件经过JVM内嵌的解析器解析执行。
    • 编译:存在JIT编译器(Just In Time Compile 即使编译器)把经常运行的代码作为“热点代码”编译成本地相关的机器代码,并且做各种层次的优化。
    • AOT编译器:Java9提供的直接将所有的代码编译成机器码执行。

上面答案分析相信也会随着知识面的不断拓宽而不断的进行优化修改

参考:https://www.cnblogs.com/java-zhao/p/5203144.html
,极客时间APP第1讲|谈谈你对Java平台的理解

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Android进阶–Exception和Error的理解

今日感悟:越是厉害的人,代码越容易理解

问题:

  • 请对比Exception和Error的区别,另外,运行时异常和一般异常有什么区别?

知识点分析:

一. 首先来个简单的类图感受一下Throwable、Exception、Error的设计和分类:

image
从上图中可以看出Exception和Error都是继承Throwable,也就是中Java中只有Throwable类型的实例才可以被抛出(Throw)或者捕获(catch)。

二. 掌握最基本的语法。如try-catch-finally块、throw、throws关键字等。同时懂得如何处理典型场景。

知识点补充:

随着Java语言的扩展,引入了一些更加便利的特性,try-with-resources和multiple catch。具体我这里引用了RxJava和Retorfit结合源码的一段。在编译时期,会自动生成相应的处理逻辑,比如:自定义AutoCloseable实现(AutoCloseable不了解的可以参考文章Java7中的Try-with-resources

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
//代码路径:
package retrofit2.adapter.rxjava2.CallExecuteObservable
@Override protected void subscribeActual(Observer<? super Response<T>> observer) {
//省略逻辑
...
try {
//划重点了,这里没有贴代码,感兴趣自己调式execute方法
Response<T> response = call.execute();
if (!disposable.isDisposed()) {
observer.onNext(response);
}
if (!disposable.isDisposed()) {
terminated = true;
observer.onComplete();
}
} catch (Throwable t) {
//划重点了,跟随优秀的开源框架一步步进去,你会发现它遵循了:
//1.尽量捕获具体异常,不要捕获Throable或者Error //2.不生吞(swallow)异常
//3.throw early
//4.仅仅捕获必要代码块,try-catch会产生额外的性能开销
Exceptions.throwIfFatal(t);
if (terminated) {
RxJavaPlugins.onError(t);
} else if (!disposable.isDisposed()) {
try {
observer.onError(t);
} catch (Throwable inner) {
Exceptions.throwIfFatal(inner);
RxJavaPlugins.onError(new CompositeException(t, inner));
}
}
}
}

回答问题:

这边就不写了,根据上面类图和文字补充,各自组织语言吧(重要的是自己要有深度思考能力)

参考:http://ifeve.com/java-7%E4%B8%AD%E7%9A%84try-with-resources/ ,极客时间APP核心技术第二讲|Exception和Error有什么区别、retrofit2.adapter.rxjava2部分源码

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Android进阶–String、StringBuffer、StringBuilder的理解

问题:

理解 Java的字符串,String、StringBuffer、StringBuilder 有什么区别?

知识点
  1. 字符串设计和实现考量
    String是Immutable(线程安全、字符串常量池复用)。Immutable对象在拷贝时候不需要额外复制数据。至于为什么imumutable,源码如下:
1
2
3
4
5
6
7
8
9
//关键点 final
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// The associated character storage is managed by the runtime. We only
// keep track of the length here.
//关键点final private
// private final char value[];
private final int count;
  1. StringBuffer、StringBuilder底层都是利用可修改的数组(JDK 9之后是byte)数组,都继承了AbstractStringBuilder,里面包含了基本操作。区别StringBuffer增加了synchronized。相关源码如下:
1
2
3
4
5
6
7
8
//AbstractStringBuilder 没有加final 也没private修饰
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
private static final int MAX_ARRAY_SIZE = 2147483639;
AbstractStringBuilder() {
}
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
//StringBuilder截取部分源码
static final long serialVersionUID = 4383685877147921099L;
public StringBuilder() {
super(16);
}
public StringBuilder(int var1) {
super(var1);
}
public StringBuilder(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuilder(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
public StringBuilder append(Object var1) {
return this.append(String.valueOf(var1));
}
public StringBuilder append(String var1) {
super.append(var1);
return this;
}
public StringBuilder append(StringBuffer var1) {
super.append(var1);
return this;
}
public StringBuilder append(CharSequence var1) {
super.append(var1);
return this;
}
public StringBuilder append(CharSequence var1, int var2, int var3) {
super.append(var1, var2, var3);
return this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//StringBuffer部分源码 多了synchronized
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
public synchronized int length() {
return count;
}
public synchronized int capacity() {
return value.length;
}
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
  1. 字符串缓存
    String在Java 6 以后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以便重复使用。

    注意:Java 6这种历史版本,并不推荐大量使用intern(),因为缓存的字符串是存在“永久代”中,这个空间比较有限。也基本不会被FULLGC之外的垃圾收集照顾到。所以,使用不当,容易OOM。后续版本中,被放到堆中,设置永久代在Java 8中被元数据区替代了。

intern()感兴趣可以参考文章:Java提高篇——理解String 及 String.intern() 在实际中的应用

  1. String自身也有相关优化
    有兴趣可以自己查看相关文章,主要是char数组换成了byte数组加上一个标志编码所谓的coder。
回答问题:
String的创建机理

由于String在Java世界中使用过于频繁,Java为了避免在一个系统中使用大量的Java对象,引入了字符串常量池的概念。其运行机制是:创建一个字符串的时候,首先检查池中是否有相等的字符串对象,如果有就不需要创建,直接从池中找到对象引用,如果没有的话,新建字符串对象,返回对象引用。但是,通过new方法创建的不会检查常量池是否存在,而是直接在堆中或者栈中创建一个新的对象,也不会把对象放入池中。上面所说的只适用于直接给String引用赋值的情况。

注意:String是immutable
Strng提供了inter()方法可以将Strng对象添加到池中,并且返回该对象的引用。(如果由equals()确定池中有该字符串,那就直接返回)。

当两个String对象拥有相等的值的时候,他们只引用字符串中同一个拷贝。当同一个字符串大量出现的时候,可以大量节省内存空间。

StringBuffer/StringBuilder

StringBuffer/StringBuilder相同点:

  • String/StringBuilder相对于String的话,他们值是可以改变的,并且值改变后,他们引用不会变。他们在构造的时候使用一个默认的数组,在后续加入数据后超过默认大小后会创建更大的数组,并且将原来数组的内容复制过来,再丢弃旧的数组。因此,项目开发的时候,对于较大对象的扩容会性能,因此,能预估大小,最好不过。

StringBuffer/StringBuilder不同点:

  • 另外StringBuffer是线程安全(方法定义前面使用了synchronize),StringBuilder不是。StringBuffer性能要低于StringBuilder。
    应用场景:
    String 适用于常量声明。如:
1
public static final int DEVICE_NOT_AVAILABLE_ERROR_CODE = 390004;// 设备未启用或者被锁住

StringBuffer,适用于频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用,比如Http参数解析和封装。

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
/**
* 获取POST请求的base url
*
* @param requestHolder
* @return
* @throws AlipayApiException
*/
private String getRequestUrl(RequestParametersHolder requestHolder) throws AlipayApiException {
StringBuffer urlSb = new StringBuffer(serverUrl);
try {
String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(),
charset);
String sysOptQuery = WebUtils.buildQuery(requestHolder.getProtocalOptParams(), charset);
urlSb.append("?");
urlSb.append(sysMustQuery);
if (sysOptQuery != null & sysOptQuery.length() > 0) {
urlSb.append("&");
urlSb.append(sysOptQuery);
}
} catch (IOException e) {
throw new AlipayApiException(e);
}
return urlSb.toString();
}

StringBuilder,适用于频繁进行字符串运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用,比如Json的封装。

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
/**
* Extracts absolute path form a given URI. E.g., passing
* <code>http://google.com:80/execute?query=cat#top</code>
* will result in <code>/execute?query=cat#top</code>.
*
* @param uri URI which absolute path has to be extracted,
* @return the absolute path of the URI,
*/
private String getAbsolutePathFromAbsoluteURI(URI uri) {
String rawPath = uri.getRawPath();
String rawQuery = uri.getRawQuery();
String rawFragment = uri.getRawFragment();
StringBuilder absolutePath = new StringBuilder();
if (rawPath != null) {
absolutePath.append(rawPath);
} else {
absolutePath.append("/");
}
if (rawQuery != null) {
absolutePath.append("?").append(rawQuery);
}
if (rawFragment != null) {
absolutePath.append("#").append(rawFragment);
}
return absolutePath.toString();
}

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Android进阶–final、finally、finalize的理解

首先补充听课心得(二)内容:默认情况在Android Studio中使用try-with-resources会提示Try-with-resources requires API level 19的警告️ ,你需要自己再build.gradle文件中设置指定JDK版本:

1
2
3
4
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

问题:

  • final、finally、finalize有什么区别?

这是一道经典的基础面试题,这里不作分析,不了解的自己Google哦(它们除了字母类似,各自之间没啥关系,有点类似Java和JavaScript有啥关系了)

补充其他知识点:
  1. final也许会有性能的好处,比如利用final可能(注意是可能)对性能有好处。比如:可能有助于将方法进行内联,可以改善编译器进行条件编译的能力等等。日常开发中,除非有特别考虑,不然最好不要指望这种小技巧带来所谓的性能提升。
  2. 不要在finally中使用return语句,finally总是执行,除非程序或者线程被终止。比如以下代码:
1
2
3
4
5
6
//try-catch异常退出、不被执行
try{
system.exit(1)
}finally{
println("会被执行吗?")
}
1
2
3
4
5
6
7
8
//try-catch无线循环,不被执行
try{
while(true){
println("do something")
}finally{
println("会被执行吗?")
}
}

如果你非要在finally中使用return语句,请注意以下结论:

  • finally块的语句是在try|catch的return语句执行之后,返回之前(try|catch中return在返回前,将其他所有操作都执行完,保留返回的值)。
  • finally中return语句会覆盖try|catch中return语句直接返回。

具体代码实践请参考文章:Java finally语句到底是在return之前还是之后执行?
try-catch-finally语句中return的执行顺序思考

  1. final不是immutable。(类似:String为什么需要final修饰?HashMap、HashSet的键值为什么不要使用不可变类型?)除了immutable内部如何实现的,个人认为主要作用:
  • 安全,尤其并发的时候线程安全
  • 类似String设计成immutable有着一个字符串常量池的属性,这样存在大量字符串的时候,节省内存空间、提高效率。具体类似讨论可以参考知乎文章:在java中String类为什么要设计成final?
  1. 小插曲,对Java的finalizer,cleaner的理解。有兴趣可以参考文章:Java的finalizer,cleaner等如何实现? Effective Java 第三版——8. 避免使用Finalizer和Cleaner机制

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Kotlin单例模式多种写法大分析

前言:

今天,在项目开发中,又运用到了单例模式另外一种Kotlin写法,于是决定记录下来,以供参考。

几种单例模式:
  • 饿汉式
  • 双重校验懒汉式
  • 静态内部类式

1. 饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 作者:huangchen on 2018/8/7 21:48
* 邮箱:huangcftt@gmail.com
*/
//java实现
public class PayManager {
private static PayManager payManager = new PayManager();
public static PayManager getPayManager() {
return payManager;
}
private PayManager(){
}
}
1
2
3
4
5
6
/**
* 作者:huangchen on 2018/8/7 21:55
* 邮箱:huangcftt@gmail.com
*/
//Kotlin实现
object PayServiceManager

是不是大吃一惊。我靠一个object 关键字就完成相同的功能?一行代码?

Kotlin的对象声明学习了Kotlin的小伙伴肯定知道,在Kotlin中类没有静态方法。如果你需要写一个可以无需用一个类的实例来调用,但需要访问类内部的函数(例如,工厂方法,单例等),你可以把该类声明为一个对象。该对象与其他语言的静态成员是类似的。

对象声明的初始化过程是线程安全的。

查看kotlin Bytecode 后 Decompile后:

kotlin.Metadata;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Metadata(
mv = {1, 1, 9},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
d2 = {"Lcn/yonghui/shop/shopapplication/pay/config/PayServiceManager;", "", "()V", "production sources for module app"}
)
public final class PayServiceManager {
public static final PayServiceManager INSTANCE;
static {
PayServiceManager var0 = new PayServiceManager();
INSTANCE = var0;
}
}

通过以上代码,我们了解事实就是这个样子的,使用Kotlin”object”进行对象声明与我们的饿汉式单例的代码基本是相同的。

2. 双重校验懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//java 代码
private PayManager(){
}
private volatile static PayManager payManager;
public static PayManager getPayManager() {
if(payManager == null){
synchronized (PayManager.class){
if(payManager == null){
payManager = new PayManager()
}
}
}
return payManager;
}
1
2
3
4
5
6
7
8
//kotlin实现
class PayServiceManager private constructor() {
companion object {
val instance: PayServiceManager by lazy {
PayServiceManager()
}
}
}
  • 显式声明构造方法为private
  • companion object用来在class内部声明一个对象
  • PayServiceManager的实例instance 通过lazy来实现懒汉式加载
  • lazy默认情况下是线程安全的,这就可以避免多个线程同时访问生成多个实例的问题

    知识点:

  • lazy()是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
  • 默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证以及相关的开销。
    有兴趣可参考:lazy讲解

3. 静态内部类式

1
2
3
4
5
6
7
8
9
10
//Java实现
private PayManager(){
}
private static class PayManagerHolder{
private static PayManager payManager = new PayManager();
}
public static PayManager getPayManager(){
return PayManagerHolder.payManager;
}
1
2
3
4
5
6
7
8
9
10
//kotlin实现
class PayServiceManager private constructor() {
companion object {
val instance =PayServiceManagerHolder.payServiceManager
}
private object PayServiceManagerHolder {
val payServiceManager = PayServiceManager()
}
}

静态内部类java代码和Kotlin代码基本类似。

总结:

对于资源占用和实例初始化时间比较少的时候,应该使用object形式的饿汉式加载。否则的话使用懒汉式。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

synchronized和ReentrantLock的理解(上)

问题:

  • synchronized和ReentrantLock有什么全部?
  • 有人说synchronized最慢,这话靠谱吗?

    回答问题:

    synchronized是Java内建的同步机制,所以也有人称其为Intrinsic Locking,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他获取的线程只能等待或者阻塞在那里。

注意:原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突。可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为。一般来说,线程以某种不必让其他线程立即可以看到的方式(不管这些线程在寄存器中、在处理器特定的缓存中,还是通过指令重排或者其他编译器优化),不受缓存变量值的约束,但是如果开发人员使用了同步,如下面的代码所示,那么运行库将确保某一线程对变量所做的更新先于对现有 synchronized 块所进行的更新,当进入由同一监控器(lock)保护的另一个 synchronized 块时,将立刻可以看到这些对变量所做的更新。类似的规则也存在于 volatile 变量上。有兴趣可以参考:Java中的ReentrantLock和synchronized两种锁定机制的对比

在Java 5以前,synchronized是仅有的同步手段,在代码中,synchronized可以用来修饰方法,也可以使用在特定的代码块上,本质上synchronized方法等同于把方法全部语句用synchronized块包起来。

ReentranLock,通常翻译为再入锁,是Java 5提供的锁实现,他的语义和synchronized基本相同。再入锁通过代码直接调用Lock()方法获取,代码书写也更加灵活。与此同时,ReentrantLock提供了很多实用方法,能够实现很多synchronized无法做到的细节控制,比如可以控制fairness,也就是公平性,或者利用定义条件等。但是,编码中也需要注意,必须明确调用unlock()方法释放,不然就会一直持有该锁。

synchronized和ReentrantLock的性能不能一概而论,早期版本synchronized在很多场景下性能相差较大,在后续版本进行了较多的改进,在低竞争场景中表现可能优于ReentrantLock。

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Java并发类库提供的线程池有哪几种?分别有什么特点?

问题:

Java并发库提供的线程池有哪几种?分别有什么特点?

知识点补充

Executor框架

image

ThreadPoolExecutor 线程池类

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

参数说明:

  • corePoolSize:核心线程数。

  • maximumPoolSize:最大线程数。

  • keepAliveTime:线程存活时间。当线程数大于core数,那么超过该时间的线程将会被终结。

  • unit:keepAliveTime的单位。java.util.concurrent.TimeUnit类存在静态静态属性: NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS

  • workQueue:Runnable的阻塞队列。若线程池已经被占满,则该队列用于存放无法再放入线程池中的Runnable。

回答问题:

通常开发者都是利用Executors提供的通用线程池创建方法,去创建不同配置的线程池,主要区别在于不同的ExecutorService类型或者不同的初始参数。
Executors目前提供了5种不同的线程池创建配置:

  • newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池;它会试图缓存线程并且重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过60s,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。

构造方法(看英文注释是最好的理解哈):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
  • newFixedThreadPool(int nThreadPool),重要指定数目(nThreads)的线程,其背后使用的无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目nThreads。

构造方法(看英文注释是最好的理解哈):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
  • newSingleThreadExcutor(),它的特点在于工作线程数目被限制为1,操作了一个无界的工作队列,所以它保证了所有任务都是被顺序执行的。最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目

构造方法(看英文注释是最好的理解哈):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
*
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

  • newSingleThreadScheduledExcutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。

构造方法(注释是最好的解释哈)

1
2
3
4
5
6
7
8
9
10
11
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

- newWorkStealingPool(int parallelism)这是一个经常被人忽略的线程池,它是Java 8 才加入这个创建方法,其内部会创建ForkJoinPool,利用Work-Stealing算法,并行的处理任务,不保证处理顺序。
构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Creates a thread pool that maintains enough threads to support
* the given parallelism level, and may use multiple queues to
* reduce contention. The parallelism level corresponds to the
* maximum number of threads actively engaged in, or available to
* engage in, task processing. The actual number of threads may
* grow and shrink dynamically. A work-stealing pool makes no
* guarantees about the order in which submitted tasks are
* executed.
*
* @param parallelism the targeted parallelism level
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code parallelism <= 0}
* @since 1.8
*/
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

Android进阶–int和Integer的理解

今天早高峰在地铁上突然有一个疑惑:String 是immutable是为了保证线程安全,Integer也是Immutable,为什么并发的时候建议使用AtomicInteger呢?

晚上回来查阅相关资料得出总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
String a="test";
String b="test";
System.out.println(a==b);
System.out.println(System.idntityHashCode(a));
System.out.println(System.identityHashCode(b));
输出:true
851664923
851664923
//为什么有觉得可变呢?
String a="test";
a="test1"

a就变成了test1,其实在这里是新建了一个”test1”字符串对象(如果常量池没有这个值的话就是新建)。然后将变量引用指向它。注意:这里并没有修改”test”这个变量的内部状态,”test”这个字符串对象是线程安全的
除非你用final修饰,否则所有的变量指向都是可变的。
这种情况下要保证线程安全性:

  • 可以考虑使用volatile确保可见性。
  • 可以使用final修饰
  • 你可以使用AtomicReference之类的原子对象,对于Integer等也有AtomicInteger之类的
  • 对相应代码区域加锁

有兴趣可以查看这两篇文章:
String是线程安全的吗?那String岂不是不能进行同步?
Java多线程系列–“JUC原子类”04之 AtomicReference原子类

综上:String Immutable为了线程安全知识相对的(字符串常量值不可变),但是引用可变,所以为了并发的时候线程安全,可以使用上面所说的几种方案。(如果有更好的认识深度,欢迎讨论)有兴趣可以查看我之前写的一篇文章关于介绍:String为什么是final修饰的

问题:

  • int和Integer有什么区别?谈谈Integer的值缓存范围?

知识点

  1. Integer缓存或者String 常量池缓存用到的设计模式-享元模式。

image

相关代码(好记性不如烂笔头,代码是最好的理解):

1
2
3
4
5
6
7
8
9
package yonghui.cn.firstthread.flyweight;
/**
* 作者:huangchen on 2018/7/26 20:57
* 邮箱:huangchen@yonghui.cn
*/
public interface IBike {
void biliing(int time);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package yonghui.cn.firstthread.flyweight;
/**
* 作者:huangchen on 2018/7/26 20:58
* 邮箱:huangchen@yonghui.cn
*/
public class ShareBike implements IBike {
private int price;//单价
private int total;//总价
@Override
public void biliing(int time) {
System.out.println("total price is" + price * time);
}
}
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
package yonghui.cn.firstthread.flyweight;
import java.util.HashMap;
/**
* 作者:huangchen on 2018/7/26 20:11
* 邮箱:huangchen@yonghui.cn
*/
public class FlyWeightFactory {
private HashMap<String, IBike> map = new HashMap<>();
public IBike getIBike(String name) {
IBike iBike;
if (map.containsKey(name)) {
System.out.println("押金已经交过了" + name);
iBike = map.get(name);
return iBike;
} else {
System.out.println("押金没有交,开始交押金299元");
iBike = new ShareBike();
map.put(name, iBike);
return iBike;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package yonghui.cn.firstthread.flyweight;
/**
* 作者:huangchen on 2018/7/26 20:16
* 邮箱:huangchen@yonghui.cn
*/
public class Client {
public void test(){
FlyWeightFactory flyWeightFactory = new FlyWeightFactory();
IBike ofo = flyWeightFactory.getIBike("ofo");
ofo.biliing(2);
IBike mobike = flyWeightFactory.getIBike("mobike");
mobike.biliing(1);
IBike ofo1 = flyWeightFactory.getIBike("ofo");
ofo1.biliing(3);
}
}

关于享元模式有兴趣可以参考:Android的设计模式-享元模式

  1. 自动装箱、自动拆箱发生在编译阶段。静态工厂方法valueof()使用了缓存机制,自动装箱的时候,缓存机制起作用。实际开发中,避免无意的装箱、拆箱行为(不管是内存使用还是运行速度、10w个Java对象比10w整数的开销要大得多,光是对象头空间占用已经是数量级的差距了)。
  2. 对象的内存结构:对象由对象头、对象实例、对齐填充三部分组成。具体有兴趣可以参考:Java对象内存布局

    回答问题:

    int是我们常说的整形,是八大基本数据类型之一,占4个字节,取值范围2的31次方减去1,Java需要号称一切都是对象,但是基本数据类型是个例外。

    Integer是int对应的包装类,他有一个int类型存字段(private final修饰,Immutable),并且提供了基本操作,比如数字运算、int和字符串之间的转换。在Java5中,引入了自动装箱、拆箱功能。Java根据上下文,自动进行转换,极大简化了相关编程。

    关于Integer的值缓存,这涉及Java5的内一个改进。大部分数据操作都集中在有限的、较小的数值范围,因而,在Java 5 中新增了静态工厂方法valueof,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照Javadoc,这个值默认缓存-128-127之间。

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
//Integer部分源码
//Immutable private final
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

参考:

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

qrcode_for_gh_1ba0785324d6_430.jpg
当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

WXCD.jpeg

|