Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the layout optimizations of Android?

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.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report