Introduction to the
Chain of responsibility is one of the behavioral design patterns. We call the model formed by connecting multiple nodes end to end “chain”. For the chain structure, each node can be separated and linked again. Therefore, the chain structure will have great flexibility. If we regard each node in the chain as an object, each object has a different processing logic. A request is passed from the first node of the chain to each node object along the path until some object processes the request, which is the chain of responsibility mode.
Definition: Avoiding coupling between the sender and receiver of the request by giving multiple objects the opportunity to process the request. Connect the objects into a chain and pass the request along the chain until there is an object to process.
Draw a picture
This is the UML diagram of the chain of responsibility, where
- AbstractHandler: abstract processor, which contains next, pointing to the next processor
- Handler1 and Handler2 are subclasses of AbstractHandler
- AbstractRequest: an abstract requester
- Request1, Request2: Subclasses of the abstract requester, the concrete requester
Simple implementation
- Defining an abstract request
public abstract class AbstractRequest { private String name; public AbstractRequest(String name){ this.name = name; } public Object getName(){ return name; } /** * request level * @return */ public abstract int getRequestLevel();Copy the code
- Define the abstract handler
Public abstract class AbstractHandler {// Protected AbstractHandler next; public abstract int getLevel(); public abstract void handle(AbstractRequest request); Public final void handleRequest(AbstractRequest Request){public final void handleRequest(AbstractRequest Request){if(getLevel() == request.getRequestLevel()){ handle(request); }else{ if(next! =null){// Call the handleRequest method on the next node next-handlerequest (request); }else{ System.out.println("no one can handle it!!" ); }}}}Copy the code
- Specific requester
More than one requester can be defined here, and only one requester is shown for length reasons
public class MyRequest extends AbstractRequest{ public MyRequest(String name) { super(name); } @Override public int getRequestLevel() { return 1; }}Copy the code
- Concrete handler
As above, more than one processor can be defined here, and only one processor is shown for length reasons
public class MyHandler1 extends AbstractHandler{ @Override public int getLevel() { return 1; } @override public void handle(AbstractRequest request) {system.out.println ("1 "+request.getName());} @override public void handle(AbstractRequest Request) {system.out.println ("1 "+request. }}Copy the code
- test
public class Test { public static void main(String[] args) { AbstractHandler one = new MyHandler1(); AbstractHandler two = new MyHandler2(); one.next = two; AbstractRequest request1 = new MyRequest("request1"); AbstractRequest request2 = new MyRequest2("request2"); one.handleRequest(request1); one.handleRequest(request2); }}Copy the code
- The results of
Handler # 1 processes request1 handler # 2 processes request2Copy the code
As you can see, it all starts with one, but you already have the chain structure.
Okhttp’s interceptor responsibility chain
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (! forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }Copy the code
- RetryAndFollowInterceptor: redirect interceptors, processing retry interceptor, with some exceptions, as long as it’s not fatal exception will initiate a request again.
- BridgeInterceptor: a BridgeInterceptor that sets generic header information, such as cookies, Connection, and content-type, and does some processing for the returned information, data compression, and so on
- CacheInterceptor: cache interceptor, the cache is available, read the local cache, if not directly from the server, if you have, determine whether to have caching strategies, determine whether after expiration, has not expired, directly from the cache, if overdue, just add some headers, the server will determine whether have change, It might then return 304, meaning it can still be fetched from the cache, or else from the server.
- ConnectInterceptor: A connection interceptor that calls the findHealthyConnection () method to find a connection and determine whether it is healthy. If not, create a socket connection and cache it. OkHttp is based on the native Socket + OKio implementation,
- CallServerInterceptor: Request interceptor that writes and reads data to the server
In the code above, we complete the chain of responsibilities by calling the Method PROCEED on the RealInterceptorChain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
Copy the code
Let’s look at the implementation of the proceed method:
RealInterceptorChain.java public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); . // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); . return response; }Copy the code
Note that the RealInterceptorChain method has an index variable, and it adds itself to the RealInterceptorChain, but changes its parameter to index+1, so that the interceptor becomes the next interceptor in the interceptor list. The request is then passed to the next interceptor for processing.
View Event distribution responsibility chain
The hierarchy of the view
When we write an Activity, we call the setContentView method to load the layout, so let’s look at the implementation of the setContentView method
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Copy the code
Here we call getWindow().setContentView(layoutResID). What does the getWindow() method do?
public Window getWindow() {
return mWindow;
}
Copy the code
It returns mWindow, what is this mWindow? We found the answer in the attach () method
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) { attachBaseContext(context); mWindow = new PhoneWindow(this, window, activityConfigCallback); . }Copy the code
So this mWindow is PhoneWindow, and PhoneWindow is the only class that implements Window, so let’s look at how setContentView () is implemented in PhoneWindow
public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }... } private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); } else { mDecor.setWindow(this); }... if (mContentParent == null) { mContentParent = generateLayout(mDecor); . } } protected DecorView generateDecor(int featureId) { ... return new DecorView(context, featureId, this, getAttributes()); }Copy the code
You can see that you finally create the DecorView, which is the root view of the Activity, and then look at the source code for the DecorView, which inherits from FrameLayout.
After mDecor is initialized, generateLayout(mDecor) is implemented. Let’s take a look at the implementation
protected ViewGroup generateLayout(DecorView decor) { ... int layoutResource; . } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) ! = 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; }... return contentParent; }Copy the code
This method is tedious and long, and here is a sample of it. The main content of this method is to load different layouts to layoutResource depending on the situation. You can see layoutResource = r.layout.screen_title,
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:fitsSystemWindows="true"> <! -- Popout bar for action modes --> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="? attr/actionBarTheme" /> <FrameLayout android:layout_width="match_parent" android:layout_height="? android:attr/windowTitleSize" style="? android:attr/windowTitleBackgroundStyle"> <TextView android:id="@android:id/title" style="? android:attr/windowTitleStyle" android:background="@null" android:fadingEdge="horizontal" android:gravity="center_vertical" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" android:foregroundGravity="fill_horizontal|top" android:foreground="? android:attr/windowContentOverlay" /> </LinearLayout>Copy the code
The ViewStub is used to display the Actionbar, and the two Framelayouts below it, title for the title and Content for the content.
This should give you an idea of the Activity hierarchy. An Activity contains a Window object, which is called a PhoneWindow. The PhoneWindow uses a DecorView as the trailing view of the entire application Window, and this DecorView divides the screen into two areas: TItleView and ContentView, the layout that we usually write is displayed in this ContentView.
As shown in the figure:
Dispatching events
When we click on the screen, we generate the click event, which is encapsulated in a class: MotionEvent. When a MotionEvent is generated, the system will pass the MotionEvent to the View hierarchy, and finally the event will be passed to a specific View. This transmission process is the event distribution. This usually involves three key approaches:
- DispatchTouchEvent (MotionEvent EV) : This method must be called if the event is passed to the current view. The result returned is influenced by the onTouchEvent method of the current view and the dispatchTouchEvent method of the subordinate view, indicating whether the event is consumed.
- OnInterceptTouchEvent (MotionEvent EV): Intercepts events, called in the dispatchTouchEvent method, which is not available in the View. If the current ViewGroup intercepts the event, the method is not called again in the same sequence of events and returns a result indicating whether the current event is intercepted.
- OnTouchEvent (MotionEvent EV) : Used to handle events, called in the dispatchTouchEvent method. The return result indicates whether the current event is consumed. If not, the current view cannot receive the event again in this sequence of events.
ViewGroup
When a click event occurs, the event is first passed to the current Activity, which calls the Activity’s dispatchTouchEvent method, and the Activity hands the specific event to the PhoneWindow. The PhoneWindow in turn hands the handling of the event to the DecorView, which in turn hands it to the root ViewGroup. So let’s start with the dispatchTouchEvent method of the ViewGroup. Because the method is relatively long, the analysis is carried out in sections
public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; / / Handle the an initial down. If (actionMasked = = MotionEvent. ACTION_DOWN) {/ / reset state cancelAndClearTouchTargets (ev); resetTouchState(); } // Check for interception. final boolean intercepted; // When the event type is ACTION_DOWN or mFirstTouchTarget! = null, to determine whether to intercept events if (actionMasked = = MotionEvent. ACTION_DOWN | | mFirstTouchTarget! = null) {// Determine the disallowIntercept flag bit, if true, 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; }... }Copy the code
If so, initialize and null the value of mFirstTouchTarget as if a complete sequence of events started down.
ViewGroup will in the two cases to determine whether the intercept events actionMasked = = MotionEvent. ACTION_DOWN | | mFirstTouchTarget! = null. First, ACTION_DOWN event is the starting point of each event sequence, and state is reset for DOWN event. So what is mFirstTouchTarget? When the event is successfully handled by a child element of the View, mFirstTouchTarget is assigned and points to the child element. That is, when the ViewGroup does not intercept the event and hand it over to the child element, mFirstTouchTarget! If the ViewGroup intercepts the event, then mFirstTouchTarget will be null. When a MOVE event or an UP event arrives, since mFirstTouchTarget is null, The onInterceptTouchEvent that results in the ViewGroup will not be called, and the sequence of events will be handled by it.
private static final class TouchTarget {
public View child;
public int pointerIdBits;
public TouchTarget next;
private TouchTarget() {
}
}
Copy the code
You can see that the TouchTarget is a linked list structure, which is assigned by the addTouchTarget method.
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
Copy the code
In the case of no multi-touch, this linked list will degenerate into a single TouchTarget object. Under multi-touch, the target is the same as a single TouchTarget object, but it saves multiple pointerId information. When the target is different, it is the linked list structure.
To go back to the previous judgment, see FLAG_DISALLOW_INTERCEPT this tag, the tag is set up by requestDisallowInterceptTouchEvent method, generally make calls in the word view, once set, The ViewGroup will not be able to intercept events other than DOWN events. This is also one of the common ways to handle sliding collisions.
When the ViewGroup decides to intercept an event, subsequent click events will be handled by default and its onInterceptTouchEvent method will not be called.
When a ViewGroup does not intercept an event, the event is propagated down to its child view for processing, as follows:
public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean alreadyDispatchedToNewTouchTarget = false; // Do not intercept the event into the following judgment if (! canceled && ! intercepted) { ... Final View[] children = mChildren; 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 (childWithAccessibilityFocus ! = null) { if (childWithAccessibilityFocus ! = child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // Determine if the touchpoint position is within the scope of the child view or if the animation is playing 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; 1. break; } resetCancelNextUpFlag(child); // Pass the event to the child element for processing, DispatchTouchEvent if dispatchTransformedTouchEvent is actually call child elements (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; }... }Copy the code
It first iterates through all the children of the ViewGroup, and then determines if the children can receive click events: if an animation is playing, and if the coordinates fall within the region of the children. If these two conditions are met, the event is passed to him for processing. DispatchTransformedTouchEvent method is actually call the child element dispatchTouchEvent method.
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; . final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }... }Copy the code
If the child’s dispatchTouchEvent returns true, mFirstTouchTarget is assigned,
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
Copy the code
It also breaks out of the loop, terminating the traversal of the child element, and if dispatchTouchEvent returns false, the ViewGroup will distribute the event to the next child element.
If the event is not handled properly after traversing all the child elements, there are two cases: first, the ViewGroup has no child elements, and second, the dispatchTouchEvent for its child element returns false. In both cases, the ViewGroup handles the event itself
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
Copy the code
This call dispatchTransformedTouchEvent approach, the child the third parameter is null, Inside the dispatchTransformedTouchEvent will direct call super dispatchTouchEvent (event), here is transferred to the View of dispatchTouchEvent method, click events make the View to deal with.
View
First look at the View’s dispatchTouchEvent method
public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; . if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //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; }}... return result; }Copy the code
View is a single element with no child elements, so you can’t pass events down, you have to process them yourself. If onTouchListener returns true, the onTouchEvent method will not be called.
Let’s take a look at the onTouchEvent method
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; . if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: ... if (! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (! focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (! post(mPerformClick)) { performClickInternal(); }}}... } mIgnoreNextUpEvent = false; break; . } return true; } return false; }Copy the code
As long as either CLICKABLE or LONG_CLICKABLE of the View is true, it will consume the event, the onTouchEvent method will return true, and the performClick method will fire when ACTION_UP events occur, If OnClickListener is set, this method calls the OnClick method.
Event distribution summary
At this point, the time distribution of viewgroups and views is over. Recursive calls to ViewGroup event passing are like a chain of responsibility. Once the responsible person is found, the responsible person will hold and consume the event. Otherwise, the incident will continue to be passed on to others responsible.
conclusion
Chain of responsibility pattern to make programming more flexible, we can see not only used in the Android source code to the chain of responsibility, also used in the framework of a lot, there are different ways to achieve this chain table structure can be, can be directly to the processor into the list, you can also use recursive way of transmission, in the face of different scenarios, the use of flexible, To exert its great power.