Joetube

业精于勤荒于嬉


  • 首页

  • 标签

  • 归档

MotionEvent事件分发机制解读

发表于 2018-01-30

阅读提示

开篇强调,事件与手势动作(点击、滑动等)是有区别,例如:一个滑动事件以ACTION_DOWN事件开始,经历多个ACTION_MOVE事件,最后以ACTION_UP事件结束。而分发机制讲述的主体是事件,没这点的意识的话,理解起来估计就没完没了。还有要理解super关键字并不是父ViewGroup(大多时候ViewGroup中的super很有可能指的就是View这个java类)。

关键函数

说到点击事件的分发怎么能不提到这三个函数:

public boolean dispatchTouchEvent(MotionEvent ev)

public boolean onInterceptTouchEvent(MotionEvent ev)

public boolean onTouchEvent(MotionEvent ev)

主体内容

dispatchTouchEvent来源

其中最难理解的也就是dispatchTouchEvent了,而且dispachTouchEvent描述了整个事件的分发与处理流程。

当点击屏幕时,事件最先传递给Activity的dispatchTouchEvent(MotionEvent ev),下面我们开始分析源码:

源码:Activity#dispatchTouchEvent

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

/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}


......
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}

显然,从内容上看onUserInteraction();并不影响事件的传递,它只是可以在一个新手势开始(ACTION_DOWN事件发生)时,在事件传递开始前做最优先的事情。

下面会执行getWindow().superDispatchTouchEvent(ev)操作,那么getWindow()又是啥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
......
mWindow = new PhoneWindow(this, window, activityConfigCallback);
......

/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}

再进入AndroidXRef,找到PhoneWindow.java:

1
2
3
4
5
6
7
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
....
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

咋样?是不是想到了传说种的Activity->PhoneWindow->DecorView。且慢,我们的目的时“事件分发机制”。那么重新找到DecorView.java:

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

而DecorView extends FrameLayout,现在知道为啥有的文章说“所有的布局都在一个FrameLayout里面”了吧?其实就是DecorView,FrameLayout extends ViewGroup,简单梳理就会知道这个dispatchTouchEvent(event)就是在ViewGroup种实现的。

最终还是看ViewGroup怎么实现这个dispatchTouchEvent(event);ViewGroup#dispatchTouchEvent(MotionEvent ev),最终返回true的话,Activity#dispatchTouchEvent(MotionEvent ev)函数就直接返回了,Activity#onTouchEvent(ev)也不会被调用,如果最终返回为false的话则Activity#onTouchEvent(ev)执行,为什么要加“最终”二字。请听我娓娓道来,前方高能。

源码解析

dispatchTouchEvent源码

没错,源码(为了便于理解,这段源码有一点点的删除,阅读的时候可直接跳过这一段源码,因为我会在后面具体分析):ViewGroup#dispatchTouchEvent(MotionEvent ev):

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

public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
.....

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//第一段-----------------------------------------------------------START
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//第一段-----------------------------------------------------------END
//第二段-----------------------------------------------------------START
if (!canceled && !intercepted) {

// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;

if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}

if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}

// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
}
}
//第二段-----------------------------------------------------------END
//第三段-----------------------------------------------------------START
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}

}
predecessor = target;
target = next;
}
}
//第三段-----------------------------------------------------------END
return handled;
}

年轻人,我知道你不想看源码,我也很无奈。

第一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}

从备注与函数名上理解,开始只是做一些重置初始工作(99.9%情况下,我们要对Google的工程师足够的相信,不然你会越陷越深)。

然后,会在判断里面看到onInterceptTouchEvent(ev),嗯,终于看到第二个函数了。那么什么情况下会调用这个函数呢?disallowIntercept从字面和后面的赋值理解,这是通过mGroupFlags字段,不允许ViewGroup拦截,通过语句判断如果ACTION_DOWN不予许拦截成立的话(!disallowIntercept == false),所以intercepted = false,(分析时避免情况复杂化,我们要猜一些变量或者函数的意义,比如:!canceled应该就时动作没被取消,正常情况下该条件是成立的)所以,会进入执行第二段。

第二段

接下来分析一下这个”mChildrenCount”:

1
2
3
4
5
// Child views of this ViewGroup
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
private int mChildrenCount;

现在,大概可以看出什么意思了吧?mChildren是指ViewGroup里面的所有子View,而mChildrenCount指的是能所有有效的ViewGroup里子View的数量,好了,我们接着执行第二段。可以看出,如果没有有效的子View,则会直接进入第三段。如果有有效的子View,且满足传递的要求(这一点可以通过备注看出,各种函数名也表面),则会在该子View执行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),再看看这个函数名:

1
2
3
4
5
6
7
8
9
10
11

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits){
//各种判断各种处理以后都会成为这种结构
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return hanled;
}

啥意思?子View不为空,则子View继续分发事件,否则执行super(赌一包辣条,这里的super就是View)的dispatchTouchEvent,所以接下来看看View的dispatchTouchEvent函数:
源码:View#dispatchTouchEvent

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
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {

if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}
}

if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}

// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}

return result;
}

熟悉不?你有没有OnTouchListener啊,要是有的话,你onTouch返回了啥东东啊?给我看看,我好决定怎么处理onTouchEvent,这里就清楚onTouch和onTouchEvent了吧!!!关于传递—>处理的流程就到这里了。接下来看看返回值对后续的过程影响。

很明显,onTouch与onTouchEvent任意一个返回true(从代码可见得:onTouch返回true后,onTouchEvent是没有机会执行的),都将成为其所在的ViewGroup的dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)的返回值,如果返回false则,会使得mFirstTouchTarget == null,直接执行第三段的handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);。明显进入[onTouch], onTouchEvent过程handle的返回值将直接依赖这两个(或没有设置onTouch监听就是一个)的返回值,从这里我们知道父ViewGroup中的[onTouch], onTouchEvent也调用了。如果返回true,将会走进addTouchTarget(child, idBitsToAssign):

源码:ViewGroup#addTouchTarget

1
2
3
4
5
6
7
8
9
10
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}

第三段

mFirstTouchTarget == null时,上面已经分析。

但是在上述条件不成立的情况下这里mFirstTouchTarget被赋值后,其他非ACTION_DOWN事件将继续通过dispatchTransformedTouchEvent传递到子View,并且。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}

}
predecessor = target;
target = next;
}

大致流程就是这样分析的,未完待续。。。。。。

小结

这篇文章是看完《Android开发艺术探索》后自己跟着源码一步步来走的,看完书以后其实只是一个轮廓,然后自己看源码后映像深刻了些,但总是感觉不放心,编程路上一位导师曾经跟我说过:当你觉得你懂了得时候,你就该写下来。当你觉得你能写明白得时候,去找一个人然后把他教会。这时候你才是真正得懂。

遗憾由于水平有限,连写都没能写得明白,但是写得过程中确实也有了不少得收获,但也不该只满足于此。

有人说对于源码得追究其实不该太过钻牛角,然而还是希望尽量满足自己得好奇心,待续。。。

Hexo-theme-next日常操作

发表于 2018-01-29

图片不知道放哪?

  对爬坑不感兴趣的,可直接跳过分析直接看总结。

  主题自带一个文件目录( 项目名/public/images ),但为了导致手欠造成破坏,所以在public目录下新建pic文件夹,然后在<image/>使用src="/pic/xxxx.jpg"(由文章的URL可以知道pulic这货才是站点根目录)。
  且慢,此处有坑!!!当运行hexo clean的 时候会发生什么?

1
2
3
D:blog> hexo clean
INFO Deleted database.
INFO Deleted public floder.

So what ?运行这个命令导致public文件被删除。。。
那么问题来了,/public只是临时文件,那上面的做法就是滚犊子。

  好了,开始正文。从上面的情况来看,我们只是修改了临时文件而已,那临时文件是怎么产生的呢?
找到/public里一个识别性较高的文件,全局搜索,啊哈!在/hexo-theme-next里有一个/source,内容和/public里的内容简直一模一样,在/source里添加一个pic目录? 然后执行hexo clean? 再执行完hexo clean与hexo g后,/public下果然出现了一个pic目录,再放点其他东西进去??? 完全可行

  总结:在主题目录/hexo-theme-next/source下的内容都会以复制的方式放入临时公共资源public目录下。所以样式,图片自定义操作基本可以靠操作这个文件来实现。

自定义样式?

  主题内容的样式太单一?想使用主题内部样式又没有文档?每次缩进还要 &emsp;?

  太苦了!强迫症、懒人基因、程序思维、职业修养。。。不能自定义的都是耍流氓。

  打开主题目录(themems/hexo-theme-next),进入css目录,再看看main.styl末尾~,我靠!竟然是custom~ custom~ custom~ ,别告诉我你一点想法也没有,再进入@import "_custom/custom";所指向的文件>>>>空的<<<<,没错这就是留给你自定义样式的。

  别急,还有!!!怎么自定义它解析的MarkDown标签?
  打开你的丑到不能直视的网页(你用MarkDown写的那种,别打开首页,然后告诉我都是泡沫!!),按下F12,偷偷喵喵一眼你写的标签被加了那些class,再custom.styl中重写样式即可。还记得_custom\custom是最后import的么?哪如果没意外的话_custom\custom中自定义的样式就会覆盖主题原来的样式。

VelocityTracker和Scroller实践

发表于 2018-01-29

VelocityTracker

使用注意事项

VelocityTracker 资源在使用完毕后需要释放,通常在 MotionEvent.ACTION_CANCEL && MotionEvent.ACTION_UP 后释放,释放调用如下方法:

1
2
velocityTracker.clear();
velocityTracker.recycle();

但是需要注意的是,掉用后会发生异常(回收任务已在线程池中),所以需要在调用前加!= null判断,在调用后将velocityTracker置空,因为每次使用后都会置空回收,因此在每次启用时都要初始化,而不是只初始化一次(例如:放在onCreate中进行初始化)。

还有如果要追踪MotionEvent.ACTION_MOVE,则需要在每次MotionEvent.ACTION_DOWN 和 MotionEvent.ACTION_MOVE时调用addMovement(event)。

具体代码如下:

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
@Override
public boolean onTouchEvent(MotionEvent event) {
if(tracker == null){
tracker = VelocityTracker.obtain();
}
tracker.addMovement(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPointId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
tracker.computeCurrentVelocity(1000, ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
final float xVelocity = tracker.getXVelocity(mPointId);
final float yVelocity = tracker.getYVelocity(mPointId);
Log.i(TAG, String.format("xVelocity = %f, yVelocity = %f", xVelocity, yVelocity));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if(tracker != null){
tracker.clear();
tracker.recycle();
tracker = null;
}
break;
default:
break;
}
return super.onTouchEvent(event);
}

Scroller

注意事项

Scroller所做的事情只是将View中的内容移动位置移动,而非View本身,如果需要移动View则应该考虑在其所在的ViewGroup中使用Scroller。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scroller = new Scroller(context);
....
public void smoothScrollTo(int destX, int destY){
int scrollX = getScrollX();//先推导一个方向,再同理推导
int scrollY = getScrollY();
int deltaX = destX + scrollX;
int deltaY = destY + scrollY;
scroller.startScroll(scrollX, scrollY, -deltaX, -deltaY, 1000);
// Log.i(TAG, String.format("scrollX = %d, scrollY = %d, deltaX = %d, deltaY = %d", scrollX, scrollY, deltaX, deltaY));
invalidate();
}
...
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(), scroller.getCurrY());
invalidate();
}
}

注意事项,在View.invalidate()中又注释到:

1
2
3
4
5
6
7
8
9
10
11
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
}

而onDraw(Canvas canvas)只在draw(Canvas canvas)里有调用,所以实际上draw(Canvas canvas),而draw(Canvas canvas)会调用computeScroll()方法,所以就会一直scrollTo(scroller.getCurrX(), scroller.getCurrY());,直到条件返回为false。

至于deltaX与deltaY的计算,建议先从单个坐标(另外一个置零最好)分析。

AndroidStudio日常较量

发表于 2018-01-29

问题:the sdk plat-form tools revision (25.0.3) is too old to check APIs

   分析:compile ‘com.android.support:appcompat-v7:26.+’引入了高版本包,而本地的Android SDK Platform-Tools

   解决1:分别降低目标SDK版本targetSdkVersion 和编译版本compileSdkVersion,最好能保持compileSdkVersion和targetSdkVersion一致,降低引入包的版本为:compile ‘com.android.support:appcompat-v7:25.+’(25为我选择的Version)

  解决2(推荐):SDK Manager->SDK Tools->Android SDK Platform-Tools 勾选更新,重启Android Studio

问题:在Debug模式下,Android Studio生成的debug证书与第三方所提交的证书不一致,导致第三方调用失败

解决:在gradle中添加debug配置(顺便吐槽一句),清空缓存重新编译。点击Android Studio右下角的Event Log即可看到Gradle任务执行日志。

加载器建立过程中的一些问题

发表于 2018-01-28

调试记录

  Bitmap网络加载时,若对图片先使用BitmapFactory.decodeStream非全部加载测量,则会导致InputStream起始位置移动,影响下一步的对流的解码,因此解码前需要重置,但是重置的最大限度需要控制(多了会浪费,少了会崩溃),所以网上有采用decodeByteArray解码比特数组,而比特数组内容由ByteArrayOutputStream而得(toByteArray调用的就是Array.copy(byte[], int length), ByteArrayOutputStream实质维护一个大小可扩的byte[]),虽然避免维护readlimit,但是运行时暂时会占用两个图片内存,庆幸的是这两个内存只是局部变量能GC掉。而《Android开发艺术探索》则是直接直接采用BitmapFactory.decodeStream(InputStream),没有测量的直接全部加载。关于BitmapFactory.decode
    关于加载网络图片前是否测量,本人看法如下:
    测量的目的是为了避免内存消耗过大导致OOM,本地图片的话,可以通过文件描述符获取大小后,再进行有必要的内存分配。而采用ByteArrayOutputStream硬生生是多了两个byte[],而且大小等于原始资源图片大小,如果有OMM的话,这个方案恐怕会首当其冲,但是在测试中还没发现OMM。所以选择直接采用BitmapFactory.decodeStream(InputStream),简单有效,这只是个人的猜测,具体问题我已经在Stackoveflow中提出,望各位不吝赐教。
    关于Buffer缓冲:

    不带缓冲是,每读一个字节就要写一个字节,对磁盘IO操作频繁效率低;带缓冲,一次多个字节,先写进内存然后等填满后,批量写入磁盘提高效率。

    InputStream 、 InputStreamReader 、 BufferedReader餐补,

  温故而知新,加强理解。

  缓冲输入流(BufferedInputStream)本质上是通过缓冲区中的数组实现的。在新建输入流对应的BufferedInputStream后,当缓冲区数据被读取完毕后,BufferInputStream会将该输入流的数据分批地填入到缓冲区中。缓冲区数据读取完毕后自行填充后继数据。

    错位?而BitmapFactory.decodeStream(InputStream is, Rect outPadding, Options opts)对InputStream进行读取将会使得流的起始位置发生向后移动,因此如果解码前先decodeStream测量,Buffer需要reset()重新设置读取InputStream的起始位置,并且需要在mark(int readlimit)进行控制。

单例模式

发表于 2018-01-27

背景

  最近的文章均围绕自定义的简易图片加载工具类–ImageLoad,进行学习和分析,源码暂时扣押(还没完全写完。又要搭环境、又要写文章、还要敲代码,还不给钱~~~要代码?   做梦去吧!)。

   别别别! 我开玩笑的,明天就给明天就给。

  源码送上,请各位帮帮Check Check。XXX0

  基于图片加载工具(ImageLoader)的特点:开辟线程池、创建维护图片缓存与磁盘缓存等,决定了它非单例不可。

推荐

  推荐文章–单例模式|菜鸟教程。不过你可能需要对synchronized进行了解,说到synchronized怎么能忘记volatile呢?

关于synchronized

  以后可能会写一篇,暂时推荐stackoverflow和Java 之 synchronized 详解。
  synchronized锁对象(this,非静态方法)或类(.class,静态方法),且记住static修饰的方法不属于任何对象而是与class处于同地位,所以同一个类中的synchronized静态方法与synchronized非静态方法可以异步执行。

关于volatile

  写作计划待定,暂推荐volatile,还有一篇大神的深入分析Volatile的实现原理,暂时还不敢恭维。

总结与比较

  饿汉式:在类装载的时候实例化,如果对象没有被用到那么实例对象所占的空间就是被浪费掉的。

  懒汉式:在需要使用时且发现没有实例对象时才实例化,即第一次使用时实例化,以后均使用这同一个对象。

  最后非常要注意的就是线程安全,以及通过synchronized加锁位置的不同在性能上为何会起到优化作用?

  单例模式此篇中,比较第2种Lazy(第2种方式暂标记为Lazy,实则DCL也是懒汉式)创建方式和第4种DCL方式,可以看出Lazy使用synchronized为static方法加上锁,故而当多个线程同时调用此方法时,都会进入同步等待状态。而DCL式则是在判断后再进入,当对象已存在后,多个线程调用此方法时将不会陷入同步排队等待之中。

掘金文章(Android)

发表于 2018-01-27

背景

  闲暇时间在帮助掘金社区挖掘优秀文章,挖掘的过程也是自我提高的过程。此片专注收录一些个人认为值得Mark的优秀文章(各平台均有)。

2018/01

  • Android性能优化
  • handler内存泄漏原因
  • 浅谈Android的文件存储
  • Android动画小结
  • UML刨析–类图关联和时序图

站点介绍

发表于 2018-01-27

2018-01-27

连续下雪,简直不要太美了。不想错误如此美景,所以决定在窗旁键盘前,摆好姿势,好好装个B,尼玛!冷得一匹。



先预热一哈。。。。。。。。好了,开始闲聊~



下午何时忘记了,终于撸完了简易的图片加载工具类(内存缓存、磁盘缓存、网络加载分阶段开发及讲述),对没错Demo级别的,再看看自己半年前撸的那个,感觉硬生生是搬运出来的,又想起老师告诫:视频无用(有些时候视频还是能有启蒙作用,就这个问题我们还互怼过),感慨万千,后不自觉在github上关注到了一位学弟(别误会),吃了一惊,WKF!后生可畏~,长江后浪推前浪啊!



然后在学弟的启发下(虽然内容有限,但是整理地很得当,为我省去了很多时间),建立了这一个站点, 站点的内容也主要是记录学习历程,历程就以这个工具类开始。


两年半前第一次对博客有个概念,两年前第一次用服务器搭建自己的wordpress,然后由于太过单纯,数据库既然被人留下了QQ号。后来又买了域名准备重新开始,并且做好备份。谁知。。。。,最终备案不通,线程阻塞,不了了之,只得将笔记写再OneNote上,也在其他的平台上画过一些把戏。





## 2018-01-29

  前几日在讨论交流的时候,看到一篇文章,具体内容是:一个“样样学会”的10年老码农,面临失业(市场能给的价格和自己期望的相差太远)。我对这篇文章的印象不是很深,但是讨论的时候一句话戳中了我。“有的人是10年经验,而有的人是10 x 1年”,再想想周围的人和事(当然,每个人的追求不同,所以我这里无任何批评之意),又何尝不是这样,十年经验不知进取,有问题不探索,知其然不知所以然。同一个问题遇到一遍搜索一遍,拷贝一遍,重复几次十几次几十次,看则十年经验,实则不到1-2年。游戏、新闻、短视频、武侠小说等等,十年浮躁。
  正如胡适的“你的空闲时间,决定了你的人生高度”,我并非批评这些娱乐方式,只是娱乐需要有限度,时间不应该被浪费,“有的人25岁就死了 只是到75岁才埋葬 我们究竟是活了365天 还是活了1天 重复了364遍 ​​​​”。

  献给明天的自己

## 2018-01-30

今天在啃《Android开发艺术探索》,妈耶!关于有些知识,我还以为我一直的想法是对的,结果特么原来的固有思维导致我在阅读的时候越读越难懂,后来摒弃已有的知识重新整理,感觉自己已是漏洞百出。还有现在感觉看源码的正确知识应该是:关键函数、函数及变量命名、注释,而是不是一味地函数跟踪(大佬可能有更多的建议,等我慢慢积累了再添加吧)。

## 2018-01-31

今天心情有点不太好,跑了1.5KM。然后心情就好了。。。

## 2018-02-10

和盼姐聊了一席话,感觉需要努力的地方实在时太多。故而计划在过年的这几天每天花3小时和盼姐学习英语。

## 2018-02-11

My dreaming-girl lied me that she would get married, I want to ignore it for not being hurted, but I don’t think I owe somebody nothing, so I replied this girl without take care of her purpose.

My sister pan told me that I had msut hurted someone deeply, and I am a bed gay, but the distance between us makes loving becoming fade. Sister pan asked me if there any prossible for me to decide to give my hands to this girl, I replied that if she come to ShangHai, I would, no ~ , I must give her my hands. I alse asked myself why i can’t go to her city .

Doing something for not being regret.

## 2018-03-01

春节,一直在和盼姐练习口语,虽然只有10来天,但是还是觉得受益匪浅。毕竟能做到跟人说英语不脸红了(虽然很蹩脚)。

2月份在家里的阅读量有所下降,可能是由于春节的原因,但基本还是做到了一月至少一本非专业书籍。为了毕设也看了一点Tensorflow,只是皮毛中的皮毛。至于android貌似近乎停滞,心中有愧。

在这一个半年里敲过了《第一行代码》、看完了《群英传》,然而关于《Android开发艺术探索》的咀嚼近日陷入了迷茫,看得似懂非懂,感觉实际的时候用到得并不多,看完了也不太熟,有些怀疑自己“假大空”,有问题还是要搜索,所以不敢再往下看了。幸得前辈指导,是自己性子没沉下来,眼高手低又急于求切了,所以现在要做得就是把这本书先嚼完,然后对照《群英传》与网络上得素材反复实践练习,对照《开发艺术探索》理解,循序渐进,做到“知其然,知其所以然”。

所以关于《Android 源码设计模式解析与实战》暂时搁置。

3月份目前的安排是完成毕设初稿,先稳定好整体节奏,然后将二月份遗留得《白说》阅读完毕,购买《偶遇》并在不影响论文进度得条件下,尽量阅读完毕(每天抽),关于Android得练习,暂时将上午得闲暇时间联系并阅读Android Training。

微笑着待人,微笑着生活

未完待续…

Joe Wang

Joe Wang

8 日志
6 标签
© 2018 Joe Wang
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4