1. Prepare for 2021(1)
Ii. Prepare for 2021(2)
13. View drawing process
Drawing starts with the performTraversals() method of the root View ViewRoot and traverses the tree from top to bottom. Each View control is responsible for drawing itself, and the ViewGroup is responsible for telling its children to draw. The core code for performTraversals() is as follows:
private void performTraversals() { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . PerformMeasure (childWidthMeasureSpec, childHeightMeasureSpec); . // Execute the layout process performLayout(LP, desiredWindowWidth, desiredWindowHeight); . // Execute the draw process performDraw(); }Copy the code
The transfer process of preformLayout and performDraw is similar to that of performMeasure, except that the transfer process of performDraw is implemented by dispatchDraw in the draw method
- Understand the MeasureSpec
MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec is a static inner class of the View class that says how the View should be measured
MeasureSpec is a MeasureSpec method that measures the size of the object by packaging the SpecMode and SpecSize into an int.
// Create a MeasureSpec(MeasureSpec) public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }Copy the code
- ViewGroup#addView(view), no LayoutParams for the child view, so when is LayoutParams generated
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
Copy the code
It can be seen that if the view is not set LayoutParams, through generateDefaultLayoutParams () method to generate a
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
Copy the code
The default LayoutParams gives the width and height wrAP_content
- Add the View XML
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
View result = root;
// Look for the root node.
int type;
// Find the root node
while((type = parser.next()) ! = XmlPullParser.START_TAG && type ! = XmlPullParser.END_DOCUMENT) {// Empty
}
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
rInflate(parser, root, inflaterContext, attrs, false);
} else {
/ / 1
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
/ / 2, 3
if(root ! =null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
/ / 4
rInflateChildren(parser, temp, attrs, true);
if(root ! =null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
/ / 5
return result;
}
Copy the code
Create a root View object temp from createViewFromTag.
2, if the root is empty, not just by the root. GenerateLayoutParams (attrs) method will be temp width and height attributes into LayoutParams set temp.
If root is null, the parent layout of Temp is not determined, and there is no need to set LayoutParams. LayoutParams will be set when it is added to other layouts.
4. Add all child Views of Temp using the rInflateChildren method.
5. Return the root view (temp must be included in the root view).
- Difference between getWidth() and getMeasuredWidth()
Generally at the time of custom controls getMeasuredWidth/getMeasuredHeight assignment of it in the View of the setMeasuredDimension, So you can see in the onMeasure method use getMeasuredWidth/getMeasuredHeight initialization parameter. GetWidth /getHeight is not assigned until onLayout is complete. In general, if both assignments are completed, the values are the same.
-
Why are onMeasure and onLayout executed twice or more
private void performTraversals() { ...... boolean newSurface = false; // If newSurface is true and performDraw cannot be called, scheduleTraversals if (! hadSurface) { if (mSurface.isValid()) { // If we are creating a new surface, then we need to // completely redraw it. Also, when we get to the // point of drawing it we will hold off and schedule // a new traversal instead. This is so we can tell the // window manager about all of the windows being displayed // before actually drawing them, so it can display then // all at once. newSurface = true; . }}... if (! cancelDraw && ! newSurface) { if (! skipDraw || mReportNextDraw) { ...... performDraw(); }} else {//viewVisibility is the View property of wm.add, If (viewVisibility == view.visible) {// Try again // Perform the scheduleTraversals again, That is, performTraversals scheduleTraversals(); } else if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); }}... }Copy the code
When the View is initialized, two performTraversals are called. The first performTraversals leads to the first two onMeasure calls and the first onLayout call. The latter performTraversals function results in the final onMeasure,onLayout and onDraw function being called
- View#draw method details
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || ! mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; // Step 1, draw the background, if needed int saveCount; if (! dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0; if (! verticalEdges && ! horizontalEdges) { // Step 3, draw the content if (! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (debugDraw()) { debugDrawFocus(canvas); } // we're done... return; }Copy the code
General steps of the DRAW process:
- Draw backgroud (drawBackground)
- If the colors fade, save the canvas layer to prepare for fading (not a necessary step)
- Draw the View’s content (onDraw method)
- Draw children (dispatchDraw method)
- If necessary, draw the color to fade, restore layer (not a necessary step)
- Draw decorators, such as scrollBar (onDrawForeground)
-
View onDraw is an empty implementation that is custom implemented by various types of views
-
DispatchDraw () controls the subview drawing, which is an empty implementation in the View, but has a specific implementation in the ViewGroup;
-
How to call onDraw() with a custom control that inherits ViewGroup
- Set WILL_NOT_DRAW to false with the setWillNotDraw method
- SetBackground setBackground
- How many ways can you customize a View
- Inherit View to override onDraw method
Mainly used for effects with irregular solid lines, that is, the effect is not easy to achieve through the combination of layout. You have to draw it yourself. To do this, you need to support wrap_content, and you need to handle the padding yourself
- Inherit from ViewGroup to derive a special Layout
This is mainly used to implement custom layouts that look like several views combined. This approach requires proper handling of the measurement and layout of the ViewGroup as well as the measurement and layout of the child elements. For example, you can customize an auto-wrap LinerLayout.
- Inheriting a particular View, such as a TextView
This method is mainly used to extend an existing View and add some specific functionality. This method is simpler and does not require wrap_content and padding support.
- Inherit a specific ViewGroup, such as a LinearLayout
This approach is also common and is similar to the second approach above, which is closer to the bottom of the View.
- Custom View needs to pay attention to
- Let the View support wrAP_content
Controls that directly inherit views and ViewGroups need to handle wrAP_content methods in onMeasure methods. The solution is to set a fixed size 2 in the case of wrap_content. Let the View support the padding
Controls that directly inherit from the View need to handle the padding in the onDraw method, otherwise the user setting of the padding property will not take effect. Control that directly inherits ViewGroup needs to consider the effect of the padding and child element margin in onMeasure and onLayout. Otherwise, the padding and child element margin will be invalid.
- Try not to use handlers in views
View already provides the POST family of methods, which can completely replace the role of Handler
- If there are threads or animations in the View, you need to stop them in time
The onDetachedFromWindow method on a View stops threads and animations because the View’s onDetachedFromWindow method is called when the View is removed or the Activity that contains the View exits. If left untreated, memory leaks are likely
-
When a View has sliding nesting, you need to handle sliding conflicts
-
Don’t create too many temporary objects in the View’s onDraw method, that is, new objects. Because the onDraw method is called frequently, a large number of temporary objects can cause memory jitter and affect the View’s performance
14. Distribution of events
Touch event-related details are encapsulated as MotionEvent objects
The main Touch events can be roughly divided into the following four types:
Motionevent.action_down: Press the event (the start of all events)
MotionEvent.ACTION_MOVE: Slide event
MotionEvent.ACTION_CANCEL: Terminates the event due to no human cause
Motionevent. ACTION_UP: Lift the event (corresponding to DOWN)
After a click event is generated, the order of delivery is: Activity (Window) -> ViewGroup -> View
- Source code analysis
- Event distribution for the Activity
When a click event occurs, the event is first passed to the Activity’s dispatchTouchEvent() for event distribution
public boolean dispatchTouchEvent(MotionEvent ev) {
// The event column usually starts DOWN, so this is basically true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// Focus 1
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// Focus 2
return onTouchEvent(ev);
}
Copy the code
Focus on 1
GetWindow () yields a Window object, the Window class is abstract, and PhoneWindow is the only implementation of the Window class
SuperDispatchTouchEvent (EV) is an abstract method. See what superDispatchTouchEvent() does in the PhoneWindow class
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor is an instance of DecorView
//DecorView is the top-level view of the view, inherited from FrameLayout, and is the parent class of all interfaces
}
Copy the code
The next see mDecor. SuperDispatchTouchEvent (event) :
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView inherits from FrameLayout
// Then its parent class is ViewGroup, and super.dispatchTouchEvent(event) should be dispatchTouchEvent() for ViewGroup.
}
Copy the code
From there, ViewGroup events are distributed
Concerns 2
When the viewGroup fails to deliver the event, the Activity will handle it itself
- 2.ViewGroup event distribution
ViewGroup dispatchTouchEvent() source code analysis, this method is more complex, intercept several important logical fragments to introduce, to parse the entire distribution process.
ACTION_DOWN has occurred or ACTION_DOWN has occurred and mFirstTouchTarget has been assigned
final boolean intercepted;
if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget! =null) {//disallowIntercept: Whether to disable event intercept (false by default)
/ / can in child View by calling requestDisallowInterceptTouchEvent method to modify this value, don't let the View to intercept events
final booleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)! =0;
// This method is entered by default
if(! disallowIntercept){// Call the interceptor method
intercepted=onInterceptTouchEvent(ev);
ev.setAction(action);
}else{
intercepted=false; }}else{
// Start continuously intercepting the touch when no targets is touched and the event is not Down.
intercepted=true;
}
Copy the code
The main content of this paragraph is to determine whether to intercept. If the current event is motionEvent.action_down, call the value of the ViewGroup onInterceptTouchEvent() method to determine whether to intercept it. If mFirstTouchTarget! ACTION_DOWN has already occurred, and the event has already been handled by a child View of the ViewGroup. Call the ViewGroup onInterceptTouchEvent() method. Determine whether to intercept. If it is neither of the above, that is, it is already a MOVE or UP event, and there is no object to process the previous event, set to True and start intercepting all subsequent events. This explains that if the child View’s onTouchEvent() method returns false, the next series of events will not be handled by the child View. If the VieGroup onInterceptTouchEvent() is true for the first time, then mFirstTouchTarget = null, then onInterceptTouchEvent() will not be called in the future and will be set to true.
When a ViewGroup does not intercept an event, the event is propagated down to its child views or viewgroups for processing.
/* Start from the bottom superview, ** find newTouchTarget (mFirstTouchTarget above) ** If find newTouchTarget already exists, it is receiving a touch event, then break the loop. * /
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);// If the current view cannot get user focus, skip this loop
if(childWithAccessibilityFocus ! =null) {
if(childWithAccessibilityFocus ! = child) {continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// If the view is not visible, or the touched coordinate point is not within the scope of the view, skip this loop
if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
// Start receiving touch events and exit the loop.
if(newTouchTarget ! =null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// If the touch position is in the child's region, the event is distributed to the child View or ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Get the TouchDown time
mLastTouchDownTime = ev.getDownTime();
// Get the TouchDown Index
if(preorderedList ! =null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break; }}}else {
mLastTouchDownIndex = childIndex;
}
// Get the x and y coordinates of TouchDown
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// Add TouchTarget, mFirstTouchTarget! = null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// represents and distributes to NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
Copy the code
DispatchTransformedTouchEvent () method is called the child element dispatchTouchEvent () method. Which dispatchTransformedTouchEvent () method is an important logic is as follows:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
Copy the code
Since the child passed is not empty, the dispatchTouchEvent() of the child element is called. If the child’s dispatchTouchEvent() method returns true, the mFirstTouchTarget is assigned and the for loop is broken out.
// Add TouchTarget, mFirstTouchTarget! = null.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// represents and distributes to NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
Copy the code
Where addTouchTarget(Child, idBitsToAssign); The internal completion mFirstTouchTarget is assigned. If mFirstTouchTarget is null, the ViewGroup will be allowed to intercept all operations by default. If you iterate through all the child views or viewgroups, there are no consumption events. The ViewGroup handles the event itself.
- 3. Event distribution of View
public boolean dispatchTouchEvent(MotionEvent event) {
if(mOnTouchListener ! =null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
Copy the code
First condition: mOnTouchListener! = null
//mOnTouchListener is assigned in the setOnTouchListener method of the View class
public void setOnTouchListener(OnTouchListener l) {
// Whenever we register a Touch event for the control, mOnTouchListener must be assigned a value (not null)
mOnTouchListener = l;
}
Copy the code
Second condition :(mViewFlags & ENABLED_MASK) == ENABLED
This condition determines whether the currently clicked control is enable
Since many views are enable by default, this condition is always true
Montouchlistener.ontouch (this, event)
If you return true in the onTouch method, all three conditions are true, and the whole method returns true directly.
If false is returned in the onTouch method, the onTouchEvent(event) method is executed.
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if(mTouchDelegate ! =null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true; }}// If the control is clickable, it will enter the next two lines of switch judgment;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
// If the current event is finger lift, it will enter the MotionEvent.ACTION_UP case.
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
booleanprepressed = (mPrivateFlags & PREPRESSED) ! =0;
// After a lot of judgment, the performClick() method is executed.
if((mPrivateFlags & PRESSED) ! =0 || prepressed) {
boolean focusTaken = false;
if(isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); }if(! mHasPerformedLongPress) { removeLongPressCallback();if(! focusTaken) {if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if(! post(mPerformClick)) { performClick(); }}}if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if(! post(mUnsetPressedState)) { mUnsetPressedState.run(); } removeTapCallback(); }break; . }// If the control is clickable, it must return true
return true;
}
// If the control is unclickable, it must return false
return false;
}
Copy the code
- Four,
For a ViewGroup, when an event is sent to the current ViewGroup, the dispatchTouchEvent method is called. OnInterceptTouchEvent is called to determine whether to intercept the current event. If onInterceptTouchEvent returns false, the current event will not be intercepted, and the event will continue to be passed to the child View of the current ViewGroup. If the child View is a ViewGroup, repeat the ViewGroup event distribution process. If the fruit View is a View, go to the View distribution process below.
If we set the onTouchListener for the current View, it will execute its callback method onTouch. The return value from this method determines whether the event should be passed on. If false is returned, the event has not been consumed, and will continue to be passed on. If true, the event has been consumed and no longer needs to be passed on. If false is returned, the current View’s onTouchEvent method will be executed. If we set the onLongClickListener listener for the current View, its callback method onLongClick will be executed first, similar to the onTouch method. If this method returns true to indicate that the event was consumed and not passed down, if it returns false, the event will continue to be passed down. For analysis purposes, we assume that false is returned, and if we set the onClickListener listener, its callback method onClick will be executed. This method has no return value, so it is the last method executed in our event distribution mechanism; One thing you’ll notice is that whenever your current View is clickable or LongClickable, the View’s onTouchEvent method returns true by default, which means that the View consumes the event when it’s delivered to the View. But that’s not the case with viewGroups;
Some points to note about the event distribution mechanism:
(1) If no View consumes a DOWN event other than the Activity, the event is no longer passed to the Activity’s child views. Instead, the Activity calls its onTouchEvent method to handle the event.
(2) : Once a ViewGroup decides to intercept an event, the remainder of the sequence of events is no longer handled by the child views of the ViewGroup, that is, events are stopped passing down at the ViewGroup layer. The onInterceptTouchEvent method will no longer be called for subsequent events;
(3) : If a View starts processing events but does not consume DOWN events, then subsequent events in the sequence of events will not be handled by the View.
(4) : Whether the View’s onTouchEvent method is executed depends on the return value of its onTouchListener callback method onTouch, onTouch returns true, onTouchEvent method is not executed; OnTouch returns false, the onTouchEvent method is executed, because onTouchEvent will execute onClick, so there is a relationship between the onClick and onTouch return value;
Event distribution mechanism:www.jianshu.com/p/a4f82b7a4…
Summary of Android event distribution
Event propagation (default) :
Touch the event down
1 Activity->dispatchTouchEvent
2 decorView->dispatchTouchEvent not analyzed in detail because we developers have no control over it
3 decorView->onInterceptTouchEvent
4 Developer ViewGroup->dispatchTouchEvent
Developer’s ViewGroup->onInterceptTouchEvent
6 View->dispatchTouchEvent
7 View->onTouchEvent
8 ViewGroup->onTouchEvent
9 decorView->onTouchEvent
10 activity->onTouchEvent
Touch event movement (consume directly in the Activity because there is no consumption above)
11 Activity->dispatchTouchEvent
12 decorView->dispatchTouchEvent
(onInterceptTouchEvent will not be emitted because the internal ‘mFirstTouchTarget·’ is null)
13 activity->onTouchEvent
Touch event lift (consume directly in the Activity because there is no consumption above)
14 Activity->dispatchTouchEvent
15 decorView->dispatchTouchEvent
(onInterceptTouchEvent will not be fired because the internal mFirstTouchTarget is empty)
16 activity->onTouchEvent
Remark:
1 If onInterceptTouchEvent returns true once in any ViewGroup, the onInterceptTouchEvent will not be called after the touch event. Suppose the touch event is
① Press ② move ③ move ④ release
If onInterceptTouchEvent returns false on the (1) down event, then (3) moving and (4) releasing will not call onInterceptTouchEvent.
2. If dispatchTouchEvent returns false when the ViewGroup touches down then dispatchTouchEvent does not receive subsequent events.
① Press ② move ③ move ④ release
If the above four events ① return false, ②③④ will receive no notification.
3 If a ViewGroup returns true on a dispatchTouchEvent touch, it will receive a postordered event, even if you return false on a move event, as long as you return true on a dispatchTouchEvent touch
① Press ② move ③ move ④ release
If the above four events (①) return true and (②) return false, you will still receive event notifications (③ and ④).
The ViewGroup dispatchTouchEvent will call onInterceptTouchEvent. If it returns true, it will call its parent View’s dispatchTouchEvent, that is, its onTouchEvent . If onInterceptTouchEvent returns false, the dispatchTouchEvent given to the child layout class determines its own dispatchTouchEvent return value. If dispatchTouchEvent returns false, then the parent of the ViewGroup (View, the parent of the ViewGroup) is called. The dispatchTouchEvent of super.dispatchTouchEvent) determines the return value.
5 Interception returns true. Suppose we have the following situation
① Press ② move ④ release
View in dispatchTouchEvent directly returned to the super dispatchTouchEvent (ev) | | true ViewGroup onInterceptTouchEvent method if it is moving events then returns true, Other touch events false
So the sequence of events is zero
Touch the event down
1 Activity->dispatchTouchEvent
2 decorView->dispatchTouchEvent
3 decorView->onInterceptTouchEvent
5. Developer’s ViewGroup->onInterceptTouchEvent Returns false
6 View->dispatchTouchEvent Returns true
7 View->onTouchEvent
Touch event movement
8 Activity->dispatchTouchEvent
9 decorView->dispatchTouchEvent
10 decorView->onInterceptTouchEvent
11 viewGroup->onInterceptTouchEvent Returns true. If true is returned, subsequent events will not be called. Note that dispatchTouchEvent does not affect subsequent events
12 View->dispatchTouchEvent will receive a touch event of Cancel
13 View->onTouchEvent will receive a touch event of Cancel. If there’s a listener click event it’s not going to be handled when it’s lifted, so if you set the listener click event it’s not going to be called back
Touch event lift (consume directly in the Activity because there is no consumption above)
14 Activity->dispatchTouchEvent
15 decorView->dispatchTouchEvent
16 decorView->onInterceptTouchEvent
17 viewGroup->dispatchTouchEvent does not call intercepting methods. The intercepting method returns true after the sequential event is no longer judged in the interception
18 viewGroup->onTouchEvent
6 ViewGroup dispatchTouchEvent will not be received if false is returned from dispatchTouchEvent. Postsequence events can still be received if the press returns true and the moved event returns false
7 When a ViewGroup dispatchTouchEvent returns true and does not call super.dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent will not be called
8 under press event of ViewGroup dispatchTouchEvent returns true | |. Super dispatchTouchEvent If onInterceptTouchEvent returns true, onTouchEvent is called directly and subsequent events are dispatchTouchEvent to onTouchEvent instead of passing onInterceptTouchEvent
9 Activity The onTouchEvent of “dispatchTouchEvent” will only be called if the child View’s “dispatchTouchEvent” returns false, even if it returns false (The pressed event is a new touch event), and the activity’s onTouchEvent is not called back
- Use analytical
OnInterceptTouchEvent Returns true when a ViewGroup does not want its child View to call a touch event. If you want to consume onInterceptTouchEvent returns true and dispatchTouchEvent returns true
- How to solve the sliding conflict, and in which method
There are two common ways to handle sliding conflicts
The first is to handle the interception rule through the outer View, writing the interception logic in the outer View. That is, if the parent container needs the event, it intercepts the current event, if it doesn’t, it doesn’t, and the child control will accept and intercept the event.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if(The parent container requires this click event) intercepted =true;
else
intercepted = false;
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastXintercept = x;
mLastYintercept = Y;
return intercepted;
}
Copy the code
The second approach is through requestDisallowInterceptTouchEvent (true) method, the event passed on ignore pay onInterceptTouchEvent method of container, all events are passed to the child controls, internal logic processing in child controls. If the child control needs this event, it consumes it, otherwise it passes it on to the parent container.
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mlastX;
int deltaY = y - mlastY;
if(the parent container need this click events) getParent (). RequestDisallowInterceptTouchEvent (false);
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mlastX = x;
mlastY = Y;
return super.dispatchTouchEvent(ev);
}
Copy the code
A few issues to pay attention to
- although
requestDisallowInterceptTouchEvent
It works anywhere, but we’re used to writing ondispatchTouchEvent
Method, after all, is responsible for event distribution. - The parent ViewGroup cannot intercept DOWN events. The interception status of MOVE or UP events depends on the situation
- How to determine the sliding direction
You can determine which axis you’re moving along by comparing the distance you’re moving along the X axis and the distance you’re moving along the Y axis
public boolean onTouchEvent(MotionEvent event) {
// Go back to the starting coordinate when triggered
float x= event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// Store the coordinates when pressed
downX = x;
downY = y;
break;
case MotionEvent.ACTION_UP:
// Get the distance difference
float dx= x-downX;
float dy = y-downY;
// Prevent is pressed also judge
if (Math.abs(dx)>8&&Math.abs(dy)>8) {
// Determine the direction by distance difference
int orientation = getOrientation(dx, dy);
}
break;
}
return super.onTouchEvent(event);
}
private int getOrientation(float dx, float dy) {
if (Math.abs(dx)>Math.abs(dy)){
/ / move the X axis
return dx>0?'r':'l';
}else{
/ / Y axis movement
return dy>0?'b':'t'; }}Copy the code
15. Apk packaging process
- Compile packaging steps
- Package the resource file to generate the R.java file
- Process the AIDL file and generate the corresponding Java file
- Compile the project source code to generate a class file
- Convert all the class files to generate the classes.dex file
- Package to generate APK files
- Sign the APK file
- Align the signed APK file
The main process of alignment is to offset all resource files in the APK package to integer multiples of 4 bytes from the start of the file so that APK files can be accessed faster through memory mapping. The purpose of alignment is to reduce runtime memory usage.
- The role of the aapt
The aAPT tool compiles the resource file and generates a resource. Arsc file, which is equivalent to a file index table and records a lot of information about the resource.
Implementation principle of LocalBroadcastReceiver
Broadcastmanager is an application that broadcasts broadcasts only within the application itself.
- Broadcasts sent from it will only spread within your own App, so you don’t have to worry about leaking private data
- Other apps can’t send this broadcast to your App, because your App can’t receive this broadcast from someone other than your App, so you don’t have to worry about security vulnerabilities to exploit it
- More efficient than the global broadcast of the system
The important part:
It has two internal classes, ReceiverRecord and BroadcastRecord
2. It contains three collections to manage
3. It has an internal Handler object
Let’s start with the LocalBroadcastManager constructor:
private static LocalBroadcastManager mInstance; private final Handler mHandler; public static LocalBroadcastManager getInstance(Context context) { synchronized (mLock) { if (mInstance == null) { mInstance = new LocalBroadcastManager(context.getApplicationContext()); } return mInstance; } } private LocalBroadcastManager(Context context) { mAppContext = context; mHandler = new Handler(context.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_EXEC_PENDING_BROADCASTS: executePendingBroadcasts(); break; default: super.handleMessage(msg); }}}; }Copy the code
The Handler constructor creates a Handler object. The Handler object passes in a Looper object called MainLooper, meaning that the Handler’s worker thread is the main thread.
BroadcastRecord (BroadcastRecord) {BroadcastRecord (BroadcastRecord); ReceiverRecord (BroadcastRecord) {BroadcastRecord (BroadcastRecord);
private static class ReceiverRecord {
final IntentFilter filter;
final BroadcastReceiver receiver;
booleanbroadcasting; ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) { filter = _filter; receiver = _receiver; }... }private static class BroadcastRecord {
final Intent intent;
finalArrayList<ReceiverRecord> receivers; BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) { intent = _intent; receivers = _receivers; }}Copy the code
ReceiverRecord is a BroadcastReceiver (BroadcastReceiver) and its corresponding IntentFilter. BroadcastRecord is a set of receiverRecords that match broadcastReceivers. An Intent can be received by multiple broadcastReceivers if its Action rules are the same.
- Register broadcast method source:
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
synchronized (mReceivers) {
// Create a ReceiverRecord object, passing in our broadcast receiver and the filter for that broadcast receiver
ReceiverRecord entry = new ReceiverRecord(filter, receiver);
// Get the IntentFilter set for this broadcast receiver
ArrayList<IntentFilter> filters = mReceivers.get(receiver);
if (filters == null) {
// Create one if it does not exist
filters = new ArrayList<IntentFilter>(1);
mReceivers.put(receiver, filters);
}
// Add this filter to the set of rules for the broadcast receiver
filters.add(filter);
for (int i=0; i<filter.countActions(); i++) {
// Then iterate over the filter to get its Action
String action = filter.getAction(i);
ArrayList<ReceiverRecord> entries = mActions.get(action);
if (entries == null) {
entries = new ArrayList<ReceiverRecord>(1);
mActions.put(action, entries);
}
ArrayList
map Action to ArrayList
entries.add(entry); }}}Copy the code
The registerReceiver() method does two things:
1. Add the specified IntentFilter rule to the incoming broadcast receiver
IntentFilter (ArrayList); IntentFilter (ArrayList); IntentFilter (ArrayList)
- UnregisterReceiver () method:
public void unregisterReceiver(BroadcastReceiver receiver) {
synchronized (mReceivers) {
// Remove the object whose key is receiver from the mReceivers table
ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
if (filters == null) {
return;
}
// Retrieve all actions in the filter of this broadcast receiver, then in the mActions table, obtain the corresponding set of broadcast receivers for this action, then determine whether there are any receivers we need to remove, if there are, remove them
for (int i=0; i<filters.size(); i++) {
IntentFilter filter = filters.get(i);
for (int j=0; j<filter.countActions(); j++) {
String action = filter.getAction(j);
ArrayList<ReceiverRecord> receivers = mActions.get(action);
if(receivers ! =null) {
for (int k=0; k<receivers.size(); k++) {
if(receivers.get(k).receiver == receiver) { receivers.remove(k); k--; }}if (receivers.size() <= 0) {
mActions.remove(action);
}
}
}
}
}
}
Copy the code
The unregisterReceiver() method also does two things:
1. Remove broadcast receivers from the mReceivers table
2. Remove the broadcast receiver from the mActions table
- SendBroadcast () method
public boolean sendBroadcast(Intent intent) {
synchronized (mReceivers) {
final String action = intent.getAction();
final String type = intent.resolveTypeIfNeeded(
mAppContext.getContentResolver());
final Uri data = intent.getData();
final String scheme = intent.getScheme();
final Set<String> categories = intent.getCategories();
final booleandebug = DEBUG || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) ! =0);
// Get the set of all broadcast receivers for our Intent Action
ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
if(entries ! =null) {
ArrayList<ReceiverRecord> receivers = null;
// Then iterate over the collection to match
for (int i=0; i<entries.size(); i++) {
ReceiverRecord receiver = entries.get(i);
if (receiver.broadcasting) {
if (debug) {
Log.v(TAG, " Filter's target already added");
}
continue;
}
// When action, type, scheme, data, and categories are all identical, the match is successful
int match = receiver.filter.match(action, type, scheme, data,
categories, "LocalBroadcastManager");
if (match >= 0) {
// The match is successful
if (receivers == null) {
receivers = new ArrayList<ReceiverRecord>();
}
// Then add the matched ReceiverRecord object to a collection
receivers.add(receiver);
receiver.broadcasting = true;
} else{}}if(receivers ! =null) {
for (int i=0; i<receivers.size(); i++) {
receivers.get(i).broadcasting = false;
}
ReceiverRecord objects are added to mPendingBroadcasts, and broadcasts are received one by one by iterating over mPendingBroadcasts
mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
if(! mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {// This is very important because we know from the beginning that we are creating a handler in the constructor, and the method called sendBroadcast() is not actually sending a broadcast, but a Message via the handler, The message is then processed in the handlerMessage() callback, which confirms that it is a local broadcast. Other applications do not get the broadcast because it is sent by a Handler inside LocalBroadcastManager
mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
}
return true; }}}return false;
}
Copy the code
The sendBroadcast() method looks long, but half the code is doing matching, and it does three things:
1. Match the Intent’s rules with the broadcast receiver’s rules from the ArrayList of actions in mActions
2. Add the matches to mPendingBroadcasts
3. Most importantly, this is done by sending a Message to the handler
- ExecutePendingBroadcasts () method
ExecutePendingBroadcasts (), which is used to process messages in the Handler, is broadcastManager’s executePendingBroadcasts() method. It actually sends a Message to the executePendingBroadcasts() method using the handler:
private void executePendingBroadcasts(a) {
while (true) {
BroadcastRecord[] brs = null;
synchronized (mReceivers) {
final int N = mPendingBroadcasts.size();
if (N <= 0) {
return;
}
brs = new BroadcastRecord[N];
// Convert mPendingBroadcasts into an array
mPendingBroadcasts.toArray(brs);
// Then empty mPendingBroadcasts
mPendingBroadcasts.clear();
}
for (int i=0; i<brs.length; i++) {
BroadcastRecord br = brs[i];
for (int j=0; j<br.receivers.size(); j++) {
After that, the broadcasts can be received by the broadcast receiver of mPendingBroadcasts. The broadcasts can be received by calling onReceive()br.receivers.get(j).receiver.onReceive(mAppContext, br.intent); }}}}Copy the code
This method starts a while loop to poll the mPendingBroadcasts for broadcasts. After the process is complete, the collection is cleared and the next broadcasts are repeated.
- LocalBroadcastManager summary
- LocalBroadcastManager is efficient because it is implemented internally by a Handler, and its sendBroadcast() method doesn’t have the same meaning as what we normally use. The sendBroadcast() method is actually a Message sent by the handler (this method also matches rules).
- Since it uses Handler to transmit broadcast internally, it is definitely more efficient than system broadcast with Binder. With Handler, other applications cannot send broadcast to our application, and the broadcast sent within our application will not leave our application
- LocalBroadcastManager internal cooperation is based on two Map sets: mReceivers and mActions, and mPendingBroadcasts, which store broadcast objects to be received
RecyclerView cache
RecyclerView Recyler can recycle ViewHolder. Recyer can recycle by the following 5 objects:
ArrayList mAttachedScrap
ViewHolder lists that are not separated from RecyclerView and that still depend on RecyclerView (for example, itemViews that have been slid out of sight but not removed) but have been marked removed are added to mAttachedScrap
Find the ViewHolder by ID and position
ArrayList mChangedScrap
Represents the list of ViewHolder that the data has changed. The ViewHolder that needs to change when storing notifXXX is matched by position and ID
ArrayList mCachedViews
Cache ViewHolder, mainly used to solve the situation of RecyclerView sliding jitter, and ViewHoder used to save Prefetch
Maximum quantity: mViewCacheMax = mRequestedCacheMax + extraCache (extraCache is calculated from prefetch)
ViewCacheExtension mViewCacheExtension
A developer-customizable layer of caching, an instance of the virtual class ViewCacheExtension, Developers can implement methods getViewForPositionAndType (Recycler Recycler, int position, int type) to implement its own cache.
Applicable scenario: android. Jlelse. Eu/anatomy – of -…
Fixed position
The content remains the same
The number of co., LTD.
- mRecyclerPool
ViewHolder cache pool. If a ViewHolder cannot be stored in the limited mCachedViews, the ViewHolder will be stored in RecyclerViewPool
Find the ViewHolder by Type
A maximum of five types are cached by default
RecyclerView in the design of the above five cache object is divided into three levels. Each ViewHolder is created by querying the cache in order of priority. Every time the ViewHolder cache is recycled, the Recycler cache is recycled in order of priority. The three levels of cache are:
- Level 1 cache: returns both layout and content valid
ViewHolder
- Matches by position or ID
- Level-1 cache matching is not required
onCreateViewHolder
andonBindViewHolder
- MAttachScrap in
adapter.notifyXxx
When used in - MChangedScarp is used every time a View is drawn because
getViewHolderForPosition
Non-call multiple - MCachedView: used to resolve sliding jitter. The default value is 2
- Level 2 cache: Returns the View
- Return View by matching position and type
- You need to inherit
ViewCacheExtension
implementation - If the position is fixed and the content does not change, for example Header if the content is fixed, you can use it
- Level 3 cache: returns valid layout, invalid content
ViewHolder
- Matches by type, with each type cache value default =5
- Layout is valid, but content is invalid
- multiple
RecycleView
Shareable, can be used for multipleRecyclerView
The optimization of the
18. LeakCanary works correctly
- ReferenceQueue
ReferenceQueue is a queue that holds references, of which there are four in Java.
- Strong references (When we create an object, strong references are created by default. As long as a strong reference exists, the garbage collector will not reclaim the object referenced by a strong reference even if it throws OOM.
- SoftReference (when memory is low, the garbage collector will reclaim the referenced object.)
- WeakReference (garbage collector will reclaim the referenced object when GC is performed.)
- Virtual reference (PhantomReference, rarely used.)
ReferenceQueue object, which queues the reference object when the garbage collector is about to reclaim the object it points to. Reference to the object is to say that when we construct WeakReference, we construct the object passed in the method. Reference object is to say that we refer to the reference itself. The concepts of the two should not be confused.
Here’s an example:
ReferenceQueue<Activity> mQueue = new ReferenceQueue<>();
WeakReference<Activity> mWeakReference = new WeakReference<Activity>(mActivity,mQueue);
Copy the code
If the mWeakReference points to mActivity is recovered during GC, our mWeakReference will also be added to our mWeakReference queue
- LeakCanary Principle Overview
LeakCanary registers a lifecycleCallbacks with the Application, logs the activity object through the set, weak references, and reference queue during the Destory of the activity lifecycle, and checks after five seconds when the main thread is idle, The cyclic reference queue removes the empty key from the set and determines if there are any activity objects in the set. If there are any activity objects in the set, it is not collected. If there are no activity objects in the set, it calls the GC and rechecks that the set is not collected.
/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link Executor} this {@link RefWatcher} has been constructed with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
if (debuggerControl.isDebuggerAttached()) {
return;
}
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
watchExecutor.execute(new Runnable() {
@Override public void run(a) { ensureGone(reference, watchStartNanoTime); }}); }void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (gone(reference) || debuggerControl.isDebuggerAttached()) {
return;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if(! gone(reference)) {long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == null) {
// Could not dump the heap, abort.
return;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
newHeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs, heapDumpDurationMs)); }}private boolean gone(KeyedWeakReference reference) {
return! retainedKeys.contains(reference.key); }private void removeWeaklyReachableReferences(a) {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while((ref = (KeyedWeakReference) queue.poll()) ! =null) { retainedKeys.remove(ref.key); }}Copy the code
Save the activity number for destroy (uuID.randomuuid ().toString()) into the Set retainedKeys as its key. Then put the activity (watchedReference) into the KeyedWeakReference.
The first point is that when a weak reference is used in conjunction with the ReferenceQueue, the Java virtual machine will add the weak reference to its associated ReferenceQueue if the object held by the weak reference is garbage collected. When a KeyedWeakReference holds an Activity object that is garbage collected, the object will be added to the reference queue. RetainedKeys sets all deStoryed Activity keys that are not reclaimed. This collection can be used to determine an Activity to have recycled, but need to use removeWeaklyReachableReferences before judgment () the update method.