In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly explains "what are the layout optimizations of Android". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Next, let the editor take you to learn "what are the layout optimizations of Android?"
The present situation and Development trend of layout Optimization is time-consuming
As we all know, layout loading has always been a time-consuming disaster area. In particular, the startup phase, as the first View load, is even more time-consuming.
Layout loading takes time for two reasons.
Read the xml file, which is an IO operation.
Parsing xml objects and creating View by reflection
Some very common practices are
Reduce the number of nesting layers of layout and reduce overpainting
Empty interface, error interface and other interfaces for lazy loading
In addition to these practices, what other means can we optimize?
Solution
Asynchronous loading
Write the layout in a code way
Asynchronous loading
Google provided the solution of AsyncLayoutInflater, asynchronous loading a long time ago, but there are many holes in this way, which will be described below.
Write the layout in a code way
Code writing way to write the layout, we may think of using java to declare the layout, for a slightly more complex layout, this way is not desirable, there are maintainability check, difficult to modify and other problems. In order to solve this problem, github has given birth to a series of excellent open source libraries.
Litho: https://github.com/facebook/litho
X2C: https://github.com/iReaderAndroid/X2C
In order to retain the advantages of xml and solve the performance problems caused by it, we have developed an X2C scheme. That is to say, during the compilation and generation of APK, the layout translation that needs to be translated is generated into the corresponding java file, so that for the developer to write the layout or write the original xml, but for the program, the runtime loads the corresponding java file.
We use APT (Annotation Processor Tool) + JavaPoet technology to complete the whole process of [annotations]-> [annotations]-> [translate xml]-> [generate java] during compilation.
These two open source libraries are basically not used in large projects, but their value is worth affirming, and the core idea is very meaningful.
Xml layout loading time-consuming problem, google also wants to improve this situation, recently released by Compose beta, he is using a declarative UI approach to write layouts, avoiding the time-consuming caused by xml. At the same time, real-time preview of the layout is also supported. This should be the trend in the future.
Compose-samples: https://github.com/android/compose-samples
Summary
The above talked about the current situation and development trend of layout optimization, and then let's take a look at what layout optimization methods can be applied to the project.
Progressive loading
Asynchronous loading
Compose declarative UI
Progressive loading what is progressive loading
Progressive loading, to put it simply, is a partial load, and after the current frame is loaded, the next frame is loaded.
One extreme approach is to load the xml file, just want to load a blank xml, and the layout is all lazily loaded using the ViewStub tag.
The advantage of this design is that it can ease the pressure of loading View at the same time. The usual practice is that we load the core View first and then load other View step by step.
Some people may ask, such a design is very chicken ribs, what is the use?
Indeed, in the high-end machine above the role is not obvious, or even can not be seen, but in the middle and low-end machine, the effect is still very obvious. In our project, the time spent on the first frame of a complex page can be reduced by about 30%.
Advantages: the adaptation cost is low, and the effect is obvious on the middle and low end machines.
Disadvantages: still need to read the xml file in the main thread
Core pseudocode 1start () {
2 loadA () {
3 loadB () {
4 loadC ()
5}
6}
7}
The above way of writing is OK, but this approach has an obvious disadvantage, that is, it will cause too many callback nesting layers. Of course, we can also use RxJava to solve this problem. However, if Rxjava is not used in the project, referencing it will result in an increase in package size.
A simple way to do this is to use the idea of a queue to add all the ViewStubTask to the queue and load the next one when the current ViewStubTask is loaded, which avoids the problem of callback nesting too many layers.
For the modified code, see
1val decorView = this.window.decorView
2ViewStubTaskManager.instance (decorView)
3. AddTask (ViewStubTaskContent (decorView))
4. AddTask (ViewStubTaskTitle (decorView))
5. AddTask (ViewStubTaskBottom (decorView))
6. Start ()
1class ViewStubTaskManager private constructor (val decorView: View): Runnable {
two
3 private var iViewStubTask: IViewStubTask? = null
four
5 companion object {
six
7 const val TAG = "ViewStubTaskManager"
eight
9 @ JvmStatic
10 fun instance (decorView: View): ViewStubTaskManager {
11 return ViewStubTaskManager (decorView)
12}
13}
fourteen
15 privateval queue: MutableList = CopyOnWriteArrayList ()
16 privateval list: MutableList = CopyOnWriteArrayList ()
seventeen
eighteen
19 fun setCallBack (iViewStubTask: IViewStubTask?): ViewStubTaskManager {
20 this.iViewStubTask = iViewStubTask
21 return this
22}
twenty-three
24 fun addTask (viewStubTasks: List): ViewStubTaskManager {
25 queue.addAll (viewStubTasks)
26 list.addAll (viewStubTasks)
27 return this
28}
twenty-nine
30 fun addTask (viewStubTask: ViewStubTask): ViewStubTaskManager {
31 queue.add (viewStubTask)
32 list.add (viewStubTask)
33 return this
34}
thirty-five
thirty-six
37 fun start () {
38 if (isEmpty ()) {
39 return
40}
41 iViewStubTask?.beforeTaskExecute ()
42 / / specifies that when decorView draws the next frame, it will call back the runnable in it.
43 ViewCompat.postOnAnimation (decorView, this)
44}
forty-five
46 fun stop () {
47 queue.clear ()
48 list.clear ()
49 decorView.removeCallbacks (null)
50}
fifty-one
52 private fun isEmpty () = queue.isEmpty () | | queue.size = = 0
fifty-three
54 override fun run () {
55 if (! isEmpty ()) {
56 / / when the queue is not empty, load the current viewStubTask first
57 val viewStubTask = queue.removeAt (0)
58 viewStubTask.inflate ()
59 iViewStubTask?.onTaskExecute (viewStubTask)
60 / / after loading is complete, postOnAnimation loads the next
61 ViewCompat.postOnAnimation (decorView, this)
62} else {
63 iViewStubTask?.afterTaskExecute ()
64}
sixty-five
66}
sixty-seven
68 fun notifyOnDetach () {
69 list.forEach {
70 it.onDetach ()
71}
72 list.clear ()
73}
seventy-four
75 fun notifyOnDataReady () {
76 list.forEach {
77 it.onDataReady ()
78}
79}
eighty
81}
eighty-two
83interface IViewStubTask {
eighty-four
85 fun beforeTaskExecute ()
eighty-six
87 fun onTaskExecute (viewStubTask: ViewStubTask)
eighty-eight
89 fun afterTaskExecute ()
ninety
ninety-one
92}
Source address: https://github.com/gdutxiaoxu/AnchorTask, the core code is mainly in ViewStubTask,ViewStubTaskManager, if you are interested, please take a look.
Asynchronous loading
Asynchronous loading, to put it simply, is to create a View in a child thread. In practical applications, we usually preload View first. Common solutions are as follows:
When appropriate, the promoter thread inflate layout. Then when you take it, go directly to the cache to check whether the View has been created, and if so, use the cache directly. Otherwise, wait for the child thread inlfate to complete.
AsyncLayoutInflater
Officials have provided a class that can be used for asynchronous inflate, but has two disadvantages:
New has to come out on the spot every time.
Asynchronously loaded view can only be obtained through callback callback (dead hole)
Therefore, we can imitate the official AsyncLayoutInflater to modify it. The core code is in AsyncInflateManager. This paper mainly introduces two methods.
The asyncInflate method, in the child thread inflateView, and stores the load result in mInflateMap.
1 @ UiThread
2fun asyncInflate (
3 context: Context
4 vararg items: AsyncInflateItem?
5) {
6 items.forEach {item->
7 if (item = = null | | item.layoutResId = = 0 | | mInflateMap.containsKey (item.inflateKey) | | item.isCancelled () | | item.isInflating ()) {
8 return
9}
10 mInflateMap [item.inflateKey] = item
11 onAsyncInflateReady (item)
12 inflateWithThreadPool (context, item)
13}
fourteen
15}
GetInflatedView method, which is used to obtain the view from asynchronous inflate. The core idea is as follows
First take the View from the cached result, get the view and return it directly.
Did not get the view, but the child thread is in the inflate, waiting to return
If you have not already started inflate, the UI thread will do the inflate
1 / *
2 * used to obtain the view from asynchronous inflate
3 *
4 * @ param context
5 * @ the layoutId that param layoutResId needs to get
6 * @ param parent container
7 * @ param inflateKey each View corresponds to an inflateKey, because the same layout may be used in many places, but multiple inflate is required, and InflateKey is used to distinguish it.
8 * @ param inflater external inflater. If there is an inflater outside, it will be passed in for possible SyncInflate.
9 * @ return the last view from inflate
10 * /
11 @ UiThread
12 fun getInflatedView (
13 context: Context?
14 layoutResId: Int
15 parent: ViewGroup?
16 inflateKey: String?
17 inflater: LayoutInflater
18): View {
19 if (! TextUtils.isEmpty (inflateKey) & & mInflateMap.containsKey (inflateKey)) {
20 val item = mInflateMap [inflateKey]
21 val latch = mInflateLatchMap [inflateKey]
22 if (item! = null) {
23 val resultView = item.inflatedView
24 if (resultView! = null) {
25 / / get the view and return directly.
26 removeInflateKey (item)
27 replaceContextForView (resultView, context)
28 Log.i (TAG, "getInflatedView from cache: inflateKey is $inflateKey")
29 return resultView
30}
thirty-one
32 if (item.isInflating () & & latch! = null) {
33 / / did not get the view, but in inflate, wait for the return
34 try {
35 latch.await ()
36} catch (e: InterruptedException) {
37 Log.e (TAG, e.message, e)
38}
39 removeInflateKey (item)
40 if (resultView! = null) {
41 Log.i (TAG, "getInflatedView from OtherThread: inflateKey is $inflateKey")
42 replaceContextForView (resultView, context)
43 return resultView
44}
45}
forty-six
47 / / if you have not started inflate, set it to false,UI thread for inflate
48 item.setCancelled (true)
49}
50}
51 Log.i (TAG, "getInflatedView from UI: inflateKey is $inflateKey")
52 / / failed to get the View of asynchronous inflate, UI thread inflate
53 return inflater.inflate (layoutResId, parent, false)
54}
Simple Demo demonstration
Step 1: choose to call the AsyncUtils#asyncInflate method to preload View at the right time
1object AsyncUtils {
two
3 fun asyncInflate (context: Context) {
4 val asyncInflateItem =
5 AsyncInflateItem (
6 LAUNCH_FRAGMENT_MAIN
7 R.layout.fragment_asny
8 null
9 null
10)
11 AsyncInflateManager.instance.asyncInflate (context, asyncInflateItem)
12}
thirteen
14 fun isHomeFragmentOpen () =
15 getSP ("async_config") .getBoolean ("home_fragment_switch", true)
16}
Step 2: when getting View, look for View in the cache first.
1 override fun onCreateView (
2 inflater: LayoutInflater, container: ViewGroup?
3 savedInstanceState: Bundle?
4): View? {
5 / / Inflate the layout for this fragment
6 val startTime = System.currentTimeMillis ()
7 val homeFragmentOpen = AsyncUtils.isHomeFragmentOpen ()
8 val inflatedView: View
nine
10 inflatedView = AsyncInflateManager.instance.getInflatedView (
11 context
12 R.layout.fragment_asny
13 container
14 LAUNCH_FRAGMENT_MAIN
15 inflater
16)
seventeen
18 Log.i (
19 TAG
20 "onCreateView: homeFragmentOpen is $homeFragmentOpen, timeInstance is ${System.currentTimeMillis ()-startTime}, ${inflatedView.context}"
21)
22 return inflatedView
23max / return inflater.inflate (R.layout.fragment_asny, container, false)
24}
Advantages and disadvantages
Advantages:
The time it takes to create a View can be greatly reduced. After using this scheme, the View is basically within the 10ms.
Shortcoming
Since View is created in advance and exists in a map, you need to remove View from map according to your business scenario, otherwise memory leak will occur.
If View is cached, remember to reset the state of view at the right time, otherwise strange things will happen sometimes.
At this point, I believe you have a deeper understanding of "what is the layout optimization of Android?" you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 252
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.