乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

Android Studio 常见错误异常以及解决

发表于 2019-12-17

案例1:

1
Invoke-customs are only supported starting with Android O (--min-api 26)
解决方案:

将以下代码添加到app的build.gradle的android节点下

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

案例2

1
2
Could not create service of type FileAccessTimeJournal using GradleUserHomeScopeServices.createFileAccessTimeJournal()
Owner PID: 4567
解决方案
1
2
#杀进程
sudo kill -9 1902

案例3

在Fragment中显示popwindow报错:token null is not valid; is your activity running?

解决方案

布局绘制完后显示, 如下:

1
2
3
4
5
6
7
8
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//待布局绘制完成后显示
view.viewTreeObserver.addOnGlobalLayoutListener {
mTipPopView.showAsDropDown(binding.topBg)

}
}

案例4

Vector矢量图兼容性问题 低版本机型直接崩溃

解决方案

代码中使用:AppCompatResources.getDrawable

不要使用:ContextCompat.getDrawable

示例:

1
toolbar.setBackgroundDrawable(AppCompatResources.getDrawable(this, R.drawable.bg_toolbar_v))

本文为作者原创 转载时请注明出处 谢谢

img

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android APT快速教程

发表于 2019-12-17

简介

APT(Annotation Processing Tool)即注解处理器,是一种用来处理注解的工具。JVM会在编译期就运行APT去扫描处理代码中的注解然后输出java文件。

image

简单来说~~就是你只需要添加注解,APT就可以帮你生成需要的代码

许多的Android开源库都使用了APT技术,如ButterKnife、ARouter、EventBus等

动手实现一个简单的APT

image

小目标

在使用Java开发Android时,页面初始化的时候我们通常要写大量的view = findViewById(R.id.xx)代码

作为一个优(lan)秀(duo)的程序员,我们现在就要实现一个APT来完成这个繁琐的工作,通过一个注解就可以自动给View获得实例

本demo地址

第零步 创建一个项目

创建一个项目,名称叫 apt_demo
创建一个Activity,名称叫 MainActivity
在布局中添加一个TextView, id为test_textview

image

第一步 自定义注解

创建一个Java Library Module名称叫 apt-annotation

在这个module中创建自定义注解 @BindView

1
2
3
4
5
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
  • @Retention(RetentionPolicy.CLASS):表示这个注解保留到编译期
  • @Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)

第二步 实现APT Compiler

创建一个Java Library Module名称叫 apt-compiler

在这个Module中添加依赖

1
2
3
dependencies {
implementation project(':apt-annotation')
}

image

在这个Module中创建BindViewProcessor类

直接给出代码~~

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public class BindViewProcessor extends AbstractProcessor {
private Filer mFilerUtils; // 文件管理工具类
private Types mTypesUtils; // 类型处理工具类
private Elements mElementsUtils; // Element处理工具类

private Map<TypeElement, Set<ViewInfo>> mToBindMap = new HashMap<>(); //用于记录需要绑定的View的名称和对应的id

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);

mFilerUtils = processingEnv.getFiler();
mTypesUtils = processingEnv.getTypeUtils();
mElementsUtils = processingEnv.getElementUtils();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes; //将要支持的注解放入其中
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();// 表示支持最新的Java版本
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
System.out.println("start process");
if (set != null && set.size() != 0) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);//获得被BindView注解标记的element

categories(elements);//对不同的Activity进行分类

//对不同的Activity生成不同的帮助类
for (TypeElement typeElement : mToBindMap.keySet()) {
String code = generateCode(typeElement); //获取要生成的帮助类中的所有代码
String helperClassName = typeElement.getQualifiedName() + "$$Autobind"; //构建要生成的帮助类的类名

//输出帮助类的java文件,在这个例子中就是MainActivity$$Autobind.java文件
//输出的文件在build->source->apt->目录下
try {
JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
Writer writer = jfo.openWriter();
writer.write(code);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}

}
return true;
}

return false;
}

//将需要绑定的View按不同Activity进行分类
private void categories(Set<? extends Element> elements) {
for (Element element : elements) { //遍历每一个element
VariableElement variableElement = (VariableElement) element; //被@BindView标注的应当是变量,这里简单的强制类型转换
TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement(); //获取代表Activity的TypeElement
Set<ViewInfo> views = mToBindMap.get(enclosingElement); //views储存着一个Activity中将要绑定的view的信息
if (views == null) { //如果views不存在就new一个
views = new HashSet<>();
mToBindMap.put(enclosingElement, views);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class); //获取到一个变量的注解
int id = bindAnnotation.value(); //取出注解中的value值,这个值就是这个view要绑定的xml中的id
views.add(new ViewInfo(variableElement.getSimpleName().toString(), id)); //把要绑定的View的信息存进views中
}

}

//按不同的Activity生成不同的帮助类
private String generateCode(TypeElement typeElement) {
String rawClassName = typeElement.getSimpleName().toString(); //获取要绑定的View所在类的名称
String packageName = ((PackageElement) mElementsUtils.getPackageOf(typeElement)).getQualifiedName().toString(); //获取要绑定的View所在类的包名
String helperClassName = rawClassName + "$$Autobind"; //要生成的帮助类的名称

StringBuilder builder = new StringBuilder();
builder.append("package ").append(packageName).append(";\n"); //构建定义包的代码
builder.append("import com.example.apt_api.template.IBindHelper;\n\n"); //构建import类的代码

builder.append("public class ").append(helperClassName).append(" implements ").append("IBindHelper"); //构建定义帮助类的代码
builder.append(" {\n"); //代码格式,可以忽略
builder.append("\t@Override\n"); //声明这个方法为重写IBindHelper中的方法
builder.append("\tpublic void inject(" + "Object" + " target ) {\n"); //构建方法的代码
for (ViewInfo viewInfo : mToBindMap.get(typeElement)) { //遍历每一个需要绑定的view
builder.append("\t\t"); //代码格式,可以忽略
builder.append(rawClassName + " substitute = " + "(" + rawClassName + ")" + "target;\n"); //强制类型转换

builder.append("\t\t"); //代码格式,可以忽略
builder.append("substitute." + viewInfo.viewName).append(" = "); //构建赋值表达式
builder.append("substitute.findViewById(" + viewInfo.id + ");\n"); //构建赋值表达式
}
builder.append("\t}\n"); //代码格式,可以忽略
builder.append('\n'); //代码格式,可以忽略
builder.append("}\n"); //代码格式,可以忽略

return builder.toString();
}

//要绑定的View的信息载体
class ViewInfo {
String viewName; //view的变量名
int id; //xml中的id

public ViewInfo(String viewName, int id) {
this.viewName = viewName;
this.id = id;
}
}
}

image

image

2-1 继承AbstractProcessor抽象类

我们自己实现的APT类需要继承AbstractProcessor这个类,其中需要重写以下方法:

  • init(ProcessingEnvironment processingEnv)
  • getSupportedAnnotationTypes()
  • getSupportedSourceVersion()
  • process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)

2-2 init(ProcessingEnvironment processingEnv)方法

这不是一个抽象方法,但progressingEnv参数给我们提供了许多有用的工具,所以我们需要重写它

1
2
3
4
5
6
7
8
9
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);

mFilerUtils = processingEnv.getFiler();
mTypesUtils = processingEnv.getTypeUtils();
mElementsUtils = processingEnv.getElementUtils();
}
12345678
  • mFilterUtils 文件管理工具类,在后面生成java文件时会用到
  • mTypesUtils 类型处理工具类,本例不会用到
  • mElementsUtils Element处理工具类,后面获取包名时会用到

限于篇幅就不展开对这几个工具的解析,读者可以自行查看文档~

2-3 getSupportedAnnotationTypes()

由方法名我们就可以看出这个方法是提供我们这个APT能够处理的注解

这也不是一个抽象方法,但仍需要重写它,否则会抛出异常(滑稽),至于为什么有兴趣可以自行查看源码~

这个方法有固定写法~

1
2
3
4
5
6
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes; //将要支持的注解放入其中
}

2-4 getSupportedSourceVersion()

顾名思义,就是提供我们这个APT支持的版本号

这个方法和上面的getSupportedAnnotationTypes()类似,也不是一个抽象方法,但也需要重写,也有固定的写法

1
2
3
4
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();// 表示支持最新的Java版本
}

2-5 process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)

最主要的方法,用来处理注解,这也是唯一的抽象方法,有两个参数

  • set 参数是要处理的注解的类型集合
  • roundEnv表示运行环境,可以通过这个参数获得被注解标注的代码块
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
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
System.out.println("start process");
if (set != null && set.size() != 0) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);//获得被BindView注解标记的element

categories(elements);//对不同的Activity进行分类

//对不同的Activity生成不同的帮助类
for (TypeElement typeElement : mToBindMap.keySet()) {
String code = generateCode(typeElement); //获取要生成的帮助类中的所有代码
String helperClassName = typeElement.getQualifiedName() + "$$Autobind"; //构建要生成的帮助类的类名

//输出帮助类的java文件,在这个例子中就是MainActivity$$Autobind.java文件
//输出的文件在build->source->apt->目录下
try {
JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
Writer writer = jfo.openWriter();
writer.write(code);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}

}
return true;
}
return false;
}

process方法的大概流程是:

  1. 扫描所有被@BindView标记的Element
  2. 遍历Element,调用categories方法,把所有需要绑定的View变量按所在的Activity进行分类,把对应关系存在mToBindMap中
  3. 遍历所有Activity的TypeElment,调用generateCode方法获得要生成的代码,每个Activity生成一个帮助类

2-6 categories方法

把所有需要绑定的View变量按所在的Activity进行分类,把对应关系存在mToBindMap中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void categories(Set<? extends Element> elements) {
for (Element element : elements) { //遍历每一个element
VariableElement variableElement = (VariableElement) element; //被@BindView标注的应当是变量,这里简单的强制类型转换
TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement(); //获取代表Activity的TypeElement
Set<ViewInfo> views = mToBindMap.get(enclosingElement); //views储存着一个Activity中将要绑定的view的信息
if (views == null) { //如果views不存在就new一个
views = new HashSet<>();
mToBindMap.put(enclosingElement, views);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class); //获取到一个变量的注解
int id = bindAnnotation.value(); //取出注解中的value值,这个值就是这个view要绑定的xml中的id
views.add(new ViewInfo(variableElement.getSimpleName().toString(), id)); //把要绑定的View的信息存进views中
}
}

注解应该已经很详细了~
实现方式仅供参考,读者可以有自己的实现

2-7 generateCode方法

按照不同的TypeElement生成不同的帮助类(注:参数中的TypeElement对应一个Activity)

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 String generateCode(TypeElement typeElement) {
String rawClassName = typeElement.getSimpleName().toString(); //获取要绑定的View所在类的名称
String packageName = ((PackageElement) mElementsUtils.getPackageOf(typeElement)).getQualifiedName().toString(); //获取要绑定的View所在类的包名
String helperClassName = rawClassName + "$$Autobind"; //要生成的帮助类的名称

StringBuilder builder = new StringBuilder();
builder.append("package ").append(packageName).append(";\n"); //构建定义包的代码
builder.append("import com.example.apt_api.template.IBindHelper;\n\n"); //构建import类的代码

builder.append("public class ").append(helperClassName).append(" implements ").append("IBindHelper"); //构建定义帮助类的代码
builder.append(" {\n"); //代码格式,可以忽略
builder.append("\t@Override\n"); //声明这个方法为重写IBindHelper中的方法
builder.append("\tpublic void inject(" + "Object" + " target ) {\n"); //构建方法的代码
for (ViewInfo viewInfo : mToBindMap.get(typeElement)) { //遍历每一个需要绑定的view
builder.append("\t\t"); //代码格式,可以忽略
builder.append(rawClassName + " substitute = " + "(" + rawClassName + ")" + "target;\n"); //强制类型转换

builder.append("\t\t"); //代码格式,可以忽略
builder.append("substitute." + viewInfo.viewName).append(" = "); //构建赋值表达式
builder.append("substitute.findViewById(" + viewInfo.id + ");\n"); //构建赋值表达式
}
builder.append("\t}\n"); //代码格式,可以忽略
builder.append('\n'); //代码格式,可以忽略
builder.append("}\n"); //代码格式,可以忽略

return builder.toString();
}
123456789101112131415161718192021222324252627

大家可以对比生成的代码

1
2
3
4
5
6
7
8
9
10
11
package com.example.apt_demo;
import com.example.apt_api.template.IBindHelper;

public class MainActivity$$Autobind implements IBindHelper {
@Override
public void inject(Object target ) {
MainActivity substitute = (MainActivity)target;
substitute.testTextView = substitute.findViewById(2131165315);
}

}

有没有觉得字符串拼接很麻烦呢,不仅麻烦还容易出错,那么有没有更好的办法呢(留个坑)

同样的~ 这个方法的设计仅供参考啦啦啦~

image

第三步 注册你的APT

这应该是最简单的一步
这应该是最麻烦的一步

image

客官别急,往下看就知道了~

注册一个APT需要以下步骤:

  1. 需要在 processors 库的 main 目录下新建 resources 资源文件夹;
  2. 在 resources文件夹下建立 META-INF/services 目录文件夹;
  3. 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
  4. 在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;

image

正如我前面所说的~
简单是因为都是一些固定的步骤
麻烦也是因为都是一些固定的步骤

最后一步 对外提供API

4.1 创建一个Android Library Module,名称叫apt-api,并添加依赖

1
2
3
4
5
dependencies {
...

api project(':apt-annotation')
}

4.2 分别创建launcher、template文件夹
image

4.3 在template文件夹中创建IBindHelper接口

1
2
3
4
public interface IBindHelper {
void inject(Object target);
}
123

这个接口主要供APT生成的帮助类实现

4.4在launcher文件夹中创建AutoBind类

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
public class AutoBind {
private static AutoBind instance = null;

public AutoBind() {
}

public static AutoBind getInstance() {
if(instance == null) {
synchronized (AutoBind.class) {
if (instance == null) {
instance = new AutoBind();
}
}
}
return instance;
}

public void inject(Object target) {
String className = target.getClass().getCanonicalName();
String helperName = className + "$$Autobind";
try {
IBindHelper helper = (IBindHelper) (Class.forName(helperName).getConstructor().newInstance());
helper.inject(target);
} catch (Exception e) {
e.printStackTrace();
}
}
}

这个类是我们的API的入口类,使用了单例模式
inject方法是最主要的方法,但实现很简单,就是通过反射去调用APT生成的帮助类的方法去实现View的自动绑定

完成!拉出来遛遛

在app module里添加依赖

1
2
3
4
5
dependencies {
...
annotationProcessor project(':apt-compiler')
implementation project(':apt-api')
}

我们来修改MainActivity中的代码

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

@BindView(value = R.id.test_textview)
public TextView testTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

AutoBind.getInstance().inject(this);
testTextView.setText("APT 测试");
}
}
1234567891011121314

大功告成!我们来运行项目试试看
image

TextView已经正确显示了文字,我们的小demo到这里就完成啦~

还可以更好

我们的APT还可以变得更简单!

痛点

  • 生成代码时字符串拼接麻烦且容易出错
  • 继承AbstrctProcessor时要重写多个方法
  • 注册APT的步骤繁琐

下面我们来逐个击破~

使用JavaPoet来替代拼接字符串

JavaPoet是一个用来生成Java代码的框架,对JavaPoet不了解的请移步官方文档
JavaPoet生成代码的步骤大概是这样(摘自官方文档):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();

javaFile.writeTo(System.out);

使用JavaPoet来生成代码有很多的优点,不容易出错,可以自动import等等,这里不过多介绍,有兴趣的同学可以自行了解

使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion()

1
2
3
4
5
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.example.apt_annotation.BindView")
public class BindViewProcessor extends AbstractProcessor {
...
}

这是javax.annotation.processing中提供的注解,直接使用即可

使用Auto-Service来自动注册APT

这是谷歌官方出品的一个开源库,可以省去注册APT的步骤,只需要一行注释
先在apt-compiler模块中添加依赖

1
2
3
4
5
dependencies {
...

implementation 'com.google.auto.service:auto-service:1.0-rc2'
}

然后添加注释即可

1
2
3
4
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
...
}

总结

APT是一个非常强大而且频繁出现在各种开源库的工具,学习APT不仅可以让我们在阅读开源库源码时游刃有余也可以自己开发注解框架来帮自己写代码~

本文转载自:https://juejin.im/post/5bcdb901f265da0ac8496fed

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Java中getName()、getCanonicalName()和getSimpleName()的异同

发表于 2019-12-17

最简单的方法, 就是写个测试类测试一下:

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
package com.getname.pkg;

public class Main {

public class Demo1 {
class Demo2 {
}
}

public static void main(String[] args) {
/* 三种方式获取普通类Main的名字 */
System.out.println("-----三种方式获取普通类的名字-----");
String name = Main.class.getName();
System.out.println("\t" + "getName()方法:" + name);

String canonicalname = Main.class.getCanonicalName();
System.out.println("getCanonicalName()方法:" + "\t" + canonicalname);

String simplename = Main.class.getSimpleName();
System.out.println("getSimpleName()方法:" + "\t" + simplename);

/* getName()和getCanonicalName()获取数组名比较 */
int[] aaa = { 1, 2, 3 };
System.out.println("\r\n" + "-----对于数组 int[]-----");
name = aaa.getClass().getName();
System.out.println("\t" + "getName()方法:" + name);

canonicalname = aaa.getClass().getCanonicalName();
System.out.println("getCanonicalName()方法:" + "\t" + canonicalname);

/* getName()和getCanonicalName()获取内部类名比较 */
System.out.println("\r\n" + "-----对于内部类-----");
name = Demo1.Demo2.class.getName();
System.out.println("\t" + "getName()方法:" + name);

canonicalname = Demo1.Demo2.class.getCanonicalName();
System.out.println("getCanonicalName()方法:" + "\t" + canonicalname);
}
}

打印结果为:

1
2
3
4
5
6
7
8
9
10
11
12
-----三种方式获取普通类的名字-----
getName()方法:com.getname.pkg.Main
getCanonicalName()方法: com.getname.pkg.Main
getSimpleName()方法: Main

-----对于数组 int[]-----
getName()方法:[I
getCanonicalName()方法: int[]

-----对于内部类-----
getName()方法:com.getname.pkg.Main$Demo1$Demo2
getCanonicalName()方法: com.getname.pkg.Main.Demo1.Demo2

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

彻底搞懂Git Rebase

发表于 2019-12-17

使用 Git 已经好几年了,却始终只是熟悉一些常用的操作。对于 Git Rebase 却很少用到,直到这一次,不得不用。

一、起因

上线构建的过程中扫了一眼代码变更,突然发现,commit 提交竟然多达 62 次。我们来看看都提交了什么东西:
commit1

这里我们先不说 git 提交规范,就单纯这么多次无用的 commit 就很让人不舒服。可能很多人觉得无所谓,无非是多了一些提交纪录。

然而,并非如此,你可能听过破窗效应,编程也是如此!

二、导致问题

1.不利于代码 review
设想一下,你要做 code review ,结果一个很小的功能,提交了 60 多次,会不会有一些崩溃?

2.会造成分支污染
你的项目充满了无用的 commit 纪录,如果有一天线上出现了紧急问题,你需要回滚代码,却发现海量的 commit 需要一条条来看。

遵循项目规范才能提高团队协作效率,而不是随心所欲。

三、Rebase 场景一:如何合并多次提交纪录?

基于上面所说问题,我们不难想到:每一次功能开发, 对多个 commit 进行合并处理。

这时候就需要用到 git rebase 了。这个命令没有太难,不常用可能源于不熟悉,所以我们来通过示例学习一下。

1.我们来合并最近的 4 次提交纪录,执行:

1
git rebase -i HEAD~4

2.这时候,会自动进入 vi 编辑模式:

1
s cacc52da add: qrcodes f072ef48 update: indexeddb hacks 4e84901a feat: add indexedDB floders 8f33126c feat: add test2.js# Rebase 5f2452b2..8f33126c onto 5f2452b2 (4 commands)## Commands:# p, pick = use commit# r, reword = use commit, but edit the commit message# e, edit = use commit, but stop for amending# s, squash = use commit, but meld into previous commit# f, fixup = like "squash", but discard this commit's log message# x, exec = run command (the rest of the line) using shell# d, drop = remove commit## These lines can be re-ordered; they are executed from top to bottom.## If you remove a line here THAT COMMIT WILL BE LOST.## However, if you remove everything, the rebase will be aborted.#

有几个命令需要注意一下:

  • p, pick = use commit
  • r, reword = use commit, but edit the commit message
  • e, edit = use commit, but stop for amending
  • s, squash = use commit, but meld into previous commit
  • f, fixup = like “squash”, but discard this commit’s log message
  • x, exec = run command (the rest of the line) using shell
  • d, drop = remove commit

按照如上命令来修改你的提交纪录:

1
s cacc52da add: qrcodes f072ef48 update: indexeddb hacks 4e84901a feat: add indexedDB floderp 8f33126c feat: add test2.js

3.如果保存的时候,你碰到了这个错误:

1
error: cannot 'squash' without a previous commit

注意不要合并先前提交的东西,也就是已经提交远程分支的纪录。

4.如果你异常退出了 vi 窗口,不要紧张:

1
git rebase --edit-todo

这时候会一直处在这个编辑的模式里,我们可以回去继续编辑,修改完保存一下:

1
git rebase --continue

5.查看结果

1
git log

三次提交合并成了一次,减少了无用的提交信息。

commit2

四、Rebase 场景二:分支合并

1.我们先从 master 分支切出一个 dev 分支,进行开发:

1
git:(master) git checkout -b feature1

git1
2.这时候,你的同事完成了一次 hotfix,并合并入了 master 分支,此时 master 已经领先于你的 feature1 分支了:
git2
3.恰巧,我们想要同步 master 分支的改动,首先想到了 merge,执行:

1
git:(feature1) git merge master

git3
图中绿色的点就是我们合并之后的结果,执行:

1
git:(feature1) git log

就会在记录里发现一些 merge 的信息,但是我们觉得这样污染了 commit 记录,想要保持一份干净的 commit,怎么办呢?这时候,git rebase 就派上用场了。

4.让我们来试试 git rebase ,先回退到同事 hotfix 后合并 master 的步骤:
git4
5.使用 rebase 后来看看结果:

1
git:(feature1) git rebase master

这里补充一点:rebase 做了什么操作呢?

首先,git 会把 feature1 分支里面的每个 commit 取消掉;
其次,把上面的操作临时保存成 patch 文件,存在 .git/rebase 目录下;
然后,把 feature1 分支更新到最新的 master 分支;
最后,把上面保存的 patch 文件应用到 feature1 分支上;

git5

从 commit 记录我们可以看出来,feature1 分支是基于 hotfix 合并后的 master ,自然而然的成为了最领先的分支,而且没有 merge 的 commit 记录,是不是感觉很舒服了。

6.在 rebase 的过程中,也许会出现冲突 conflict。在这种情况,git 会停止 rebase 并会让你去解决冲突。在解决完冲突后,用 git add 命令去更新这些内容。

注意,你无需执行 git-commit,只要执行 continue

1
git rebase --continue

这样 git 会继续应用余下的 patch 补丁文件。

7.在任何时候,我们都可以用 --abort 参数来终止 rebase 的行动,并且分支会回到 rebase 开始前的状态。

1
git rebase —abort

五、更多 Rebase 的使用场景

git-rebase 存在的价值是:对一个分支做「变基」操作。

1.当我们在一个过时的分支上面开发的时候,执行 rebase 以此同步 master 分支最新变动;
2.假如我们要启动一个放置了很久的并行工作,现在有时间来继续这件事情,很显然这个分支已经落后了。这时候需要在最新的基准上面开始工作,所以 rebase 是最合适的选择。

六、为什么会是危险操作?

根据上文来看,git-rebase 很完美,解决了我们的两个问题:
1.合并 commit 记录,保持分支整洁;
2.相比 merge 来说会减少分支合并的记录;

如果你提交了代码到远程,提交前是这样的:
git2

提交后远程分支变成了这样:
git5

而此时你的同事也在 feature1 上开发,他的分支依然还是:
git6

那么当他 pull 远程 master 的时候,就会有丢失提交纪录。这就是为什么我们经常听到有人说 git rebase 是一个危险命令,因为它改变了历史,我们应该谨慎使用。

除非你可以肯定该 feature1 分支只有你自己使用,否则请谨慎操作。

结论:只要你的分支上需要 rebase 的所有 commits 历史还没有被 push 过,就可以安全地使用 git-rebase来操作。

本文转载自:http://jartto.wang/2018/12/11/git-rebase/

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android中Aop和Apt有什么区别?

发表于 2019-12-16

什么是Aop?

AOP指的是:面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。

代表框架:

  • Hugo(Jake Wharton)
  • SSH
  • SpringMVC

Android 中应用

  • 日志
  • 持久化
  • 性能监控
  • 数据校验
  • 缓存
  • 按钮防抖
  • 其他更多

Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率

使用姿势

在Java中使用aop编程需要用到AspectJ切面框架,AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

1.在build.gradle文件中引入AspectJ

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
pply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
...
}

final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}

JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)

MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}


dependencies {
...
implementation 'org.aspectj:aspectjrt:1.8.9'
}

2.创建注解类

1
2
3
4
5
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface SingleClick {
long clickIntervals() default 800;
}

3.创建切面类

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
/**
* 添加切面注解
*/
@Aspect
public class AopTest {
private static final String TAG = "linhaojian";
/**
* 定义切入点(定义那些类或者方法需要改变)
*/
@Pointcut("execution(* com.lhj.test_apt..*ck(..))")
public void pointcut1() {
}
/**
* 使用注解方式,定义注解
*/
@Pointcut("execution(@com.lhj.test_apt.DebugLog * *ck(..))")
public void pointcut() {
}
/**
* 前置通知,切点之前执行
* @param point
*/
@Before("pointcut()")
public void logBefore(JoinPoint point){
Log.e(TAG,"logBefore");
}
/**
* 环绕通知,切点前后执行
* @param joinPoint
* @throws Throwable
*/
@Around("pointcut()")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e(TAG,"logAround");
// 1.执行切点函数(如果不调用该方法,切点函数不被执行)
joinPoint.proceed();
}
/**
* 后置通知,切点之后执行
* @throws Throwable
*/
@After("pointcut()")
public void logAfter(JoinPoint point){
Log.e(TAG,"logAfter");
}
/**
* 返回通知,切点方法返回结果之后执行
* @throws Throwable
*/
@AfterReturning("pointcut()")
public void logAfterReturning(JoinPoint point, Object returnValue){
Log.e(TAG,"logAfterReturning ");
}
/**
* 异常通知,切点抛出异常时执行
* @throws Throwable
*/
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void logAfterThrowing(Throwable ex){
Log.e(TAG,"logAfterThrowing : "+ex.getMessage());
}
}

4.使用

1
2
3
4
5
@SingleClick(clickIntervals = 2000)
@Override
public void onClick(View v) {
Toast.makeText(this, "1", Toast.LENGTH_SHORT).show();
}

难点:
AspectJ语法比较多,但是掌握几个简单常用的,就能实现绝大多数切片,完全兼容Java(纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。)

优点:
AspectJ除了hook之外,AspectJ还可以为目标类添加变量,接口。另外,AspectJ也有抽象,继承等各种更高级的玩法。它能够在编译期间直接修改源代码生成class,强大的团战切入功能,指哪打哪,鞭辟入里。有了此神器,编程亦如庖丁解牛,游刃而有余。

什么是Apt?

APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入

代表框架:

  • DataBinding
  • Dagger2
  • ButterKnife
  • EventBus3
  • DBFlow
  • AndroidAnnotation

使用姿势

1,在android工程中,创建一个java的Module,写一个类继承AbstractProcessor

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
@AutoService(Processor.class) // javax.annotation.processing.IProcessor 
@SupportedSourceVersion(SourceVersion.RELEASE_7) //java
@SupportedAnnotationTypes({ // 标注注解处理器支持的注解类型
"com.annotation.SingleDelegate",
"com.annotation.MultiDelegate"
})
public class AnnotationProcessor extends AbstractProcessor {

public static final String PACKAGE = "com.poet.delegate";
public static final String CLASS_DESC = "From poet compiler";

public Filer filer; //文件相关的辅助类
public Elements elements; //元素相关的辅助类
public Messager messager; //日志相关的辅助类
public Types types;

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
filer = processingEnv.getFiler();
elements = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
types = processingEnv.getTypeUtils();

new SingleDelegateProcessor().process(set, roundEnvironment, this);
new MultiDelegateProcessor().process(set, roundEnvironment, this);

return true;
}
}

2,重写AbstractProcessor类中的process方法,处理我们自定义的注解,生成代码:

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
public class SingleDelegateProcessor implements IProcessor {  
@Override
public void process(Set<? extends TypeElement> set, RoundEnvironment roundEnv,
AnnotationProcessor abstractProcessor) {
// 查询注解是否存在
Set<? extends Element> elementSet =
roundEnv.getElementsAnnotatedWith(SingleDelegate.class);
Set<TypeElement> typeElementSet = ElementFilter.typesIn(elementSet);
if (typeElementSet == null || typeElementSet.isEmpty()) {
return;
}
// 循环处理注解
for (TypeElement typeElement : typeElementSet) {
if (!(typeElement.getKind() == ElementKind.INTERFACE)) { // 只处理接口类型
continue;
}

// 处理 SingleDelegate,只处理 annotation.classNameImpl() 不为空的注解
SingleDelegate annotation = typeElement.getAnnotation(SingleDelegate.class);
if ("".equals(annotation.classNameImpl())) {
continue;
}
Delegate delegate = annotation.delegate();

// 添加构造器
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC);

// 创建类名相关 class builder
TypeSpec.Builder builder =
ProcessUtils.createTypeSpecBuilder(typeElement, annotation.classNameImpl());

// 处理 delegate
builder = ProcessUtils.processDelegate(typeElement, builder,
constructorBuilder, delegate);

// 检查是否继承其它接口
builder = processSuperSingleDelegate(abstractProcessor, builder, constructorBuilder, typeElement);

// 完成构造器
builder.addMethod(constructorBuilder.build());

// 创建 JavaFile
JavaFile javaFile = JavaFile.builder(AnnotationProcessor.PACKAGE, builder.build()).build();
try {
javaFile.writeTo(abstractProcessor.filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}

3,在项目Gradle中添加 annotationProcessor project 引用

1
2
compile project(':apt-delegate-annotation')  
annotationProcessor project(':apt-delegate-compiler')

4,如果有自定义注解的话,创建一个java的Module,专门放入自定义注解。项目与apt Module都需引用自定义注解Module

4-1,主工程:

1
2
compile project(':apt-delegate-annotation')  
annotationProcessor project(':apt-delegate-compiler')

4-2,apt Module:

1
2
3
compile project(':apt-delegate-annotation')  
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.4.0'

5,生成的源代码在build/generated/source/apt下可以看到

难点

就apt本身来说没有任何难点可言,难点一在于设计模式和解耦思想的灵活应用,二在与代码生成的繁琐,你可以手动字符串拼接,当然有更高级的玩法用squareup的javapoet,用建造者的模式构建出任何你想要的源代码

优点

它的强大之处无需多言,看代表框架的源码,你可以学到很多新姿势。总的一句话:它可以做任何你不想做的繁杂的工作,它可以帮你写任何你不想重复代码。懒人福利,老司机必备神技,可以提高车速,让你以任何姿势漂移。它可以生成任何源代码供你在任何地方使用,就像剑客的剑,快疾如风,无所不及

Aop和Apt对比

如图所示:

和

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Kotlin中@JvmOverloads 注解

发表于 2019-12-16

在Kotlin中@JvmOverloads注解的作用就是:在有默认参数值的方法中使用@JvmOverloads注解,则Kotlin就会暴露多个重载方法。
可能还是云里雾里,直接上代码,代码解释一切:
如果我们再kotlin中写如下代码:

1
2
3
fun f(a: String, b: Int = 0, c: String="abc"){
...
}

相当于在Java中声明

1
2
void f(String a, int b, String c){
}

默认参数没有起到任何作用。

但是如果使用的了@JvmOverloads注解:

1
2
@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){
}

相当于在Java中声明了3个方法:

1
2
3
void f(String a)
void f(String a, int b)
void f(String a, int b, String c)

是不是很方便,再也不用写那么多重载方法了。

注:该注解也可用在构造方法和静态方法。

1
2
3
4
5
class MyLayout: RelativeLayout {

@JvmOverloads
constructor(context:Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0): super(context, attributeSet, defStyleAttr)
}

相当Java中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyLayout extends RelativeLayout {

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

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

public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

kotlin中 data class 到底是个什么鬼

发表于 2019-12-16

data class就是一个类中只包含一些数据字段,类似于vo,pojo,java bean。一般而言,我们在Java中定义了这个数据类之后要重写一下toString,equals等方法。要生成get,set方法。

然而在Kotlin中这些都不在需要自己手动去敲了,编译器在背后默默给我们生成了如下的东西:

  • equals()/hashCode()
  • toString()方法
  • componentN()方法
  • copy()方法

如何申明一个简单的数据类? 有一下几点要求:

  • 主构造函数必须要至少有一个参数
  • 主构造函数中的所有参数必须被标记为val或者var
  • 数据类不能有以下修饰符:abstract,inner,open,sealed
  • data class只能实现接口(Kotlin1.1以前的规则),现在也可以继承其它类
1
data class User(var id: Int, var name: String)

就这么一行代码,你已然拥有了一个数据类

主构造函数中的所有参数必须被标记为var或者val,var就表示可读写,val就表示只读,这就相当于表明了数据字段的访问权限

componentN()方法是干嘛用的?

在主构造函数中有多少个参数,就会依次生成对应的component1,component2,component3……这些函数返回的就是对应字段的值

componentN函数是用来实现解构申明的

1
2
3
4
5
6
data class User(var id: Int,var name:String) 
fun main(args: Array<String>) {
var user: User = User(123, "liuliqianxiao")
var (id,name) = user//多重赋值
print("$id,$name")
}

拿上面的例子来说,给id赋值,其实调用的是user.component1(),给name赋值其实调用的是component2()函数。

有了这个解构申明,想在一个函数中返回多个结果,就可以申明一个简单的数据类来返回了,然后取值也很方便。

copy函数

默认生成的copy函数就是用现在的数据字段生成了一个新的对象。

1
fun copy(id: Int = this.id,name: String = this.name) = User(id,name)

如果只想改变其中的某些字段,就可以在调用copy的时候采用命名参数的方法进行调用

1
2
3
4
fun main(args: Array<String>) {
var user: User = User(123, "liuliqianxiao")
var other: User = user.copy(name = "mdzz")//只想改变名字
}

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Github Token的获取+PicGo图床配置

发表于 2019-12-14

Github获取token

在GitHub上获取个人访问令牌(Personal Access Token, PAT)的步骤如下:

  1. 登录到你的GitHub账户。
  2. 点击右上角的用户头像,选择Settings(设置
  3. 在侧边栏中点击Developer settings(开发者设置)
  4. 点击Personal access tokens(个人访问令牌)
  5. 点击Generate new token(生成新令牌)
  6. 填写必要的信息,例如令牌的描述和选择所需的权限
  7. 完成后,点击Generate token(生成令牌)
  8. 复制生成的令牌并保存,因为生成后不会再显示

PicGo图床配置

GitHub图床

  • 仓库名 :用户名/仓库名
  • 分支名 :master
  • 该仓库token : xxxxxxxxxx
  • 指定存储路径 :img/
  • 自定义域名访问(CDN) : https://cdn.jsdelivr.net/gh/用户名/仓库名

截图如下:

示例图如下:

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

java五大排序算法之选择排序

发表于 2019-12-09

一.选择排序介绍

选出最小的一个数与第一个位置的数交换

二.选择排序原理分析

第1趟比较:拿第1个元素依次和它后面的每个元素进行比较,如果第1个元素大于后面某个元素,交换它们,经过第1趟比较,数组中最小的元素被选出,它被排在第一位

第2趟比较:拿第2个元素依次和它后面的每个元素进行比较,如果第2个元素大于后面某个元素,交换它们,经过第2趟比较,数组中第2小的元素被选出,它被排在第二位

……

第n-1趟比较:第n-1个元素和第n个元素作比较,如果第n-1个元素大于第n个元素,交换它们

三.选择排序代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void selectionSort(int[] nums) {

if (nums == null || nums.length < 2) {
return;
}

for(int i = 0; i < nums.length - 1; i++) {
for(int j = i + 1; j < nums.length; j++) {
if(nums[i] > nums[j]) {
swap(nums, i, j);
}
}
}

}

public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}

四.选择排序的优化

使用临时变量存储最小值的角标值,减少交换的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void selectSort(int[] numbers) {
int size = numbers.length; // 数组长度
int temp = 0; // 中间变量
for (int i = 0; i < size-1; i++) {
int k = i; // 待确定的位置
// 选择出应该在第i个位置的数
for (int j = size - 1; j > i; j--) {
if (numbers[j] < numbers[k]) {
k = j;
}
}
// 交换两个数
temp = numbers[i];
numbers[i] = numbers[k];
numbers[k] = temp;
}
}

五.选择排序的时间复杂度

时间复杂度:O(n²)

空间复杂度:O(1),只需要一个附加程序单元用于交换

稳定性:选择排序是不稳定的排序算法,因为无法保证值相等的元素的相对位置不变,例如 [3, 4, 3, 1, 5]这个数组,第一次交换,第一个3和1交换位置,此时原来两个3的相对位置发生了变化。

本帖附件

点击下载

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Java面试题之TCP和UDP的区别

发表于 2019-12-09

网络层的划分

一 网络层的划分

  • 物理层 :负责在物理线路上传输原始的二进制数据(0和1),该层数据以比特流的形式传输

  • 链路层: 负责在通信的实体间建立数据链路连接,该层数据以帧的形式传输

  • 网络层: 负责创建逻辑链路,以及实现数据包的分片和重组,实现拥塞控制、网络互连等功能,该层数据以IP数据报(IP分组)的形式传输

  • 传输层: 负责向用户提供端到端的通信服务,实现流量控制以及差错控制,这一层主要重点是两个协议 : UDP 和 TCP

  • 应用层: 为应用程序提供了网络服务,应用层协议最著名的就是HTTP, FTP了, 还有一个重要的DNS

    二、TCP和UDP的区别

三 TCP的三次握手

  • 第一次握手:建立连接
  • 第二次握手:响应连接
  • 第三次握手:测试数据

本帖附件

点击下载

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…454647…50

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

493 日志
143 标签
RSS
© 2025 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%