Android based
Activity
Activity Start mode
- Standard: Creates a new Actvity every time you start an Activity
- SingleTop: Reuse at the top of the stack. The current Activity is reused when the launched Activity is at the top of the stack. The data delivered by the Intent is then received via the onNewIntent method, and the Activity is recreated if it is not at the top of the stack
- SingleTask: in-stack reuse. When Acitity is above the Activty started, the Activity above will be removed from the stack first, and then the current Activity will be reused
- SingleInstace: Singleton pattern that creates a new task stack in which the current activity instance sits alone
The difference between onStart and onResume in an Activity and the difference between onPuse and onStop
First, activities can be divided into three categories:
- Foreground Activity: an active Activity that can interact with the user
- Visible but not foreground Activities: Common in Acitivty with a transparent background at the top of the stack, activities below are visible but cannot interact with the user
- Background Activities: and activities that are backpaused, such as activities that have been stopped
So onStart and onStop usually refer to whether the current activity is in the foreground, while onResume and onPause are in the foreground
How do I keep my Activity alive
The state of an Activity is usually saved automatically, and you need to use this feature only if you need to save additional data
- In general, instances of an Activity that have been called onPause or onStop remain in memory, and all information and state data about the Activity do not disappear. All changes are retained when the Activity returns to the foreground
- However, when the system runs out of memory, calls to onPuse and onStop may be recycled and destroyed. In this case, there will be no instance object of the Activity in memory. If the Activity returns to the foreground later, all previous changes will disappear. We can rewrite the onSaveInstanceState() method. The onSaceInstanceState() method takes a Bundle object that the developer can store data into. This way, even if the Activity is destroyed, when the user calls his onCreate method and restarts the Activity, The Bundle object is passed as an argument to the onCreate method. The developer can retrieve data from the Bundle object and use this data to restore the Activty to its state before it was destroyed
- Note that the onSaveInstaceState() method may not be called because there are scenarios where you don’t need to save the state data. For example, when the user presses the BACK button to exit the Activity, it is clear that the user wants to close the Activity. The onSacveInstanceState() method will not be called. If onSaveInstanceState() is called, it will occur before onPause and onStop
The Activity’s life cycle when toggling vertically
The life cycle at this point is related to the manifest file configuration
- Android :configChanges without setting the Activity, the screen will be recalled for each lifecycle by default first destroying the current Activity before reloading
- Set the Activity adnroid: configChanges = “orientation” | keyboardHidden | sereenSize “won’t cut the screen to call each life cycle in the will only perform onConfigChanged method
Which methods must be executed when two activities jump between
For example, there are two activities called A and B. Normally, when A starts B, A calls onPuse() and THEN B calls onCreate(), onStart(), onResume() and then A calls onStop. But if B is transparent or dialog style, I don’t get rid of calling A’s onStop method
How do I set an Activity to a window style
Set the android: them = “@ adnroid: style/Theme. The Dialog”
The relationship between Activity, Window, and View
- Activity: one of the four components of Android, responsible for interface display, user interaction and business logic processing
- Window: is the functional department responsible for interface display and interaction, which is equivalent to the subordinate of Activity. The Activity life cycle is responsible for business processing
- View: is the element placed in the Window container, Window is the carrier of the View, and View is the concrete display of the Window
Activity Startup process
- Click the desktop APP icon and the Launcher process uses Binder IPC to initiate a startActivity request to the Sysetm_server process
- After receiving the request, the system_server process sends a process creation request to the Zygote process through the Socket
- The Zygote process generates a new child, the APP process
- The APP process makes attchApplication requests to the System_server process through Binder IPC
- After receiving the request, the System_server process performs a series of preparatory work and then sends a ScheduleLaunchActivity request to the APP via binder IPC
- Upon receiving the request, the Binder thread of the APP process sends the LAUNCH_ACTIVITY message to the chair thread via Handelr
- After receiving the Message, the main thread creates the target Acitivity through reflection and calls methods such as acivity.oncreate ()
Fragment
How to Toggle Fragement (without re-instantiation)
The correct way to switch is to use add(), which is to call hide() and add() to add a Fragment. When you switch again, you only need to hide and then call show() to add another Fragment. This way you can switch multiple Framgnet instances without recreating the instance
The advantages of fragments
- Fragments enable you to split an activity into multiple reusable components, each with its own life cycle and UI
- Fragments make it easy to create dynamic and flexible UI designs that can be adapted to different screen sizes. From phones to tablets
- Fragment is a separate module that is tightly tied to an activity. Can dynamically remove, add, swap, etc
- Fragment offers a new way to unify your UI across different Android devices
- Fragment Solve the switching between activities is not smooth, light switching
- Fragment replaces TabActivity for navigation with better performance
The difference between Fragment’s replace and add methods
- Add does not reinitialize the fragment, replace does every time. So if you retrieve data during the Fragment life cycle, using replace will retrieve data repeatedly;
- When the same fragment is added, replace does not change, and Add reports an IllegalStateException.
- Replace removes all fragments with the same ID and then adds the current fragment. Add overwrites the previous fragment. So add is usually accompanied by hide() and show() to avoid overlapping layouts;
- With Add, if your app is in the background or otherwise destroyed by the system, the fragment referenced in hide() will be destroyed when you open it again, so there will still be layout overlap. You can use replace or add to check if it has been added.
How are values passed between Fragments and activities
- The Activity sends a value to the Fragment
The values that are going to be passed into the bundle object; Create the Fragment object Framgent in the Activity and pass it into the Fragment by calling fragment.setargunments ()
Get the Hundle object in that Fragment by calling getArguments(), and you can get the values inside
- Fragment sends a value to the Activity
Call getFragmentManager() in your Activity to get the fragmentManager, call findFragmentByTag(), or get the Fragment object from findFragmentById()
By way of interface callback
The FragmentPageAdapter is different from the FragmentStatePageAdapter
- FragmentStatePageAdapter
The FragmentStatePageAdapter will destroy any fragments that are not needed. Generally, the ViewHolder will store the Fragment being displayed and the fragments on either side of the Fragment (A, B, C). The onSaveInstanceState method is used to save the Bundle information from Fragemnt when the Fragment is destroyed. When the onSaveInstanceState method is used to save the Bundle information from Fragemnt, the onSaveInstanceState method is used to save the Bundle information from Fragemnt when the Fragment is destroyed. You can use the saved information to restore the original state
- FragmentPageAdapter
The FragmentPageAdapter calls the Detach method of the object instead of using the remove method. So the FragmentPageAdapter simply destroys the Fragment view, and the instance remains in the FragmentManager
The difference between getFragmentManager, getSupportFragmentManager, getChildFragmentManager?
Activity: getFragmentManager gets the FragmnetActivity of the FragmentManager: Yes Obtain the FragmentManmger Fragment using getSupterFragmentManager: Yes Obtain the FragmentManmger Fragment using getChildFragmentManager
Service
How to bind an Activity to a Service, and how to start its own Service in an Activity?
An Activity is bound to a service by bindService(Intent Service, ServiceConnection Conn, int Flags). When the binding is successful, the Service passes the proxy object to Conn via a callback, and we get the Service proxy object provided by the Service
You can start a Service in an Activity using the startService and bindService methods. In general, if you want to obtain Service objects, you must use bindService () method, such as music player, third-party payment, etc. You can use the startService () method if you just want to start a background task
Describe the Service life cycle
Services have bound and unbound modes, and a mixture of the two modes. The lifecycle approach is different for different uses
- Unbound mode: onCreate() and onStartCommand() are executed when startService is called for the first time, and onDestory is called when Service is closed
- Binding mode: the first bindService () executes onCreate(), onBind() executes onUnbind(), onDestory()
The above two life cycles are in relatively pure mode scenarios. It is also important to note that there is only one Service instance, meaning that if the current Service to be started already exists, it will not be created again and the onCreate () method will not be called
A Service can be bound by multiple clients. The Service will not be destroyed until all binding objects have performed onBind (), but if one client has performed onStart(), If all bind clients unBind(), the Service will not be destroyed
What is the relationship between Activity, Intent, and Service
They are among the most frequently used classes in Android development. Activity and Service are one of the four components of Android. They’re both subclasses of ContextWrapper, so they’re kind of brothers
- The Activity is responsible for the display and interaction of the user interface
- Service handles background tasks
An Activity and a Service can pass data between them in an Intent, so you can think of an Intent as a messenger
Are services executed in the main thread and can time-consuming operations be performed in the Service?
By default, services and activities run in the main thread of the current app process if not shown. Service cannot perform time-consuming operations (network requests, copying databases, large files)
Are the Service and Activity on the same thread
The default for the same app is to be in the same Thread, main Thread.
Can you play toast in Service?
You can. One of the conditions for toasting is that there must be a Context, and Service itself is a subclass of Context, so toasting in a Service is perfectly acceptable. For example, when we complete the download task in Service, we can play a toast to notify the user
Can the service lifecycle method onstartConmand() perform network operations?
You can perform network operations directly in the Service and in the onStartCommand() method
What is an IntentService? What are the advantages?
IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService
- A Service does not start a single process; it is in the same process as the application in which it resides
- A Service is also not dedicated to a new thread, so time-consuming tasks should not be handled directly within a Service
Characteristics of IntentService
- A separate worker thread is created to handle all Intent requests
- A separate worker thread is created to handle the code that implements the onHandleIntent() method without having to deal with multithreading
- After all requests are processed, IntentService stops automatically, without calling stopSelf() to stop the Service
- Provide the default implementation for Service onBind(), which returns NULL
- Provide a default implementation for Service onStartCommand that adds the request Intent to the queue
Difference between a Service and a Thread
A service is just a component that can run in the background even if the user no longer interacts with your application. Therefore, a service should be created only when needed
If you need to perform some work outside of the main thread, but only when users interact with your application, you should create a new thread instead of creating a service. For example, if you need to play some music, but only when your activity is running, you can create a thread in onCreate(), start it in onStart(), and stop it in onStop(). You can also consider using AsyncTask or HandlerThread instead of the traditional Thread class
Since you cannot control the same Thread in different activities, consider a service implementation at this point. If you use a service, it runs in the main thread of your application by default. Therefore, if the service performs intensive computations or blocking operations, you should still create a new thread in the service to do so (avoid ANR).
BroadcastReceiver
What is the difference between BroadcastReceiver and LocalBroadcastReceiver
4. BroadcastReceiver (BroadcastReceiver) Static registration with a Binder (BroadcastReceiver) (2) starting from Android3.1, the BroadcastReceiver that receives system broadcast cannot receive broadcast after the App process exits; For custom broadcasts, you can override the value of flag so that broadcasts can still be received even if the App process exits. (3) Statically registered broadcasts are handled by PackageManagerService.
Dynamic registration: 1. Registration in the code, the program can be run at the time; 2. Follow the component lifecycle; 3. AMS(Active ManagerService) is responsible for dynamically registered broadcasts. Note: For dynamic registration, it is best to register the Activity in onResume () and unregister it in onPause (). OnStop () and onDestory() may be destroyed without executing the App when the system is out of memory, and onPause () must be executed before the App is destroyed to ensure that the broadcast is cancelled before the App is destroyed.
LocalBroadcastManager implementation principle: is the application broadcast, using the Handler implementation 1. The singleton pattern is used and an external Context is converted into an Application Context to avoid memory leaks. 2. Create a Handler in the constructor to send and receive messages. This Handler is created on the main thread, that is, the broadcast receiver receives messages on the main thread, so it cannot do time-consuming operations in onReceiver (). Note: Broadcasts sent by LocalBroadcastManager can only be dynamically registered through LocalBroadcastManager, not statically registered.
1. If the BroadcastReceiver does not finish executing in the onReceiver () method within 10 seconds, an ANR exception will occur. 2. The callback method onReceive () returns a different Context for broadcast receivers with different registration methods. Registration: static context for ReceiverRestrictedContext. Dynamic registration: Context is the context of the Activity. LocalBroadcastManager dynamic registration: context is the Application context.
Android messaging
Why was Handler designed? What’s the use?
A thing is designed for a purpose, and the purpose of a Handler is to switch threads
As a major member of the Android messaging mechanism, it manages all message events related to the interface. Common usage scenarios are as follows:
- Interface message processing after cross-process
For example, when AMS communicates between processes, the Binder threads send messages to ApplicationThread’s message Handler, which is then distributed to the main thread for execution
- Switch to the main thread for UI update after network interaction
After the child thread network operation, you need to switch to the main thread for UI update
In short, Hanlder exists to solve the problem of not being able to access the UI in child threads
Why is it recommended that child threads not access (update) the UI?
Because UI controls in Android are not thread-safe, it’s not a mess if multiple threads access UI controls
Then why not lock it?
It reduces the efficiency of UI access
. UI control itself is a component close to the user, lock will naturally block, then the efficiency of UI access will be reduced, the final reaction to the user is the mobile phone has a point cardIt's too complicated
. UI access itself a relatively simple operation logic, directly create UI, modify UI. If you lock it then you make the logic of the UI access very complicated, it doesn’t have to be
So Android’s single-threaded model for handling UI operations, coupled with handlers, is a more appropriate solution
Why does child thread access to UI crash and how to resolve it?
The crash occurred in the checkThread method of the ViewRootImpl class:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
}
}
Copy the code
Check if the current thread is the same thread that created the ViewRootImpl. If not, it will crash
The ViewRootImpl is created when the interface is drawn, after the onResume, so if you update the UI in the child thread, you will find that the current thread (the child thread) is not the same thread as the View thread (the main thread) and crash
There are three solutions:
- The main thread creates the View, and the main thread updates the View
- in
ViewRootImpl
The child thread updates the UI before creation, as in the onCreate method - The child thread switches to the main thread for UI updates, for example
The Handler, the post
methods
What is MessageQueue? What data structure is used to store data?
The name should be a queue structure. What are the characteristics of queues? First in, first out, usually at the end of the team to add data, at the front of the team to get data or delete data.
The message in the Hanlder also seems to satisfy this characteristic, and the first message must be processed first. However, there are special cases in handlers, such as delayed messages.
The existence of delayed messages makes this queue some special, and it cannot guarantee the first in, first out completely, but needs to be judged according to the time. Therefore, Android adopts the form of linked list to realize this queue, which also facilitates the insertion of data.
Let’s take a look at the process of sending a message. Whichever way you send a message, it goes to the sendMessageDelayed method
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);
}
Copy the code
The sendMessageDelayed method mainly calculates the time the message needs to be processed. If delayMillis is 0, the message processing time is the current time.
Then there is the key method, enqueueMessage.
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Copy the code
Don’t look at what we don’t know, just see what we want to see:
- First of all, I set
Message
The when field represents the processing time of the message - The MSG of the current message is inserted into the table header when the execution time is greater than the message time of the table header.
- Otherwise, you have to go through the queue, which is theta
The list
Find when when is less than a node and insert it.
All right, so without saying anything else, inserting a message is just finding the right place to insert a linked list based on the execution time of the message, the WHEN field.
The specific method is to use the fast and slow Pointers P and prev through an infinite loop, and move backward one space at a time until we find a node p whose WHEN is greater than the when field we want to insert the message, and insert it between P and prev. Or iterate to the end of the list and insert to the end of the list.
So a MessageQueue is a special queue structure implemented in linked lists for storing messages.
How is delayed messaging implemented?
To sum up, the implementation of delayed messages is mainly related to the unified storage method of messages, the enqueueMessage method mentioned above
In both instant and delayed messages, a specific time is calculated and then assigned by the process as the when field of the message
The appropriate location is then found in the MessageQueue (arrange when from small to large) and the message is inserted into the MessageQueue
In this way, a MessageQueue is a linked list structure arranged by message time
How was the message extracted from MessageQueue?
Now that we’ve talked about message storage, let’s look at message retrieval, the queue.next method
Message next() { for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg ! = null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) { if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; }}}}Copy the code
Strange, why the message is also used in an endless loop?
The purpose of the loop is to ensure that a message must be returned, and if no message is available, it is blocked until a new message arrives
The nativePollOnce method is the blocking method, and the nextPollTimeoutMillis parameter is the blocking time
So when does it block? There are two cases:
- 1, There is a message, but the current time is less than the message execution time.
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
Copy the code
In this case, the blocking time is the message time minus the current time, and then the next loop, blocking
- 2, When there is no message, this is the last line of the above code:
if (msg ! = null) {} else { // No more messages. nextPollTimeoutMillis = -1; }Copy the code
Minus one means always blocking
What happens when MessageQueue has no messages? How do you wake up after blocking? What about pipe/epoll?
Following the logic above, the next method is blocked when a message is unavailable or not available through the PIPE /epoll mechanism
Epoll mechanism is an I/O multiplexing mechanism. The specific logic is that a process can monitor multiple descriptors. When a descriptor is ready (usually read or write ready), it can notify the program to perform the corresponding read/write operation, which is blocked. In Android, a Linux Pipe is created to handle blocking and wake up
- When the message queue is empty, the reader of the pipe waits for something new to read in the pipe and passes
epoll
The mechanism is blocked - When there is a message to be processed, it is written through the write end of the pipe, waking up the main thread
How are synchronous barriers and asynchronous messages implemented?
In the Handler mechanism, there are three types of messages:
A synchronous message
: Just plain old news.Asynchronous messaging
: message set by setAsynchronous(true).Synchronous barrier messages
: Messages added through the postSyncBarrier method, characterized by empty target, i.e. no corresponding handler.
What is the relationship between these three?
- Under normal circumstances, both synchronous and asynchronous messages are processed normally, that is, messages are fetched and processed according to the time when.
- When a synchronous barrier message is encountered, it searches the message queue for the asynchronous message and blocks or returns the message depending on the time.
This means that the synchronous barrier message is not returned, it is just a flag, a tool, which means that the asynchronous message will be processed first.
So the point of synchronous barriers and asynchronous messages is that some messages need to be “rushed.”
Are there specific usage scenarios for synchronous barriers and asynchronous messages?
There are many scenarios, such as the drawing method scheduleTraversals.
void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; MTraversalBarrier = mhandler.getLooper ().getQueue().postSyncBarrier(); / / sent via Choreographer rendering task mChoreographer postCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); } } Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);Copy the code
Joined the synchronization barrier in this method, the follow-up to join an asynchronous message MSG_DO_SCHEDULE_CALLBACK, finally will perform to the FrameDisplayEventReceiver, VSYNC signal used in application
What happens to the Message after it is distributed? How are messages reused?
Look at the Loop method. After the dispatchMessage method has been executed, the recycleUnchecked operation is also added
public static void loop() { for (;;) { Message msg = queue.next(); // might block try { msg.target.dispatchMessage(msg); } msg.recycleUnchecked(); } } //Message.java private static Message sPool; private static final int MAX_POOL_SIZE = 50; void recycleUnchecked() { flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; }}}Copy the code
In the recycleUnchecked method, all resources are freed, and the current empty message is inserted into the sPool header
The sPool here is a message object pool, which is also a linked list structure of messages with a maximum length of 50
So how is Message reusable? In the instantiation method of Message obtain:
public static Message obtain() { synchronized (sPoolSync) { if (sPool ! = null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }Copy the code
The first message in the message pool sPool is directly multiplexed, and sPool points to the next node. The number of message pools is reduced by one
What does Looper do? How do I get the current thread’s Looper? Why not just store threads and objects with maps
After the Handler sends the message, the message is stored in MessageQueue, and Looper is the role that manages the MessageQueue. Looper constantly looks for messages from MessageQueue, known as the loop method, and passes the messages back to Handler for processing
Looper is obtained via ThreadLocal:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }Copy the code
Create a Looper using the prepare method and add it to the sThreadLocal. Obtain a Looper from the sThreadLocal using the myLooper method
How ThreadLocal works? What are the benefits of this mechanic design?
Here’s how ThreadLocal works.
//ThreadLocal.java public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); }Copy the code
As you can roughly see from the get and set methods in the ThreadLocal class, there is a ThreadLocalMap variable that stores data in the form of key-value pairs.
key
Is this, the current ThreadLocal variable.value
T, which is the value we’re going to store.
Then continue to see where ThreadLocalMap comes from, which is the getMap method:
//ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code
The ThreadLocalMap variable is stored in the Thread class Thread
So the basic mechanics of ThreadLocal are clear:
Each thread has a threadLocals variable that stores the ThreadLocal and corresponding objects to be saved
This has the advantage of accessing the same ThreadLocal object on different threads, but retrieving different values
ThreadLocal (ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal, ThreadLocal
What’s the good of that? Why not just store threads and objects with maps?
For example:
ThreadLocal
Is the teacherThread
Is the classmateLooper
That’s the pencil
Now the teacher has bought a batch of pencils and wants to distribute them to the students. How to distribute them? There are two ways:
- 1, The teacher wrote each student’s name on each pencil and put it in a big box. Let the students look for it by themselves.
This method is to store classmates and pencils in the Map, and then use classmates to find pencils in the Map
This is a bit like using a Map to store all threads and objects. The downside is that it can be messy, each thread is connected to each other, and it can cause memory leaks
- 2. The teacher will give each pencil to each student and put it in their pocket (MAP). When using the pencil, each student can take it out of his pocket
In this way, the Map stores the teacher and the pencil, and the teacher says when you use it, the students just need to take it out of the pocket
Obviously, this approach is more scientific, and this is ThreadLocal’s approach. Since the pencils are used by students themselves, it is best to give the pencils to students for their own care at the beginning, and separate them from each other
Can Looper be created more than once?
Prepare creates a Looper using the prepare method, which determines whether the current thread has a Looper object and throws an exception if it does:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }Copy the code
Therefore, only one Looper can be created for the same thread
What is the quitAllowed field in Looper? What’s the use?
Let’s look at some of the ways he uses it:
void quit(boolean safe) { if (! mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); }}}Copy the code
If this field is false, it will not allow you to quit, and an error will be reported
But what does this quit method do? It’s never been used. And what about safe?
The quit method is to quit the message queue and terminate the message loop
- First of all, I set
mQuitting
Field to true - Then determine whether it is safe to exit. If it is, execute it
removeAllFutureMessagesLocked
Method, which empties all delayed messages, and sets the next node of the non-delayed message to null (p.ext =null). - If it does not exit safely, execute
removeAllMessagesLocked
Method to empty all messages and set the message queue to null (mMessages = NULL)
Then look at how messages are sent and processed after the quit method is called:
Boolean enqueueMessage(Message MSG, long when) { synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; }}Copy the code
When the quit method is called and McOntract is true, the message cannot be sent and an error is reported
Looking at message handling, the loop and next methods:
Message next() { for (;;) { synchronized (this) { if (mQuitting) { dispose(); return null; } } } } public static void loop() { for (;;) { Message msg = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. return; }}}Copy the code
Obviously, the loop exits the loop when the next method returns NULL when McOntract is true
So when is the quit method usually used?
- In the main thread, you can’t normally exit because the main thread stops. So when the APP needs to quit, the quit method is called, and the message involved is EXIT_APPLICATION, you can search for it.
- In the child thread, if all the messages are processed, you need to call the quit method to stop the message loop
Looper. Loop method is an infinite loop, why doesn’t it get stuck (ANR)?
- 1, the main thread itself is required to run, because to deal with various views, interface changes. So we need this infinite loop to keep the main thread running and not exiting.
- 2. The only operation that really freezes is one that takes too long to process a message, resulting in frame drops and ANR drops, not the loop method itself
- 3. Outside of the main thread, other threads handle events that accept other processes, such as
Binder Threads (ApplicationThreads)
, will accept events sent by AMS - 4, After receiving the cross-process message, it will be handed to the main thread
Hanlder
Then the message is distributed. So the life cycle of an Activity is dependent on the main threadLooper.loop
When different messages are received, corresponding actions are taken, for example, receivedmsg=H.LAUNCH_ACTIVITY
The callActivityThread.handleLaunchActivity()
Method, and finally executes to the onCreate method - 5. Block in loop when there is no message
queue.next()
In thenativePollOnce()
Method, the main thread releases CPU resources and goes to sleep until the next message arrives or a transaction occurs. So an infinite loop is not a huge drain on CPU resources
How does a Message find the Handler to which it belongs and distribute it?
In the loop method, we find the Message to process and call this code to process the Message:
msg.target.dispatchMessage(msg);
Copy the code
So the message is handed to msg.target. What is the target?
Find out where it came from:
//Handler
private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
MSG. Target = this is set when a Hanlder is used to send a message, so target is the Handler that added the message to the message queue
What is the difference between Handler post(Runnable) and sendMessage
The main sent messages in Hanlder can be divided into two types:
- post(Runnable)
- sendMessage
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
The difference between POST and sendMessage is as follows:
The POST method sets a callback to Message.
So what does this callback do? Let’s switch to the message handling method dispatchMessage:
public void dispatchMessage(@NonNull Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }Copy the code
This code can be viewed in three parts:
- 1, if
msg.callback
Non-null, which means that when a message is sent via post, it is passed to msg.callback for processing, and there is no further action - 2, if
msg.callback
Is empty, that is, through the sendMessage when sending a message, will determine whether the current mCallback Handler is empty, if not null to the Handler. Callback. HandleMessage processing - 3, if
mCallback.handleMessage
Return true, there is no follow-up - 4, if
mCallback.handleMessage
Return false, the handleMessage method overridden by the Handler class is called
So the difference between POST (Runnable) and sendMessage is how subsequent messages are handled, whether to MSG.Callback or handler. callback or handler.handleMessage
Handler. Callback. HandleMessage and Handler handleMessage what’s different? Why?
Then the code above that, the difference between the two processing methods Handler. The Callback. The handleMessage method will return true:
- If it is
true
Handler.handleMessage is no longer executed - If it is
false
, both methods are executed
So when is there a Callback and when isn’t? There are two ways in which hanlders can be created:
val handler1= object : Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
val handler2 = Handler(object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
return true
}
})
Copy the code
The common approach is the first, to subclass Handler and rewrite the handleMessage method. In the second case, the system gives us a way to use the Callback without subclass derivation
Is there a one-to-one correspondence between Handler, Looper, MessageQueue, and thread?
- There can only be one thread
Looper
Object, so threads and Looper are one-to-one correspondences. MessageQueue
The object is created when new Looper is created, so Looper and MessageQueue correspond one to one.Handler
MessageQueue is used to add a message to a MessageQueue and then distribute the message to the original handler according to the target field of the message. That is, multiple Hanlder objects can use the same thread, the same Looper, the same MessageQueue.
Conclusion: Looper, MessageQueue, thread are one-to-one correspondence, while they can be one-to-many with Handler
What is done with the Handler in ActivityThread? (Why the main thread doesn’t need to create a separate Looper)
Two main things were done:
- 1, in the main method, create the main thread
Looper
andMessageQueue
And calls the loop method to open the message loop for the main thread.
public static void main(String[] args) {
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
- 2, create a Handler for the four components of the start and stop event processing
final H mH = new H();
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int STOP_SERVICE = 116;
public static final int BIND_SERVICE = 121;
Copy the code
IdleHandler is what? What are the usage scenarios?
As mentioned earlier, when MessageQueue has no messages, it blocks in the next method. Before blocking, MessageQueue also does one thing: check for IdleHandler, and if so, execute its queueIdle method.
private IdleHandler[] mPendingIdleHandlers; Message next() { int pendingIdleHandlerCount = -1; for (;;) {synchronized (this) {// When the message is executed, Just set pendingIdleHandlerCount if (pendingIdleHandlerCount < 0 && (mMessages = = null | | now < mMessages. When)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // Initialize handlers if (handlers = null) {handlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; Handlers = mIdleHandlers. ToArray (mPendingIdleHandlers); Int I = 0; int I = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } // If queueIdle returns false, then IdleHandler (! keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; }}Copy the code
When there is no message processing, each IdleHandler object in the mIdleHandlers collection is processed and its queueIdle method is called. Finally, the current IdleHandler is deleted based on the queueIdle value.
Then see how the IdleHandler is added:
AddIdleHandler (new IdleHandler() {@override public Boolean queueIdle() {return false; }}); public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); }}Copy the code
IdleHandler is used to do idle tasks when there are no current messages in the message queue and it needs to be blocked.
Common usage scenarios are: Startup optimization
We typically place events (such as interface view drawing and assignment) in onCreate or onResume. However, both methods are actually called before the interface is drawn, which means that to some extent the time of these two methods will affect the startup time
So we can reduce startup time by putting some operations into IdleHandler, which is called after the interface is drawn.
However, there may be some potholes to be aware of
If used incorrectly, IdleHandler will never execute, such as calling the View’s invalidate method directly or indirectly in the View’s onDraw method
Its reason lies in the ontouch method that executes invalidate, will add a synchronization barrier, before until the asynchronous message blocks in the next method, and after FrameDisplayEventReceiver asynchronous task executes ontouch method, thus an infinite loop
HandlerThread is what? What are the usage scenarios?
Direct look at the source code:
public class HandlerThread extends Thread {
@Override
public void run() {
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
}
Copy the code
Oh, I see. HandlerThread is a Thread class that encapsulates Looper
Just to make it easier for us to use Handler in child threads
NotifyAll is a notifyAll method used to wake up other threads. NotifyAll is a notifyAll method used to wake up other threads.
public Looper getLooper() { if (! isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }Copy the code
GetLooper method, so to wait means to wait for the Looper to be created and then tell the side to return the Looper correctly
IntentService is what? What are the usage scenarios?
Old rules, directly look at the source code:
public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
Copy the code
Check the source code:
- First of all, this is a
Service
- And it maintains one internally
HandlerThread
That is, there is a full Looper running - A child thread is also maintained
ServiceHandler
- After a Service is started, it is executed by a Handler
onHandleIntent
methods - After the task is complete, it is automatically executed
stopSelf
Stop the current Service
So, this is a Service that can perform a time-consuming task on a child thread and stop automatically after the task is executed
What is the cause of the Handler memory leak?
A memory leak caused by a Handler usually occurs when sending a delayed message. After the Activity is closed, the MessageQueue on the main thread holds a reference to the message, which holds a reference to the Handler. Handler holds a reference to the Activity as an anonymous inner class, so you have the following reference chain
Main thread – > ThreadLocal – > Looper – > MessageQueue – > Message – > Handler – > Activity
The root cause is that the head of the reference chain, the main thread, will not be recycled, causing the Activity to fail to be recycled and causing a memory leak, with the Handler being the trigger
The reason why the child thread updates the UI through the Handler is because the running child thread is not recycled, and the child thread holds a reference to the Actiivty (otherwise it would not be able to call the Activity’s Handler), thus causing a memory leak, but the main reason for this is the child thread itself
So in both cases, the Handler is not the culprit in the event of a memory leak, but the culprit is the thread that leads them
The View map
View Drawing process
The rendering process of View starts from performTraversals of View wroot. It goes through measure, layout and draw to draw the View. PerformTraversals call performMeasure, performLayout, and performDraw, and they call measure, Layout, and Draw, and then onMeasure, OnLayout, dispatchDraw.
- Measure:
For custom measurements of a single view, you only need to calculate the size according to the MeasureSpec passed by the parent view.
For the measurement of ViewGroup, onMeasure method should be overwritten. In onMeasure method, the parent container will Measure all the child views, and the child elements will act as the parent container to Measure its own child elements repeatedly. The Measure process is then passed down from the DecorView, iterating through the dimensions of all child views to determine the total viewGroup size. The same is true for the Layout and Draw methods.
- Layout: according to the
measure
The layout size and layout parameters obtained by the child View, put the child View in the appropriate position.
For a custom single view, calculate its own position.
For ViewGroup, you need to override the onLayout method. In addition to calculating the position of each View, determine the position of each child View in the parent container and the width and height of each child View (getMeasuredWidth and getMeasuredHeight). Finally, call the Layout method of all child Views to set the position of each child View.
- Draw: Draws a View object onto the screen.
Draw () calls four methods in turn:
1) drawBackground(), according to the View position parameters obtained in the layout process, to set the background boundary. 2) onDraw(), draw the content of the View itself, generally custom single View will override this method, to achieve some drawing logic. 3) dispatchDraw(), draw sub-view 4) onDrawScrollBars(canvas), draw decorations like scroll indicator, scroll bar, and foreground
MeasureSpec = MeasureSpec
MeasureSpec is a parent View’s MeasureSpec and a child View’s LayoutParams. A child View’s MeasureSpec is a parent View’s MeasureSpec and a child View’s LayoutParams.
- First of all,
MeasureSpec
MeasureSpec is a combination of size and mode. The value in MeasureSpec is an integer (32 bits)
Int specMode = MeasureSpec. GetMode (MeasureSpec) int specSize = MeasureSpec Through the Mode and the Size to generate new SpecMode int measureSpec = measureSpec. MakeMeasureSpec (Size, Mode).Copy the code
- Second, for each child View
MeasureSpec
The value is calculated based on the child View’s layout parameters and the parent container’s MeasureSpec value, so there is a parent layout measurement mode, child View layout parameters, and the child View’s ownMeasureSpec
Diagram:
GetChildMeasureSpec calculates a child View’s MeasureSpec based on the child View’s layout parameters and the parent container’s MeasureSpec.
- Finally, the practical application:
If you want to customize the width and height, override the onMeasure method and pass the calculated width and height to the setMeasuredDimension method. For custom viewgroups, override the onMeasure method and call measureChildren to traverse all child views. Then can get wide high through getMeasuredWidth/getMeasuredHeight, finally through the setMeasuredDimension method the total width of high storage itself
View’s three measurement modes
= match_parent (layout_width=”100dp”); = match_parent (layout_width=”100dp”);
MeasureSpec.AT_MOST: Indicates that the child layout is limited to a maximum size. When childView sets its width and height to WRap_content, the parent container sets it to AT_MOST.
MeasureSpec.UNSPECIFIED: The UNSPECIFIED size of the child layout appears in the heightMode of the AadapterView item and the heightMode of the ScrollView childView. This pattern is rare
How does Scroller achieve elastic sliding of View?
- in
MotionEvent.ACTION_UP
When the event is triggered, the startScroll() method is called, which does not perform the actual sliding operation, but records the sliding correlation (sliding distance, sliding time). - Then call
invalidate/postInvalidate()
Method, asking the View to redraw, causing the view.draw method to be executed - Called in the draw method when the View is redrawn
computeScroll
Method, and computeScroll will go to Scroller to get the current scrollX and scrollY; Then through the scrollTo method to achieve sliding; And then callpostInvalidate
Method to redraw the second time. The same as the previous process, such repetition leads to the View sliding continuously with small amplitude, and multiple small amplitude sliding constitutes elastic sliding until the whole sliding is finished.
mScroller = new Scroller(context); @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: Mscroll.startscroll (getScrollX(), 0, dx, 0); invalidate(); break; } return super.onTouchEvent(event); } @override public void computeScroll() {// Override computeScroll(); And completed in the internal logic of smooth scrolling if (mScroller.com puteScrollOffset ()) {scrollTo (mScroller. GetCurrX (), mScroller. GetCurrY ()); invalidate(); }}Copy the code
What are Windows and decorViews? How does a DecorView relate to the Window
The role of DecorView
- A DecorView is a top-level View, essentially a FrameLayout
- Contains two parts, the title bar and the contents bar
- The interior column ID is content, the part of the activity that setContentView sets, and the final layout is added to the FrameLayout with ID content
- Access to the content:
ViewGroup content = findViewById(R.android.id.content)
- Get the View set:
content.getChidlAt(0)
What is Window?
- Represents the concept of a window that is all
View
The direct manager of any view throughWindow
Render (Click the event from Window->DecorView->View; The Activity ofsetContentView
The underlying throughWindow
Complete) Window
Is an abstract class whose concrete implementation isPhoneWindow
- create
Window
Need to pass throughWindowManager
create WindowManager
Outside accessWindow
The entrance of theWindow
The implementation is locatedWindowManagerService
In theWindowManager
andWindowManagerService
The interaction is throughIPC
complete
How does a DecorView relate to the Window?
- In the Activity startup process, the corresponding method to handle onResume() saves the DecorView as a member variable inside the Window
- What’s the point of associating a DecorView with a Window? For example, when an Activity’s onSaveInstanceState() saves data, the window DecorView triggers the entire View tree for state saving
//ActivityThread.java final void handleResumeActivity(IBinder token, ...) {//1. Create DecorView, set INVISIBLE View decor = R.window.getDecorView (); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getwinDowManager (); wm.addView(decor, l); //3. Set DecorView to visible R.activity. MakeVisible (); }Copy the code
LinearLayout, FrameLayout or RelativeLayout which is more efficient and why
To compare the efficiency of the three that must be drawn under the condition of the same layout more fluency and the drawing process, here the fluency is bad to express, and interference by other external factors, such as CPU, GPU, and so on, I said in the process of drawing the comparison, 1, fragments from top to bottom way of a stacked layout, Of course, it is the fastest to draw. You only need to draw itself. However, due to its drawing method, it cannot be used directly in complex scenes, so in terms of working efficiency, Fragment is only used in a single scene. RelativeLayout is a View tree with a View tree that calculates the locations of each of its relative controls. You can use a View tree with a relative View tree to calculate the location of each of its relative controls. Drawing efficiency is the lowest, but the layout of the general work is used more, so the efficiency between the three separate advantages and disadvantages, that together is also advantages and disadvantages, so can not absolutely distinguish the efficiency of the three, good horse with good ammonium that demand
Why does RelativeLayout call Measure twice
Invalidate(), postInvalidate(), requestLayout()
The invalidate method performs the draw process and redraws the View tree. When changing the explicit and implicit view, background, focus/enable, etc., which all belong to the category of appearance, invalidate operation will be triggered. To update the interface display, call the invalidate method directly.
PostInvalidate is called in the child thread, refreshes the UI, and has the same effect as invalidate
RequestLayout () When the width and height of the View have changed and no longer fit the current area, call requestLayout to rearrange the View. When a View executes a requestLayout method, it will recurse up to the top parent View and then execute the requestLayout of that top parent View, so the onMeasure of other views, onLayout may also be called
Why custom Viewwrap_content does not work
- in
GetDefaultSize () in onMeasure()
By default, when the View’s measurement mode is AT_MOST or EXACTLY, the size of the View is set to the specSize of the child View MeasureSpec. - Because AT_MOST corresponds
wrap_content
; EXACTLY correspondingmatch_parent
So, by default,wrap_content
andmatch_parent
It has the same effect. - Because I’m evaluating the child View MeasureSpec
getChildMeasureSpec()
The child View MeasureSpec is set towrap_content
ormatch_parent
In this case, the child View MeasureSpec’s specSize is set to parenSize = the current free space of the parent container
So wrap_content does the same thing as match_parent: it equals the current free space of the parent container
As shown in the
There are several ways to get the width and height of a View in an Activity
- OnWindowFocusChanged: The View is initialized and the width and height are already set. Note that onWindowFocusChanged will be called multiple times, and this callback will be executed when the Activity gains focus and loses focus
- View.post (Runnable): Post a runnable to the end of the message queue and wait for Looper to call the runnable while the view is initialized
- ViewTreeObserver: This is done using the ViewTreeObserver’s many callbacks, such as the OnGlobalLayoutListener interface, when the state of the View tree changes or the visibility of the View inside the View tree changes, The OnGlobalLayout method will be called back, which is a good time to get the View width and height. Note that OnGlobalLayout will be called multiple times as the state of the View tree changes
- View. Measure (int widthMeasureSpec, int heightMeasureSpec): Measure the width and height of the view manually
Why does onCreate not get the width and height of the View
This is because the View mapping process starts at perfromTraversals(), which is called after the onResume method
The difference between View#post and Handler#post
-
Handler.post, which is basically the same execution time as the line in onCreate;
-
View. Post, on the other hand, states that the execution time must start after Act#onResume occurs; Or to put it another way it works the same way as your view. post method is written inside Act#onResume (but only once, since onCreate is not triggered multiple times like onResume);
-
Of course, although this is the post method, the corresponding postDelayed method is similar
Why SurfaceView
We know that the View redraws the View by refreshing, and the system redraws the screen by issuing VSSYNC signal. The refresh interval is 16ms. If we can finish drawing within 16ms, there will be no problem. This will cause the interface to lag, affecting the user experience, so Android provides SurfaceView to solve this problem
View and SurfaceView
- The View works for active updates, while the SurfaceView works for passive updates, such as frequent interface refreshes
- The View refreshes the page in the main thread, while the SurfaceView opens a child thread to refresh the page
- The View does not implement double buffering when drawing; the SurfaceView implements double buffering in the underlying mechanism
What causes screen refresh to lose frames
- This is usually caused by the main thread performing a time-consuming operation
- The Layout is too complex to render in 16ms
- Too many animations are executed at the same time, causing the CPU and GPU to be overloaded
- The View is overdrawn, causing some pixels to be drawn more than once in the same frame time
- Long pauses for GC collections or frequent GCS generate large pauses
Android refreshes at 60 frames per second and calls onDraw every 16ms
The SurfaceFingle will receive the Vsync signal from Vsync, and onDraw will be performed on the Vsync signal the next time
What does Android mean by refreshing the screen every 16.6 ms? Call onDraw() every 16.6ms? Will the screen refresh every 16.6ms if it stays the same?
We often say that Android refreshes the screen every 16.6ms, in fact, it means that the bottom layer will switch the picture of each frame at this fixed frequency, and the picture data of each frame is the screen data calculated by our app after receiving the screen refresh signal to perform the traversing and drawing View tree work. However, the APP can not receive the screen refresh signal every 16.6ms. Only after the APP registers with the bottom layer to monitor the next screen refresh signal, can it receive the notification of the arrival of the next screen refresh signal. Only when a View initiates a refresh request will the app register with the bottom layer to listen for the next screen refresh signal.
In other words, only when there is a need to refresh the interface, our app will traverse and draw the View tree to recalculate the screen data when the next screen refresh signal comes. If there is no need to refresh the interface and the interface remains unchanged, our app will not receive the screen refresh signal event every 16.6ms, but the bottom layer will still switch the screen of each frame at this fixed frequency, but the screen of the following frames are the same.
The display of an interface is the result of measuring, laying out, and drawing all views in an Activity’s View tree. Will the screen refresh immediately after all this work is done?
Our app is only responsible for the calculation of screen data. After receiving the screen refresh signal, the calculation is finished. As for the screen refreshes, these are made up of the bottom layer switching between each frame of the screen at a fixed frequency. So even after all the screen data has been calculated, whether the screen will refresh immediately depends on whether it’s time for the bottom layer to switch to the next frame
GetWidth () and getMeasuredWidth()
GetMeasuredWidth () gets the view’s original size, which is the size the view was configured in the XML file or set in the code. GetWidth () gets the final display size of the view, which may or may not be equal to the original size.
Requestlayout, OnLayout, onDraw, DrawChild
RequestLayout () method: This causes the measure() procedure and layout() procedure to be called. Note: Just rearrange the View tree layout procedures including measure() and layout() procedures, do not call draw() procedures, but do not redraw any View including the caller itself.
OnLayout () method (if the View is a ViewGroup object, you need to implement this method to lay out each child View)
Call the onDraw() method to draw the View itself (this method needs to be overridden for each View, ViewGroup does not need to implement this method)
DrawChild () to re-call the draw() method of each subview
Custom View precautions
View support wrap_content this is because the view directly inherits the view or viewGroup control, if not in the onMeasure special processing wrap_content, then when borrowed in off-center use wrap_content will not achieve the desired effect. It’s the same as setting match_parent
2. If you want the view to support padding, this is because the view directly inherits the control. If the padding is not handled in the draw method, the padding property will not take effect. In addition, if the control directly inherits the viewGroup and does not handle the effect of the margin between the padding and child view in the onMeasure and onLayout methods, then setting the margin between the padding and child view will not work
You can use the View. post family of methods instead, unless you must use handlers to pass messages
If there are threads or animations in the view, you can stop them at onDetachedFromWindow (). This method is called when the activity exits and the view is removed. You also need to stop the thread or animation if the view becomes invisible.
5. Handle slide conflicts when a view has nested slides
View Event Distribution
What are the main coordinate parameters of a View? What are the main points to pay attention to
Left, Right, top,Bottom Note that these values are the coordinates of the view and its parent control. It’s not the absolute distance from the top left corner of the screen, so notice that. In addition, X and Y are also coordinate values relative to the parent control. TranslationX TranslationY, these two values will default to 0, is relative to the parent controls in the upper left corner of the offset. X =left+tranX,y=top+tranY. A lot of people don’t understand why this is happening, but it’s just that if the View is moving, if it’s moving, if it’s shifting, you have to notice that the values of top and left don’t change. No matter how you drag the view, the values of x,y,tranX,tranY change as you drag and move it. Just think about that
When is it better to use onTouchEvent or GestureDetector?
Use the former when there is only a need for sliding, and the latter when there is a need for double-clicking, etc
What problem is Scroller designed to solve?
The View scrollTo and scrollBy slides badly, they are instantaneous. The Scroller can work with view’s computeScroll to complete the gradient sliding effect. Experience better
What’s important about ScrollTo and ScrollBy
The former is an absolute slide, the latter is a relative slide. You slide the contents of the view, not the view itself. That’s important. For example, textView calls these two methods to slide the content of the displayed word
What are the consequences of using animation to slide the view?
In fact, the view animation is to move the surface UI of the view, that is, the visual effect presented to the user. The animation itself cannot move the real position of the view. Property animations are excluded. When the animation ends, the view will eventually return to its position. Of course, you can set the fillafter property so that the view representation stays at the changed position after the animation ends. So there is a very serious consequence. Let’s say your button is on the left side of the screen, you now animate it and set fillafter to the right. You will notice that clicking on the right button does not trigger the click event, but clicking on the left button does. The reason is that the right button is just a representation of the view, and the real button is left untouched. If you must do this, you can put a new button in the position of the right button in advance. When you finish the animation, you can make the right button enable and the left button gone
There are a couple of ways to slide a view. What do you notice? Which scenarios are applicable?
A: Scrollto, scrollby. This is the simplest, but you can only slide the contents of the view, not the view itself b: animation. Animations can slide view content, but be aware that non-attribute animations, as we mentioned in question 5, affect the interaction, so be careful when using them. However, most of the complex sliding effects are done by property animations, which are at killer-level C: changing layout parameters. This is best understood by dynamically modifying view parameters such as margin via Java code. But it’s used less often. I don’t really use it myself, you know
How is the event passing mechanism of a view expressed in pseudocode
For a root ViewGroup, if a click event is accepted, its dispatchTouchEvent method is first called. If the onInterceptTouchEvent of the viewGroup returns true, it means that the event is being intercepted. The event is then handled by the viewgroup itself, and the ViewGroup’s onTouchEvent method is called. If the onInterceptTouchEvent of the viewGroup returns false that means I don’t intercept the event, then I pass the event to my child, and then the child’s dispatchTouchEvent is called, and so on until the event is handled
What is the priority of onTouchEvent, OnClickListerner and OnTouchListener?
OnTouchListener has the highest priority. If the onTouch method returns false, the onTouchEvent will be called, and if it returns true, the onTouchListener will not be called. OnClick has the lowest priority
What happens if a view does not consume down events while processing events?
If a view’s onTouchEvent returns false on a Down event, then the sequence of events to which the Down event is a part will not be handled by the view’s subsequent moves and ups, but will be handled by its parent view
What happens if the view doesn’t consume move or Up events?
The sequence of events to which the event belongs disappears, and the parent view does not process it
Does the View’s onTouchEvent always get called once an event is passed to it
Yes, since the View itself doesn’t have an onInterceptTouchEvent method, the onTouchEvent method is used whenever an event comes into the view. And the default is to consume, return true. Unless the view is unclickable, which means both clickable and longgclikable are true for fale Button clickable and false for TextView
Does enable affect the onTouchEvent return value of the view?
No, onTouchEvent returns true as long as either Clickable or longClickable is true
RequestDisallowInterceptTouchEvent can interfere with the parent element in child elements distribution of events? And if so, all of them
Sure, but the Down event won’t interfere
When does MotionEvent.Action_Cancel occur
Step1: The parent View receives ACTION_DOWN. If no event is intercepted, the ACTION_DOWN precursor event quilt View receives it, and subsequent events from the parent View are sent to the child View.
Step2: At this time, if the parent View blocks ACTION_UP or ACTION_MOVE, the parent View specifies that the child View will not accept subsequent messages at the moment when the parent View blocks the message for the first time, and the child View will receive the ACTION_CANCEL event
Does the event go first to the DecorView or Window
ViewRootImpl – > DecorView – > Activity – > PhoneWindow – > DecorView – > ViewGroup
Click event is blocked, but want to pass to the View below, how to operate
The child View call parent View requestDisallowInterceptTouchEvent () method is set to true, will not perform the parent View onInterceptTouchEvent (), the click event can be spread to the View below
Consume ACTION_DOWN events in onTouchEvent in ViewGroup. How do ACTION_UP events pass
ACTION_MOVE and ACTION_UP will be sent down from the top (via dispatchTouchEvent) to the control where the ACTION_DOWN event is consumed (return true). If the ACTION_DOWN event is consumed at dispatchTouchEvent, then the event is stopped. If the ACTION_DOWN event is consumed at onTouchEvent, An ACTION_MOVE or ACTION_UP event is passed to the control’s onTouchEvent handler and the transfer ends. It’s not going to pass down
animation
Type of animation
View Animation \
- Tween animation \
- Frame by frame animation \
Attribute animation
The main difference between animation and property animation
- Different objects, tween animation can only work on view, property animation can work on all objects.
- Property changes are different, tween animation only changes the display effect, does not change the view’s properties, such as position, width and height, while property animation actually changes the object’s properties.
- Different animation effects, tween animation can only achieve displacement, zoom, rotation and transparency of four animation operations, and attribute animation can also achieve all the effects of tween animation and other animation effects.
ValueAnimator class and ObjectAnimator class
- contrast
ValueAnimator
Class &ObjectAnimator
Class, both of which are property animations, are essentially the same: change the value and then assign it to the property of the object to animate it. - But the difference is this:
ValueAnimator
The class changes the value first, and thenManual assignmentTo animate objects with properties; isindirectPerform operations on object properties;
The ValueAnimator class is essentially a mechanism for changing values
The ObjectAnimator class changes the value and then automatically assigns it to the properties of the object to animate it; Is to operate directly on object properties;
ObjectAnimator is more intelligent and automated
The difference between an interpolator and an estimator
Considerations for using animation
OOM issues
: This problem mainly occurs in frame animation. OOM is easy to appear when there are a large number of pictures and the pictures are large. This problem should be paid special attention to in actual development and frame animation should be avoided as far as possible.Memory leaks
: In the property animation, there is a kind of infinite loop animation. This kind of animation needs to stop in time when the Activity exits, otherwise the Activity cannot be released, resulting in memory leakage. After verification, it is found that View animation does not have this problem.Compatibility issues
: Animation in the following 3.0 system compatibility problems, in some special scenes may not work properly, so to do a good job of adaptation.View animation problem
: A View animation animates the View image and does not really change the state of the View. Therefore, sometimes the View cannot be hidden after the animation is completed, that is, setVisibility(view.goen) becomes invalid. Call View.clearAnimation () to clear the view animation.Don't use PX
: In the animation process, try to use DP, using PX will cause different effects on different devices.Interaction of animation elements
: Starting from 3.0, when a view is moved (panned), the click event of a property animation is triggered at the position after the move, but the view animation remains at the original position. In the system before Android3.0, no matter the View animation or property animation, the new position can not trigger the click event, while the old position can still trigger the click event (because property animation is not available before Android3.0, is implemented through the compatibility package, the bottom is also called View animation).Hardware acceleration
: In the process of using animation, it is recommended to enable hardware acceleration, which will improve the smoothness of animation.
As for hardware acceleration, intuitively speaking, graphics drawing acceleration is realized by GPU. The difference from hardware and software acceleration mainly lies in whether graphics drawing is processed by GPU or CPU. If it is GPU, it is considered as hardware-accelerated drawing; otherwise, software drawing
Bitmap
What is a Bitmap and how do YOU store images
A Bitmap, or Bitmap, is essentially an in-memory representation of the contents of an image. It treats the content of an image as a finite number of pixels that store data; Each pixel stores the ARGB value of the position of the pixel. Once the ARGB value of each pixel is determined, the content of the picture will be determined accordingly. Where, A represents transparency, and RGB represents three color channel values of red, green and blue
How is Bitmap memory calculated
Bitmaps have always been the biggest memory hogs on Android, and there are three ways to calculate their size:
getRowBytes()
This is inAPI Level 1
Added, returns the size of the bitmap row, which needs to be multiplied by the height of the bitmap to get the size of the BTImapgetByteCount()
This is inAPI Level 12
Added is a wrapper around getRowBytes() multiplied by heightgetAllocationByteCount()
This is inAPI Level 19
Add the
Here I put an image in the project’s drawable-xxhdpi folder and use the method to get the memory size of the image:
var bitmap = BitmapFactory.decodeResource(resources, R.drawable.test)
img.setImageBitmap(bitmap)
Log.e(TAG,"dpi = ${resources.displayMetrics.densityDpi}")
Log.e(TAG,"size = ${bitmap.allocationByteCount}")
Copy the code
The printed result is
size=1960000
Copy the code
How do you calculate that?
Image memory = Width * height * bytes per pixel.
The number of bytes in this pixel is related to bitmap. Config, which is an enumeration class that describes information about each pixel, such as:
ARGB_8888
. Common type, 32 bits total, 4 bytes, representing transparency and RGB channels respectively.RGB_565
. 16 bits, 2 bytes, can only describe RGB channels.
So our image memory calculation here shows:
Width 700 * height 700 * 4 bytes per pixel = 1960,000
Relationship between Bitmap memory and drawable directory
First, put a screen density comparison table for the drawable directory, from Guo Lin’s blog:
table
In the previous case, we put the image in the drawable-xxhdpi folder, and the dPI corresponding to the drawable-xxhdpi folder is the DPI-480 of our test phone. So the memory of the image is the width * height * bytes per pixel that we calculate.
What happens if we put the image in another folder, such as the drawable-hdpi folder (dPI is 240)?
Print the result again:
size = 7840000
Copy the code
This is because the actual memory footprint of an image is calculated by the formula:
Memory footprint = Width * scaling ratio * Height * scaling ratio * Bytes per pixel
This scaling is related to the screen density DPI:
Zoom ratio = DPI of the device/DPI of the directory where the image resides
So the actual memory footprint of this image:
Width 700 * (480/240) * height 700 * (480/240) * 4 bytes per pixel = 7840000
Bitmap load optimization? How to optimize without changing the image quality?
There are two commonly used optimization methods:
- Modify the Bitmap. The Config
As I mentioned earlier, different conifgs represent different amounts of space for each pixel, so if we change the default ARGB_8888 to RGB_565, then the size of each pixel will be 2 bytes instead of 4, and the image footprint will be halved.
It may degrade the image quality somewhat, but I can’t see any change in my actual tests.
- Modify inSampleSize
InSampleSize, sample rate, this parameter is used for image size compression, it will be inSampleSize every pixel on the width and height dimensions, so as to achieve the effect of zoom image. This method only changes the image size and does not affect the image quality.
val options=BitmapFactory.Options()
options.inSampleSize=2
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test2,options)
img.setImageBitmap(bitmap)
Copy the code
In a real project, we can set an inSampleSize that is similar in size to the target image to reduce the actual memory usage:
fun getImage(): Bitmap { var options = BitmapFactory.Options() options.inJustDecodeBounds = true BitmapFactory.decodeResource(resources, Options. inSampleSize = getImageSampleSize(options.outWidth,) // Calculate the optimal sample rate options.inSampleSize = getImageSampleSize(options.outWidth, options.outHeight) options.inJustDecodeBounds = false return BitmapFactory.decodeResource(resources, R.drawable.test2, options) }Copy the code
What is inJustDecodeBounds?
One of the inJustDecodeBounds is set to true and false, so what exactly is it?
Since we need to get the size of the image itself, loading decodeResource directly would increase memory, so an inJustDecodeBounds parameter is available. If inJustDecodeBounds is ture, decode’s bitmap is null, meaning that the actual bitmap is not returned, but the size of the image is put into the options value.
So this parameter is used to get information about the size of the image without taking up memory
How to implement Bitmap memory overcommitment?
If there is a requirement that different images can be loaded in the same imageView, do we need to create a new Bitmap each time, taking up new memory? If we write it like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.actvitiy_bitmap)
btn1.setOnClickListener {
img.setImageBitmap(getBitmap(R.drawable.test))
}
btn2.setOnClickListener {
img.setImageBitmap(getBitmap(R.drawable.test2))
}
}
fun getBitmap(resId: Int): Bitmap {
var options = BitmapFactory.Options()
return BitmapFactory.decodeResource(resources, resId, options)
}
Copy the code
As a result, the Bitmap will frequently request and free memory, leading to a lot of GC and memory jitter.
To prevent this, we can use the inBitmap parameter, which is used for Bitmap memory overcommitment. In this way, the same memory space can be reused by multiple Bitmap objects, reducing frequent GC.
val options by lazy {
BitmapFactory.Options()
}
val reuseBitmap by lazy {
options.inMutable = true
BitmapFactory.decodeResource(resources, R.drawable.test, options)
}
fun getBitmap(resId: Int): Bitmap {
options.inMutable = true
options.inBitmap = reuseBitmap
return BitmapFactory.decodeResource(resources, resId, options)
}
Copy the code
There are a couple of caveats here
inBitmap
Want to be withinMutable
Attribute. Otherwise, it cannot be reused.- in
The Android 4.4
Previously, only those of the same size could be reusedBitmap
Memory area;After 4.4
Just reuse the Bitmap object size ratio of memory spaceinBitmap
You can point to a small amount of memory.
Therefore, before multiplexing, it is necessary to determine whether the new Bitmap memory is smaller than the Bitmap memory that can be reused before multiplexing.
How to handle the loading of large hd images?
If the image is large in high definition, it means that image compression is not allowed, such as weibo long picture, Qingming River picture.
So we need to display the image locally, which uses the BitmapRegionDecoder attribute, mainly used to display a rectangular area of the image.
Let’s say I want to display the 100 by 100 area in the upper left corner:
fun setImagePart() {
val inputStream: InputStream = assets.open("test.jpg")
val bitmapRegionDecoder: BitmapRegionDecoder =
BitmapRegionDecoder.newInstance(inputStream, false)
val options = BitmapFactory.Options()
val bitmap = bitmapRegionDecoder.decodeRegion(
Rect(0, 0, 100, 100), options)
image.setImageBitmap(bitmap)
}
Copy the code
In the actual project use, we can swipe according to the gesture, and then constantly update our Rect parameters to achieve specific functions
How do I pass large graphs across processes?
Bundle direct delivery
. Bundle is most commonly used for transferring between activities and across processes, but the transfer size is limited to 1M.
Intent.putextra ("image",bitmap); // Intent.putextra ("image",bitmap); bundle.putParcelable("image",bitmap)Copy the code
Bitmaps can be passed directly because they implement the Parcelable interface for serialization. Parcelable uses a Binder mechanism to write Parcel serialized data to a shared memory (buffer) from which byte streams are read and then deserialized into objects for use. This shared memory, also known as the cache, has a size limit of 1M and is public. So the images it is easy to easy over the size and error TransactionTooLargeException.
So this scheme is not reliable.
The file transfer
.
Saving the image to a file and then transferring only the file path is certainly possible, but not efficient.
putBinder
So that’s the test. Bitmaps are passed with binders.
PutBinder ("bitmap", BitmapBinder(mBitmap)) // Bind val bundle = bundle () bundle.putbinder ("bitmap", BitmapBinder(mBitmap)) BitmapBinder = bundle.getBinder("bitmap") as BitmapBinder val bitmap: Bitmap? Imagebinder.getbitmap () //Binder subclass class BitmapBinder :Binder(){private var bitmap: bitmap? = null fun ImageBinder(bitmap: Bitmap?) { this.bitmap = bitmap } fun getBitmap(): Bitmap? { return bitmap } }Copy the code
Why is there no size limit with putBinder?
- because
putBinder
The file itself is put into a shared memory. After obtaining the FD, you only need to fetch the Bitmap data from the shared memory, which makes the transfer very efficient. - with
Intent/bundle
The file descriptor fd is disabled for direct transmission, and space can only be allocated in the cache of the parcel to store data, so the 1M limit cannot be exceeded.
The file descriptor is a simple integer that identifies each file and socket opened by the process. The first open file is 0, the second is 1, and so on
mvc&mvp&mvvm
An MVP, MVVM, MVC distinction
MVC
- Architecture is introduced
So, for example, we get data from a database or from the web. So, View, that’s our XML layout file, Controller, that’s our Activity
- Contact model
View –> Controller, which reflects some of the View’s user events (click and touch events) onto the Activity. Controller –> Model, which is the Activity to read and write some data that we need. Controller –> View, that is, the Activity gets the data and reflects the updated content to the View.
Such a complete project architecture came out, is our early development more commonly used project architecture.
- The advantages and disadvantages
This shortcoming is relatively obvious, the main performance is that our Activity is too heavy, often a write is hundreds of lines. The reason for this problem is that the Controller layer is too closely related to the View layer, which means that there is too much code in the Activity to manipulate the View.
But! But! In fact, Android can not be called the traditional MVC structure, because the Activity can be called the View layer or the Controller layer, so I think the Android default development structure, in fact, is not MVC project architecture. Because it is the default form of Android development from the beginning, everything is thrown into the Activity, and then you can encapsulate it, and you can’t distinguish between the layers. Of course this is my personal opinion, can discuss.
MVP
- Architecture is introduced
Before, because there is a view operation in the Activity, and do the Controller work. So the MVP architecture is to separate the view from the Controller from the Activity layer, and separate the Presenter from the Controller. The View layer is then written as an interface, the Activity implements the View interface, and the Presenter class implements methods.
Model: Data Model, such as when we get data from a database or network. View: Our XML layout file and Activity. Presenter: a separate class that does only dispatch work.
- Contact model
View –> Presenter, which reflects some of the View’s user events to the Presenter. Presenter –> Model. Presenter reads and writes data that we need. Controller –> View, Presenter gets the data and feeds it back to the Activity to update the View.
- The advantages and disadvantages
The advantage of this is that it really reduces the Activity’s burden. It lets the Activity do most of the updating of the View, and then transfers the interaction with the Model to the Presenter, who controls the interaction with the Model and the View. So make the project more clear and simple, sequential thinking development.
The disadvantages are also obvious. First, the amount of code increases greatly. Each page or function point needs to write a Presenter class. Second, because the Presenter holds an Activity object, it can cause memory leaks or null view Pointers.
MVVM
- Architecture is introduced
MVVM features bidirectional binding and is officially supported by Google, which has updated many of the architectural components in Jetpack, such as ViewModel, Livedata, DataBinding, etc., so this is now the mainstream framework and the official preferred framework.
Model: Data Model, such as when we get data from a database or network. View: Our XML layout file and Activity. ViewModel: An association layer that binds models and views so that they can be bound to each other for real-time updates
- Contact model
View –> ViewModel –>View, two-way binding, data changes can be reflected in the interface, interface changes can be reflected in the data. ViewModel –> Model, manipulate some data we need.
- The advantages and disadvantages
The advantage is that the official support is strong, so we have updated a lot of related libraries to make the MVVM architecture stronger and better to use, and the feature of bidirectional binding can save us a lot of View and Model interaction. Also basically solved the problems of the above two architectures.
Be specific about what you understand MVVM
Let’s start with how MVVM addresses the weaknesses and issues of the other two architectures:
The problem of high coupling degree between different levels is solved
, that is, to achieve better decoupling. In the MVP layer, presenters still hold a reference to the View, but in MVVM, the View and Model are bidirectionally bound, so that the viewModel basically only needs to handle the business logic, without the need for relational interface elements.Solved the problem of too much code or too much stereotype code
. Because of bidirectional binding, there is much less UI-related code, which is the key to less code. A key component of this is DataBinding, where all UI changes are handed over to the data model being observed.Fixed possible memory leaks
. One of the architectural components of MVVM is LiveData, which has life cycle awareness and can sense the life cycle of activities and so can clean up after its associated life cycle is destroyed, greatly reducing the memory leak problem.Fixed null pointer to View when Activity stops
. LiveData is used in MVVM, so if the observer’s life cycle is inactive (such as returning an Activity in the stack) when the View needs to be updated, it will not receive any LiveData events. That is, it ensures that the interface responds only when it is visible, thus eliminating the null pointer problem.Solves lifecycle management issues
. This is mainly due to the Lifecycle component, which enables controls to observe the Lifecycle and to perform Lifecycle events anytime, anywhere.
2) More about responsive programming
Responsive programming, in plain English, is I build relationships between things, and then I can leave them alone. They are driven by this relationship. It’s what we call the observer model, or the subscription publishing model.
Why? Because the essence of MVVM is something like this. Both bidirectional binding and life cycle perception are observer modes that make everything observable, so we just need to stabilize this observation relationship and the project will be robust.
3) Finally, why is MVVM so powerful?
In my opinion, MVVM is powerful not because of the architecture itself, but because of the advantages of responsive programming, plus the official support of Google, so many supported components, to maintain the MVVM architecture, in fact, is also the official want to unify the project architecture.
Good architectural ideas + official support = power
What is a ViewModel? What do you understand as a ViewModel?
As those of you who read my last article know, a ViewModel is a layer of the MVVM architecture that connects views to models. And what we are going to say today is an official framework – ViewModel.
The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner
There are two messages:
- Focus on a lifecycle approach. Due to the
ViewModel
The Activity lifecycle is applied to the entire Activity, so some state maintenance work is saved, most notably in the case of screen rotation, which used to save and read data, whileViewModel
No, it can automatically retain data.
Second, because the ViewModel remains a local singleton during its lifetime, it makes it easier for multiple fragments of an Activity to communicate because they can get the same ViewModel instance, meaning that the data state can be shared.
- Store and manage data related to the interface.
The ViewModel layer is basically responsible for maintaining the state of the UI on the interface, which is essentially maintaining the corresponding data, because the data will eventually be represented on the UI. So the ViewModel layer is actually the interface related data management, storage and other operations.
Why was the ViewModel designed and what problems did it solve?
- in
ViewModel
How does MVVM implement the ViewModel level before components are designed?
You write your own class, and then you implement bidirectional binding between View and data through interfaces and internal dependencies. So Google created the ViewModel component in order to standardize the implementation of the MVVM architecture and try to make the ViewModel level touch only the business code, not the VIew level reference and so on. It then works with other components, including LiveData, Databindingrang, to make the MVVM architecture more complete, standardized, and robust.
- What’s the problem solved?
In fact, it has already said some, such as:
2) Because of the nature of single instances in scope, multiple fragments can easily communicate with each other and maintain the same data state. 3) Improved MVVM architecture to make decoupling more pure.
Talk about the ViewModel principle.
- First of all, how do you preserve the life cycle
Before ViewModel2.0, the idea was to add a HolderFragment to an Activity and set the setRetainInstance(true) method to keep that Fragment alive while the Activity was rebuilding. This ensures that the state of the ViewModel does not change with the state of the Activity.
After 2.0, is actually use the Activity of onRetainNonConfigurationInstance () and getLastNonConfigurationInstance () these two methods, This is equivalent to saving the instance of the ViewModel and then restoring it, so the ViewModel data is guaranteed.
- How do I guarantee unique instances in scope
First, an instance of a ViewModel is captured by reflection, which takes the context of the application with it. This ensures that no references to views such as activities or fragments are held. And then the instance is created and it’s stored in a ViewModelStore container, which is actually a collection class, and the ViewModelStore class is actually the instance that’s stored on the interface, and our ViewModel is a child of a collection class.
So every time we fetch, we first look to see if there is any ViewModel in the collection. If there is no ViewModel, we instantiate it. If there is a ViewModel, we get the ViewModel directly. Finally, when the interface is destroyed, the ViewModelStore clear method is executed to clear the ViewModel data in the collection. Here’s a snippet of code:
Public <T extends ViewModel> T get(Class<T> modelClass) {public <T extends ViewModel> T get(Class<T> modelClass) {public <T extends ViewModel> T get(Class<T> modelClass) mViewModelStore.get(key); If (modelclass.isinstance (ViewModel)) {return (T) ViewModel; } // If not, instantiate the ViewModel by reflection. And stored into the ViewModelStore viewModel = modelClass. GetConstructor (Application. The class). NewInstance (mApplication); mViewModelStore.put(key, viewModel); return (T) viewModel; } public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); public final void clear() { for (ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); } } @Override protected void onDestroy() { super.onDestroy(); if (mViewModelStore ! = null && ! isChangingConfigurations()) { mViewModelStore.clear(); }}Copy the code
How does the ViewModel automate the processing lifecycle? Why do I not lose state after I rotate the screen? Why can viewModels follow the Activity/Fragment life cycle without causing memory leaks?
These three questions are very similar in that they are all about the life cycle, essentially asking why the ViewModel can manage the life cycle and not be affected by things like rebuilding.
- ViewModel2.0 before
Use a Viewless HolderFragment to maintain its life cycle. We know that the ViewModel instance is stored in a ViewModelStore container. The empty fragment can be used to manage the container as long as the Activity is active. The HolderFragment will not be destroyed, ensuring the lifetime of the ViewModel.
Furthermore, setting the setRetainInstance(True) method ensures that the life cycle of configChange is not changed, allowing the Fragment to survive the rebuilding of the Activity. To sum up, the ViewModelStore is managed with an empty fragment, and then the viewModel map is deleted when the corresponding activity is destroyed. Keep the ViewModel lifecycle the same as the Activity. This is also a clever approach used by many tripartite libraries, such as Glide, which also builds empty fragments to manage.
- After 2.0, there is androidx support
Is used to a subclass of Activity ComponentActivity, then rewrite the onRetainNonConfigurationInstance () method to save ViewModelStore, and at the time of need, Is the reconstruction of the Activity to through getLastNonConfigurationInstance () method to obtain the ViewModelStore instance. This ensures that the ViewModel in the ViewModelStore will not change as the Activity is rebuilt.
Also thanks to the LifecycleOwner interface, you can use the Lifecycles component to sense the lifecycle of each page and subscribe to clearing the ViewModel when the Activity is destroyed, not because of a configuration destory. That is, call the ViewModelStore clear method.
getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner Source, @nonnull Lifecycle.Event Event) {if (Event == Lifecycle.event.ON_DESTROY) { isChangingConfigurations()) { getViewModelStore().clear(); }}}});Copy the code
The onRetainNonConfigurationInstance method besides, here is in the Activity is destroyed because of the configuration change is called, when compare with onSaveInstanceState method call time, OnSaveInstanceState stores bundles, which have type and size restrictions and require serial numbers in the main thread. And there is no limit to the onRetainNonConfigurationInstance way, so more inclined to use it.
Before 2.0, they created an empty fragment and followed it through its life cycle. Since 2.0, LifecycleOwner interfaces have been implemented for both activities and fragments, so viewModels can use Lifecycles to sense their Lifecycles and manage instances
ViewModelScope understand?
Here is the main test ViewModel and some other components of the relationship. Coroutines, for example, are used for thread switching. If you need to stop some tasks in multiple coroutines, you must manage these coroutines, usually by adding a CoroutineScope. If you need to remove coroutines, you can go to cancel the CoroutineScope, and all coroutines tracked by him will be cancelled.
GlobalScope.launch {
longRunningFunction()
anotherLongRunningFunction()
}
Copy the code
However, this global approach is not recommended, and viewModelScope is generally recommended when scoping.
ViewModelScope is a Kotlin extension property of the ViewModel. It can exit when the ViewModel is destroyed (onCleared() method is called). So as long as the ViewModel is in use, you can use the viewModelScope to start various coroutines in the ViewModel without worrying about task leakage.
class MyViewModel() : ViewModel() { fun initialize() { viewModelScope.launch { processBitmap() } } suspend fun processBitmap() = WithContext (dispatchers.default) {// Here to do the time operation}}Copy the code
What is LiveData?
LiveData is an observable data store class. Unlike regular observable classes, LiveData has lifecycle awareness, meaning that it follows the lifecycle of other application components such as activities, fragments, or services. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state.
The official introduction is as follows, in fact, said more clearly, the main role in two points:
Data storage class
. That is, a class for storing data.Can be viewed
. The data store class is observable, which means it has one more feature than a normal data store class that responds to changes in the data.
The main idea is to use the idea of observer mode to decouple the observer from the observed and perceive the change of data, so it is generally used in ViewModel. ViewModel is responsible for triggering the update of data, and the update will be notified to LiveData, and then LiveData will notify the active observer.
var liveData = MutableLiveData<String>()
liveData.observe(this, object : Observer<String> {
override fun onChanged(t: String?) {
}
})
liveData.setVaile("xixi")
//子线程调用
liveData.postValue("test")
Copy the code
Why was LiveData designed and what problems did it solve?
LiveData, as an observer mode design idea, is often compared with Rxjava. The biggest advantage of the observer mode is that the upstream of event transmitting and the downstream of event receiving do not interfere with each other, which greatly reduces the strong coupling caused by mutual dependency.
Secondly, LiveData can also seamlessly integrate into the MVVM architecture, mainly reflected in its ability to sense life cycles such as activities, which brings many benefits:
- There will be no memory leak and the observer will bind to
Lifecycle
Object and cleans itself up after its associated life cycle has been destroyed. - The Activity will not crash if it stops during the observer’s lifetime
Inactive state
(such as returning an Activity in the stack), it will not receive any LiveData events. - Automatically determines the lifecycle and calls back the method if the observer’s lifecycle is in
STARTED
或RESUMED
State, LiveData will think that the observer is active and will callonActive
Method, otherwise called if the LiveData object does not have any active observersonInactive()
methods
Talk about how LiveData works.
In terms of principles, there are actually two methods:
- The subscription method, which is
observe
Methods. Through this method, the subscriber and the observed are associated to form the observer pattern.
A brief look at the source code:
@MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { assertMainThread("observe"); / /... LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing ! = null && ! existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } owner.getLifecycle().addObserver(wrapper); } public V putIfAbsent(@NonNull K key, @NonNull V v) { Entry<K, V> entry = get(key); if (entry ! = null) { return entry.mValue; } put(key, v); return null; }Copy the code
The putIfAbsent method stores the lifecycle related wrapper and observer as the key and value in the mObservers.
- The callback method, that is
onChanged
Methods. Notifying the observer by changing the stored value is calledonChanged
Methods. From changing the stored value methodsetValue
Look up:
@MainThread protected void setValue(T value) { assertMainThread("setValue"); mVersion++; mData = value; dispatchingValue(null); } private void dispatchingValue(@Nullable ObserverWrapper initiator) { //... do { mDispatchInvalidated = false; if (initiator ! = null) { considerNotify(initiator); initiator = null; } else { for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; } } } } while (mDispatchInvalidated); mDispatchingValue = false; } private void considerNotify(ObserverWrapper observer) { if (! observer.mActive) { return; } // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet. // // we still first check observer.active to keep it as the entrance for events. So even if // the observer moved to an active state, if we've not received that event, we better not // notify for a more predictable notification order. if (! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; //noinspection unchecked observer.mObserver.onChanged((T) mData); }Copy the code
If the observer is not in an active state (active, that is, in a visible state, STARTED or RESUMED), it returns without notice. Otherwise the onChanged method is normally notified to the observer.
Of course, if you want to be able to listen in at all times and get the callback, you can call the observeForever method