0%

2016年即将过去。在此对于过去一年自己所做的进行一次简单的总结。

满意的地方

  • github坚持更新,有时间就会练习算法,对常用数据结构和算法进行了一次全面的复习
  • 看了很多书。见识增加了很多。包括并不仅局限于下列书籍。但是,看得多,忘得也多,还是得多实践。
    • Head First
    • Java 并发编程实战
    • Effective JavaScript
    • 设计模式
    • 深入理解java虚拟机高级特性
  • 坚持写博客。
  • 技能树多项开发,主技能Android开发, Java,其余辅助技能JavaScript,PythonGroovy, Kotlin都多多少少有所涉猎。对于跨平台开发积累了一定的经验(RN)。
  • 对于Android源码有进一步的理解
  • 分析很多源码,但很多又忘了(尴尬)
  • 开始使用gitbook来做总结

不满意的地方

  • 书虽然看得很多,也学到很多新的知识,但是暂时没有可以应用的地方
  • 对于大型项目的整体把控能力还有需要提高。
  • 做了很多项目,但其实从项目本身获取到的技术提升并不多
  • 生活上并没有什么大的改善,依然是死宅
  • 分析表达能力需要接着提高

2017年的期待

  • 继续拓展,深挖自己的技术,希望能够将自己所学到的技术都能应用到真实项目中
  • 希望能够去一趟日本
  • 能够接触更多与机器学习相关的东西
  • 坚持写博客,坚持逛github
  • 每天再晚也要学一会
  • 学习更多关于多媒体开发方面的知识
  • 学习K(Keep)P(Problem)T(Try)总结
  • 架构/中间件的学习

Animator

其是Android3.0之后加入的作用于对象属性的动画,Animator是所有属性动画的基类,其下有ValueAnimator,AnimatorSet,由ValueAnimator再拓展出ObjectAnimator,TimeAnimator

其整体结构图

ValueAnimator

为作用对象提供简单的能够计算动画时间和值得时间引擎,它运行在自定义的Handler里面以确保所有的属性改变都会在UI线程。主要API:

1
2
3
4
5
ValueAnimator ofInt(int... values) 
ValueAnimator ofArgb(int... values) //颜色值变化
ValueAnimator ofFloat(float... values)
ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)
ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

这些方法里面都是进行值得初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}

都是重新封装转化成PropertyValuesHolder,这是封装动画属性和值得对象,之后会详细介绍

1
2
3
4
5
public void setEvaluator(TypeEvaluator value) {
if (value != null && mValues != null && mValues.length > 0) {
mValues[0].setEvaluator(value);
}
}

设置估值器,最后也是由PropertyValuesHolder保存

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
//参数代表是否反转动画
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mPlayingBackwards = playBackwards;
//需要反转动画
if (playBackwards && mSeekFraction != -1) {
if (mSeekFraction == 0 && mCurrentIteration == 0) {
// special case: reversing from seek-to-0 should act as if not seeked at all
mSeekFraction = 0;
} else if (mRepeatCount == INFINITE) {
mSeekFraction = 1 - (mSeekFraction % 1);
} else {
mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
}
mCurrentIteration = (int) mSeekFraction;
mSeekFraction = mSeekFraction % 1;
}
if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
(mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
// if we were seeked to some other iteration in a reversing animator,
// figure out the correct direction to start playing based on the iteration
if (playBackwards) {
mPlayingBackwards = (mCurrentIteration % 2) == 0;
} else {
mPlayingBackwards = (mCurrentIteration % 2) != 0;
}
}
int prevPlayingState = mPlayingState;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
mPaused = false;
updateScaledDuration(); // in case the scale factor has changed since creation time
//创建AnimatorHandler
AnimationHandler animationHandler = getOrCreateAnimationHandler();
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
if (prevPlayingState != SEEKED) {
setCurrentPlayTime(0);
}
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
}
//开始动画
animationHandler.start();
}

开始动画的逻辑其实很简单,主要是判断动画是否需要反转。最终动画的更新交给AnimationHandler

AnimationHandler

处理所有运行中的动画的定时脉冲,这个机制能确保所有的动画都运行在UI线程,其使用Choreograhper(用于调整动画输入和绘制的时机)来执行周期的回调

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
private AnimationHandler() {
mChoreographer = Choreographer.getInstance();
}

public void start() {
scheduleAnimation();
}

private void scheduleAnimation() {
if (!mAnimationScheduled) {
//动画未执行过,由Choreographer回调执行
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}

final Runnable mAnimate = new Runnable() {
@Override
public void run() {
mAnimationScheduled = false;
doAnimationFrame(mChoreographer.getFrameTime());
}
};

void doAnimationFrame(long frameTime) {
//参数代表执行哪一帧动画
mLastFrameTime = frameTime;

// mPendingAnimations holds any animations that have requested to be started
// We're going to clear mPendingAnimations, but starting animation may
// cause more to be added to the pending list (for example, if one animation
// starting triggers another starting). So we loop until mPendingAnimations
// is empty.
while (mPendingAnimations.size() > 0) {
//如果有等待执行的动画,清除并优先执行这些动画
ArrayList<ValueAnimator> pendingCopy =
(ArrayList<ValueAnimator>) mPendingAnimations.clone();
mPendingAnimations.clear();
int count = pendingCopy.size();
for (int i = 0; i < count; ++i) {
ValueAnimator anim = pendingCopy.get(i);
// If the animation has a startDelay, place it on the delayed list
if (anim.mStartDelay == 0) {
anim.startAnimation(this);
} else {
mDelayedAnims.add(anim);
}
}
}

// Next, process animations currently sitting on the delayed queue, adding
// them to the active animations if they are ready
//将延时队列中的动画加入到准备完成队列,开始执行动画,清空队列
int numDelayedAnims = mDelayedAnims.size();
for (int i = 0; i < numDelayedAnims; ++i) {
ValueAnimator anim = mDelayedAnims.get(i);
if (anim.delayedAnimationFrame(frameTime)) {
mReadyAnims.add(anim);
}
}
int numReadyAnims = mReadyAnims.size();
if (numReadyAnims > 0) {
for (int i = 0; i < numReadyAnims; ++i) {
ValueAnimator anim = mReadyAnims.get(i);
anim.startAnimation(this);
anim.mRunning = true;
mDelayedAnims.remove(anim);
}
mReadyAnims.clear();
}

// Now process all active animations. The return value from animationFrame()
// tells the handler whether it should now be ended
int numAnims = mAnimations.size();
for (int i = 0; i < numAnims; ++i) {
mTmpAnimations.add(mAnimations.get(i));
}
for (int i = 0; i < numAnims; ++i) {
ValueAnimator anim = mTmpAnimations.get(i);
if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
mEndingAnims.add(anim);
}
}
mTmpAnimations.clear();
if (mEndingAnims.size() > 0) {
for (int i = 0; i < mEndingAnims.size(); ++i) {
mEndingAnims.get(i).endAnimation(this);
}
mEndingAnims.clear();
}

// Schedule final commit for the frame.
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);

// If there are still active or delayed animations, schedule a future call to
// onAnimate to process the next frame of the animations.
if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
scheduleAnimation();
}
}

流程图

ValueAnimator

插值器

插值器是描述动画变化的频率。其主要方法是

1
2
//输入动画基准值,得到计算后的某个时刻的值
float getInterpolation(float input);

类关系图:
interpolator

系统已经为开发者准备了一些默认的插值器,我们来简单看下这些插值器的算法

LinearInterpolator(线性插值器)
1
2
3
4
//输入既是输出
public float getInterpolation(float input) {
return input;
}

AccelerateDecelerateInterpolator(加减速插值器)
1
2
3
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

其特性是在动画开始和结束阶段变化缓慢,中间阶段变化迅速

AccelerateInterpolator(加速插值器)
1
2
3
4
5
6
7
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}


其特点是动画变化速度一直加快,有个加速因子,默认为1。

AnticipateInterpolator
1
2
3
4
public float getInterpolation(float t) {
// a(t) = t * t * ((tension + 1) * t - tension)
return t * t * ((mTension + 1) * t - mTension);
}

AnticipateOvershootInterpolator
1
2
3
4
5
6
7
8
public float getInterpolation(float t) {
// a(t, s) = t * t * ((s + 1) * t - s)
// o(t, s) = t * t * ((s + 1) * t + s)
// f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
// f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
}

还有很多别的插值器。这里就不多加赘述了。只要将相应的算法写入getInterpolation中即可

使用的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void animateValue(float fraction) {
//计算动画值
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}

TypeEvaluator(估值器)

根据当前的动画频率,起始值,结束值计算当前值

1
public T evaluate(float fraction, T startValue, T endValue);
ArgbEvaluator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;

int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;

return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}

算法是线性函数,主要因子fraction来自插值器的计算, y= a + kx

IntEvaluator
1
2
3
4
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}

估值器主要实在KeyFrames中使用,而KeyFrames存在PropertyValuesHolder当中,最终Choreographer来更新动画使用

Choreograhper

协调动画,输入和绘制的时间。其会收到来自显示系统的定时脉冲,然后安排工作作为下一帧渲染的一部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
private Choreographer(Looper looper) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;

mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
  • 初始化,创建FrameHandler,创建回调队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}

private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}

synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
  • 将回调加入回调队列,如果没有延时,锁定当前帧,如果使用VSYNC,诺当前运行在相同的线程,直接通过FrameDisplayEventReceiver要求同步,其他情况发送消息到Handler中执行。Handler处理三种消息,MSG_DO_FRAME,MSG_DO_SCHEDULE_VSYNC, MSG_DO_SCHEDULE_CALLBACK
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
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;

// Update the frame time if necessary when committing the frame.
// We only update the frame time if we are more than 2 frames late reaching
// the commit phase. This ensures that the frame time which is observed by the
// callbacks will always increase from one frame to the next and never repeat.
// We never want the next frame's starting frame time to end up being less than
// or equal to the previous frame's commit frame time. Keep in mind that the
// next frame has most likely already been scheduled by now so we play it
// safe by ensuring the commit time is always at least one frame behind.
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
//执行所有的回调
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
  • MSG_DO_FRAME:判断当前帧时间,低于上次绘制时间,要求同步。否则调用doCallbacks,执行回调队列中的回调
流程图

ObjectAnimator

支持对目标对象的属性做动画,对ValueAnimator的拓展

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
//可以直接设置target,propertyName
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setIntValues(values);
return anim;
}

public void setTarget(@Nullable Object target) {
final Object oldTarget = getTarget();
//老的目标与当前目标不同,取消。设置弱引用目标对象
if (oldTarget != target) {
if (isStarted()) {
cancel();
}
mTarget = target == null ? null : new WeakReference<Object>(target);
// New target should cause re-initialization prior to starting
mInitialized = false;
}
}

public void start() {
// See if any of the current active/pending animators need to be canceled
AnimationHandler handler = sAnimationHandler.get();
if (handler != null) {
int numAnims = handler.mAnimations.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
//相同的目标和属性,取消之前的动画
anim.cancel();
}
}
}

//等待的动画也一样
numAnims = handler.mPendingAnimations.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}

//延时的动画也一样
numAnims = handler.mDelayedAnims.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
}
if (DBG) {
Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvh = mValues[i];
Log.d(LOG_TAG, " Values[" + i + "]: " +
pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
pvh.mKeyframes.getValue(1));
}
}
super.start();
}

void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up.
cancel();
return;
}

super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
}

public void setupStartValues() {
//初始化动画
initAnimation();

final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupStartValue(target);
}
}
}

void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//初始化getter setter
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}
  • ObjectAnimatorValueAnimator多了targetProperty,这些信息都在PropertyValuesHolder,必须要要有getter/setter才能更新目标对象

PropertyValuesHolder 处理setter, getter的寻找

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
void setupSetterAndGetter(Object target) {
mKeyframes.invalidateCache();
//属性不为空时,从Propery拿值,并设置到keyFrame
if (mProperty != null) {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (testValue == null) {
testValue = convertBack(mProperty.get(target));
}
kf.setValue(testValue);
kf.setValueWasSetOnStart(true);
}
}
return;
} catch (ClassCastException e) {
Log.w("PropertyValuesHolder","No such property (" + mProperty.getName() +
") on target object " + target + ". Trying reflection instead");
mProperty = null;
}
}
// We can't just say 'else' here because the catch statement sets mProperty to null.
//如果property为空,从目标对象中寻找getter setter
if (mProperty == null) {
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);
}
List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
//getter没找到,再次寻找
setupGetter(targetClass);
//依然没找到
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
try {
Object value = convertBack(mGetter.invoke(target));
//找到getter,将值传给keyFrame
kf.setValue(value);
kf.setValueWasSetOnStart(true);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
}
}

void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
//setter不为空,会调用目标对象的setter
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
  • PropertyValuesHolder主要持有动画的相关属性信息,包括关键帧集,插值器,估值器等

KeyFrame

动画关键帧,保存动画某一帧的时间/值。被ValueAnimator使用来计算两个值之间的动画值。PropertyValuesHolder保存了KeyFrames其保存了多个KeyFrame。简单来说KeyFrame就是我们做动画的起始值,中间值,结束值,这些都是由开发者定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframes = KeyframeSet.ofInt(values);
}

keyFrameSet:
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
//当只设置了一个值时,会有两个关键帧,起始关键这和结束关键帧
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else {
//起始关键帧
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
//计算每个阶段的关键帧
keyframes[i] =
(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
  • 动画至少会包含两个关键帧,一个起始关键帧,一个结束关键帧。中间有多少关键帧由开发者自行定义

AnimatorSet

允许同时播放多个动画或者定义动画的播放顺序

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
118
119
120
121
122
123
124
125
126
public void start() {
mTerminated = false;
mStarted = true;
mPaused = false;

for (Node node : mNodes) {
//禁止异步运行node.animation.setAllowRunningAsynchronously(false);
}

//设置所有动画的长度
if (mDuration >= 0) {
// If the duration was set on this AnimatorSet, pass it along to all child animations
for (Node node : mNodes) {
// TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
// insert "play-after" delays
node.animation.setDuration(mDuration);
}
}
//设置所有动画的插值器
if (mInterpolator != null) {
for (Node node : mNodes) {
node.animation.setInterpolator(mInterpolator);
}
}
// First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
//对所有的动画节点进行排序
sortNodes();

//清除老的动画监听
int numSortedNodes = mSortedNodes.size();
for (int i = 0; i < numSortedNodes; ++i) {
Node node = mSortedNodes.get(i);
// First, clear out the old listeners
ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
if (oldListeners != null && oldListeners.size() > 0) {
final ArrayList<AnimatorListener> clonedListeners = new
ArrayList<AnimatorListener>(oldListeners);

for (AnimatorListener listener : clonedListeners) {
if (listener instanceof DependencyListener ||
listener instanceof AnimatorSetListener) {
node.animation.removeListener(listener);
}
}
}
}

// nodesToStart holds the list of nodes to be started immediately. We don't want to
// start the animations in the loop directly because we first need to set up
// dependencies on all of the nodes. For example, we don't want to start an animation
// when some other animation also wants to start when the first animation begins.
final ArrayList<Node> nodesToStart = new ArrayList<Node>();
for (int i = 0; i < numSortedNodes; ++i) {
Node node = mSortedNodes.get(i);
if (mSetListener == null) {
mSetListener = new AnimatorSetListener(this);
}
//动画节点没有依赖别的节点,添加到即将开始的队列中
if (node.dependencies == null || node.dependencies.size() == 0) {
nodesToStart.add(node);
} else {
//如果有依赖的话,添加依赖监听,并把依赖添加到临时依赖链中
int numDependencies = node.dependencies.size();
for (int j = 0; j < numDependencies; ++j) {
Dependency dependency = node.dependencies.get(j);
dependency.node.animation.addListener(
new DependencyListener(this, node, dependency.rule));
}
node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
}
node.animation.addListener(mSetListener);
}
// Now that all dependencies are set up, start the animations that should be started.
//开始动画
if (mStartDelay <= 0) {
for (Node node : nodesToStart) {
node.animation.start();
mPlayingSet.add(node.animation);
}
} else {
mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
mDelayAnim.setDuration(mStartDelay);
mDelayAnim.addListener(new AnimatorListenerAdapter() {
boolean canceled = false;
public void onAnimationCancel(Animator anim) {
canceled = true;
}
public void onAnimationEnd(Animator anim) {
if (!canceled) {
int numNodes = nodesToStart.size();
for (int i = 0; i < numNodes; ++i) {
Node node = nodesToStart.get(i);
node.animation.start();
mPlayingSet.add(node.animation);
}
}
mDelayAnim = null;
}
});
mDelayAnim.start();
}
//动画开始回调
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this);
}
}
//如果没有动画,动画结束回调
if (mNodes.size() == 0 && mStartDelay == 0) {
// Handle unusual case where empty AnimatorSet is started - should send out
// end event immediately since the event will not be sent out at all otherwise
mStarted = false;
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationEnd(this);
}
}
}
}
  • 流程:
    1. 首先禁止异步运行动画,设置所有动画的时间和插值器。
    2. 对所有的动画进行排序(根据依赖关系),取消所有老的监听。如果动画没有依赖,加入即将开始的动画队列,否则创建依赖监听,创建临时依赖
    3. 没有延时,启动动画。否则创建延时动画,延时动画结束后,后续动画开始
    4. 动画开始监听回调,如果没有动画,动画结束监听回调
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
动画排序
private void sortNodes() {
if (mNeedsSort) {
mSortedNodes.clear();
ArrayList<Node> roots = new ArrayList<Node>();
int numNodes = mNodes.size();
//没有依赖的动画变成根节点
for (int i = 0; i < numNodes; ++i) {
Node node = mNodes.get(i);
if (node.dependencies == null || node.dependencies.size() == 0) {
roots.add(node);
}
}
ArrayList<Node> tmpRoots = new ArrayList<Node>();
while (roots.size() > 0) {
//遍历每个根节点
int numRoots = roots.size();
for (int i = 0; i < numRoots; ++i) {
Node root = roots.get(i);
mSortedNodes.add(root);
if (root.nodeDependents != null) {
int numDependents = root.nodeDependents.size();
//根节点有依赖
for (int j = 0; j < numDependents; ++j) {
Node node = root.nodeDependents.get(j);
//将此根节点从依赖中移除node.nodeDependencies.remove(root);
if (node.nodeDependencies.size() == 0) {
tmpRoots.add(node);
}
}
}
}
roots.clear();
roots.addAll(tmpRoots);
tmpRoots.clear();
}
mNeedsSort = false;
if (mSortedNodes.size() != mNodes.size()) {
throw new IllegalStateException("Circular dependencies cannot exist"
+ " in AnimatorSet");
}
} else {
// Doesn't need sorting, but still need to add in the nodeDependencies list
// because these get removed as the event listeners fire and the dependencies
// are satisfied
int numNodes = mNodes.size();
for (int i = 0; i < numNodes; ++i) {
Node node = mNodes.get(i);
if (node.dependencies != null && node.dependencies.size() > 0) {
int numDependencies = node.dependencies.size();
for (int j = 0; j < numDependencies; ++j) {
Dependency dependency = node.dependencies.get(j);
if (node.nodeDependencies == null) {
node.nodeDependencies = new ArrayList<Node>();
}
if (!node.nodeDependencies.contains(dependency.node)) {
node.nodeDependencies.add(dependency.node);
}
}
}
// nodes are 'done' by default; they become un-done when started, and done
// again when ended
node.done = false;
}
}
}
```
* 动画排序规则:
* 如果需要排序
1. 没有依赖的动画变成根节点.
2. 循环遍历所有根节点,将根节点从依赖节点中移除
* 不需要排序
1. 添加回依赖

##### Node
代表动画和其依赖。其包含一个动画信息,节点动画依赖集合,独立节点集合

##### Dependency
动画节点与节点之间的依赖描述

```java
static final int WITH = 0; // dependent node must start with this dependency node
static final int AFTER = 1; // dependent node must start when this dependency node finishes
  • 包含两个规则,一起,后续
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

//多个动画一起播放,添加动画``WITH``依赖
public void playTogether(Animator... items) {
if (items != null) {
mNeedsSort = true;
Builder builder = play(items[0]);
for (int i = 1; i < items.length; ++i) {
builder.with(items[i]);
}
}
}

public Builder with(Animator anim) {
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
node.addDependency(dependency);
return this;
}

//多个动画串行播放
public void playSequentially(Animator... items) {
if (items != null) {
mNeedsSort = true;
if (items.length == 1) {
play(items[0]);
} else {
mReversible = false;
for (int i = 0; i < items.length - 1; ++i) {
play(items[i]).before(items[i+1]);
}
}
}
}
//在某个动画之前播放,添加``AFTER``依赖
public Builder before(Animator anim) {
mReversible = false;
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
node.addDependency(dependency);
return this;
}

LayoutTransition

ViewGroup布局发生改变时播放动画。这个动画的核心概念是两个类型的改变会引起四种不同的动画运行。这两种改变时appearingdisappearing

1
2
ViewGroup
setLayoutTransition(LayoutTransition transition)

动画类型,用一个字节表示

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

private static final int FLAG_APPEARING = 0x01;
private static final int FLAG_DISAPPEARING = 0x02;
private static final int FLAG_CHANGE_APPEARING = 0x04;
private static final int FLAG_CHANGE_DISAPPEARING = 0x08;
private static final int FLAG_CHANGING = 0x10;


private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
FLAG_APPEARING | FLAG_DISAPPEARING;

private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
Animator baseAnimator, final long duration, final View child) {

// If we already have a listener for this child, then we've already set up the
// changing animation we need. Multiple calls for a child may occur when several
// add/remove operations are run at once on a container; each one will trigger
// changes for the existing children in the container.
if (layoutChangeListenerMap.get(child) != null) {
return;
}

// Don't animate items up from size(0,0); this is likely because the objects
// were offscreen/invisible or otherwise measured to be infinitely small. We don't
// want to see them animate into their real size; just ignore animation requests
// on these views
if (child.getWidth() == 0 && child.getHeight() == 0) {
return;
}

// Make a copy of the appropriate animation
final Animator anim = baseAnimator.clone();

// Set the target object for the animation
anim.setTarget(child);

// A ObjectAnimator (or AnimatorSet of them) can extract start values from
// its target object
anim.setupStartValues();

// If there's an animation running on this view already, cancel it
Animator currentAnimation = pendingAnimations.get(child);
if (currentAnimation != null) {
currentAnimation.cancel();
pendingAnimations.remove(child);
}
// Cache the animation in case we need to cancel it later
pendingAnimations.put(child, anim);

// For the animations which don't get started, we have to have a means of
// removing them from the cache, lest we leak them and their target objects.
// We run an animator for the default duration+100 (an arbitrary time, but one
// which should far surpass the delay between setting them up here and
// handling layout events which start them.
ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
setDuration(duration + 100);
pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
pendingAnimations.remove(child);
}
});
pendingAnimRemover.start();

// Add a listener to track layout changes on this view. If we don't get a callback,
// then there's nothing to animate.
final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {

// Tell the animation to extract end values from the changed object
anim.setupEndValues();
if (anim instanceof ValueAnimator) {
boolean valuesDiffer = false;
ValueAnimator valueAnim = (ValueAnimator)anim;
PropertyValuesHolder[] oldValues = valueAnim.getValues();
for (int i = 0; i < oldValues.length; ++i) {
PropertyValuesHolder pvh = oldValues[i];
if (pvh.mKeyframes instanceof KeyframeSet) {
KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes;
if (keyframeSet.mFirstKeyframe == null ||
keyframeSet.mLastKeyframe == null ||
!keyframeSet.mFirstKeyframe.getValue().equals(
keyframeSet.mLastKeyframe.getValue())) {
valuesDiffer = true;
}
} else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) {
valuesDiffer = true;
}
}
if (!valuesDiffer) {
return;
}
}

long startDelay = 0;
switch (changeReason) {
case APPEARING:
startDelay = mChangingAppearingDelay + staggerDelay;
staggerDelay += mChangingAppearingStagger;
if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
anim.setInterpolator(mChangingAppearingInterpolator);
}
break;
case DISAPPEARING:
startDelay = mChangingDisappearingDelay + staggerDelay;
staggerDelay += mChangingDisappearingStagger;
if (mChangingDisappearingInterpolator !=
sChangingDisappearingInterpolator) {
anim.setInterpolator(mChangingDisappearingInterpolator);
}
break;
case CHANGING:
startDelay = mChangingDelay + staggerDelay;
staggerDelay += mChangingStagger;
if (mChangingInterpolator != sChangingInterpolator) {
anim.setInterpolator(mChangingInterpolator);
}
break;
}
anim.setStartDelay(startDelay);
anim.setDuration(duration);

Animator prevAnimation = currentChangingAnimations.get(child);
if (prevAnimation != null) {
prevAnimation.cancel();
}
Animator pendingAnimation = pendingAnimations.get(child);
if (pendingAnimation != null) {
pendingAnimations.remove(child);
}
// Cache the animation in case we need to cancel it later
currentChangingAnimations.put(child, anim);

parent.requestTransitionStart(LayoutTransition.this);

// this only removes listeners whose views changed - must clear the
// other listeners later
child.removeOnLayoutChangeListener(this);
layoutChangeListenerMap.remove(child);
}
};
// Remove the animation from the cache when it ends
anim.addListener(new AnimatorListenerAdapter() {

@Override
public void onAnimationStart(Animator animator) {
if (hasListeners()) {
ArrayList<TransitionListener> listeners =
(ArrayList<TransitionListener>) mListeners.clone();
for (TransitionListener listener : listeners) {
listener.startTransition(LayoutTransition.this, parent, child,
changeReason == APPEARING ?
CHANGE_APPEARING : changeReason == DISAPPEARING ?
CHANGE_DISAPPEARING : CHANGING);
}
}
}

@Override
public void onAnimationCancel(Animator animator) {
child.removeOnLayoutChangeListener(listener);
layoutChangeListenerMap.remove(child);
}

@Override
public void onAnimationEnd(Animator animator) {
currentChangingAnimations.remove(child);
if (hasListeners()) {
ArrayList<TransitionListener> listeners =
(ArrayList<TransitionListener>) mListeners.clone();
for (TransitionListener listener : listeners) {
listener.endTransition(LayoutTransition.this, parent, child,
changeReason == APPEARING ?
CHANGE_APPEARING : changeReason == DISAPPEARING ?
CHANGE_DISAPPEARING : CHANGING);
}
}
}
});

child.addOnLayoutChangeListener(listener);
// cache the listener for later removal
layoutChangeListenerMap.put(child, listener);
}

  • 主要流程
    1. 启动一个延时动画来移除为执行的动画
    2. 监听布局变化,如果是ValueAnimator,判断起始帧和结束帧是否一样,如果一样表示结束.否则根据布局改变的原因设置动画类型,开始动画
    3. 移除缓存动画

ViewPropertyAnimator

自动优化View对象的属性动画。如果想做1个或者两个属性动画,使用ObjectAnimator,其也可以设置属性值,合理的刷新视图。但是如果有多个属性要同时动画,或者想要使用更简洁的语法,那么可以使用ViewPropertyAnimator

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
private void animateProperty(int constantName, float toValue) {
float fromValue = getValue(constantName);
float deltaValue = toValue - fromValue;
animatePropertyBy(constantName, fromValue, deltaValue);
}

private void animatePropertyBy(int constantName, float startValue, float byValue) {
// First, cancel any existing animations on this property
if (mAnimatorMap.size() > 0) {
Animator animatorToCancel = null;
Set<Animator> animatorSet = mAnimatorMap.keySet();
for (Animator runningAnim : animatorSet) {
PropertyBundle bundle = mAnimatorMap.get(runningAnim);
if (bundle.cancel(constantName)) {
// property was canceled - cancel the animation if it's now empty
// Note that it's safe to break out here because every new animation
// on a property will cancel a previous animation on that property, so
// there can only ever be one such animation running.
if (bundle.mPropertyMask == NONE) {
// the animation is no longer changing anything - cancel it
animatorToCancel = runningAnim;
break;
}
}
}
if (animatorToCancel != null) {
animatorToCancel.cancel();
}
}

NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
mPendingAnimations.add(nameValuePair);
mView.removeCallbacks(mAnimationStarter);
mView.postOnAnimation(mAnimationStarter);
}
  • 上述流程, 做属性动画都会调用animateProperty,其内部会调用animatePropertyBy
    1. 首先会取消这个属性上存在的动画
    2. 构建NameValuesHolder
    3. 调用removeCallbacks(runnable),取消之前动画
    4. 调用postOnAnimation(runnable),开始动画

Transition

主要用于做转场动画,其会保存场景转变的信息。任何转变有两个主要工作: 1. 捕获属性值 2. 基于捕获的属性值改变做动画。**无法与TextureViewSurfaceView一起使用。对于SurfaceView,由于其是从非UI线程更新UI,因此会造成不同步。对于TextureView,由于转场动画依赖ViewOverlay,而其又无法与TextureView一起工作。

结构图

transition

1
2
3
4
5
6
7
8
9
Transition.java

//捕获开始场景的属性
public abstract void captureStartValues(TransitionValues transitionValues);

//捕获结束场景的属性
public abstract void captureEndValues(TransitionValues transitionValues);


开始转场动画
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
public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
if (Transition.DBG) {
Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
sceneRoot + ", " + transition);
}
sPendingTransitions.add(sceneRoot);
if (transition == null) {
transition = sDefaultTransition;//使用默认转场
}
final Transition transitionClone = transition.clone();
sceneChangeSetup(sceneRoot, transitionClone);
Scene.setCurrentScene(sceneRoot, null);
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}

private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {

// Capture current values
ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);

if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
runningTransition.pause(sceneRoot);
}
}

if (transition != null) {
transition.captureValues(sceneRoot, true);
}

// Notify previous scene that it is being exited
Scene previousScene = Scene.getCurrentScene(sceneRoot);
if (previousScene != null) {
previousScene.exit();
}
}

private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
final Transition transition) {
if (transition != null && sceneRoot != null) {
MultiListener listener = new MultiListener(transition, sceneRoot);
sceneRoot.addOnAttachStateChangeListener(listener);
//设置绘制前监听
sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
}
}
  • 流程
    1. 未设置Transition,使用默认Transition,也就是AutoTransition
    2. 暂停当前正在运行的转场动画,捕获当前场景,取消之前的转场动画
    3. 设置当前场景
    4. 设置绘制(preDraw)监听,在其当中捕获场景和开始转场动画
AutoTransition

组合转场动画,包括渐变和区域改变。TransitionTransitionManger管理。一般可以使用beginDelayedTransition开始一个转场动画,默认的转场动画为AutoTransition:

1
2
3
TransitionManager.beginDelayedTransition(transitionGroup);

textView.setVisibility(visible ? View.VISIBLE : View.GONE);

效果如下:

autotransition
源码分析

继承自TransitionSet

1
2
3
4
5
6
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
  • 顺序播放渐出,区域变化,渐入动画
TransitionSet

继承自Transition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void captureStartValues(TransitionValues transitionValues) {
if (isValidTarget(transitionValues.view)) {
for (Transition childTransition : mTransitions) {
if (childTransition.isValidTarget(transitionValues.view)) {
childTransition.captureStartValues(transitionValues);
transitionValues.targetedTransitions.add(childTransition);
}
}
}
}

@Override
public void captureEndValues(TransitionValues transitionValues) {
if (isValidTarget(transitionValues.view)) {
for (Transition childTransition : mTransitions) {
if (childTransition.isValidTarget(transitionValues.view)) {
childTransition.captureEndValues(transitionValues);
transitionValues.targetedTransitions.add(childTransition);
}
}
}
}
  • 调用各个Transition的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
ArrayList<TransitionValues> endValuesList) {
long startDelay = getStartDelay();
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; i++) {
Transition childTransition = mTransitions.get(i);
// We only set the start delay on the first transition if we are playing
// the transitions sequentially.
if (startDelay > 0 && (mPlayTogether || i == 0)) {
long childStartDelay = childTransition.getStartDelay();
if (childStartDelay > 0) {
childTransition.setStartDelay(startDelay + childStartDelay);
} else {
childTransition.setStartDelay(startDelay);
}
}
childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList,
endValuesList);
}
}
  • 使用各自Transition创建动画。为第一个转场动画设置必要的延时
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
@Override
protected void runAnimators() {
if (mTransitions.isEmpty()) {
start();
end();
return;
}
setupStartEndListeners();
int numTransitions = mTransitions.size();
if (!mPlayTogether) {
//串行播放动画
// Setup sequence with listeners
// TODO: Need to add listeners in such a way that we can remove them later if canceled
for (int i = 1; i < numTransitions; ++i) {
Transition previousTransition = mTransitions.get(i - 1);
final Transition nextTransition = mTransitions.get(i);
previousTransition.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
nextTransition.runAnimators();
transition.removeListener(this);
}
});
}
Transition firstTransition = mTransitions.get(0);
if (firstTransition != null) {
firstTransition.runAnimators();
}
} else {
//并行播放动画
for (int i = 0; i < numTransitions; ++i) {
mTransitions.get(i).runAnimators();
}
}
}
  • 动画开始,由各个Transition开始动画,在这里会区分动画是串行播放,还是并行播放。
ChangeText(自定义transition)
autotransition
1
2
3
4
5
6
7
textView.animate().alpha(0f).setListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
textView.setText(mSecondText ? TEXT_2 : TEXT_1);
textView.animate().setListener(null);
textView.animate().alpha(1f).start();
}
}).start();
  • 上述代码也能实现相同的功能,ChangeText只是对其进行了进一步的封装。这里不分析其源码
ChangeBound
autotransition
源码分析

继承Transition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void captureValues(TransitionValues values) {
View view = values.view;

if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
//view已经布局完毕,保存当前view边界状态
values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom()));
values.values.put(PROPNAME_PARENT, values.view.getParent());
if (mReparent) {
//如果动画scene的parent一样,记录当前屏幕位置
values.view.getLocationInWindow(tempLocation);
values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
}
if (mResizeClip) {
values.values.put(PROPNAME_CLIP, view.getClipBounds());
}
}
}
  • 保存当前View的边界状态

创建动画,代码很长,核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
if (numChanges > 0) {
Animator anim;
if (!mResizeClip) {
view.setLeftTopRightBottom(startLeft, startTop, startRight, startBottom);
if (numChanges == 2) {
if (startWidth == endWidth && startHeight == endHeight) {
Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
endTop);
anim = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
topLeftPath);
} else {
final ViewBounds viewBounds = new ViewBounds(view);
Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
endLeft, endTop);
ObjectAnimator topLeftAnimator = ObjectAnimator
.ofObject(viewBounds, TOP_LEFT_PROPERTY, null, topLeftPath);

Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
endRight, endBottom);
ObjectAnimator bottomRightAnimator = ObjectAnimator.ofObject(viewBounds,
BOTTOM_RIGHT_PROPERTY, null, bottomRightPath);
AnimatorSet set = new AnimatorSet();
set.playTogether(topLeftAnimator, bottomRightAnimator);
anim = set;
set.addListener(new AnimatorListenerAdapter() {
// We need a strong reference to viewBounds until the
// animator ends.
private ViewBounds mViewBounds = viewBounds;
});
}
} else if (startLeft != endLeft || startTop != endTop) {
Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
endLeft, endTop);
anim = ObjectAnimator.ofObject(view, TOP_LEFT_ONLY_PROPERTY, null,
topLeftPath);
} else {
Path bottomRight = getPathMotion().getPath(startRight, startBottom,
endRight, endBottom);
anim = ObjectAnimator.ofObject(view, BOTTOM_RIGHT_ONLY_PROPERTY, null,
bottomRight);
}
} else {
int maxWidth = Math.max(startWidth, endWidth);
int maxHeight = Math.max(startHeight, endHeight);

view.setLeftTopRightBottom(startLeft, startTop, startLeft + maxWidth,
startTop + maxHeight);

ObjectAnimator positionAnimator = null;
if (startLeft != endLeft || startTop != endTop) {
Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
endTop);
positionAnimator = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
topLeftPath);
}
final Rect finalClip = endClip;
if (startClip == null) {
startClip = new Rect(0, 0, startWidth, startHeight);
}
if (endClip == null) {
endClip = new Rect(0, 0, endWidth, endHeight);
}
ObjectAnimator clipAnimator = null;
if (!startClip.equals(endClip)) {
view.setClipBounds(startClip);
clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
startClip, endClip);
clipAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mIsCanceled;

@Override
public void onAnimationCancel(Animator animation) {
mIsCanceled = true;
}

@Override
public void onAnimationEnd(Animator animation) {
if (!mIsCanceled) {
view.setClipBounds(finalClip);
view.setLeftTopRightBottom(endLeft, endTop, endRight,
endBottom);
}
}
});
}
anim = TransitionUtils.mergeAnimators(positionAnimator,
clipAnimator);
}
if (view.getParent() instanceof ViewGroup) {
final ViewGroup parent = (ViewGroup) view.getParent();
parent.suppressLayout(true);
TransitionListener transitionListener = new TransitionListenerAdapter() {
boolean mCanceled = false;

@Override
public void onTransitionCancel(Transition transition) {
parent.suppressLayout(false);
mCanceled = true;
}

@Override
public void onTransitionEnd(Transition transition) {
if (!mCanceled) {
parent.suppressLayout(false);
}
}

@Override
public void onTransitionPause(Transition transition) {
parent.suppressLayout(false);
}

@Override
public void onTransitionResume(Transition transition) {
parent.suppressLayout(true);
}
};
addListener(transitionListener);
}
return anim;
}
} else {
sceneRoot.getLocationInWindow(tempLocation);
int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
// TODO: also handle size changes: check bounds and animate size changes
if (startX != endX || startY != endY) {
final int width = view.getWidth();
final int height = view.getHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
final BitmapDrawable drawable = new BitmapDrawable(bitmap);
drawable.setBounds(startX, startY, startX + width, startY + height);
final float transitionAlpha = view.getTransitionAlpha();
view.setTransitionAlpha(0);
sceneRoot.getOverlay().add(drawable);
Path topLeftPath = getPathMotion().getPath(startX, startY, endX, endY);
PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
sceneRoot.getOverlay().remove(drawable);
view.setTransitionAlpha(transitionAlpha);
}
});
return anim;
}
}
  • 主要功能是判断View的边界有没有发生改变,如果发生改变,使用对应的位置变化动画,主要包括topLeft, bottomRight, position。如果没有发生改变,则在View上绘制Overlay并对其做path动画。

设置pathMotion

其是一个抽象类,用于描述TransitionPath信息

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

public PathMotion() {}

public PathMotion(Context context, AttributeSet attrs) {}

/**
* Provide a Path to interpolate between two points <code>(startX, startY)</code> and
* <code>(endX, endY)</code>. This allows controlled curved motion along two dimensions.
*
* @param startX The x coordinate of the starting point.
* @param startY The y coordinate of the starting point.
* @param endX The x coordinate of the ending point.
* @param endY The y coordinate of the ending point.
* @return A Path along which the points should be interpolated. The returned Path
* must start at point <code>(startX, startY)</code>, typically using
* {@link android.graphics.Path#moveTo(float, float)} and end at <code>(endX, endY)</code>.
*/
public abstract Path getPath(float startX, float startY, float endX, float endY);
}
  • 实现getPath以便提供两点之间的变化频率
Slide

继承自Visibility

autotransition
源码分析
1
2
3
4
5
6
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
int[] position = new int[2];
view.getLocationOnScreen(position);
transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
}
  • 保存当前View位置

createAnimator在父类Visibility中被重写,当中调用onAppearonDisappear,这两个方法分别有各自的重载方法,由子类提供具体的实现,默认无动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
if (endValues == null) {
return null;
}
int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
float endX = view.getTranslationX();
float endY = view.getTranslationY();
float startX = mSlideCalculator.getGoneX(sceneRoot, view);
float startY = mSlideCalculator.getGoneY(sceneRoot, view);
return TranslationAnimationCreator
.createAnimation(view, endValues, position[0], position[1],
startX, startY, endX, endY, sDecelerate, this);
}
  • 使用CalculateSlide接口的实现计算View离开或者进入场景时的位置,最后由TranslationAnimationCreator创建动画
Scale
autotransition

设置alpha

autotransition
源码分析

private Animator createAnimation(final View view, float startScale, float endScale, TransitionValues values) {
final float initialScaleX = view.getScaleX();
final float initialScaleY = view.getScaleY();
float startScaleX = initialScaleX * startScale;
float endScaleX = initialScaleX * endScale;
float startScaleY = initialScaleY * startScale;
float endScaleY = initialScaleY * endScale;

    if (values != null) {
        Float savedScaleX = (Float) values.values.get(PROPNAME_SCALE_X);
        Float savedScaleY = (Float) values.values.get(PROPNAME_SCALE_Y);
        // if saved value is not equal initial value it means that previous
        // transition was interrupted and in the onTransitionEnd
        // we've applied endScale. we should apply proper value to
        // continue animation from the interrupted state
        if (savedScaleX != null && savedScaleX != initialScaleX) {
            startScaleX = savedScaleX;
        }
        if (savedScaleY != null && savedScaleY != initialScaleY) {
            startScaleY = savedScaleY;
        }
    }

    view.setScaleX(startScaleX);
    view.setScaleY(startScaleY);

    Animator animator = TransitionUtils.mergeAnimators(
            ObjectAnimator.ofFloat(view, View.SCALE_X, startScaleX, endScaleX),
            ObjectAnimator.ofFloat(view, View.SCALE_Y, startScaleY, endScaleY));
    addListener(new TransitionListenerAdapter() {
        @Override
        public void onTransitionEnd(Transition transition) {
            view.setScaleX(initialScaleX);
            view.setScaleY(initialScaleY);
        }
    });
    return animator;

}

  • 合并x, y缩放的动画,合并是采用TransitionSet,如下
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
public static Transition mergeTransitions(Transition... transitions) {
int count = 0;
int nonNullIndex = -1;
for (int i = 0; i < transitions.length; i++) {
if (transitions[i] != null) {
count++;
nonNullIndex = i;
}
}

if (count == 0) {
return null;
}

if (count == 1) {
return transitions[nonNullIndex];
}

TransitionSet transitionSet = new TransitionSet();
for (int i = 0; i < transitions.length; i++) {
if (transitions[i] != null) {
transitionSet.addTransition(transitions[i]);
}
}
return transitionSet;
}
Explode

继承自Visibility

autotransition
源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues, TransitionValues endValues) {
if (endValues == null) {
return null;
}
Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
float endX = view.getTranslationX();
float endY = view.getTranslationY();
calculateOut(sceneRoot, bounds, mTempLoc);
float startX = endX + mTempLoc[0];
float startY = endY + mTempLoc[1];

return TranslationAnimationCreator.createAnimation(view, endValues, bounds.left, bounds.top,
startX, startY, endX, endY, sDecelerate, this);
}
  • 通过calculateOut计算扩散的距离,使用TranslationAnimationCreator创建动画
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
private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
sceneRoot.getLocationOnScreen(mTempLoc);
int sceneRootX = mTempLoc[0];
int sceneRootY = mTempLoc[1];
int focalX;
int focalY;

Rect epicenter = getEpicenter();
if (epicenter == null) {
focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+ Math.round(sceneRoot.getTranslationX());
focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+ Math.round(sceneRoot.getTranslationY());
} else {
focalX = epicenter.centerX();
focalY = epicenter.centerY();
}

int centerX = bounds.centerX();
int centerY = bounds.centerY();
double xVector = centerX - focalX;
double yVector = centerY - focalY;

if (xVector == 0 && yVector == 0) {
// Random direction when View is centered on focal View.
xVector = (Math.random() * 2) - 1;
yVector = (Math.random() * 2) - 1;
}
double vectorSize = Math.hypot(xVector, yVector);
xVector /= vectorSize;
yVector /= vectorSize;

double maxDistance =
calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);

outVector[0] = (int) Math.round(maxDistance * xVector);
outVector[1] = (int) Math.round(maxDistance * yVector);
}
  • 计算场景动画的偏移量,如果为0,随机算出偏移量,算出x, y偏移量的平方根,求出最大距离
    TransitionName

设置要做动画的标识,在之后的动画中会对这些标识的View做动画

autotransition
ImageTransform
autotransition
ReColor
autotransition
Rotate

继承自Transition的旋转动画

autotransition
源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
final View view = endValues.view;
float startRotation = (Float) startValues.values.get(PROPNAME_ROTATION);
float endRotation = (Float) endValues.values.get(PROPNAME_ROTATION);
if (startRotation != endRotation) {
view.setRotation(startRotation);
return ObjectAnimator.ofFloat(view, View.ROTATION,
startRotation, endRotation);
}
return null;
}
  • 创建旋转动画
Progress
autotransition
Change Scene
autotransition
源码分析
1
2
3
4
5
6
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
  • 先淡出效果(Fade),改变区域(ChangeBound),淡入效果(Fade)
Fade

渐变转场动画,继承自Visibility

1
2
3
4
5
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
transitionValues.values.put(PROPNAME_TRANSITION_ALPHA,
transitionValues.view.getTransitionAlpha());
}
  • 捕获初始场景的alpha
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Animator createAnimation(final View view, float startAlpha, final float endAlpha) {
if (startAlpha == endAlpha) {
return null;
}
view.setTransitionAlpha(startAlpha);
//创建渐变属性动画
final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", endAlpha);
if (DBG) {
Log.d(LOG_TAG, "Created animator " + anim);
}
final FadeAnimatorListener listener = new FadeAnimatorListener(view);
anim.addListener(listener);
anim.addPauseListener(listener);
addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
view.setTransitionAlpha(1);
}
});
return anim;
}
  • 创建渐变属性动画,其会由onAppear()onDisAppear调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues,
TransitionValues endValues) {
if (DBG) {
View startView = (startValues != null) ? startValues.view : null;
Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
startView + ", " + view);
}
//创建渐入动画
return createAnimation(view, 0, 1);
}

@Override
public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
TransitionValues endValues) {
//创建渐出动画
return createAnimation(view, 1, 0);
}
  • 这两个方法由VisibilitycreateAnimator调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
if (visInfo.visibilityChange
&& (visInfo.startParent != null || visInfo.endParent != null)) {
if (visInfo.fadeIn) {
return onAppear(sceneRoot, startValues, visInfo.startVisibility,
endValues, visInfo.endVisibility);
} else {
return onDisappear(sceneRoot, startValues, visInfo.startVisibility,
endValues, visInfo.endVisibility
);
}
}
return null;
}
  • View显示或隐藏时会创建对应的渐变动画。TransitionplayTransition会调用createAnimators,其内部会调用createAnimator
  • 整体流程是启动Transition->创建动画(createAnimators)->创建单个动画(createAnimatorTransition子类实现,默认空)->VisibilityonAppear()/onDisappear->子类createAnimator

总结:

Transition动画其实是对属性动画的一种高级封装。其主要流程是TransitionManager调用beginDelayedTransition,内部监听了OnPreDrawListener,其在onPreDraw调用captureView用于获取当前View的状态,调用playTransition开始转场动画。之后创建动画createAnimator。这其中captureView都是由子类实现,而createAnimator则可以由子类选择重写。最终动画的开始还有调用animator.start()

Picasso是常用的图片加载库,其有很多设计上值得学习的地方,比如根据网络调整线程池大小,批量处理图片结果,以及消息分发。文本会对它进行深入分析

基本使用

1
Picasso.with(xxx).load(xxx).into(xxx);
  • Adapter内下载
  • 图片转换
  • 预置图片
  • 资源加载

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Picasso with(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("context == null");
}
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
  • 创建Picasso对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Picasso build() {
Context context = this.context;

if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {
cache = new LruCache(context);
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}

Stats stats = new Stats(cache);

Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
  • 创建默认下载器
  • 创建默认缓存,LRU缓存
  • 自定义线程池
  • 创建默认请求转换器
  • 创建分配器
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
RequestCreator

请求创建者

public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
checkMain();

if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}

if (!data.hasImage()) {
//没有数据,取消请求
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}

if (deferred) {
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
if (width == 0 || height == 0 || target.isLayoutRequested()) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//没有指定大小,按View大小
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}

Request request = createRequest(started);
String requestKey = createKey(request);

if (shouldReadFromMemoryCache(memoryPolicy)) {
//使用内存缓存
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}

if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//新的资源,创建Action,进入请求队列
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);

picasso.enqueueAndSubmit(action);
}

  • 判断需要的资源是否有缓存,没有,创建请求,进入队列
  • 进入队列后,会由Dispatcher分发请求
  • Dispatcher内有有个Handler用于消息分发,有个主线程Handler用于通知主线程刷新UI
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
Dispatcher

提交请求
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}

BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}

if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}

hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}

if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
  • 判断请求动作是否包含在暂停里面
  • 判断是否已经存在hunterMap
  • 加入hunterMap
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
BitmapHunter
解码Bitmap

@Override public void run() {
try {
updateThreadName(data);

if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}

result = hunt();//获取结果
//分发结果
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch (Downloader.ResponseException e) {
if (!e.localCacheOnly || e.responseCode != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (NetworkRequestHandler.ContentLengthException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (IOException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (OutOfMemoryError e) {
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
exception = new RuntimeException(writer.toString(), e);
dispatcher.dispatchFailed(this);
} catch (Exception e) {
exception = e;
dispatcher.dispatchFailed(this);
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}
  • 解码图片,按需求设置转换器,分发结果
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
Bitmap hunt() throws IOException {
Bitmap bitmap = null;

if (shouldReadFromMemoryCache(memoryPolicy)) {
//内存缓存获取
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
//网络策略
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();

// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
//从流中读取
InputStream is = result.getStream();
try {
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}

if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifOrientation != 0) {
//bitmap 转换
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
//更新状态
stats.dispatchBitmapTransformed(bitmap);
}
}
}

return bitmap;
}

整体流程大概是

  • 创建RequestHandler->进入队列->分发请求->分发结果->通知主线程

  • 这里面Dispatcher起到请求和结果分发器的作用内部。一个线程池,两个Handler。线程池默认3个线程。会根据网络状况自动调整线程数量

    • WIFI,以太网会有四个线程池
    • 移动网络: 4G,三个线程池,3G两个线程池,2G一个线程池
  • Dispatcher会批量打包结果一起通知主线程

架构图

picasso

原文

Instant RunAndroid Studio的一项神奇的功能,用于减少增量代码的构建和部署时间。它看起来很神奇。你首次运行或者调试,你就如你预期般的那样工作,之后每次代码的改动,会花很少的时间去构建和部署

原理

构建图

instant build

Instant Run的目标很简单

移除尽可能多的步骤,使剩下的东西尽可能快

具体是:

  • 只构建和部署新增的东西
  • 不重新安装应用
  • 不重新启动应用
  • 不重新启动Activity

热部署,温部署,冷部署

hot_warm_cold

热部署: 部署新的改变,不需要重新应用,甚至不需要重启当前Activity。能用于方法内简单的改变

温部署: Activity需要重启后,新的改变才能生效。通常用于资源改变

冷部署: 应用重启,但不重新安装。任何结构型变化,如继承关系或者方法签名会使用冷部署

打包流程

merged_combined

manifest文件被合并,打包。伴随着资源一起打包进APKjava源代码被编译成字节码,转换成.dex文件。它们也会打包进入APK

首次点击运行或调试(Instant Run打开), Gradle做的额外操作

first_run_instant_run

字节码被添加到.class文件中,一个新的App Server类被注入到app

一个新的Application类定义也被加入到App,注入自定义类加载器以及将启动App Server。一般来说,manifest会被修改以便app能使用(如果你创建了自己的Application类,Instant Run版本会代理这个Application类)

这时Instant Run运行了。因此如果你改变了代码,Instant Run会尝试避免使用热,温,冷部署来避免全构建

应用Instant Run改变之前,as会检查Instant Run内是否有一个打开的Socket连接着App Server

热部署

hot_swapping

as监控开发过程中哪些字段被修改了。运行自定义Gradle任务来未修改的类生成.dex文件

这些新.dex文件被as挑选,并部署到app中的App Server

由于类的原始版本已经存在了,Gradle转换更新版本以便高效覆盖这些之前存在的类。转换结束,更新的类被App Server使用自定义的类加载器加载。

从现在起,每次方法被调用,这个注入到原始类文件中的监控类都会检查是否已经更新。如果更新了,那么后续操作会代理到新的覆盖类中。

温部署

温部署重启Activity。当Activity启动,资源被加载。因此更新资源需要重启Acitivty以便强制资源重新加载

当前,任何资源的改变会导致,所有的资源被重新打包到app,但使用增量打包器会只打包和部署更改的资源

注意温部署对于manifest的改变是无效的,因为manifest信息的读取实在apk安装的时候。manifest的改变会触发全量构建和部署

冷部署

部署后,app和其子项目被分成10个片段,每个片段有自己的dex文件。类安装它们的包名分割。使用冷部署,修改类会要求所有其他在相同片段中的类重新加载。

这个策略依赖于运行时加载多个dex文件的能力。5.0以上采用ART具备这种能力。之下会采用全量构建部署

Instant Run技巧和提示

  • manifest的修改会导致全量构建部署
  • Instant Run值监控主进程,如果app使用多进程。热部署和温部署在其他进程中会降到冷部署,如果低于5.0,会采用全量构建部署
  • Windows防火墙可能会导致Instant Run无法启动
  • Instant Run不支持Jack编译器

Retrofit分析

Retrofit可以当做是OkHttp3的应用层。其接收Java注解的请求。

用法

1
2
3
Retrofit retrofit = new Retrofit.Builder().baseUrl(url).addConvertFactory(factory).build();
retrofit.create(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
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();

@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}

create方法接收客户端定义的api接口

主要工作:

  1. 使用代理模式创建出接口对应的类
  2. 加载接口里面的方法,并缓存
  3. 如果方法不存在,创建ServiceMethod,并缓存
  4. 使用API接口的方法创建OkHttpCall
  5. okHttpCallcallAdapter关联
ServiceMethod
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
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw methodError("'"
+ Utils.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
responseConverter = createResponseConverter();

for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}

if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}

if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}

Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}

if (relativeUrl == null && !gotUrl) {
throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
}
if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
throw methodError("Non-body HTTP method cannot contain @Body.");
}
if (isFormEncoded && !gotField) {
throw methodError("Form-encoded method must contain at least one @Field.");
}
if (isMultipart && !gotPart) {
throw methodError("Multipart method must contain at least one @Part.");
}

return new ServiceMethod<>(this);
}

主要工作:

  1. 创建请求适配器
  2. 创建响应适配器
  3. 解析方法注解
  4. 解析参数注解

流程图

Retrofit

1
2
3
Runtime.java

nativeLoad

C代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java_lang_Runtime.cc

static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader,
jstring javaLdLibraryPathJstr) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == nullptr) {
return nullptr;
}

SetLdLibraryPath(env, javaLdLibraryPathJstr);

std::string error_msg;
{
JavaVMExt* vm = Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg);
if (success) {
return nullptr;
}
}

// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}

从上述代码可以看出关键代码在于LoadNativeLibrary

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
java_vm_ext.cc

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader,
std::string* error_msg) {
error_msg->clear();

// See if we've already loaded this library. If we have, and the class loader
// matches, return successfully without doing anything.
// TODO: for better results we should canonicalize the pathname (or even compare
// inodes). This implementation is fine if everybody is using System.loadLibrary.
SharedLibrary* library;
Thread* self = Thread::Current();
{
// TODO: move the locking (and more of this logic) into Libraries.
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
}
if (library != nullptr) {
if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) {
// The library will be associated with class_loader. The JNI
// spec says we can't load the same library into more than one
// class loader.
StringAppendF(error_msg, "Shared library \"%s\" already opened by "
"ClassLoader %p; can't open in ClassLoader %p",
path.c_str(), library->GetClassLoader(), class_loader);
LOG(WARNING) << error_msg;
return false;
}
VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
<< " ClassLoader " << class_loader << "]";
if (!library->CheckOnLoadResult()) {
StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
"to load \"%s\"", path.c_str());
return false;
}
return true;
}

// Open the shared library. Because we're using a full path, the system
// doesn't have to search through LD_LIBRARY_PATH. (It may do so to
// resolve this library's dependencies though.)

// Failures here are expected when java.library.path has several entries
// and we have to hunt for the lib.

// Below we dlopen but there is no paired dlclose, this would be necessary if we supported
// class unloading. Libraries will only be unloaded when the reference count (incremented by
// dlopen) becomes zero from dlclose.

Locks::mutator_lock_->AssertNotHeld(self);
const char* path_str = path.empty() ? nullptr : path.c_str();
void* handle = dlopen(path_str, RTLD_NOW);
bool needs_native_bridge = false;
if (handle == nullptr) {
if (android::NativeBridgeIsSupported(path_str)) {
handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
needs_native_bridge = true;
}
}

VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";

if (handle == nullptr) {
*error_msg = dlerror();
VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
return false;
}

if (env->ExceptionCheck() == JNI_TRUE) {
LOG(ERROR) << "Unexpected exception:";
env->ExceptionDescribe();
env->ExceptionClear();
}
// Create a new entry.
// TODO: move the locking (and more of this logic) into Libraries.
bool created_library = false;
{
// Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
std::unique_ptr<SharedLibrary> new_library(
new SharedLibrary(env, self, path, handle, class_loader));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
if (library == nullptr) { // We won race to get libraries_lock.
library = new_library.release();
libraries_->Put(path, library);
created_library = true;
}
}
if (!created_library) {
LOG(INFO) << "WOW: we lost a race to add shared library: "
<< "\"" << path << "\" ClassLoader=" << class_loader;
return library->CheckOnLoadResult();
}
VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";

bool was_successful = false;
void* sym;
if (needs_native_bridge) {
library->SetNeedsNativeBridge();
sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
} else {
sym = dlsym(handle, "JNI_OnLoad");
}
if (sym == nullptr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
was_successful = true;
} else {
// Call JNI_OnLoad. We have to override the current class
// loader, which will always be "null" since the stuff at the
// top of the stack is around Runtime.loadLibrary(). (See
// the comments in the JNI FindClass function.)
ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);

VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
int version = (*jni_on_load)(this, nullptr);

if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
fault_manager.EnsureArtActionInFrontOfSignalChain();
}

self->SetClassLoaderOverride(old_class_loader.get());

if (version == JNI_ERR) {
StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
} else if (IsBadJniVersion(version)) {
StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
path.c_str(), version);
// It's unwise to call dlclose() here, but we can mark it
// as bad and ensure that future load attempts will fail.
// We don't know how far JNI_OnLoad got, so there could
// be some partially-initialized stuff accessible through
// newly-registered native method calls. We could try to
// unregister them, but that doesn't seem worthwhile.
} else {
was_successful = true;
}
VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
<< " from JNI_OnLoad in \"" << path << "\"]";
}

library->SetResult(was_successful);
return was_successful;
}

导言

WindowManager是整个绘制系统的核心桥梁。一般通过getSystemService(Context.WindowService)可以获取。

1
2
3
4
5
6
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx.getDisplay());
}});

WindowManagerImpl

通过代码可以发现WindowManagerImpl的核心实现都在WindowManagerGlobal

WindowManagerGlobal

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
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}

int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}

// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}

可以发现addView里面主要用到ViewRootImpl来添加视图

ViewRootImpl

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
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
}

public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}

public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
sWindowManagerService = getWindowManagerService();
ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
} catch (RemoteException e) {
Log.e(TAG, "Failed to get WindowManagerService, cannot set animator scale", e);
}
}
return sWindowManagerService;
}
}

通过AIDL获取WindowManagerService这时FrameworkNative建立起连接了。之后ViewRootImpl会调用setView

1
2
3
4
5
6
7
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

要求系统开始布局

1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

通过Choreographer发送绘制命令CALLBACK_TRAVERSAL要求系统开始绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//绘制所有View节点
performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

performMeasure

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
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

performMeasure会调用view.measure()里面会去调用onMeasure,这里会去真正的做测量操作

performLayout

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
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;

// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

mHandlingLayoutInLayoutRequest = false;

// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}

}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;

先进行布局,检查需要布局的数目,检查这些布局请求是否合理,不合理重新布局。

performDraw

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
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;

if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}

performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}

判断此次绘制是否取消或者是新的页面。

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
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}

final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;

mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

// For whatever reason we didn't create a HardwareRenderer, end any
// hardware animations that are now dangling
if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
}
mAttachInfo.mPendingAnimatingRenderNodes.clear();
}

if (mReportNextDraw) {
mReportNextDraw = false;
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.fence();
}

if (LOCAL_LOGV) {
Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
if (mSurfaceHolder != null && mSurface.isValid()) {
mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
try {
mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
}
}
}

调用draw()方法
如果开启了硬件加速,使用ThreadedRender.draw绘制,否则采用传统canvas绘制

cavas绘制

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
class ANDROID_API Canvas

底层采用Skia引擎来绘制

DrawFrameTask

void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");

bool canUnblockUiThread;
bool canDrawThisFrame;
{
TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = info.out.canDrawThisFrame;
}

// Grab a copy of everything we need
CanvasContext* context = mContext;

// From this point on anything in "this" is *UNSAFE TO ACCESS*
if (canUnblockUiThread) {
unblockUiThread();
}

if (CC_LIKELY(canDrawThisFrame)) {
context->draw();
}

if (!canUnblockUiThread) {
unblockUiThread();
}
}

CanvasContext

void CanvasContext::draw() {
LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no canvas or surface!");

SkRect dirty;
mDamageAccumulator.finish(&dirty);

// TODO: Re-enable after figuring out cause of b/22592975
// if (dirty.isEmpty() && Properties::skipEmptyFrames) {
// mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
// return;
// }

mCurrentFrameInfo->markIssueDrawCommandsStart();

EGLint width, height;
mEglManager.beginFrame(mEglSurface, &width, &height);
if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
mCanvas->setViewport(width, height);
dirty.setEmpty();
} else if (!mBufferPreserved || mHaveNewSurface) {
dirty.setEmpty();
} else {
if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
SK_RECT_ARGS(dirty), width, height);
dirty.setEmpty();
}
profiler().unionDirty(&dirty);
}

if (!dirty.isEmpty()) {
mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
dirty.fRight, dirty.fBottom, mOpaque);
} else {
mCanvas->prepare(mOpaque);
}

Rect outBounds;
mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);

profiler().draw(mCanvas);

bool drew = mCanvas->finish();

// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
mCurrentFrameInfo->markSwapBuffers();

if (drew) {
swapBuffers(dirty, width, height);
}

// TODO: Use a fence for real completion?
mCurrentFrameInfo->markFrameCompleted();
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
}

uml

导言

加载布局的时候经常会用到

1
View.inflate(Context context, @LayoutRes int resource, ViewGroup root) 

或者

1
LayoutInflater.from(Context context).inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

关键就是LayoutInflater,它是通过

1
2
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

因此可以看出它其实属于系统的一种服务。由此我们来分析当使用系统服务时都做了什么?

分析

由源码我能知道获取系统服务总是通过

1
context.getSystemService(xxx)

因此在ContextImpl找到

1
2
3
4
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry
1
2
3
4
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}

SYSTEM_SERVICE_FETCHERS是个Map容器,保存着ServiceFetcher

SystemServiceRegistry可以看出,我们平时所需要的系统服务都会在一开始的注册

1
2
3
4
static{ 
registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher)
}
ServiceFetcher
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
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;

public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}

@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
if (service == null) {
service = createService(ctx);
cache[mCacheIndex] = service;
}
return (T)service;
}
}

public abstract T createService(ContextImpl ctx);
}

这里每次获取服务的时候都会进行缓存检查确保只会存在一个相同的服务

下面是uml图
getSystemService

1
2
3
4
5
6
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});

上述代码会注册LayoutInflayer服务,可以看出实际的实现在PhoneLayoutInflayer,其继承自LayoutInflayer

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
118
119
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}

final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;

try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}

if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}

final String name = parser.getName();

if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}

if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}

rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;

if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}

if (DEBUG) {
System.out.println("-----> start inflating children");
}

// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);

if (DEBUG) {
System.out.println("-----> done inflating children");
}

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}

} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

return result;
}
}

主要加载视图的主要步骤:

解析xml根元素,根元素如果是merge直接调用rInflate,root作为根节点。否则调用createViewFromTag加载视图,然后挂到根节点下

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
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}

if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}

try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}

if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}

if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}

return view;
} catch (InflateException e) {
throw e;

} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;

} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}

通过Factory或者Factory2加载视图(主要用于Fragment),之后如果是内置View调用onCreateView,然后调用createView,自定义View直接调用createView

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
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;

try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);

if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);

boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}

Object[] args = mConstructorArgs;
args[1] = attrs;

final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;

} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;

} catch (ClassCastException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

createView首先从sConstructorMap中取出缓存的View构造器,如果不存在直接通过反射创建实例,还可以通过Filter来控制加载的View

rInflate()
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
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

final int depth = parser.getDepth();
int type;

while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

if (type != XmlPullParser.START_TAG) {
continue;
}

final String name = parser.getName();

if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}

if (finishInflate) {
parent.onFinishInflate();
}
}

解析顺序是<requestFocus/>-><tag>-><include>-><merge>-><View>

LayoutInflater uml

getSystemService

代码示例

  1. 单一职责原则
  2. 接口隔离(多用组合,少用继承)
  3. 依赖倒置(高层不依赖低层,都依赖抽象)
  4. 里氏替换(子类必须能够替换基类)
  5. 迪米特原则(解耦,类之间尽量减少联系)
  6. 开闭原则(开放拓展,关闭修改)

设计原则

  1. 封装变化
  2. 针对接口编程(针对超类型编程),而不是针对实现编程
  3. 多用组合,少用继承
  4. 为了交互对象之间的松耦合设计而努力
  5. 对拓展开放,对修改关闭
  6. 最少知识(墨忒耳法则(Law of Demeter))
  7. 好莱坞原则,别调用我们,我们会调用你
  8. 一个类应该只有一个引起变化的原因
  9. 依赖倒置原则(不能让高层组件依赖低层组件,两者都应该依赖于抽象)
  • 变量不可以持有具体类的引用
  • 不要让类派生自具体类
  • 不要覆盖基类中已实现的方法

策略模式

定义了算法族,分别封装起来,让它们之间可以互相替换,让算法的变化独立于使用算法的客户

观察者模式

定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

装饰这模式

动态地将责任附加到对象上。若要拓展功能,装饰着提供了比继承更有弹性的替代方案

工厂方法模式

  • 方法工厂

定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

  • 抽象工厂

提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类

单件模式

确保一个类只有一个实例,并提供一个全局访问点

命令模式

“请求”封装成对象,以便使用不同的请求,列队或者日志来参数化其他对象。命令模式也支持可撤销的操作

适配器模式

将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类型可以合作无间。

  • 对象适配器
  • 类适配器(需要多重继承,java不支持)

外观模式

提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

模板方法模式

在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤

迭代器模式

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示

组合模式

允许你将对象组合成树形结构来表现”整体/部分”层次结构。组合能让客户以一致的方式处理对象以及对象组合

####状态模式

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了类。策略模式和状态模式是双胞胎,在出生时才分开

代理模式

为另一个对象提供一个替身或占位符以控制对这个对象的访问,远程代理,虚拟代理,动态代理

桥接模式

把事物抽象和其具体行为、具体特征分离开来,使它们可以各自独立的变化

  • 使实现解耦,让它和界面之间不再永久绑定
  • 抽象和实现可以独立拓展,不会影响对方
  • 对于“具体的抽象类”所做的改变,不会影响到客户。
  • 增加复杂度

生成器

封装一个产品的构造过程,并允许按步骤构造。

  • 将一个复杂对象的创建过程封装起来
  • 允许对象通过多个步骤来创建,并且可以改变过程
  • 向客户隐藏产品内部的表现
  • 产品的实现可以被替换,因为客户只看到一个抽象的接口

责任链

让一个以上的对象有机会能够处理某个请求的。

  • 将请求的发送者和接受者解耦
  • 可以简化对象,因为它不需要知道链的结构。
  • 通过改变链内的成员或调动它们的次序,允许你动态地新增或者删除责任

享元模式(Flyweight)

让某个类的实例能够用来提供许多“虚拟实例”。

  • 减少运行时对象实例的个数,节省内存
  • 将许多“虚拟”对象的状态集中管理

解释器(Interpreter)

未语言创建解释器

  • 将每一种语法规则表示成一个类,方便于实现语言
  • 因为语法由许多类组成,所以可以轻易改变或拓展此语言
  • 通过在类结构中加入新的方法,可以在解释的同时增加新的行为

中介者(Mediator)

集中相关对象之间复杂的沟通和控制方式

  • 将对象彼此解耦,增加对象的复用性
  • 集中控制逻辑,简化系统维护
  • 让对象之间所传递的信息变得简单而且大幅减少

备忘录

让对象返回之前的状态

  • 将被存储的状态放在外面,不要和关键对象混在一起,这可以帮助维护内聚
  • 保持关键对象的数据封装
  • 提供了容易实现的恢复能力

原型

创建对象的种类,并且通过拷贝这些原型创建新的对象

  • 对客户隐藏创建新实例的复杂性
  • 提供让客户能够产生未知类型对象的选项

访问者

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

  • 允许对组合结构加入新的操作,而无需改变结构本身
  • 想要加入新的操作,相对容易
  • 访问者所进行的操作,其代码是集中在一起的

参考

Head First
设计模式:可复用面向对象软件的基础

原文

写这篇文章的目的是为了描述我建议的Android app架构。我一步步的通过下面的原因来选择不同的组件。

模板应用的目的很简单:它是master/detail结构,用来展示指定用户的Github仓库。虽然它简单,但它集合了一些通用的东西:

  • 使用RESTAPI
  • 本地数据存储
  • 本地数据加载
  • 架构逻辑层以及页面导航

让我们来看看这背后都有什么!

###Consuming REST API

REST Representational State Transfer表述性状态传递

Retrofit是开发者必备的知名网络库
我将解释为何我认为它是必备的库:

  • 类型安全
  • 接口可读性好,采用注解API地址
  • 对如何工作采用完全抽象层
  • 支持多部分请求体(上传文件)
  • 使用注解直接管理头部信息
  • 能够使用多种序列化类型(JSON, XML, protobuf, etc)的转换器
  • 能够添加全局的请求拦截器
  • 方便的进行MOCK测试

使用时仅需在build.gradle文件中添加

1
compile 'com.squareup.retrofit:retrofit:{{last_version}}'

然后我能够声明GitHubService接口和我们需要使用这个接口的方法

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

下一步通过RestAdapter去实现这个接口

1
2
3
4
5
6
final Retrofit loRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.build();

final GitHubService loService = loRetrofit.create(GitHubService.class);
return loService;

我使用Merlin。它能够观察网络的连接状态以及改变。它提供流畅API,设置简单。

1
2
final MerlinsBeard merlinsBeard = MerlinsBeard.from(context);

调用

1
merlinsBeard.isConnected()

来检查网络是否可用。

###解析数据

现在我们已经能够从服务器拿到数据了,我们需要将这些数据转换成POJO对象。通常的格式是JSON,我们使用Jackson来作为转换器。

当然,我将会说明一下为什么我选择Jackson。首先,我很满意它流畅的注解API。能够获取或存储那些没有通过@JsonProperty注解声明的属性

1
2
3
4
5
6
7
8
9
10
11
12
@JsonIgnore
private Map<String, Object> mAdditionalProperties = new HashMap<String, Object>();

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return mAdditionalProperties;
}

@JsonAnySetter
public void setAdditionalProperty(final String psName, final Object poValue) {
mAdditionalProperties.put(psName, value poValue;
}

Retrofit组合

https://github.com/square/retrofit/tree/master/retrofit-converters/jackson.

1
compile 'com.squareup.retrofit2:converter-jackson:{{last_version}}'

然后我们将其设置到我们之前的RestAdapter

1
2
3
4
5
6
7
final Retrofit loRetrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(JacksonConverterFactory.create()) // add the Jackson specific converter
.build();

final GitHubService loService = loRetrofit.create(GitHubService.class);
return loService;

福利
我通常使用jsonchema2pojo

Generate Plain Old Java Objects from JSON or JSON-Schema.

也可以使用moshi或者LoganSquare

####组件之间的通讯和数据传递
通常的做法是使用事件总线

Now can using RxJava to replace above all

####线程管理

RxAndroid thread

####任务管理
多线程和并发是开发者经常要考虑的问题。我罗列出一些不能再主线程操作的任务

  • 调用远程API
  • 数据库CURD操作
  • 读取本地文件

建议采取:

  • 使用Service
  • 设置ServiceHelper来进行网络请求
  • 使用专门的类来处理查询操作

但需要注意的是Service是运行在主线程中,因此可以使用IntentService或者使用Android Priority Job Queue或者是官方的JobScheduler

####Job Manager(android-priority-jobqueue)
#####Job Manager Configuration

1
2
3
4
5
6
7
8
final Configuration loConfiguration = new Configuration.Builder(poContext)
.minConsumerCount(1) // always keep at least one consumer alive
.maxConsumerCount(3) // up to 3 consumers at a time
.loadFactor(3) // 3 jobs per consumer
.consumerKeepAlive(120) // wait 2 minute
.build();

final JobManager loJobManager = new JobManager(poContext, loConfiguration);

#####Job Configuration
我们可以给一个任务设置一些有用的参数,例如

  • 它的优先级
  • 它是否需要网络
  • 如果不执行,是否需要持久化
  • 延时运行
  • 重试机制

这个库的持久化引擎非常强大。例如,网络不可用时,任务会被持久化到设备上。一旦网络可用JobManager获取持久化的任务并执行它们

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
public abstract class AbstractQuery extends Job {
private static final String TAG = AbstractQuery.class.getSimpleName();
private static final boolean DEBUG = true;

protected enum Priority {
LOW(0),
MEDIUM(500),
HIGH(1000);
private final int value;

Priority(final int piValue) {
value = piValue;
}
}

protected boolean mSuccess;
protected Throwable mThrowable;
protected AbstractEventQueryDidFinish.ErrorType mErrorType;

//region Protected constructor
protected AbstractQuery(final Priority poPriority) {
super(new Params(poPriority.value).requireNetwork());
}

protected AbstractQuery(final Priority poPriority, final boolean pbPersistent, final String psGroupId, final long plDelayMs) {
super(new Params(poPriority.value).requireNetwork().setPersistent(pbPersistent).setGroupId(psGroupId).setDelayMs(plDelayMs));
}
//endregion

//region Overridden methods
@Override
public void onAdded() {
}

@Override
public void onRun() throws Throwable {
try {
execute();
mSuccess = true;
} catch (Throwable loThrowable) {
if (BuildConfig.DEBUG && DEBUG) {
Logger.t(TAG).e(loThrowable, "");
}
mErrorType = AbstractEventQueryDidFinish.ErrorType.UNKNOWN;
mThrowable = loThrowable;
mSuccess = false;
}

postEventQueryFinished();
}

@Override
protected void onCancel() {
}

@Override
protected int getRetryLimit() {
return 1;
}
//endregion

//region Protected abstract method for specific job
protected abstract void execute() throws Exception;

protected abstract void postEventQueryFinished();

public abstract void postEventQueryFinishedNoNetwork();
//endregion
}
  • 枚举描述了我需要的优先级
  • 提供两个构造函数
  • 异常处理
  • 默认重试次数

有三个方法被实现

  • execute: 执行特定的代码
  • postEventQueryFinished : 通知任务结果
  • postEventQueryFinishedNoNetwork : 通知网络不可用

后两个通常基于总线

下面是我定义的抽象事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 public abstract class AbstractEventQueryDidFinish<QueryType extends AbstractQuery> extends AbstractEvent {
public enum ErrorType {
UNKNOWN,
NETWORK_UNREACHABLE
}

public final QueryType query;

public final boolean success;
public final ErrorType errorType;
public final Throwable throwable;

public AbstractEventQueryDidFinish(final QueryType poQuery, final boolean pbSuccess, final ErrorType poErrorType, final Throwable poThrowable) {
query = poQuery;
success = pbSuccess;
errorType = poErrorType;
throwable = poThrowable;
}
}
  • 查询刚完成
  • 终端状态
  • 异常处理

下面是我的查询用户仓库的代码

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
 public class QueryGetRepos extends AbstractQuery {
private static final String TAG = QueryGetRepos.class.getSimpleName();
private static final boolean DEBUG = true;

//region Fields
public final String user;
//endregion

//region Constructor matching super
protected QueryGetRepos(@NonNull final String psUser) {
super(Priority.MEDIUM);
user = psUser;
}
//endregion

//region Overridden method
@Override
protected void execute() throws Exception {
final GitHubService gitHubService = // specific code to get GitHubService instance

final Call<List<DTORepo>> loCall = gitHubService.listRepos(user);
final Response<List<DTORepo>> loExecute = loCall.execute();
final List<DTORepo> loBody = loExecute.body();

// TODO deal with list of DTORepo
}

@Override
protected void postEventQueryFinished() {
final EventQueryGetRepos loEvent = new EventQueryGetRepos(this, mSuccess, mErrorType, mThrowable);
busManager.postEventOnMainThread(loEvent);
}

@Override
public void postEventQueryFinishedNoNetwork() {
final EventQueryGetRepos loEvent = new EventQueryGetRepos(this, false, AbstractEventQueryDidFinish.ErrorType.NETWORK_UNREACHABLE, null);
busManager.postEventOnMainThread(loEvent);
}
//endregion

//region Dedicated EventQueryDidFinish
public static final class EventQueryGetRepos extends AbstractEventQueryDidFinish<QueryGetRepos> {
public EventQueryGetRepos(final QueryGetRepos poQuery, final boolean pbSuccess, final ErrorType poErrorType, final Throwable poThrowable) {
super(poQuery, pbSuccess, poErrorType, poThrowable);
}
}
//endregion
}

现在,通过QueryFactor这个简单的代理类来进行请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class QueryFactory {
//region Build methods
public QueryGetRepos buildQueryGetRepos(@NonNull final String psUser) {
return new QueryGetRepos(psUser);
}
//endregion

//region Start methods
public void startQuery(@NonNull final Context poContext, @NonNull final AbstractQuery poQuery) {
final Intent loIntent = new ServiceQueryExecutorIntentBuilder(poQuery).build(poContext);
poContext.startService(loIntent);
}

public void startQueryGetRepos(@NonNull final Context poContext, @NonNull final String psUser) {
final QueryGetRepos loQuery = buildQueryGetRepos(psUser);
startQuery(poContext, loQuery);
}
//endregion
}

Service处理如下声明的查询:

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
public class ServiceQueryExecutor extends IntentService {
private static final String TAG = ServiceQueryExecutor.class.getSimpleName();

//region Extra fields
AbstractQuery query;
//endregion

MerlinsBeard merlinsBeard;
JobManager jobManager;

//region Constructor matching super
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*/
public ServiceQueryExecutor() {
super(TAG);
}
//endregion

//region Overridden methods
@DebugLog
@Override
protected void onHandleIntent(final Intent poIntent) {
// TODO get AbstractQuery from Intent
// TODO get MerlinsBeard and JobManager instances

// If query requires network, and if network is unreachable, and if the query must not persist
if (query.requiresNetwork() &&
!merlinsBeard.isConnected() &&
!query.isPersistent()) {
// then, we post an event to notify the job could not be done because of network connectivity
query.postEventQueryFinishedNoNetwork();
} else {
// otherwise, we can add the job
jobManager.addJobInBackground(query);
}
}
//endregion
}

###数据持久化

####ORM 方式
对象关系映射是软件开发中经常使用的技术

####OrmLite
第一步设置要被映射的POJO
我创建抽象类来匹配_id

1
2
3
4
5
6
7
8
9
10
public abstract class AbstractOrmLiteEntity {
@DatabaseField(columnName = BaseColumns._ID, generatedId = true)
protected long _id;

//region Getter
public long getBaseId() {
return _id;
}
//endregion
}

现在创建一个POJO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@DatabaseTable(tableName = "REPO", daoClass = DAORepo.class)
public class RepoEntity extends AbstractOrmLiteEntity {
@DatabaseField
public Integer id;

@DatabaseField
public String name;

@DatabaseField
public String location;

@DatabaseField
public String url;
}

我们来看一下DAO。这个设计目的在于通过抽象的接口来访问具体的数据。

OrmLite提供了Dao接口以及其实现BaseDaoImpl。CURD所需的操作都具备。

然而,这些都是同步执行的。使用RxJava来异步执行这些操作。

因此我使用RxJava重写了所有的方法
我创建了如下接口

1
public interface IRxDao<T, ID> extends Dao<T, ID>

所有的方法名以”rx”为前缀。返回一个特定类型的Observable对象

1
public abstract class RxBaseDaoImpl<DataType extends AbstractOrmLiteEntity, IdType> extends BaseDaoImpl<DataType, IdType> implements IRxDao<DataType, IdType>

使用long作为ID类型

1
2
public interface IOrmLiteEntityDAO<DataType extends AbstractOrmLiteEntity> extends Dao<DataType, Long> {
}

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class AbstractBaseDAOImpl<DataType extends AbstractOrmLiteEntity> extends RxBaseDaoImpl<DataType, Long> implements IOrmLiteEntityDAO<DataType> {
//region Constructors matching super
protected AbstractBaseDAOImpl(final Class<DataType> poDataClass) throws SQLException {
super(poDataClass);
}

public AbstractBaseDAOImpl(final ConnectionSource poConnectionSource, final Class<DataType> poDataClass) throws SQLException {
super(poConnectionSource, poDataClass);
}

public AbstractBaseDAOImpl(final ConnectionSource poConnectionSource, final DatabaseTableConfig<DataType> poTableConfig) throws SQLException {
super(poConnectionSource, poTableConfig);
}
//endregion
}

DAORepo变成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DAORepo extends AbstractBaseDAOImpl<RepoEntity> {
//region Constructors matching super
public DAORepo(final ConnectionSource poConnectionSource) throws SQLException {
this(poConnectionSource, RepoEntity.class);
}

public DAORepo(final ConnectionSource poConnectionSource, final Class<RepoEntity> poDataClass) throws SQLException {
super(poConnectionSource, poDataClass);
}

public DAORepo(final ConnectionSource poConnectionSource, final DatabaseTableConfig<RepoEntity> poTableConfig) throws SQLException {
super(poConnectionSource, poTableConfig);
}
//endregion
}

ORMLite提供SQLiteOpenHelper的抽象子类OrmLiteSQLiteOpenHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class DatabaseHelperAndroidStarter extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "android_starter.db";
private static final int DATABASE_VERSION = 1;

//region Constructor
public DatabaseHelperAndroidStarter(@NonNull final Context poContext) {
super(poContext, DATABASE_NAME, null, DATABASE_VERSION);
}
//endregion

//region Methods to override
@Override
@SneakyThrows(SQLException.class)
public void onCreate(@NonNull final SQLiteDatabase poDatabase, @NonNull final ConnectionSource poConnectionSource) {
TableUtils.createTable(poConnectionSource, RepoEntity.class);
}

@Override
@SneakyThrows(SQLException.class)
public void onUpgrade(@NonNull final SQLiteDatabase poDatabase, @NonNull final ConnectionSource poConnectionSource, final int piOldVersion, final int piNewVersion) {
TableUtils.dropTable(poConnectionSource, RepoEntity.class, true);
onCreate(poDatabase, poConnectionSource);
}
//endregion
}

ORMLite提供TableUtils,其可以根据映射的类文件创建或者删除表

现在,我们需要一个DatabaseHelperAndroidStarter来处理数据

1
2
3
public DatabaseHelperAndroidStarter getDatabaseHelperAndroidStarter(@NonNull final Context poContext) {
return new DatabaseHelperAndroidStarter(poContext);
}

我们能通过下面的方式获得一个DAORepo实例

1
2
3
public DAORepo getDAORepo(@NonNull final DatabaseHelperAndroidStarter poDatabaseHelperAndroidStarter) {
return new DAORepo(poDatabaseHelperAndroidStarter.getConnectionSource());
}

Fragment中,通过以下方式获取仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void rxGetRepos() {
mSubscriptionGetRepos = daoRepo.rxQueryForAll()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
(final List<RepoEntity> ploRepos) -> { // onNext
// TODO deal with repos
},
(final Throwable poException) -> { // onError
mSubscriptionGetRepos = null;
// TODO deal with error
},
() -> { // onCompleted
mSubscriptionGetRepos = null;
}
);
}

但仍然有个问题:如何从网络上获取一个仓库并把它解析然后存储到本地?

我的目标是使用DTORepo来与网络通讯,RepoEntity映射到数据库。它们有相同名字的相同的字段。因此我需要一个工具用来把DTO转换成实体。这时候,我们会用到Android Transformer

它提供两个主要的注解

  • @Mappable来代表要映射的类
  • @Mapped代表要映射的成员

因此,RepoEntity变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Mappable(with = DTORepo.class)
@DatabaseTable(tableName = "REPO", daoClass = DAORepo.class)
public class RepoEntity extends AbstractOrmLiteEntity implements Serializable {
@Mapped
@DatabaseField
public Integer id;

@Mapped
@DatabaseField
public String name;

@Mapped
@DatabaseField
public String location;

@Mapped
@DatabaseField
public String url;
}

现在我们能通过以下方式将DTO转换成实体

1
2
final Transformer loTransformerRepo = new Transformer.Builder().build(RepoEntity.class);
final RepoEntity loRepo = loTransformerRepo.transform(loDTORepo, RepoEntity.class);

ormgap插件

ContentProvider方式

ContentProviderCursor及其派生配合使用异常强大。

使用ProviGen有以下优势:

  • 声明ContentProvider相关类方便
  • 使用ProviGenProvider,其是ContentProvider的一个子类
  • 提供SQLiteOpenHelper默认实现
  • 能够自定义SQLiteOpenHelper的实现
  • TableBuilder能够使用流畅的API创建SQL

MicroOrm

###依赖注入

依赖注入优点:

  • 便于阅读
  • 便于维护
  • 便于测试

###MVP架构
文章
你能在这了解到MVP基础,VIEW状态和Loading-Content-Error

另一个有用的工具是DataBinding。它能使ViewModel紧密耦合,双向绑定。

使用Mosby来解释MVP

首先设计我们要展示内容的模型类

1
2
3
4
5
6
7
public final class ModelRepoDetail {
public final RepoEntity repo; // the repo to display

public ModelRepoDetail(final RepoEntity poRepo) {
repo = poRepo;
}
}

现在我们定义了对应的View接口

1
2
3
public interface ViewRepoDetail extends MvpLceView<ModelRepoDetail> {
void showEmpty();
}

下一步是定义Presenter

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
@AutoInjector(ApplicationAndroidStarter.class) // to automatically add inject method in component
public final class PresenterRepoDetail extends MvpBasePresenter<ViewRepoDetail> {

//region Injected fields
@Inject
DAORepo daoRepo; // we need the DAO to load the repo from its ID
//endregion

//region Fields
private Subscription mSubscriptionGetRepo; // the RxJava subscription, to destroy it when needed
//endregion

//region Constructor
public PresenterRepoDetail() {
// inject necessary fields via the component
ApplicationAndroidStarter.sharedApplication().componentApplication().inject(this);
}
//endregion

//region Visible API
public void loadRepo(final long plRepoId, final boolean pbPullToRefresh) {
if (isViewAttached()) {
getView().showLoading(pbPullToRefresh);
}
// get repo asynchronously via RxJava
rxGetRepo(plRepoId);
}

public void onDestroy() {
// destroy the RxJava subscribtion
if (mSubscriptionGetRepo != null) {
mSubscriptionGetRepo.unsubscribe();
mSubscriptionGetRepo = null;
}
}
//endregion

//region Reactive job
private void rxGetRepo(final long plRepoId) {
mSubscriptionGetRepo = getDatabaseRepo(plRepoId)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
(final RepoEntity poRepo) -> { // onNext
if (isViewAttached()) {
getView().setData(new ModelRepoDetail(poRepo));
if (poRepo == null) {
getView().showEmpty();
} else {
getView().showContent();
}
}
},
(final Throwable poException) -> { // onError
mSubscriptionGetRepo = null;
if (isViewAttached()) {
getView().showError(poException, false);
}
},
() -> { // onCompleted
mSubscriptionGetRepo = null;
}
);
}
//endregion

//region Database job
@RxLogObservable
private Observable<RepoEntity> getDatabaseRepo(final long plRepoId) {
return daoRepo.rxQueryForId(plRepoId);
}
//endregion
}

主要代码放在rxGetRepo方法中。它从数据库加载数据,然后刷新UI

我们来看一下FragmentRepoDetail

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
118
119
120
121
122
123
124
125
126
127
128
@FragmentWithArgs
public class FragmentRepoDetail
extends MvpFragment<ViewRepoDetail, PresenterRepoDetail>
implements ViewRepoDetail {

//region FragmentArgs
@Arg
Long mItemId;
//endregion

//region Fields
private Switcher mSwitcher;
//endregion

//region Injected views
@Bind(R.id.FragmentRepoDetail_TextView_Empty)
TextView mTextViewEmpty;
@Bind(R.id.FragmentRepoDetail_TextView_Error)
TextView mTextViewError;
@Bind(R.id.FragmentRepoDetail_ProgressBar_Loading)
ProgressBar mProgressBarLoading;
@Bind(R.id.FragmentRepoDetail_ContentView)
LinearLayout mContentView;
//endregion

//region Data-binding
private FragmentRepoDetailBinding mBinding;
//endregion

//region Default constructor
public FragmentRepoDetail() {
}
//endregion

//region Lifecycle
@Override
public void onCreate(final Bundle poSavedInstanceState) {
super.onCreate(poSavedInstanceState);
FragmentArgs.inject(this);
}

@Override
public View onCreateView(final LayoutInflater poInflater, final ViewGroup poContainer,
final Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(poInflater, R.layout.fragment_repo_detail, poContainer, false);
return mBinding.getRoot();
}

@Override
public void onViewCreated(final View poView, final Bundle poSavedInstanceState) {
super.onViewCreated(poView, poSavedInstanceState);

ButterKnife.bind(this, poView);

mSwitcher = new Switcher.Builder()
.withEmptyView(mTextViewEmpty)
.withProgressView(mProgressBarLoading)
.withErrorView(mTextViewError)
.withContentView(mContentView)
.build();

loadData(false);
}

@Override
public void onDestroyView() {
super.onDestroyView();

ButterKnife.unbind(this);

if (mBinding != null) {
mBinding.unbind();
mBinding = null;
}
}
//endregion

//region MvpFragment
@Override
public PresenterRepoDetail createPresenter() {
return new PresenterRepoDetail();
}
//endregion

//region ViewRepoDetail
@Override
public void showEmpty() {
mSwitcher.showEmptyView();
}
//endregion

//region MvpLceView
@Override
public void showContent() {
mSwitcher.showContentView();
}

@Override
public void showLoading(final boolean pbPullToRefresh) {
mSwitcher.showProgressView();
}

@Override
public void showError(final Throwable poThrowable, final boolean pbPullToRefresh) {
mSwitcher.showErrorView();
}

@Override
public void setData(final ModelRepoDetail poData) {
mBinding.setRepo(poData.repo);

final Activity loActivity = this.getActivity();
final CollapsingToolbarLayout loAppBarLayout = (CollapsingToolbarLayout) loActivity.findViewById(R.id.ActivityRepoDetail_ToolbarLayout);
if (loAppBarLayout != null) {
loAppBarLayout.setTitle(poData.repo.url);
}
}

@Override
public void loadData(final boolean pbPullToRefresh) {
if (mItemId == null) {
mSwitcher.showErrorView();
} else {
getPresenter().loadRepo(mItemId.longValue(), pbPullToRefresh);
}
}
//endregion
}

当中的一些注解会在下一节中解释

我们来看一下对应的布局

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
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="repo"
type="fr.guddy.androidstarter.database.entities.RepoEntity"/>
</data>

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/FragmentRepoDetail_TextView_Empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/empty_repo"/>

<TextView
android:id="@+id/FragmentRepoDetail_TextView_Error"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/error_repo"/>

<ProgressBar
android:id="@+id/FragmentRepoDetail_ProgressBar_Loading"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>

<LinearLayout
android:id="@+id/FragmentRepoDetail_ContentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/FragmentRepoDetail_TextView_Name"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{repo.name}"
android:textIsSelectable="true"/>

<TextView
android:id="@+id/FragmentRepoDetail_TextView_Location"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{repo.location}"
android:textIsSelectable="true"/>

<TextView
android:id="@+id/FragmentRepoDetail_TextView_Url"
style="?android:attr/textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{repo.url}"
android:textIsSelectable="true"/>
</LinearLayout>
</FrameLayout>
</layout>

多亏了MVP架构,我们收获良多

  • 独立的类来加载和展示对应的数据:Presenter
  • 要展示的数据: Model
  • 展示的方式: View

###写更轻量的类

####注解和注解处理器的好处
android-aptAndroid 开发的一大进步。

它允许开发者在gradle文件中配置编译时的注解处理。很多库用它来生成模板代码。

####Butter Knife
####FragmentArgs
####IntentBuilder
####Icepick
####OnActivityResult
####Project Lombok
####Switcher

###Testing
frutilla
####Fluent assertions

  • truth
  • AssertJ
  • AssertJ Android

####Mocking
####UI testing
####Code coverage

1
2
3
4
5
6
7
android {
buildTypes {
debug {
testCoverageEnabled = true
}
}
}

###Code quality

###Relevant libraries