Containers for development

Arraylist

  • The bottom layer is arrays
  • Capacity expansion mechanism: Half capacity expansion by default. If half the size is not enough, the target size (the original array length +1) is used as the expanded capacity
  • ModCount will be modified when modCount is deleted and modCount will be modified when modCount is deleted. ModCount is definitely not modified by modCount.
  • Expansion leads to array replication, bulk deletion leads to finding the intersection of two collections, and array replication, so add and delete are relatively inefficient. And change, check is very efficient operation.
  • Combined with the characteristics, in use, take the most commonly used display list in Android as an example. When the list is sliding, it needs to display the array of each Item (Element), so the search operation is the highest frequency. In contrast, increment operations are only used when the list is loaded more and are inserted at the end of the list, so there is no need to move data. Deletion is much lower frequency. Therefore, ArrayList is chosen as the structure to save data

LinkedList

  • LinkedList and ArrayList are two different implementations of the List interface. The efficiency of adding and deleting a List is low, but the efficiency of changing a List is high. On the contrary, the addition and deletion of LinkedList is more efficient because it does not need to move the underlying array data, which is realized by LinkedList and only needs to modify the LinkedList node pointer.
  • LinkedList is a two-way list. When a node is obtained by subscript (Add select), the index is halved according to whether the index is in the first half or the second half to improve query efficiency

HashMap

  • A HashMap is an associative array, hash table, which is thread-unsafe and allows null keys and null values. The duration is disordered. After 1.8, when the length of the linked list reaches 8, it will be transformed into a red-black tree to improve its query and insert efficiency.

  • It is thread safe, can through the Collections of the class the static method synchronizedMap get a thread-safe HashMap Map Map = Collections. SynchronizedMap (new HashMap ())

  • When the capacity of the HashMap reaches the threshold, capacity expansion is triggered. Before and after capacity expansion, the length of the hash bucket must be 2 power. In this way, bit operation can be used instead of mod operation to find the corresponding hash bucket based on the hash value of the key, which is more efficient. Because capacity expansion doubles capacity, each node on the original linked list may now be stored in the original index, low bit, or in the expanded index, high bit. High bit = Low bit + Original hash bucket capacity

  • The hash bucket length of a HashMap is much smaller than the hash value range (the default value is 16). Therefore, when the hash value is mod to the bucket length to find the index of the bucket where the key is stored, the high hash value is ignored because the mod is done by the and operation. Therefore, only the lower part of hashCode() will participate in the operation, and the probability of different hash values will occur, but the same index will be obtained is greatly increased, which is called hash collision. That is, the collision rate will increase.

  • The perturbation function is designed to solve hash collisions. It combines the features of the high and low hash values and puts them in the low one. Therefore, when it is used with the high and low bits, it is equivalent to participating in the operation together to reduce the probability of hash collisions.

  • And operation replaces modular operation. Hash & (table.length-1) instead of hash % (table.length)

Differences between HashMap and HashTable

  • In contrast, HashTable is thread-safe and does not allow null keys and values.
  • The default size of HashTable is 11.
  • HashTable is a hash value that uses the hashCode(key.hashcode ()) of the key directly. Unlike the static final int hash(Object key) function inside a HashMap, the key’s hashCode is disturbed as a hash value.
  • Hash bucket subscript for HashTable is modular %. (because its default capacity is not 2 ^ n.). So you can’t replace modular operations with bitwise operations.)
  • During capacity expansion, the new capacity is twice the original capacity +1. int newCapacity = (oldCapacity << 1) + 1;
  • Hashtable is a subclass of Dictionary that implements the Map interface.

LinkedHashMap

  • LinkedHashMap is an associative array hash table that is thread-unsafe and allows for null keys and null values.
  • It inherits from HashMap and implements the Map

    interface. It also maintains a bidirectional linked list, adding nodes or adjusting the order of nodes in the linked list each time data is inserted, accessed or modified. To determine the order of output during iteration.
    ,v>
  • Traversal is done in the order in which nodes are inserted. This is the biggest difference from HashMap. The accessOrder argument can also be passed at construction time so that its traversal order is output in the order it was accessed. AccessOrder, which defaults to false, iterates in the order in which nodes were inserted. If true, the output is in the order in which the nodes are accessed
        Map<String.String> map = new LinkedHashMap<>();
        map.put("1"."a");
        map.put("2"."b");
        map.put("3"."c");
        map.put("4"."d");

        Iterator<Map.Entry<String.String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("Here is accessOrder=true :");

        map = new LinkedHashMap<String.String> (10.0.75f, true);
        map.put("1"."a");
        map.put("2"."b");
        map.put("3"."c");
        map.put("4"."d");
        map.get("2");//2 moves to the end of the internal list
        map.get("4");//4 adjust to the end
        map.put("3"."e");//3 adjust to the end
        map.put(null.null);// Insert two new nodes null
        map.put("5".null);/ / 5
        iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
Copy the code

Results:

1=a
2=b
3=c
4=d the following is accessOrder=trueThe most recently accessed data is placed at the end of the queue1=a
2=b
4=d
3=e
null=null
5=null
Copy the code
  • It compares to a HashMap and, in a minor optimization, overrides the containsValue() method to directly traverse the internal list to see if the values are equal. Why doesn’t it override the containsKey() method and also loop through the internal list keys for equality? Because the HashMap takes O(1) time to find the index by key and then locate the element. If you use a linked list it takes order n time.

ArrayMap (Containers in Android)

  • ArrayMap implements the implements Map<K, V> interface, so it is also an associative array, hash table. Stores data in the form of key->value structures. It is also thread-unsafe, allowing for null keys and null values.

  • Its internal implementation is based on two arrays. An int[] array that holds the hashCode for each item. An Object[] array that holds key/value pairs. It has twice the capacity of the previous array.

  • But it is not suitable for large data storage. When storing large amounts of data, its performance degrades by at least 50%. Less efficient than traditional HashMap time. Because it sort keys from smallest to largest using dichotomy,

  • When you want to get a value, ArrayMap calculates the hash value of the input key, and then uses binary lookup to find the index of the hash array, which can then be used to access the desired key-value pairs in another array. If the key in the second array key-value pair does not match the query key entered earlier, a collision is considered to have occurred. To solve this problem, we will take the key as the center point, expand up and down respectively, compare and search one by one, until we find the matching value.

SparseArray

  • SparseArray is a data structure designed to replace a HashMap on The Android platform. More specifically, it replaces a HashMap with a key of type INT and a value of type Object.

  • It is also thread-unsafe, allowing value to be null.

  • In principle,

    • Its internal implementation is also based on two arrays. An int[] array mKeys is used to hold the key of each item. The key itself is of type int. An Object[] array mValues that holds value. It has the same capacity as the key array.
  • It is also not suitable for large data storage. When storing large amounts of data, its performance degrades by at least 50%.

  • Less efficient than traditional HashMap time. Because it sorts the key from smallest to largest, use dichotomy to query the index of the key in the array

  • Applicable scenarios:

    • Small amount of data (within thousands)
    • Space is more important than time
    • Map is required, and the key is of int type.

Java container class

It can be seen from the figure that there are two categories: Collection and Map

Collection

A Collection is an abstract interface to a List and Set that contains the basic operations for those collections. Is the top-level interface to the collection

  • List

The List interface typically represents a List (array, queue, LinkedList, stack, etc.) whose elements can be repeated. Common implementation classes are ArrayList, LinkedList, and Vector.

  • Set

The Set interface usually represents a collection, whose elements are not allowed to be duplicated (guaranteed by hashCode and equals). Common implementation classes include HashSet and TreeSet. HashSet is implemented using a HashMap in a Map. TreeSet is implemented through TreeMap in Map. In addition, TreeSet also implements SortedSet interface, so it is an ordered set.

  • Difference between a List and a Set

The Set interface stores disordered and non-repeated data. The List interface stores ordered and repeatable data. Set retrieval efficiency is low, and deletion and insertion efficiency are high. A List is efficient in searching for elements, but inefficient in deleting and inserting elements. Like an array, a List can grow dynamically, automatically increasing the length of the List according to the actual storage length

Map

Map is a mapping interface in which each element is a key-value pair. AbstractMap also implements most functions of Map interface through adapter pattern. Implementation classes such as TreeMap, HashMap, and WeakHashMap are all implemented by inheriting AbstractMap.

Iterator

Iterator is an Iterator that iterates over collections. It can be used to iterate over collections, but not over maps. The implementation classes of a Collection all implement the iterator() function, which returns an iterator object that iterates through the Collection. ListIterator is specifically used to iterate over lists. Enumeration, introduced in JDK 1.0, does the same but less well than Iterator. It can only be used in hashtables, vectors, and stacks.

The Arrays and Collections

Arrays and Collections are utility classes for manipulating Arrays and Collections. For example, ArrayList and Vector use the array.copyof () method extensively. In Collections, however, there are many static methods that can return synchronized, thread-safe, versions of collection classes. Of course, if you want thread-safe collection classes, you prefer concurrent and the corresponding collection classes under the package.

What are thread-safe containers in Java?

Synchronized container class: uses synchronized:

  • 1.Vector
  • 2.HashTable

Concurrent container:

  • 3. ConcurrentHashMap: fragmentation
  • 4.CopyOnWriteArrayList: copy while writing
  • 5.CopyOnWriteArraySet: Copy while writing

Queue:

  • ConcurrentLinkedQueue: an unbounded thread-safe queue based on linked nodes implemented in a non-blocking manner.
  • 7.ArrayBlockingQueue: Array-based bounded blocking queue
  • 8.LinkedBlockingQueue: a bounded blocking queue based on a linked list.
  • 9.PriorityBlockingQueue: An unbounded blocking queue that supports priority, i.e. the elements in the blocking queue can be automatically sorted. By default, elements are arranged in a natural ascending order
  • 10.DelayQueue: An unbounded blocking queue that delays the acquisition of elements.
  • SynchronousQueue: a blocking queue that does not store elements. Each PUT operation must wait for a take operation or it cannot continue to add elements. It doesn’t actually have any elements inside, and it has capacity zero

Deque: (The Deque interface defines a bidirectional queue. Two-way queues allow in-queue and out-queue operations at the head and tail of the queue.

  • ArrayDeque: Array-based bidirectional non-blocking queue.
  • 13.LinkedBlockingDeque: Two-way blocking queue based on linked lists.

Sorted container:

  • ConcurrentSkipListMap: is a thread-safe version of TreeMap
  • ConcurrentSkipListSet: is the thread-safe version of TreeSet

Java FAQs

Question 1: The difference between Wait and sleep in multithreading

  • 1. Sleep () is a Thread method, and wait() is a method defined in Object.
  • 2. The sleep() method can be used anywhere, and the wait() method can only be used with synchronized.
  • Thread.sleep() only yields CPU, but does not change the locking behavior. Object.wait() not only frees cpus, but also releases synchronized resource locks that have already been held

Question 2: The difference between Equals and hashCode in Java

  • 1. If a base data type is compared, it compares values; if a reference type is compared, it compares where they are stored in memory. Object is a reference to an object stored in the heap and stored in the stack, so == is a comparison between values in the stack. If true is returned, the memory address of the variable is equal.
  • 2. The equals method of the Object class determines whether an Object’s memory address is referenced to the same address. If the equals method is overridden by a class, the equals code is used to determine whether the objects are equal or not.
  • 3. HashCode () computes the hashCode of the object instance and stores it as a key when the object is hashed.
  • 4. Equals = hashCode;
  • HashCode () is a local method whose implementation is closed depending on the local machine. HashCode () must be equal to equals(); HashCode () does not equal, and equals() must not equal; HashCode () is equal, equals() may or may not be equal.
  • So before overriding equals(Object obj), it is necessary to override hashCode() to ensure that the two objects whose equals(Object obj) is true have the same hashCode() return value.
  • == == == ==
  • Integer b1 = 127; Compiled to Integer at Java compile time b1 = integer.valueof (127); For values between -128 and 127, the native data type int is used for reuse in memory, i.e. the comparison between the values of the native data type int is performed. If the value ranges from -128 to 127, the address and value are compared when ==.

Question 3: The difference between int and INTEGER

Integer is a wrapper class for int, which is a basic data type in Java. Integer variables must be instantiated before they can be used. When an Integer is new, it actually generates a reference to the object, and int is the value that stores data directly. Integer defaults to null, and int defaults to 0

Q4: Talk about your understanding of Java polymorphism

  • The same message can act in different ways according to the different sending objects, determine the actual type of the referenced object during execution, and invoke its corresponding methods according to the actual type.
  • Purpose: Decouple types from each other. The requirements for polymorphism are inheritance, overwriting (because you must call methods that exist in the parent class), and superclass references to subclass objects

Question 5: The difference between String, StringBuffer, StringBuilder

  • The String class uses an array of characters to store strings. Because of the final modifier, the String object is immutable. Every operation on a String generates a new String object, which is inefficient and wastes memory space. But thread full.
  • StringBuilder and StringBuffer also use character arrays to hold characters, but both objects are mutable, that is, append strings without producing new objects
  • The difference is that StringBuffer places synchronization locks on methods and is thread-safe, while StringBuilder is not thread-safe.

Question 6: The difference between Serializable and Parcelable

Android FAQs

ThreadLocal

  • 1: each thread can independently change its own space resources (i.e. stored values) without conflict with other threads resources
  • 2: If two threads execute code that contains a reference to a Threadlocal variable, they cannot access each other’s Threadlocal variable

The ThreadLocalMap key is the current ThreadLocal, and the value is the stored value. That is, one thread corresponds to a ThreadLocalMap, so that it does not conflict with the resources of other threads

Examples of applications in real development

  • The main thread and child thread get data through spliteHelper

Handler message mechanism

The message that needs to update UI in the sub-thread is passed to the UI thread, so as to realize the updating processing of UI by the sub-thread, and finally realize the asynchronous message processing, so as to avoid the unsafe problem of thread operation

Related concepts:

  • Message: a unit of data that communicates between threads
  • MessageQueue: queue for message storage, a data structure (fifO)
  • Handler:
    • 1: Communication medium between main thread and child thread (adding messages to message queue)
    • 2: Primary handler of thread messages (processing messages dispatched by Looper)
  • Looper: Communication medium between message queue and handler
    • 1: message fetch: circulates messages from the message queue
    • 2: message distribution: Sends the extracted message to the corresponding processor

Source code analysis:

The thread pool

Thread pool is to put multiple thread objects into a container in advance. When using it, you can directly take threads from the pool instead of new threads, which saves the time of opening child threads and improves the efficiency of code execution.

role

  • 1: multiplexing threads
  • 2: Manage threads (unified allocation, tuning, and monitoring; Controls the maximum number of concurrent threads in the thread pool

advantages

  • 1: Reduces the performance overhead caused by thread creation/destruction
  • 2: improve thread response speed/execution efficiency
  • 3: Improve thread management

Core parameters:

  • CorePoolSize: number of core threads
  • MaximumPoolSize: The maximum number of threads that a thread pool can hold
  • KeepAliveTime: indicates the idle timeout duration of non-core threads
  • TimeUnit Unit: Indicates the unit of keepAliveTime
  • WorkQueue: indicates a task queue
  • ThreadFactory: Thread project that creates new threads for the thread pool

Common 4 types of functional thread pools:

  • Fixed length thread pool (FixedThreadPool)

It is a thread pool with a fixed number of threads. When threads are idle, they are not called back unless the pool is closed. When all threads are active, new tasks wait until a thread is free

Create a thread pool object & set the number of threads in the pool to 3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

// 2. Create a Runnable thread object & a task to execute
Runnable task =new Runnable(){
  public void run(){
    System.out.println("On a mission."); }};// 3. Submit tasks to the thread pool: execute ()
fixedThreadPool.execute(task);
        
// 4. Close the thread pool
fixedThreadPool.shutdown();
Copy the code
  • Timed thread pool (SchedledThreadPool)

It has a fixed number of core threads, while there is no limit to the number of non-core threads and they are immediately reclaimed when idle. Threads such as ScheduledThreadPoll are mainly used to execute scheduled tasks and repetitive tasks with a fixed period

Create a timer thread pool object & set the number of threads in the pool to 5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

// 2. Create a Runnable thread object & a task to execute
Runnable task =new Runnable(){
       public void run(){
              System.out.println("On a mission."); }};// 3. Submit a task to the thread pool: schedule ()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // The task will be executed after 1s
scheduledThreadPool.scheduleAtFixedRate(task,10.1000,TimeUnit.MILLISECONDS);// perform tasks every 1000ms after a delay of 10ms

// 4. Close the thread pool
scheduledThreadPool.shutdown();
Copy the code
  • Cacheable Thread pool

It is a thread pool with an indefinite number of threads, only non-core threads, and the maximum number of threads is integer.max_value. Since INTEger. MAX_VALUE is a large number, it is essentially equivalent to the maximum number of threads that can be arbitrarily large. When all threads in the thread pool are active, the pool creates new threads to handle new tasks, otherwise it uses idle threads to handle new tasks. All idle threads in the thread pool have a timeout mechanism. The timeout duration is 60 seconds, after which idle threads are reclaimed. Unlike FixedThreadPool, CachedThreadPool’s task queue is essentially an empty collection, which causes any task to be executed immediately because the SynchronousQueue cannot insert a task. SynchronousQueue is a very special queue that can be understood as a queue that cannot store elements (rarely used in practice). By the nature of CachedThreadPool, this type of thread pool is ideal for performing a large number of small tasks. When the entire thread pool is idle, threads in the thread pool will time out and be stopped. There are no threads in the CachedThreadPool and it consumes almost no system resources.

// 1. Create cacheable thread pool objects
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 2. Create a Runnable thread object & a task to execute
Runnable task =new Runnable(){
  public void run(){
        System.out.println("On a mission."); }};// 3. Submit tasks to the thread pool: execute ()
cachedThreadPool.execute(task);

// 4. Close the thread pool
cachedThreadPool.shutdown();

// The first task has been completed when the second task is executed
// Instead of creating a new thread each time, the thread that executed the first task will be reused.
Copy the code
  • Single threaded thread pool (SingleThreadExecutor)

This type of thread pool has only one core thread inside it, which ensures that all tasks are executed sequentially in the same thread. The point of SingleThreadExecutor is to unify all external tasks into one thread so that there is no need to deal with thread synchronization between these tasks

// 1. Create a single thread pool
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 2. Create a Runnable thread object & a task to execute
Runnable task =new Runnable(){
  public void run(){
        System.out.println("On a mission."); }};// 3. Submit tasks to the thread pool: execute ()
singleThreadExecutor.execute(task);

// 4. Close the thread pool
singleThreadExecutor.shutdown();

Copy the code

Thread reuse for thread pool:

This is where you dive into the source code addWorker(): it is the key to creating new threads and the key entry point for thread reuse. The result is runWoker, which takes the task in two ways:

  • FirstTask: This is the first specified runnable executable task that will run the execution task Run in the Woker worker thread. And empty indicates that the task has been executed.
  • GetTask (): This is an infinite loop, where the worker thread loops until it is able to retrieve the Runnable object or returns a timeout. In this case, the target is the workQueue.

There’s an in and an out for what we just did. In fact, the task not only performs the firstTask specified at the time of creation, but also actively fetches the task from the task queue through getTask() method, and blocks with/without time limit to ensure the survival of the thread.

What kinds of work queues do thread pools have?

1, ArrayBlockingQueue

Is a bounded blocking queue based on an array structure that sorts elements according to FIFO(first-in, first-out).

2, LinkedBlockingQueue

A linked list-based blocking queue that sorts elements in FIFO (first-in, first-out) order and typically handles more than an ArrayBlockingQueue. Static factory methods Executors. NewFixedThreadPool () and Executors. NewSingleThreadExecutor using the queue.

3, SynchronousQueue will

A blocking queue that does not store elements. Each insert operation must wait until another thread calls to remove operation, otherwise the insert has been in the blocking state, the throughput is usually more than LinkedBlockingQueue, static state factory method Executors. NewCachedThreadPool using the queue.

4, PriorityBlockingQueue

An infinite blocking queue with priority.

What about unbounded queues and bounded queues?

  1. Bounded queue

If poolSize < corePoolSize, the runnable task will be executed immediately as an argument to the new Thread. 2. When the number of submitted tasks exceeds corePoolSize, the current runable is submitted to a block queue. 3. If the poolSize of a bounded queue is smaller than maximumPoolsize, a new Thread will be created and the corresponding runnable task will be executed immediately. 4. If no, go to Step 4, reject.

  1. Unbounded queue

Compared with the bounded queue, the unbounded task queue does not have task failure unless the system resources are exhausted. When a new task arrives and the number of threads in the system is smaller than corePoolSize, a new thread is created to execute the task. When corePoolSize is reached, it does not continue to increase. If new tasks are added later and there are no idle thread resources, the task is directly queued to wait. If the speed of task creation and processing is very different, the unbounded queue keeps growing rapidly until it runs out of system memory. When the task cache queue of the thread pool is full and the number of threads in the thread pool reaches maximumPoolSize, the task rejection policy is adopted if additional tasks arrive.

Multithreaded development in Android

  • asyncTask

The maximum number of threads is 128. If the number of threads exceeds this number, an error will be reported

  • HandlerThread

Looper.prepare() creates a message queue in the run method, and looper.loop () starts the message loop. This allows handlers to be created in the HandlerThread in practice

  • intentService

Override the onHandleIntent() method to handle time-consuming tasks

Event distribution mechanism

  • For dispatchTouchEvent, onTouchEvent, return True terminates the event. Return False is to trace back to the parent View’s onTouchEvent method.
  • If a ViewGroup wants to distribute its own onTouchEvent, it needs the onInterceptTouchEvent method return True to intercept the event.
  • ViewGroup blocker onInterceptTouchEvent default is not intercepted, so the return. Super onInterceptTouchEvent () = return false.
  • A View does not have an interceptor, so in order for a View to send events to its own onTouchEvent, the default implementation of dispatchTouchEvent (super) for a View is to send events to its own onTouchEvent.

Event distribution a bit of thought

Setting both onTouch and onClick events on a button which goes first? OnTouch takes precedence over onClick, and onTouch executes twice, once ACTION_DOWN and once ACTION_UP(you may also execute ACTION_MOVE multiple times if your hand shakes). So events are passed through onTouch and then onClick.

button.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		Log.d("TAG"."onClick execute"); }}); button.setOnTouchListener(new OnTouchListener() {
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		Log.d("TAG"."onTouch execute, action " + event.getAction());
		return false; }});Copy the code

What happens if we try to change the return value in the onTouch method to true?

The onClick method is no longer executed

The reason:

View dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    if(mOnTouchListener ! =null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}
Copy the code

The retrun True method is executed when all three conditions of dispatchTouchEvent are true, and we know that the event is consumed after dispatchTouchEvent retrun True. The three conditions are:

  • The first condition is mOnTouchListener! = null, which means that whenever we register a touch event for the control, mOnTouchListener must be assigned a value.
  • The second condition is (mViewFlags & ENABLED_MASK) == ENABLED to determine whether the currently clicked control is enable. Buttons are ENABLED by default, so this condition is always true.
  • The third condition, monTouchListener.ontouch (this, event), is the onTouch method that registers the touch event in the callback controller. That is, if we return true in the onTouch method, all three conditions will be true and the whole method will return true. If we return false in the onTouch method, we will execute the onTouchEvent(event) method again.
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 (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            caseMotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) ! =0;
                if((mPrivateFlags & PRESSED) ! =0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if(isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); }if(! mHasPerformedLongPress) {// This is a tap, so remove the longpress check
                        removeLongPressCallback();
                        // Only perform take click actions if we were in the pressed state
                        if(! focusTaken) {// Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            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)) {// If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }
                mPrivateFlags |= PREPRESSED;
                mHasPerformedLongPress = false;
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                break;
            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                removeTapCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // Be lenient about moving outside of buttons
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button
                    removeTapCallback();
                    if((mPrivateFlags & PRESSED) ! =0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressedmPrivateFlags &= ~PRESSED; refreshDrawableState(); }}break;
        }
        return true;
    }
    return false;
}
Copy the code

Here we know that the onClick call must be in the onTouchEvent method!

public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    if(mOnClickListener ! =null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}
Copy the code

View drawing process

Mainly divided into: measure, layout, draw process

measure

A MeasureSpec encapsulates the layout requirements that are passed from parent to child. A MeasureSpec encapsulates the layout requirements that are passed from parent to child. MeasureSpec is a parent View’s MeasureSpec and a child View’s LayoutParams. The child View’s MeasureSpec is a child View’s MeasureSpec.

MeasureSpec is a combination of size and pattern. MeasureSpec is a 32-bit INT value where the high 2 bits are the measure mode and the low 30 bits are the measure size.

Classification of measurement modes

  • EXACTLY

Precise mode, where the width and height of the control is a specific number, such as Android: lanout_width=100dp, or match_parent (occupying the size of the parent view)

  • AT_MOST

Max mode, that is, when the control’s width is android:lanout_width=warp_content, the size of the control depends on the size of the content, as long as it does not exceed the size of the parent control

  • UPSPECIFIED

The parent container has no restrictions on the children, which can be as large as they want

Measurement process:

The process of the parent View’s measure will first measure the child View, and then measure itself after the measurement result of the child View is available.

A child View is a child View that measures the width of the parent View and the width of the parent View. A child View is a child View that measures the width of the parent View.

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

// The child View's LayoutParams, the XML layout_width and layout_height,
// The value of layout_xxx is wrapped into the LayoutParams.
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

// Based on the parent View's size and the parent View's own Padding,
// The child View's Margin and widthUsed size can be a child View's MeasureSpec (see getChildMeasureSpec).
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);    

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,           
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  + heightUsed, lp.height);  

// The parent View's MeasureSpec and the child View's LayoutParams calculate the child View's MeasureSpec, and then pass it to the child
// Then let the child View measure itself with the MeasureSpec (a measurement requirement, such as how big it can be), and recursively measure down if the child View is a ViewGroup.
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

// The spec argument represents the parent View's MeasureSpec
// padding argument the padding of the parent View + the Margin of the child View
// The child View's MeasureSpec size
// The childDimension argument represents the value of the child View's internal LayoutParams property (lp.width or lp.height).
// Can be wrap_content, match_parent, an exact size,
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  // Get the mode of the parent View
    int specSize = MeasureSpec.getSize(spec);  // Get the size of the parent View

   // The size of the parent View - your own Padding+ the Margin of the child View, which is the size of the child View.
    int size = Math.max(0, specSize - padding);   
  
    int resultSize = 0;    // Initializes the value, and finally generates the child View's MeasureSpec from the two values
    int resultMode = 0;    // Initializes the value, and finally generates the child View's MeasureSpec from the two values
  
    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1, Parent View is EXACTLY correct!
    case MeasureSpec.EXACTLY:   
        // The width or height of the subview is an exact size.
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size is the exact value
            resultMode = MeasureSpec.EXACTLY;    / / mode for EXACTLY.
        }   
        // the width or height of the child View is MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size. So be it.  
            resultSize = size;                   //size indicates the size of the superview
            resultMode = MeasureSpec.EXACTLY;    / / mode for EXACTLY.
        }   
        // the width or height of the child View is WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                   //size indicates the size of the superview
            resultMode = MeasureSpec.AT_MOST;    / / mode for AT_MOST.
        }  
        break;  
  
    // Parent has imposed a maximum size on us  
    // The parent View is AT_MOST!
    case MeasureSpec.AT_MOST:  
        //2.1, subview width or height is an exact size
        if (childDimension >= 0) {  
            // Child wants a specific size... so be it  
            resultSize = childDimension;        //size is the exact value
            resultMode = MeasureSpec.EXACTLY;   / / mode for EXACTLY.
        }  
        // the width or height of the child View is MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size, but our size is not fixed.  
            // Constrain child to not be bigger than us.  
            resultSize = size;                  //size indicates the size of the superview
            resultMode = MeasureSpec.AT_MOST;   / / mode for AT_MOST
        }  
        // the width or height of the child View is WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                  //size indicates the size of the superview
            resultMode = MeasureSpec.AT_MOST;   / / mode for AT_MOST
        }  
        break;  
  
    // Parent asked to see how big we want to be  
    // The parent View is UNSPECIFIED.
    case MeasureSpec.UNSPECIFIED:  
        // the width or height of the subview is an exact size.
        if (childDimension >= 0) {  
            // Child wants a specific size... let him have it  
            resultSize = childDimension;        //size is the exact value
            resultMode = MeasureSpec.EXACTLY;   / / mode for EXACTLY
        }  
        // the width or height of the child View is MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size... find out how big it should  
            // be  
            resultSize = 0;                        / / the size is zero! , its value is undetermined
            resultMode = MeasureSpec.UNSPECIFIED;  / / mode for UNSPECIFIED
        }   
        // The width or height of the child View is WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size.... find out how  
            // big it should be  
            resultSize = 0;                        / / the size is zero! , its value is undetermined
            resultMode = MeasureSpec.UNSPECIFIED;  / / mode for UNSPECIFIED
        }  
        break;  
    }  
    // Build the MeasureSpec object based on the mode and size obtained by the above logic condition.
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}    
Copy the code

The measurement process of sub-view

The View measurement process is mainly in the onMeasure() method. If you want to customize the View measurement, you should override the onMeasure() method

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  setMeasuredDimension(
  getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            
  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
// Get the android:minHeight property or the View background image size
protected int getSuggestedMinimumWidth() { 
   return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }//@param size normally indicates that the Android :minHeight attribute or the size of the View background image is set
public static int getDefaultSize(int size, int measureSpec) {    
   int result = size;    
   int specMode = MeasureSpec.getMode(measureSpec);    
   int specSize = MeasureSpec.getSize(measureSpec);    
   switch (specMode) {    
   case MeasureSpec.UNSPECIFIED:        // Indicates that the size of the View's parent View is undetermined. Set to the default value
     result = size;  
     break;    
   case MeasureSpec.AT_MOST:    
   case MeasureSpec.EXACTLY:        
     result = specSize;  
     break;   
 }    
return result;
}
Copy the code

Measure procedure of ViewGroup

/ / FrameLayout measurement
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){... int maxHeight =0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {    
   final View child = getChildAt(i);    
   if(mMeasureAllChildren || child.getVisibility() ! = GONE) {MeasureChildWithMargins is the top one. // If the sub View is not GONE, the measureChildWithMargins method is the top one
    // MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec
    // The child View is a MeasureSpec of the child View. // The child View is a MeasureSpec of the child View.
    // Pass the leaf node. The leaf node has no child View.
     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
     final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
     maxWidth = Math.max(maxWidth, child.getMeasuredWidth() +  lp.leftMargin + lp.rightMargin);        
     maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); . . }}... .// After all the children have measured, go through a series of calculations and then set their measurements using setMeasuredDimension,
// For FrameLayout you might use the maximum View size, for LinearLayout you might add height,
// The specific measurement principle to look at the source code. In general, the parent View will wait until all the child views have finished measuring before measuring itself.setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); . }Copy the code

This section describes custom FlowLayout

To do FlowLayout, first involved in the following questions:

  • (2) How to get the width of the FlowLayout? (3) How to get the height of the FlowLayout

Steps:

  • Overwrite onMeasure() to calculate the width and height of the current FlowLayout
    • First, you come in with a schema that uses MeasureSpec to get the values suggested by the system
    • Then, calculate the size of the FlowLayout:
      • Traversing the width of the child view (involving the margin of the child view) > measure the width of the control if it is larger than the newline
    • Finally, set to the system via setMeasuredDimension () :
  • Rewrite onLayout() to lay out all child controls
    • Rewrite onLayout() to lay out all child controls
    • Then calculate the top and left coordinates of the current row control according to whether to break the line:

Java dynamic proxy implementation and principle detailed analysis

Static agent

  • Both proxied and proxied classes implement the same interface
  • The proxy class holds the power object of the proxied class
  • A method that calls a proxy class actually executes a method of the proxied class

A dynamic proxy

The way proxy classes create proxies while the program is running is called dynamic proxies. In our static proxy example above, the proxy class (studentProxy) is self-defined and compiled before the program runs.

The advantage of dynamic proxies over static proxies is that it is easy to uniformly handle the functions of proxy classes without changing the methods in each proxy class. Dynamic proxy simple implementation

// Create an InvocationHandler associated with the proxy object
  InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
// Create a stuProxy object with each Invocation method replacing the Invoke method from the Invocation
  Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), newClass<? >[]{Person.class}, stuHandler);Copy the code

Create a StuInvocationHandler class that implements the InvocationHandler interface. This class holds an instance target of the proxied object. There is an Invoke method in InvocationHandler, and all methods that execute a proxy object are replaced with invoke methods.

The invoke method executes the corresponding method of the proxied object target.

public class StuInvocationHandler<T> implements InvocationHandler {
   //invocationHandler holds the proxied object
    T target;
    
    public StuInvocationHandler(T target) {
       this.target = target;
    }
    
    /** * proxy: represents the dynamic proxy object * method: represents the method being executed * args: represents the argument passed in when calling the target method */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Proxy Execution" +method.getName() + "Method"); * /// Insert monitoring method during proxy process, calculate the method time
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        returnresult; }}Copy the code

Perform operations:

public class ProxyTest {
    public static void main(String[] args) {
        
        // Create an instance object, which is the proxied object
        Person zhangsan = new Student("Zhang");
        
        // Create an InvocationHandler associated with the proxy object
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
        
        // Create a stuProxy object to delegate Zhangsan. Each Invocation method replaces the Invoke method from the Invocation Invocation
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), newClass<? > [] {Person. Class}, stuHandler);// The agent performs the method of paying the class feestuProxy.giveMoney(); }}Copy the code

The principle of analysis

  • Above we created a dynamic Proxy object using the newProxyInstance method of the Proxy class, which simply encapsulates the steps to create a dynamic Proxy class (the standard part in red) :
public static ObjectnewProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<? >[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager();if(sm ! =null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* * Look up or generate the designated proxy class. */Class<? > cl = getProxyClass0(loader, intfs);/* * Invoke its constructor with the designated invocation handler. */
        try {
            if(sm ! =null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<? > cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null; }}); }return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e); }}Copy the code

In fact, we should pay most attention to the Class<? > cl = getProxyClass0(loader, intfs); This is where the proxy class is generated and this class file is cached in the Java virtual machine

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /** * Call InvocationHandler (); /** * Call InvocationHandler (); /** * Call InvocationHandler (); The InvocationHandler in turn holds an instance of the * proxied object and wonders if it is.... ? Yeah, that's what you think it is. * *super(paramInvocationHandler) is the constructor that calls the superclass Proxy. The parent class holds: protected InvocationHandler h; * Protected Proxy(InvocationHandler h) {* Objects. RequireNonNull (h); * this.h = h; *} * * /
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  // This static block was originally at the end, so I brought it to the front to describe it
   static
  {
    try
    {
      // See what is in the static block here and see if you can find the giveMoney method. Remember that giveMoney got the name M3 from reflection, and forget all else
      m1 = Class.forName("java.lang.Object").getMethod("equals".new Class[] { Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString".new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney".new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode".new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw newNoClassDefFoundError(localClassNotFoundException.getMessage()); }}/** ** This calls the giveMoney method of the proxy object, directly invoking the Invoke method of InvocationHandler and passing m3 in. *this.h.invoke(this, m3, null); This is simple and clear. * Now, think again, the proxy object holds an InvocationHandler object, and the InvocationHandler object holds an object that is proestedand refers to the Invoke method in the InvacationHandler. Well, that's it. * /
  public final void giveMoney()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw newUndeclaredThrowableException(localThrowable); }}// Note that toString, hashCode, and equals methods are omitted to save space. The principle is exactly the same as the giveMoney method.

}
Copy the code

We can think of InvocationHandler as a mediation class that holds a proxied object and invokes the proxied object’s corresponding method in the Invoke method. References to the proxied object are held in an aggregate manner, and external calls to Invoke are eventually converted to calls to the proxied object.

Okhttp source code analysis

Dispatcher:

  • synchronous

Dispatcher performing synchronous calls: joins the runningSyncCall queue directly, not actually executing the Call, but assigning it to external execution

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
Copy the code
  • asynchronous

To queue a Call: ReadyAsyncCall is queued if the number of calls currently being executed is greater than maxRequest(64), or if the number of calls on the Host of the call is greater than maxRequestsPerHos(5), otherwise runningAsyncCalls are queued and executed

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else{ readyAsyncCalls.add(call); }}Copy the code

How do asynchronous queues go from ready to running? Finished is called at the end of each call

 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if(! calls.remove(call))throw new AssertionError("Call wasn't in-flight!");
      // After each remove, execute promoteCalls to rotate.
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    // When the thread pool is empty, the callback is executed
    if (runningCallsCount == 0&& idleCallback ! =null) {
      idleCallback.run();
    }
  }

  private void promoteCalls() {
     // If the currently executing thread is larger than maxRequests(64), no action
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}Copy the code

This finished calls. Remove (call) deletes the call and promoteCalls() is executed. If the current thread is larger than maxRequest, no operation is performed. If it is smaller than maxRequest, readyAsyncCalls is iterated, a call is pulled out, and the call is placed in runningAsyncCalls, and then execute. If runningAsyncCalls exceed maxRequest during traversal, they are not added, otherwise they are added all the time. So you can say: PromoteCalls () is responsible for converting ready calls to running calls. Specific execution requests are implemented in RealCall and synchronized execution in RealCall. AsyncCall is implemented in AsyncCall execute. Inside are calling RealCall getResponseWithInterceptorChain method to implement the responsibility chain calls

Okhttp caching mechanism

CacheControl corresponds to the CacheControl in HTTP

public final class CacheControl {

  private final boolean noCache;
  private final boolean noStore;
  private final int maxAgeSeconds;
  private final int sMaxAgeSeconds;
  private final boolean isPrivate;
  private final boolean isPublic;
  private final boolean mustRevalidate;
  private final int maxStaleSeconds;
  private final int minFreshSeconds;
  private final boolean onlyIfCached;
  private final boolean noTransform;

  /** * Cache control request directives that require network validation of responses. Note that such * requests may be assisted by the cache via conditional GET requests. */
  public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();

  /**
   * Cache control request directives that uses the cache only, even if the cached response is
   * stale. If the response isn't available in the cache or requires server validation, the call
   * will fail with a {@code 504 Unsatisfiable Request}.
   */
  public static final CacheControl FORCE_CACHE = new Builder()
      .onlyIfCached()
      .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
      .build();
}
Copy the code
  • noCache()

Corresponding to “no-cache”, if it appears in the header of the response, it does not mean that the response is not allowed to cache, but that the client needs to verify with the server again and make an additional GET request to GET the latest response. If a request header appears, it indicates that a cached response is not applicable, that is, a memory network request to retrieve the response.

  • noStore()

Corresponding to “no-store”, if it appears in the response header, the response cannot be cached

  • maxAge(int maxAge,TimeUnit timeUnit)

Corresponding to “max-age”, sets the maximum inventory time for cached responses. If the cache call reaches the maximum lifetime, no network request will be made

  • maxStale(int maxStale,TimeUnit timeUnit)

Corresponding to “max-stale”, the maximum expiration time that cache responses can accept. If this parameter is not specified, stale cache responses will not be used

  • minFresh(int minFresh,TimeUnit timeUnit)

For “min-fresh”, set the minimum number of seconds that a response will continue to refresh. If a response expires after minFresh has passed, the cached response cannot be used and the network request needs to be renewed

  • onlyIfCached()

Corresponds to onlyIfCached, which is used in the request header to indicate that the request only accepts the response in the cache. If there is no response in the cache, a response with status code 504 is returned.

CacheStrategy class,

Strategy principle

Based on whether the networkRequest and cacheResponse values are null, different policies are given as follows:

networkRequest cacheResponse Result the results
null null Only -if-cached (503 error must be returned if no network request is made and the cache does not exist or is out of date)
null nulnon-nulll Does not make network request, returns cache directly, does not request network
non-null null Need to make a network request, and cache does not exist or past, direct access to the network
non-null non-null The Header contains the ETag/ last-Modified tag. The request needs to be made if the condition is met, or the network needs to be accessed

Construction of the CacheStrategy class

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if(cacheResponse ! =null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        // Get the header value in cacheReposne
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1); }}}}/**
     * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
     */
    public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();

      if(candidate.networkRequest ! =null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null.null);
      }

      return candidate;
    }
    /**
     * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
     */
    public CacheStrategy get() {
      // Get the current cache policy
      CacheStrategy candidate = getCandidate();
     // If a network request is not null and cacheControl in the request is cache only
      if(candidate.networkRequest ! =null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        // Use a cache-only policy
        return new CacheStrategy(null.null);
      }
      return candidate;
    }

    /** Returns a strategy to use assuming the request can use the network. */
    private CacheStrategy getCandidate() {
      // No cached response.
      // If no response is cached, return a policy with no response
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }
       // If it is HTTPS, the handshake is missing and a policy with no response is returned
      // Drop the cached response if it's missing a required handshake.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }
     
      // The response cannot be cached
      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
      if(! isCacheable(cacheResponse, request)) {return new CacheStrategy(request, null);
      }
     
      // Get the CacheControl in the request header
      CacheControl requestCaching = request.cacheControl();
      // If no cache is set in the request, no cache is set
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
      // Get the age of the response
      long ageMillis = cacheResponseAge();
      // Gets the time of the last response refresh
      long freshMillis = computeFreshnessLifetime();
      // If there is a maximum duration requirement in the request, select the minimum duration requirement
      if(requestCaching.maxAgeSeconds() ! = -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      // If there is a minimum refresh time in the request
      if(requestCaching.minFreshSeconds() ! = -1) {
         // Update the minimum time limit with the minimum update time in the request
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
      // Maximum validation time
      long maxStaleMillis = 0;
      // Response cache controller
      CacheControl responseCaching = cacheResponse.cacheControl();
      // If the response (server) side does not have to validate and there is a maximum validation seconds
      if(! responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() ! = -1) {
        // Update the maximum validation time
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
     // Response caching is supported
       // Duration + minimum refresh time < last refresh time + maximum validation time can be cached
      // Now - time that has passed (sent) + time that can survive < max-age
      if(! responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder();if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning"."110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning"."113 HttpURLConnection \"Heuristic expiration\"");
        }
       // Cache the response
        return new CacheStrategy(null, builder.build());
      }
    
      // If you want to cache requests, certain conditions must be met
      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
      if(etag ! =null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if(lastModified ! =null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if(servedDate ! =null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        // Return a periodic request if there are no conditions
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }
      
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      // Returns a conditional cache request policy
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
Copy the code

From the above analysis, we can see that the cache strategy implemented by OKHTTP is essentially a lot of if/else judgments, which are written down in the RFC standard documentation. With that said, it’s time to start today’s topic: —-CacheInterceptor

CacheInterceptor class

  //CacheInterceptor.java
 @Override 
 public Response intercept(Chain chain) throws IOException {
    // If there is a cache, it is fetched from the cache, possibly nullResponse cacheCandidate = cache ! =null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    // Get the cache policy object
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    // Request in the policy
    Request networkRequest = strategy.networkRequest;
     // Response in the policy
    Response cacheResponse = strategy.cacheResponse;
     // Cache non-null judgment,
    if(cache ! =null) {
      cache.trackResponse(strategy);
    }
    // Cache policy is not null and cache response is null
    if(cacheCandidate ! =null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
     // Disable network use (according to cache policy), cache is invalid again, return directly
    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }
     // Cache is valid, no network is used
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    // Cache is invalid, execute next interceptor
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null&& cacheCandidate ! =null) { closeQuietly(cacheCandidate.body()); }}// There is a local cache, which response to use according to the criteria
    // If we have a cache response too, then we're doing a conditional get.
    if(cacheResponse ! =null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else{ closeQuietly(cacheResponse.body()); }}// Use network response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if(cache ! =null) {
       // Cache to local
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.}}}return response;
  }
Copy the code

To summarize the above process:

  1. If the cache is configured, the value is obtained from the cache
  2. Caching strategies
  3. The cache monitor
  4. Disable network use (according to cache policy), cache is invalid again, return directly
  5. The cache is valid and no network is used
  6. Cache invalid, next interceptor executed
  7. There is a local cache, and the root condition selects which response to use
  8. Using network Response
  9. Cache to local

Arouter source code analysis

  • When using the @route annotation, the program uses the corresponding annotation handler at compile time.
  • Call arouter.init (Context), add the fully qualified name of the generated file to the RouterMap by reflection, and then add the group to the following collection by instantiating the corresponding class by reflection and calling loadInto
  • A routing table map (key=path,value=RouteMeta) is a routing table map (key=path,value=RouteMeta).
//Warehouse simplified source code
class Warehouse {
    // group Sets of nodes
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
   // group Sub-node set
    static Map<String, RouteMeta> routes = new HashMap<>();
}
Copy the code
  • When the application calls the navigation() method, it sets the map of the routing table to the corresponding value, fetches the corresponding RouteMeta object through the path, and finally jumps to the corresponding activity
// navigation() in _ARouter
final class _ARouter {
  protected Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
     switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1! = flags) { intent.setFlags(flags); }else if(! (currentContextinstanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0! = postcard.getEnterAnim() ||0! = postcard.getExitAnim()) && currentContextinstanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null! = callback) {// Navigation over.callback.onArrival(postcard); }}});break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null; }}Copy the code

Use of route interceptor – if you need to log in to jump to a page, you can use interceptor. After logging in, you can jump to the corresponding interface through the unified processing of interceptor, similar to the principle of AOP

Eventbus source code parsing

The analysis consists of three parts:

Eventbus.getdefault ().register(obj)

public void register(Object subscriber) {
        // Use reflection to get the Class object of the obj passed in.
        // The subscriber is the MainActivity objectClass<? > subscriberClass = subscriber.getClass();/ / step 1
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        / / step 2
        synchronized (this) {
            for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code

Step 1 is to get all the @SUBSCRIBE annotated methods in the currently registered object

  • SubscriberMethod defines the Method name, ThreadMode thread model, eventType class object, priority refers to the priority of the received event, sticky refers to whether the event is sticky, The SubscriberMethod encapsulates this information
  • The first thing to do is get it from the cache, if there’s a direct return in the cache, there’s nothing in the cache if there’s no data in the cache it calls a different method based on the Boolean ignoreGeneratedIndex,
  • If the set of methods is still empty, the program will throw an exception to remind the user that the registered object and its parent class do not Subscribe to public methods. (In many cases, EventBus will throw this exception if it is not obturated during the package.) EventBus can’t find the method because the method name was obfuscated), stores the retrieved method set in the cache, and returns the method set.

Step 2 Troubleshoot the following problems

  • Are these methods already registered for the event and should you consider whether the method names are the same
  • There are multiple methods registered for this event in a registered object. How do we save these methods

EventBus.getDefault().post(xxx);

public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
       // Add the Event to the List
        eventQueue.add(event);

        if(! postingState.isPosting) { postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); postingState.isPosting =true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
               // Iterate over the list if the list is empty
                while(! eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); }}finally {
                postingState.isPosting = false;
                postingState.isMainThread = false; }}}Copy the code
  • The current Event is added to the eventQueue, and the while loop handles the post of each Event
  • Get event type
  • Get the subscriber and subscription method based on the event type
  • The subscription event is processed based on whether the current sender is the main thread and the subscription method thread model
    • The subscription method is the main thread
      • The sender is the main thread that directly reflects the subscription method
      • The sender is the child thread that performs the subscription method reflectively through the Handler message mechanism
    • Subscription methods are child threads
      • The sender is the main thread that performs the reflection subscription method through the thread pool
      • The sender is a child thread that directly reflects the execution of the subscription method

Glide execution process and level 3 cache

Glide execution flow

  • Glide. With (this), get a requestManager object
    • The Application parameter is passed in, and the life of the Application object is the life of the Application, so Glide doesn’t need to do anything special. It is automatically synchronized with the life of the Application, and if the Application is shut down, Glide’s loading terminates at the same time.
    • Passing non-application parameters Whether you pass an Activity, FragmentActivity, Fragment from a V4 package, or Fragment from an APP package in the Glide. With () method, the final flow is the same. Add a hidden Fragment to the current Activity. So why add a hidden Fragment here? Glide needs to know the life cycle of the load. Since the Fragment life cycle is synchronized with the Activity, it can listen to the Fragment if the Activity is destroyed, so that Glide can catch the event and stop the image loading.

Caching strategies

Memory cache –> disk cache –> Network load

process

The cache key is the unique identifier that implements memory and disk caching

	// Create the EngineKey object
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
Copy the code

The EngineKey object is created by calling the BuildKey method of the keyFactory and passing in the load data (String,URL, etc.), width and height of the image, signature, setting parameters, etc.

Overriding the equals and hashcode methods in the EngineKey ensures that it is considered the same EngineKey object only if all the parameters passed in to the EngineKey are the same.

Memory cache

Caching principle: weak reference mechanism and LruCache algorithm

  • Weak reference mechanism: When the JVM does garbage collection, objects associated with weak references are reclaimed regardless of whether there is currently sufficient memory
  • LruCache algorithm: Internal LinkedHashMap is used to store external cache objects as strong references. When the cache is full, LruCache removes the previously used cache objects and adds new cache objects

Cache implementation: images in use are cached using weak reference mechanism, and images not in use are cached using LruCache.

Why use a weak reference www.it610.com/article/128…

  • This protects the currently used resources from being reclaimed by the LruCache algorithm
  • With weak references, you can cache strongly referenced resources in use without blocking unreferenced resources that need to be reclaimed by the system.

Disk cache

RxJava2 source code analysis

Our purpose:

  1. Know how the source (Observable) sends data out.
  2. Know how the endpoint (Observer) receives the data.
  3. When the source and destination are connected
  4. Know how thread scheduling works
  5. Do you know how the operator is implemented

Conclusion:

  • In the subscribeActual() method, sources and ends are associated
  • source.subscribe(parent); When this code executes, the ObservableEmitter starts sending data to the Observer from sending ObservableOnSubscribe. That is, data is pushed from source to destination.
  • Dispose calls Observer onXXXX() only when the relationship between Observable and Observer is not disposed
  • Observer onComplete() and onError() are mutually exclusive only once, as CreateEmitter automatically dispose() after calling either of them. Verify this conclusion according to the above point
  • Error is followed by complete, and complete is not displayed. On the other hand will crash
  • It is also important to note that onSubscribe() is a callback from the thread where we subscribe() and is not affected by thread scheduling.

Thread scheduling subscribeOn

  • Returns an ObservableSubscribeOn wrapper class object
  • When the object returned in the previous step is subscribed, the subscribeActual() method in the class is called back, where the thread is immediately switched to the corresponding schedulers.xxx () thread.
  • In the switched thread, execute source.subscribe(parent); Subscribe to an upstream (endpoint)Observable
  • The upstream (endpoint)Observable starts sending data, which simply calls the onXXX() method corresponding to the downstream observer, so the operation takes place in the switched thread.