从任意位置缩放到屏幕大小

图片替换文本

代码实例:

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
package com.example.androidx_branch.scalestudy

import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.ViewUtils
import androidx.constraintlayout.widget.ConstraintSet
import com.example.androidx_branch.R
import com.example.androidx_branch.scrollerpicker.library.util.ScreenUtil
import com.uppack.lksmall.baseyu.weight.util.ViewUtil
import kotlinx.android.synthetic.main.activity_picture.*
import kotlinx.android.synthetic.main.activity_scalea.*
import java.util.function.LongFunction

/**
* @Author Yu
* @Date 2021/10/27 9:51
* @Description TODO
*/
class ScaleStudyActivity : AppCompatActivity() {
lateinit var scaleAnimal: ObjectAnimator
lateinit var scaleYAnimal: ObjectAnimator
lateinit var scaleXAnimal: ObjectAnimator
var animatorSet: AnimatorSet = AnimatorSet()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scalea)

btnStart.setOnClickListener {
beginBigToSmall(true)
//beginSmallToBig(true)
animatorSet.start()
}
btnEnd.setOnClickListener {
beginBigToSmall(false)
// beginSmallToBig(false)
animatorSet.start()
}
}

// 初始状态从小到达这么写
fun beginSmallToBig(toBig: Boolean) {
scaleXAnimal = ObjectAnimator.ofFloat(
imgView,
"scaleX",
imgView.scaleX,
if (toBig) ScreenUtil.getScreenWidth() / imgView.width.toFloat() else 1f
)
scaleYAnimal = ObjectAnimator.ofFloat(
imgView,
"scaleY",
imgView.scaleY,
if (toBig) ScreenUtil.getScreenHeight() / imgView.height.toFloat() else 1f
)
animatorSet.playTogether(scaleXAnimal, scaleYAnimal)
animatorSet.setDuration(2000)
imgView.pivotX =
(imgView.left.toFloat()) / ((ScreenUtil.getScreenWidth() / imgView.width.toFloat()) - 1)
imgView.pivotY =
(imgView.top.toFloat()) / ((ScreenUtil.getScreenHeight() / imgView.height.toFloat()) - 1)
}

// 初始状态从大到小
fun beginBigToSmall(toSmall: Boolean) {
var targetX = ViewUtil.dip2px(40f)// 在实际项目中这个是目标最终的距离离x的位置
var targetY = ViewUtil.dip2px(100f)// 在实际项目中这个是目标最终的距离离Y的位置
scaleXAnimal = ObjectAnimator.ofFloat(
imgView,
"scaleX",
imgView.scaleX,
if (toSmall) ViewUtil.dip2px(100f) / imgView.width.toFloat() else 1f
)
scaleYAnimal = ObjectAnimator.ofFloat(
imgView,
"scaleY",
imgView.scaleY,
if (toSmall) ViewUtil.dip2px(90f) / imgView.height.toFloat() else 1f
)
animatorSet.playTogether(scaleXAnimal, scaleYAnimal)
animatorSet.setDuration(2000)
imgView.pivotX =targetX/(1f-ViewUtil.dip2px(100f)/imgView.width.toFloat() )
imgView.pivotY =targetY/(1f-ViewUtil.dip2px(90f)/imgView.height.toFloat() )
}

}
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
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/imgView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/btnEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="回去"
app:layout_constraintLeft_toRightOf="@+id/btnStart"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Kotlin协程

给类标记为挂起点使用的注解是suspend。通过反射的方式我们得知,这个suspend标记的方法的返回值是Continuation。

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
interface ApiInterFace {
// 下载文件
@Streaming
@GET
suspend fun downloadFile(@Url fileUrl: String?): Response<ResponseBody>
}
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.lang.reflect.Proxy
fun main() {
var s: ApiInterFace = Proxy.newProxyInstance(
ApiInterFace::class.java.classLoader, arrayOf(ApiInterFace::class.java)
) { proxy, method, args ->
var typeArray = method.genericParameterTypes
for (value in typeArray) {
System.out.println(value)
}
""
} as ApiInterFace
runBlocking {
GlobalScope.launch {
s.downloadFileWithDynamicUrlAsync("Das")
}
delay(1000)
}
}
// 执行结果:
// class java.lang.String
// kotlin.coroutines.Continuation<? super java.lang.String>

Retrofit源码解析

在阅读源码的时候,里面大量使用到了Type这类。那这个类是干嘛的呢?

java泛型在运行时会被擦除,我们就无法得到在编译时期的泛型信息。因此java提供了

Type,它可以让我们在运行的时候也能得到这些信息。

类型擦除是指泛型在运行的时候会去擦除泛型的类信息

接口解析过程:

1
2
3
4
5
6
interface ApiInterFace {
// 下载文件
@Streaming
@GET
suspend fun downloadFile(@Url fileUrl: String?): Response<ResponseBody>
}

  使用Retrofit调用create(Class service)得时候,会去解析我们的service方法,service也就是上面我演示的ApiInterface方法。他会去解析这个接口的downloadFile方法的注解,泛型参数信息,和所有参数上面的注解信息。因为现在引入了Kotlin的协程,所以在解析方法的注解的时候,会判断最后一个注解是不是协程,如果是的话会给参数isKotlinSuspendFunction标记为true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final class RequestFactory {
static final class Builder {
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;//这个方法
this.methodAnnotations = method.getAnnotations();// 这个方法被标记的所有注解
this.parameterTypes = method.getGenericParameterTypes();//这个方法所有的泛型参数类型
this.parameterAnnotationsArray = method.getParameterAnnotations();//这个方法的参数的所有注解信息
}
}
}
/**
* 以上面ApiSerice这个接口为例子:
* this.methodAnnotations = [@retrofit2.http.Get(value=),@retrofit2.http.Streaming()]
* this.parameterTYpes = [String,Continuation<Response<REsponseBody>>]
* this.parameterAnnotationsArray = [@retrofit2.http.Url()]
*/

关于方法的注解解析都在RequestFactory.java 类完成。

  Retrofit为啥要使用动态代理?

在Retrofit使用动态代理可以适配新出现的技术,比如以前使用Okhttp的时候,我们都是要创建一个Call对象,然后通过这个call对象去进行网络请求。然后现在出现了kotlin的协程,kotlin由于使用了动态代理,我们可以去通过代理创建可以搭配kolin的协程进行的网络请求。

  在Retrofit的代理实现的类是继承自ServiceMethod抽象类。目前Retrofit已知的有:

classDiagram
      HttpServiceMethod--|>ServiceMethod
      SuspendForBody--|>HttpServiceMethod
      SuspendForResponse--|>HttpServiceMethod
      CallAdapted--|> HttpServiceMethod
 

      ServiceMethod : +ServiceMethod parseAnnotations()
      ServiceMethod : +T invoke()
      
    
      class HttpServiceMethod{
            +HttpServiceMethod parseAnnotations()
          -CallAdapter createCallAdapter()
          -Converter createREsponseConverter()
          +ReturnT invoke()
          +ReturnT adapter()
          -RequestFactory requestFory
          -okhttp3.Call.Factory callFactory
          -Converter responseConverter
      }
      class CallAdapted{
          -T adapter()
      }
      class SuspendForBody{
          -T adapter()
      }
      class SuspendForResponse{
          -T adapter()
      }

  结合类图在结合代码再来看下:

1
2
3
4
5
6
7
8
9
// Retrofit.java
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}

  loadServiceMethod(method)方法就是去创建了ServiceMethod对象,它通过Method上面的注解判断这次的请求是协程呢还是Call呢还是其他,然后通过创建出对应的ServiceMethod对象,调用这个对象的invoke方法,通过上面的类图我们可以看到只有HttpServiceMethod类实现了invoke方法。

1
2
3
4
5
// HttpSercieMethod.java
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}

  可以看到invoke实现其实就是去创建了OkHttpCall对象,然后调用adapter方法。通过类如可以看到实现adapter方法的类只有三个(哪三个,去看图)。

  现在来看下loadServiceMethod是怎么通过注解来确定HttpServiceMethod具体是哪个对象的。

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

synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}

  可以看到是调用了ServiceMethod.parseAnnotations()方法获取到具体的HttpServiceMethod对象。

1
2
3
4
5
6
7
8
9
10

abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

abstract @Nullable T invoke(Object[] args);
}

  这里通过RequestFactory方法将解析完的接口上面的全部注解信息全部保存在了内 RequestFactory类里面。

  然后将我们解析完成的数据交给了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
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
if (isKotlinSuspendFunction) {
Type[] parameterTypes = method.getGenericParameterTypes();
Type responseType = Utils.getParameterLowerBound(0,
(ParameterizedType) parameterTypes[parameterTypes.length - 1]);
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 {
}

adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);// 这一步就是自动往我们的注解数组里面手动调价一个SkipCallbackExecutor的注解,用来实现跳过手动执行call.execu的方法。
} else {
adapterType = method.getGenericReturnType();// 获取方法的返回值类型。
}

// 返回正真的HttpServiceMethod对象
if (!isKotlinSuspendFunction) {
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);
}
}

  看上面的代码可以知道,首先判断是不是协程,是的话在判断是不是需要返回Response。如果需要的话标志参数continuationWantsResponse设置为true。如果是协程的话,会给我们的注解组数里面手动添加一个SkipCallbackExecutor的新注解,用来实现跳过手动执行call的调用网络请求的方法。

从可用工厂返回一个适合ResponseBody类型的转换器。

1
2
3
4
// Retrofit.java
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}

一个进行响应请求的适配器,他们的构造函数都需要:

  • RequestFactory requestfactory // 解析请求接口上面注解的所有数据
  • okhttp3.Call.Factory callFactory
  • Converter<ResponseBody,ResponseT> responseConverter // 适合ResponseBody的类型转换器
  • CallAdaper<ResponseT,ReturnT>callAdapeter

然后纯协程写的方法不带Response返回类型的还会在多一个参数 fasle

现在来一个一个介绍这几个参数都是在干嘛:

callFactory

    他的获取方式是通过retrofit.callFactory

1
2
//HttpSerciceMethod.java
okhttp3.Call.Factory callFactory = retrofit.callFactory;

    retrofit里面的callfactoty是什么时候创建的呢?

就是我们创建Retrofit对象的时候。也就是给Retrofit创建我们的OkhttpClient的时候就创建好了。

callAdapeter

    通过遍历callAdapterFactories数组,通过reruenTyped来确定返回哪个CallAdaper

1
2
3
4
5
6
7
8
9
// HttpSerciveMethod.java
CallAdapter<ResponseT, ReturnT> callAdapter =
createCallAdapter(retrofit, method, adapterType, annotations);
// HttpServicemethod.java
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
}
}
1
2
3
4
5

// Retrofit.java
public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Retrofit.java
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
Objects.requireNonNull(returnType, "returnType == null");
Objects.requireNonNull(annotations, "annotations == null");

int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
}

    在Retrofit里面看到了这个callAdapterFactories。。。。。问题又来了 这个是啥?

    在Retrofit类里面提供了两个公开方法:

  • addConverterFactory(Converter.Factory factory)

  • addCallAdapterFactory(CallAdapter.Factory factory)

看名知意:addConverterfactory():添加网络请求回来的结果解析工厂,最常用的有:

1
addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))

看名知意:addCallAdapterFactory():进行网络请求。常用的有支持RxJava的:

1
addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())

  在上面创建callAdapter的时候传入一个叫adapterType的参数,那这个adapterType参数又是什么呢?

adapterType

  这个adapterType是一个Type类型,也就是说他是一个泛型对象,在HttpSermethod他的创建分为两种,一种是基于kotlin协程创建的adapterType。一种是基于最基本的adapterType。

1
2
3
4
5
6
7
8
9
if (isKotlinSuspendFunction) {
adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
} else {
// getGenericReturnType();用来返回一个方法的返回具体类型。
adapterType = method.getGenericReturnType();
// 比如 Call<String> getName();
// 那么这时候的adapterType就是Call<String>
}

   可以看到不是协程的话,那么adapterType就是获取我们方法返回的类型。如果是协程的话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Utils.java
static final class ParameterizedTypeImpl implements ParameterizedType {
private final @Nullable Type ownerType;
private final Type rawType;
private final Type[] typeArguments;
ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) {
// Require an owner type if the raw type needs it.
if (rawType instanceof Class<?>
&& (ownerType == null) != (((Class<?>) rawType).getEnclosingClass() == null)) {
throw new IllegalArgumentException();
}

for (Type typeArgument : typeArguments) {
Objects.requireNonNull(typeArgument, "typeArgument == null");
checkNotPrimitive(typeArgument);
}

this.ownerType = ownerType;
this.rawType = rawType;
this.typeArguments = typeArguments.clone();
}
}

    如是是协程的话,那么就创建一个type对象,他的rawType = retrofit2.Call。

callAdapter

现在我们再来分析下callAdapeter对象创建的时候,在Retrofit的nextCallAdapter是怎么创建的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Retrofit.java
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
Objects.requireNonNull(returnType, "returnType == null");
Objects.requireNonNull(annotations, "annotations == null");

int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
}

遍历callAdapterFactories数组找出那个可以执行returnType的类。这个方法的第一行就可以看出,他上来就判断returnType是不是Call.class对象。现在就知道了为什么在使用协程的时候,需要自己构建一个returnType指定他的rawType为Call.class的原因了。

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
// DefaultCallAdapterFactory.java
@Override public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;

return new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}

@Override public Call<Object> adapt(Call<Object> call) {
return executor == null
? call
: new ExecutorCallbackCall<>(executor, call);
}
};
}

  可以看到如果是协程的话,那么创建的callAdapter实际上是CallAdapter 通过匿名接口实现匿名内部类。

responseConverter

  和callAdapter一样的设计思路。


到现在HttpServiceMethod对象已经创建完毕,现在我们继续跟进看是怎么调用的。

这里拿SuspendForBody这个HttpServiceMethod对象来举例:

1
2
3
4
// HttpServiceMethod.java
new SuspendForBody<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
continuationBodyNullable);

首先调用他们的父类HttpServiceMethod的invoke方法:

1
2
3
4
5
// HttpServiceMethod
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}

  创建Call对象。然后执行了adapt方法。adapt方法是在HttpServiceMethod的子类实现的。所以现在回到SuspendForBofy类。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SuspendForBody.java
SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
this.isNullable = isNullable;
}
@Override protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);//对于协程这个callAdappter就是上面讲了创建了一个匿名接口的匿名内部类。

//noinspection unchecked Checked by reflection inside RequestFactory.
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
// 获取最后一个参数信息。
// 我们使用 suspend fun getMyName() 的时候经过编译后,这个suspend就被编译成了Continuation这个对象。也就是说这个方法实际上就是 Continuation fun getMyName();
try {
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}

  第一行出现了callAdapter,这个callAdapter也就是刚才我们讲的根据根据returnType去创建对应的callAdapter。对于协程来说创建的就是一个匿名接口实现的匿名内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new CallAdapter<Object, Call<?>>() {
@Override public Type responseType() {
return responseType;
}
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
// 这个executor为null。因为在解析方法的时候,他判断了这个方法是不是协程,是的话就会代码里面自动给他添加上一个SkipCallbackExecutor的注释,意思就是告诉程序,协程的网络请求又程序自动进行。所以在调用adapt方法的时候返回的是call
@Override public Call<Object> adapt(Call<Object> call) {
return executor == null
? call
: new ExecutorCallbackCall<>(executor, call);
}
};

  call的获取过程现在知道了。

  回到SuspendForBody的adapt方法:最后执行了kotlin写的扩展方法。执行了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
// HttpServiceMethod 
static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
private final boolean isNullable;

SuspendForBody(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, Call<ResponseT>> callAdapter, boolean isNullable) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
this.isNullable = isNullable;
}

@Override protected Object adapt(Call<ResponseT> call, Object[] args) {
call = callAdapter.adapt(call);
Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];
try {
return isNullable
? KotlinExtensions.awaitNullable(call, continuation)
: KotlinExtensions.await(call, continuation);// 执行网络请求
} catch (Exception e) {
return KotlinExtensions.suspendAndThrow(e, continuation);
}
}
}

关于协程里面一个请求是怎么发出去的,可以看到Retrofit对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
suspend fun <T : Any> Call<T>.await(): T {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
val body = response.body()
if (body == null) {
val invocation = call.request().tag(Invocation::class.java)!!
val method = invocation.method()
val e = KotlinNullPointerException("Response from " +
method.declaringClass.name +
'.' +
method.name +
" was null but response body type was declared as non-null")
continuation.resumeWithException(e)
} else {
continuation.resume(body)
}
} else {
continuation.resumeWithException(HttpException(response))
}
}

override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}

软件目录修改集合

Google

   修改User Data到你指定的目录:

  1. 进入我们准备存档的目录比如D:/Google/User Data
  2. 进入原来Google用户信息保存的目录 C:\Users\你的用户名\AppData\Local\Google\Chrome
  3. 把原来的User Data里面的内容复制到D盘你想保存的地方
  4. 删除c盘的User Data: 在控制台输入 RMDIR /S “User Data”
  5. 移动关联 输入 MKLINK /J “User Data” “D:/Google/User Data”

面向切面编程

Aop是一种编程思想,面向切面编程的编程思想。

深入理解Android之AOP

​ 具体实现的框架有:

方法 说明 原理 实现 特点
APT 注解处理器 编译器通过注解采集信息,生成Java/Class文件 ButterKnife,Glide,EventBus3 基于Javac,但是无法修改已经存在的代码的内部结构
AspectJ 面向切面编程 在编译后修改/生成指定的Class Hugo 功能强大,底层原理利用ASM,不够轻量
Javassit/ASM 操作class的框架 按照Class文件的格式,解析修改生成class Qzone超级补丁,TInker热修复 Javassit的java Api更易于使用,ASM性能更好

AspectJ


      这个框架有几个重要的概念,JoinPoints:切入点,也就是在那个方法进行切入。Advice:切入的方式,是在这个方法之前切入还是之后切入,还是周围切入也就是之前和之后同时切入。

在Idea使用AspecJ框架流程:

  1. 导入库文件:aspectjrt.jar文件
img-0 img-0
  1. 导入编译工具aspectjtools.jar
img-0

​ 编译程序改完后我们就可以新建Aspect类:

img-0
img-0

​ 至此我们就可以使用aspectj框架了。

@Pointcut 的 12 种用法,你知道几种? - 知乎 (zhihu.com)

切入点标签

  • @Pointcut

    ​ 用来指定切入点,他的作用仅仅用来帮我们定义切入点,不能在这个切入点里面写任何代码,他是搭配@Before @After @Around标签来使用的。

    1
    2
    3
    4
    5
    6
    7
    @Aspect
    public class Aspect1 {
    @Pointcut("execution(* main.Service1.*(..))")
    public void pointcut1(JoinPoint joinPoint){
    System.out.println("PointCut执行了");
    }
    }

    ​ 按照这个例子写我们的程序会报错:Pointcuts without an if() expression should have an empty method body

    ​ 正确的写法:

    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
    @Aspect
    public class Aspect1 {
    @Pointcut("execution(* main.Service1.*(..))")
    public void pointcut1(){
    }

    @Before(value = "pointcut1()")
    public void before(JoinPoint joinPoint){
    System.out.println("前置通知"+joinPoint);
    }

    @After(value = "pointcut1()")
    public void after(JoinPoint joinPoint){
    System.out.println("后置通知"+joinPoint);
    }
    //@4:定义了一个异常通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效
    @AfterThrowing(value = "pointcut1()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
    //发生异常之后输出异常信息
    System.out.println(joinPoint + ",发生异常:" + e.getMessage());
    }
    }
    public class Test {
    public static void main(String[] args) {
    Service1 service1 = new Service1();
    service1.m1();
    }
    }
    // 输出结果:
    // 前置通知execution(void main.Service1.m1())
    // 我是m1方法
    // 后置通知execution(void main.Service1.m1())

    在我们找到切入点的时候

  • @Before

  • @After

  • @Around

  • @AfterReturning

  • @AfterThrowing

注意这些标签都是用来帮助我们去寻找插入点 也就是 切入点

切入点的标签知道了,现在就是表达式标签了。表达式标签也就是刚才我们写在@Pointcut括号里面的那一堆东西。

表达式标签

标签 描述
execution 用于匹配方法执行的连接点
within 用于匹配指定类型内的方法执行
this 用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象类型匹配,这样就可能包括引入接口*类型匹配
target 用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配,这样就不包括引入接口类型匹配
args 用于匹配当前执行的方法传入的参数为指定类型的执行方法
@within 用于匹配所以持有指定注解类型内的方法
@target 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
@args 用于匹配当前执行的方法传入的参数持有指定注解的执行
@annotation 用于匹配当前执行方法持有指定注解的方法
bean Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

表达式标签使用介绍:

executation

1
2
executation(@注解? 修饰符匹配? 返回值类型 类路径?方法名匹配 异常类型匹配?)
// 带问号的是不必须参数

案例:

表达式 描述
public *.*(..) 任何公共方法执行
* com.pince..IPService.*() com.pince包及所有子包下IPService接口中的任何任何无参方法
* com.pince..*.*(...) com.pince包及所有子包下任何类的任何方法
* com.pince..IPSercice.() com.pince包及所有子包下IPService接口的任何只有一个参数方法
* com.pince..IPSercice+.*() com.pince包及所有子包下IPService接口及子类型的任何无参方法
* Service1.*(String) 匹配Service1中只有一个参数且为String的方法
* Service1.*(*,String) 匹配Service1中只有两个参数且第二份参数为String的方法
* Service1.*(..,String) 匹配Service1中最后要给参数为String的方法

within

用法:within(类型表达式):目标对象target的类型是否和within中指定的类型匹配

匹配原则:target.getClass().equals(within表达式中指定的类型)

自我解释:只匹配对应类的数据,你不是目标类我就不会匹配上

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.javacode2018.aop.demo9.test2;

public class C1 {
public void m1() {
System.out.println("我是m1");
}

public void m2() {
System.out.println("我是m2");
}
}
public class C2 extends C1 {
@Override
public void m2() {
super.m2();
}

public void m3() {
System.out.println("我是m3");
}
}
@Aspect
public class AspectTest2 {

@Pointcut("within(C1)") //@1
public void pc() {
}

@Before("pc()") //@2
public void beforeAdvice(JoinPoint joinpoint) {
System.out.println(joinpoint);
}

}
// 输出结果:我是m1 我是m2 我是m3
// 看结果可知并没有走到切面里面去
// 修改
@Pointcut("within(C1+)")
@Pointcut("within(C2)")

Demo

需求1:自定义注解,然后获取被注解标记的方法的入参,注解值

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface note2 {
String value();
}
1
2
3
4
5
6
7
public class Service2 {
@note2(value = "我是权限")
public String m3(int num){
System.out.println("我是m3方法");
return "我是返回值";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class Aspect1 {
//找到被note2标记的方法 并获得注解的值
@Pointcut("execution(@main.note2 String *(..)) && @annotation(noValue)&&args(num)")
public void logPoint(note2 noValue,int num){
}

@Around(value = "logPoint(noValue,num)")
public void aopFour(JoinPoint joinPoint,note2 noValue,int num) {
System.out.println(noValue.value()+" "+num);
}
}
1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Service2 service2 = new Service2();
service2.m3(90);
}
}
// 执行结果:
// 我是权限 90

上面这个案例可以看到我们的Service2的m3方法是有返回值的,那我们在切面里面想获取返回值可以这么修改:

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class Aspect1 {
//找到被note2标记的方法 并获得注解的值
@Pointcut("execution(@main.note2 String *(..)) && @annotation(noValue)&&args(num)")
public void logPoint(note2 noValue,int num){
}

@AfterReturning(value = "logPoint(noValue,num)",returning = "rvt")
public void aopFour(JoinPoint joinPoint,note2 noValue,int num,String rvt) {
System.out.println(noValue.value()+" "+num+" "+rvt);
}
}

AspectJ 命令常用参数介绍

1 -inpath: .class文件路径,可以是在jar文件中也可以是在文件目录中,路径应该包含那些AspectJ相关的文件,只有这些文件才会被AspectJ处理。输出文件会包含这些.class 。该路径就是一个单一参数,多个路径的话用分隔符隔开。

2 -classpath: 指定去哪找用户使用到的.class文件,路径可以是zip文件也可以是文件目录,该路径就是一个单一参数,多个路径的话用分隔符隔开。

3 -aspectPath: 需要被处理的切面路径,存在于jar文件或者文件目录中。在Andorid中使用的话一般指的是被@Aspect注解标示的class文件路径。需要注意的是编译版本需要与Java编译版本一致。classpath指定的路径应该包含所有的aspectpath指定的.class文件。不过默认情况下,inPath和aspectPath中的路径不一定非要放置在classPath中,因为编译器会自动处理把它们加入。路径格式与classpath和inpath样,都需要用分隔符隔开。

4 **-bootClasspath: ** 重载跟VM相关的bootClasspath,例如在Android中使用android-27的源码进行编译。路径格式与之前一样。

5 -d: 指定由AspectJ处理后的.class文件存放目录,如果不指定的话会放置在当前的工作目录中。

6 -outjar: 指定被AspectJ处理后的jar包存放的文件目录,

Epoxy学习

使用这个框架最主要就是学习如何创建我们的Models。

这个框架的设计思路就是创建Controller,Controller的作用就是用来安排我们的视图怎么展示,看他们的源码的时候在添加数据或者更新数据的时候,会调用buildModels方法。添加我们的视图模型,如果已经添加过了就不会添加到Controller的模型数组里面去。在Controller里面我们需要创建咱们的视图模型,也就是modelView,创建他的方式有三种。

  1. Custom Views
  2. DataBinding
  3. View Holders

创建模型视图:

From Custom Views

通过注解@ModelView

1
2
3
4
5
6
7
@ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
public class HeaderView extends LinearLayout {
@TextProp
public void setTitle(CharSequence text) {
titleView.setText(text);
}
}

这个方式创建视图我还不是很懂


From DataBinding

1
2
3
4
5
6
7
8
9
10
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="title" type="String" />
</data>

<TextView
android:layout_width="120dp"
android:layout_height="40dp"
android:text="@{title}" />
</layout>

然后我们只需要随便创建一个文件,然后使用注解 @EpoxyDataBindingLayouts


From ViewHolders

1
2
3
4
5
6
7
8
9
10
11
12
13
@EpoxyModelClass(layout = R.layout.header_view)
public abstract class HeaderModel extends EpoxyModelWithHolder<Holder> {
@EpoxyAttribute String title;

@Override
public void bind(Holder holder) {
holder.header.setText(title);
}

static class Holder extends BaseEpoxyHolder {
@BindView(R.id.text) TextView header;
}
}

Handler理解

Android是基于消息机制来运行的. 在 主线程也就是 ActivityThread 里,会给当前线程创建Looper,MessageQueue对象, 而 Handler也只是一个处理者.

消息类型分为:

  1. 同步屏障(同步消息): messge 的 target = null
  2. 异步消息 : message 的 isAsynchronous = true
  3. 同步消息

同步屏障有什么作用,作用就是让异步任务尽快被执行.在 Android里面被最直观的运用就是调用 requestLayout,在调用这个方法的时候,会先添加一个同步屏障,然后扔进去了一个异步任务用于view的测量布局绘制. 毕竟让用户看到页面响应是最重要的事情.

了解消息机制的原理,你还可以进行卡顿分析,这是如何做到的. 在ActivtiyThead里面,处理一个消息前会打印 <<<< 这个内容, 然后在消息处理结束会打印 >>>>> ,这个时候,你就可以通过监测两个打印的相差时间就能计算一个消息运行了多久了.

Gradle上传插件到仓库

准备工作:首先新建一个插件模块 plugin_module

把plugin_module里面的文件全部删了只留下src/main 和 build.gradle 文件。

  1. 修改build.gradle文件内容
1
2
3
4
5
6
7
apply plugin: 'groovy'  //必须
dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
}
  1. 写入我们自定义的插件MyDefaultTask.groovy
1
2
3
4
5
6
7
// 打包的数据 插件
class ReleaseInfoPlugin implements Plugin<Project> {
@Override
void apply(Project project) {

}
}
  1. 新建文件resources/META-INF/gradle-plugins

    创建文件 releaseinfo.properties 。这里需要注意这个文件名字就是我们以后通过apply pligun:”这里需要写入的插件名字”。

    文件内容:

    1
    implementation-class=ReleaseInfoPlugin // 插件类

至此准备工作做好了!


上传到本地Maven

插件上传:

在我们的插件模块的build.gradle加入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apply plugin: 'groovy'  //必须
apply plugin: 'maven-publish' // 上传maven的插件

dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
}


publishing {
publications {
maven(MavenPublication) {
groupId = com.librity.releaseplugin
artifactId = releaseInfo
version = 1.0

from components.java
}
}
}

同步完成功能,可以看到我们多了这些任务:

我们点击publishMaven…ToMavenLocal

任务执行完毕后我们会在这看到我们的maven库:


插件使用

在根目录的build.gradle写入:

1
2
3
4
5
6
7
repositories {
mavenLocal() // 表示需要依赖本地的maven仓库
}
dependencies {
// 这个就是我们刚才上传的插件 地址可以打开maven-metada0lical.xml看下都是怎么填的
classpath 'com.librity.releaseplugin:releaseInfo:1.0'
}

这些依赖写好后就可以在app的build.gradle里面进行使用了:

1
2
3
4
5
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'releaseinfo' // 刚才我们定义的插件名字
}

上传GithubMaven

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'  //必须
apply plugin: 'maven-publish' //必须添加这个插件才能上传

dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
}


publishing {
publications {
maven(MavenPublication) {
groupId = GROUDID
artifactId = ARTIFACTID
version = versions

from components.java
}
}
repositories {
maven {
def mavenLib = file(getProperty('mavenPath'))
url = "file://${mavenLib.absolutePath}"
}
}
}

在gradle.properties里面加上这几个参数:

1
2
3
4
mavenPath = ..
GROUDID = com.librity.releaseplugin
ARTIFACTID = releaseInfo
versions = 1.0

同步下工程:

然后我们把我们的工程上传到github上面。

使用

1
2
3
4
5
6
7
8
maven{
// 格式:'https://raw.githubusercontent.com/[username]/[工程名字]/[分支]
url 'https://raw.githubusercontent.com/librityYu/ReleaseInfoTask/maven'
}
dependencies {
// 我们的插件
classpath 'com.librity.releaseplugin:releaseInfo:1.0'
}

然后在我们要使用的module的build.gradle

1
2
3
plugins {
id 'releaseinfo'
}

打包时添加内容脚本插件

####需求:为每次打包生成内容数据信息

Gradle自定义插件有三种方式:https://zhuanlan.zhihu.com/p/158588813

1. 在build.gradle

直接在app目录下的build.gradle文件中写

1
2
3
4
5
6
7
8
9
10
11
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project target) {
println('MyPlugin执行了')
target.task("mytask"){
doLast {
println('MyPlugin中的task执行了')
}
}
}
}

然后在

app的build.gradle文件中引入插件:

apply plugin: MyPlugin

然后再控制台输入gradlew mytask

1
2
3
4
5
> Configure project :app
MyPlugin执行了

> Task :app:mytask
MyPlugin中的task执行了

使用这种方式可以很快的创建一个插件,缺点就是只能在当前的build.gradle使用,复用差。

2. 在buildSrc文件夹下

buildSrc是Gradle中的默认插件目录,编译的时候Gradle会自动识别这个目录,将其内的代码编译撑成插件。

在根目录新建一个文件叫buildSrc,然后在buildSrc文件新建build.gralde文件写入代码:

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 = "kotlin")
apply(plugin = "java")

buildscript {
repositories {
maven("https://maven.aliyun.com/repository/gradle-plugin")
maven("https://maven.aliyun.com/repository/central")
google()
}

dependencies {
classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20")
}
}

allprojects {
repositories {
maven("https://maven.aliyun.com/repository/gradle-plugin")
maven("https://maven.aliyun.com/repository/central")
google()
}
}

dependencies {
// 引入源代码
implementation("com.android.tools.build:gradle:4.2.2")
}

build.gradle文件内容写完后我们同步下功能,就可以在buildSrc文件里面看到文件.gradle和build这两个文件。现在我们在buildSrc下面新建文件src/main/groovy 和 src/main/resources/META-INF/gradle-plugins。

img1

然后在groovy文件加里新建我们的插件MyPlugin.groovy

1
2
3
4
5
6
7
8
9
10
11
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project target) {
println('buildSrc中MyPlugin执行了')
target.task("mytask"){
doLast {
println('buildSrc中MyPlugin中的task执行了')
}
}
}
}

写完插件怎么让编译器知道这是我们的插件呢?

  • 在main目录下的resources/META-INF/gradle-plugins目录新建propertites文件
  • properties文件的名字就是我们在build.gradle里面通过apply引入时候要写的名字,通常都是使用报名。com.yu.releaseInfo。然后文件内容写:implementation-class= com.yu.releaseInfo.MyPlugin

然后在app的build.gradle里面映入插件:

1
apply plugin :'com.yu.releaseInfo'

最后在控制台进行测试:

1
2
3
4
5
> Configure project :app
buildSrc中MyPlugin执行了

> Task :app:mytask
buildSrc中MyPlugin中的task执行了

第三种上传maven

上传maven这样所有人都可以使用咱么这个项目了。

可以参考我的另外一篇博客。 Gradle上传插件到仓库


进行实战

这里我们使用第二种方式,在buildSrc里面写我们的插件。

在groovy文件夹新建类 1. ReleaseInfoExtension.groovy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ReleaseInfoExtension {
String versionCode
String versionName
String versionInfo
String fileName
String afterTask = "assembleDebug"

ReleaseInfoExtension() {

}

@Override
String toString() {
"""
versionCode = ${versionCode}
versionName = ${versionName}
versionInfo = ${versionInfo}
fileName = ${fileName}
afterTask = ${afterTask}
"""
}
}

我们把我们的基础信息放在类里面。

2. 新建插件类ReleaseInfoPlugin.groocy

这个类用来创建我们的扩展参数和扩展方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.gradle.api.Plugin
import org.gradle.api.Project

// 打包的数据 插件
class ReleaseInfoPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
// 为工程添加扩展参数 releaseInfoYu 他的具体实现类是ReleaseInfoExtension
ReleaseInfoExtension releaseInfoExtension = project.extensions.create('releaseInfoYu', ReleaseInfoExtension)
// 为工程创建额外的task 名字叫releaseInfoTask 具体实现类ReleaseInfoTask
ReleaseInfoTask releaseInfoTask = project.tasks.create('releaseInfoTask', ReleaseInfoTask)
// 这一步骤是为了把我们自定义的releaseInfoTask添加到打包的task的之后自动执行
project.afterEvaluate {
project.tasks.findByName(releaseInfoExtension.afterTask).finalizedBy(releaseInfoTask)
}

}
}

然后在resources/META-INF/gradle.plugins 下新建文件 releaseinfo.properties。内容:

1
implementation-class=ReleaseInfoPlugin

同步下工程。

3. 在app的build.gradle引入我们的插件

apply plugin :’releaseinfo’

4. 配置打包信息

1
2
3
4
5
6
7
8
releaseInfoYu { re ->
project.getExtensions().android.applicationVariants.all { variant ->
re.versionCode = variant.getVersionCode()
re.versionName = variant.getVersionName()
}
versionInfo = "第一次提交的版本数据"
fileName = "release.xml"
}

注意我们首先要在app目录下新建一个空白文件release.xml

5. 完成写入xml的task任务

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
import groovy.xml.MarkupBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

import java.text.SimpleDateFormat

class ReleaseInfoTask extends DefaultTask{
ReleaseInfoTask(){
group = "libertyYu"
description = "update the release info"
}

@TaskAction
void doAction(){
updateInfo()
}

void updateInfo(){
String versionCodeMsg = project.extensions.releaseInfoYu.versionCode
String versionNameMsg = project.extensions.releaseInfoYu.versionName
String versionInfoMsg = project.extensions.releaseInfoYu.versionInfo
String fileName = project.extensions.releaseInfoYu.fileName
StringWriter stringWriter = new StringWriter()
MarkupBuilder xmlBuilder = new MarkupBuilder(stringWriter)
def file = project.file(fileName)
if (file == null && !destFile.exists()) {
file.createNewFile()
}
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = formatter.format(currentTime);

if (file.text != null && file.text.size() <= 0) {
//没有内容
xmlBuilder.releases {
release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
versionTime(dateString)
}
}
//直接写入
file.withWriter { writer -> writer.append(stringWriter.toString())
}
} else {
//已有其它版本内容
xmlBuilder.release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
versionTime(dateString)
}
//插入到最后一行前面
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { line, index ->
if (index != lengths) {
writer.append(line + '\r\n')
} else if (index == lengths) {
writer.append('\r\r\n' + stringWriter.toString() + '\r\n')
writer.append(lines.get(lengths))
}
}
}
}
}

}

测试

执行我们的assembleDebug任务后可以在release.xml文件看到如下内容:

1
2
3
4
5
6
7
8
<releases>
<release>
<versionCode>2</versionCode>
<versionName>1.0</versionName>
<versionInfo>第一次提交的版本数据</versionInfo>
<versionTime>2021-10-06 08:44:43</versionTime>
</release>
</releases>

Gradle知识学习

Cheer Up!

Gradle生命周期

  1. 初始化阶段

  2. 配置阶段

  3. 执行阶段

1. 初始化阶段

Gradle根据setting.gradle文件的配置为项目创建Project实例。

2. 配置阶段

​ Gradle构造一个模型表示任务,并参与构建中来。增量式构建决定我们的task是否需要运行。配置阶段完成后。整个build的project以及内部的Task关系就确定了。这个阶段非常适合为项目或指定task设置所需要的配置数据。配置阶段的实质为解析每个被加入构建项目的build.gradle脚本,比如通过apply方法引入插件,为插件扩展属性进行配置等等。。。。

​ 注意:项目的每一次构建的任何配置代码都可以被执行-即使你只运行gradle tasks。

3. 执行阶段

​ 在执行阶段,所有的task按照配置阶段规定好的顺序,一次执行。我们通常会在根据任务执行的生命周期去动态在合适的位置插入我们自己的运行期间执行的代码断。

​ 许多生命周期方法都被定义在了Gradle 和 Project方法里面。不要害怕使用生命周期钩子,它相反就是为了给开发者使用提供方便而设计出来的。


生命周期监听方法

​ 如果我们想在Gradle特定的阶段去Hook指定的任务,那么我们需要对如何监听生命周期的回调有一些了解。

​ Gradle和Project对象提供了一些方法供我们使用:

​ 生命周期监听的设置有两个方法:

 1. 实现一个特定的监听接口
 2. 提供一个用于在收到通知时执行的闭包。

Project提供的一些生命周期方法:

  • afterEvaluate(closure) , atferEvaluate(action)

  • beforeEvaluate(closure) , beforeEvaluate(action)

  • *Gradle**提供的一些生命周期方法:

  • afterProject(closure) , afterProject(action)

  • beforeProject(closure) , beforeProject(action)

  • buildFinished(closure) , buildFinished(action)

  • projectsEvaluated(closure) , projectsEvaluated(action)

  • projectsLoaded(closure) , projectsLoaded(action)

  • settingsEvaluated(closure) , settingsEvaluated(action)

  • addBuildListener(buildListener)

  • addListener(listener)

  • addProjectEvaluationListener(listener)

    可以看到每个方法都有两个不同参数,一个接收闭包,一个接收Action作为回调。注意,一些生命周期的方法只会在合适的位置上才会发生。

beforeEvaluate

beforeEvalute() 在project开始配置前调用。这个方法很容易误用,你要是直接在子模块的build.gradle中使用肯定是不会被调用的。应为project都没配置好所以也就什么事情也不会发生。这个代码块的添加只能放在父工程的build.gradle中:

1
2
3
4
5
this.project.subprojects { sub ->
sub.beforeEvaluate { project
println("### Evaluate before of "+project.path)
}
}

afterEvaluate

afaterEvaluate() 是一般比较常见的一个配置,只要在project配置成功均会被调用,不论在父模块还是在子模块。

1
2
3
project.afterEvaluate { pro->
println("### Evaluate after of " + pro.path)
}

afterProject

设置一个project配置完成后即执行的闭包或者action。

afterPeoject在配置参数失败后传入两个参数,前者当前project,后者显示失败信息。

1
2
3
4
5
6
7
this.getGradle().afterProject{project,projectState->
if(projectState.failure){
println("Evaluate afterProject of "+ project + "Falued")
}else{
println("Evaluate afterPeoject of "+ project + "Successed")
}
}

beforeProject

设置一个project配置前执行的闭包,子模块的该方法声明在root project中回调才会执行,root project的该方法声明在setting.gradle中才执行。

1
2
3
gradle.beforeProject { p ->
println("Evaluation beforeProject"+p)
}

buildFinished

构建结束时的回调,此时所有的任务都执行完毕,一个构建结果的对象BuildResult作为参数传递给闭包。

1
2
3
gradle.buildFinished { r ->
println("buildFinished "+r.failure)
}

projectEvaluated

所有的peoject都配置完成后的回调,此时,所有的project都配置完毕,准备开始生成task图。gradle对象作为参数传递给闭包。

1
2
3
gradle.projectsEvaluated {gradle ->
println("projectsEvaluated")
}

projectsLoaded

当setting中的所有project都创建好时执行闭包回调,gradle对象会作为参数传递给闭包。这个方法比较特殊,只有声明在适当的位置才会发生,如果这个声明周期挂接闭包声明在build.gradle文件,那么将不会发生这个事件。应为项目创建发生在初始阶段。放在setting.gradle中是可以执行的。

1
2
3
gradle.projectsLoaded {gradle ->
println("@@@@@@@ projectsLoaded")
}

settingsEvaluated

当 settings.gradle 加载并配置完毕后执行闭包回调,setting对象已经配置好并且准备开始加载构建 project。这个回调在 build.gradle 中声明也是不起作用的,在 settings.gradle 中声明是可以的。

1
2
3
gradle.settingsEvaluated {
println("@@@@@@@ settingsEvaluated")
}

前面说过设置监听还有两个方法,通过接口监听。

addProjectEvaluationListener

1
2
3
4
5
6
7
8
9
10
gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
@Override
void beforeEvaluate(Project project) {
println " add project evaluation lister beforeEvaluate,project path is: "+project
}
@Override
void afterEvaluate(Project project, ProjectState state) {
println " add project evaluation lister afterProject,project path is:"+project
}
})

addListener

添加一个实现来listener接口的对象到build

addBuildListener

添加一个BuildListener对象到Build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println("### buildStarted")
}
@Override
void settingsEvaluated(Settings settings) {
println("### settingsEvaluated")
}
@Override
void projectsLoaded(Gradle gradle) {
println("### projectsLoaded")
}
@Override
void projectsEvaluated(Gradle gradle) {
println("### projectsEvaluated")
}
@Override
void buildFinished(BuildResult result) {
println("### buildFinished")
}
})

Task执行图

在配置时,Gradle决定在执行阶段要运行的task顺序,他们依赖关系的内部结构被建模为一个有向无环图。我们称之为task执行图。它可以用TaskExecutionGraph来表示。可以通过gradle.taskGraph来获取。在TaskExecutationGraph中也可以设置一些Task生命周期回到。

  • addTaskExecutationGraphListener(TaskExecutionGraphListener listener)
  • addTaskExecutionListener(TaskExecutionListener listener)
  • afterTask(Action action),afterTask(Closure closure)
  • beforeTask(Action action),beforeTask(Closure closure)
  • whenReady(Action action),whenReady(Closure closure)

addTaskExecutionGraphListener

添加 task 执行图的监听器,当执行图配置好会执行通知。

1
2
3
4
5
6
gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
println("@@@ gradle.taskGraph.graphPopulated ")
}
})

addTaskExecutionListener

添加 task 执行监听器,当 task 执行前或者执行完毕会执行回调发出通知。

1
2
3
4
5
6
7
8
9
10
gradle.taskGraph.addTaskExecutionListener(new TaskExecutionListener() {
@Override
void beforeExecute(Task task) {
println("@@@ gradle.taskGraph.beforeTask "+task)
}
@Override
void afterExecute(Task task, TaskState state) {
println("@@@ gradle.taskGraph.afterTask "+task)
}
})

afterTask

1
2
3
gradle.taskGraph.afterTask { task ->
println("### gradle.taskGraph.afterTask "+task)
}

beforeTask

1
2
3
gradle.taskGraph.beforeTask { task ->
println("### gradle.taskGraph.beforeTask "+task)
}

whenReady

设置一个 task 执行图准备好后的闭包或者回调方法。
该 taskGrahp 作为参数传递给闭包。

1
2
3
gradle.taskGraph.whenReady { taskGrahp ->
println("@@@ gradle.taskGraph.whenReady ")
}

生命周期执行顺序

我们通过在生命周期回调中添加打印的方法来看下顺序:

1
2
3
4
5
6
7
8
9
task hello {
doFirst {
println '*** task hello doFirst'
}
doLast {
println '*** task hello doLast'
}
println '*** config task hello'
}

为了保证生命周期的各个回调方法都被执行,我们在 settings.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
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
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println("### gradle.buildStarted")
}

@Override
void settingsEvaluated(Settings settings) {
println("### gradle.settingsEvaluated")
}

@Override
void projectsLoaded(Gradle gradle) {
println("### gradle.projectsLoaded")
}

@Override
void projectsEvaluated(Gradle gradle) {
println("### gradle.projectsEvaluated")
}

@Override
void buildFinished(BuildResult result) {
println("### gradle.buildFinished")
}
})

gradle.afterProject { project,projectState ->
if(projectState.failure){
println "### gradld.afterProject "+project+" FAILED"
} else {
println "### gradle.afterProject "+project+" succeeded"
}
}

gradle.beforeProject { p ->
println("### gradle.beforeProject "+p)
}

gradle.allprojects(new Action<Project>() {
@Override
void execute(Project project) {
project.beforeEvaluate { project
println "### project.beforeEvaluate "+project
}
project.afterEvaluate { pro ->
println("### project.afterEvaluate " + pro)
}
}
})



gradle.taskGraph.addTaskExecutionListener(new TaskExecutionListener() {
@Override
void beforeExecute(Task task) {
if (task.name.equals("hello")){
println("@@@ gradle.taskGraph.beforeTask "+task)
}
}

@Override
void afterExecute(Task task, TaskState state) {
if (task.name.equals("hello")){
println("@@@ gradle.taskGraph.afterTask "+task)
}
}
})

gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
println("@@@ gradle.taskGraph.graphPopulated ")
}
})

gradle.taskGraph.whenReady { taskGrahp ->
println("@@@ gradle.taskGraph.whenReady ")
}

执行 task info

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
./gradlew hello
### gradle.settingsEvaluated
### gradle.projectsLoaded

> Configure project :
### gradle.beforeProject root project 'TestSomething'
### project.beforeEvaluate root project 'TestSomething'
### gradle.afterProject root project 'TestSomething' succeeded
### project.afterEvaluate root project 'TestSomething'

> Configure project :app
### gradle.beforeProject project ':app'
### project.beforeEvaluate project ':app'
*** config task hello
### gradle.afterProject project ':app' succeeded
### project.afterEvaluate project ':app'

> Configure project :common
### gradle.beforeProject project ':common'
### project.beforeEvaluate project ':common'
### gradle.afterProject project ':common' succeeded
### project.afterEvaluate project ':common'

### gradle.projectsEvaluated
@@@ gradle.taskGraph.graphPopulated
@@@ gradle.taskGraph.whenReady

> Task :app:hello
@@@ gradle.taskGraph.beforeTask task ':app:hello'
*** task hello doFirst
*** task hello doLast
@@@ gradle.taskGraph.afterTask task ':app:hello'

BUILD SUCCESSFUL in 1s
1 actionable task: 1 execu

因此,生命周期回调的执行顺序是:

  • gradle.settingsEvaluated->
    gradle.projectsLoaded->
    gradle.beforeProject->
    project.beforeEvaluate->
    gradle.afterProject->
    project.afterEvaluate->
    gradle.projectsEvaluated->
    gradle.taskGraph.graphPopulated->
    gradle.taskGraph.whenReady->
    gradle.buildFinished

非常好的博客:https://www.heqiangfly.com/2016/03/18/development-tool-gradle-lifecycle/