Android performance optimization_Shorten the construction layout time by 20 times (below), and easily win the offer

}
scaleType = ImageView.ScaleType.FIT_XY
setImageResource(R.drawable.user_portrait_gender_female)
}.also { addView(it) }
}.also { addView(it) }

RelativeLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
topMargin = 10f.dp()
}
setPadding(0, 0, 10f.dp(), 10f.dp())

TextView(this@Factory2Activity2).apply {
layoutParams =
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)
.apply {
addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE)
}
text = “2020.04.30”
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }

View(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 1f.dp())
setBackgroundColor(Color.parseColor("#eeeeee"))

}.also { addView(it) }

RelativeLayout(this@Factory2Activity2).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
topMargin = 40f.dp()
}

LinearLayout(this@Factory2Activity2).apply {
layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
}
orientation = LinearLayout.HORIZONTAL

Button(this@Factory2Activity2).apply {
layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
rightMargin = 20f.dp()
gravity = Gravity.LEFT
}
setBackgroundResource(R.drawable.bg_orange_btn)
text = “cancel”
}.also {
addView(it)
}
Button(this@Factory2Activity2).apply {
layoutParams =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
leftMargin = 20f.dp()
gravity = Gravity.RIGHT
}
setBackgroundResource(R.drawable.bg_orange_btn)
text = “OK”
}.also { addView(it) }
}.also { addView(it) }
}.also { addView(it) }
}
}

Describe the above code in pseudocode, the structure is like this:

Container control.apply { child control.apply { //Set control properties }.also { addView(it) } }



The code is stinky, long and redundant, and not at all readable. If you want to fine-tune the control where the gems are displayed, you can try this, I can't find that control anyway.

But after running the test code, I was pleasantly surprised to find that the average time spent building the layout is only 1.32 ms , which is 1/20 of the static layout .

At first I thought that the nested layout was particularly time-consuming, so I ConstraintLayoutflattened the nest with the following code:

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>

</androidx.constraintlayout.widget.ConstraintLayout>

This time I achieved zero nesting, and re-run the code with expectations. But the time spent parsing the layout has not changed at all. . . All right

Since there is such a big performance gap between static layout and dynamic layout, let's improve the readability of dynamic layout code! !

DSL

DSLs are a great way to improve the readability of your build code!

DSL = domain specific language, that is, "specific domain language", a concept corresponding to it is called "general programming language", general programming language has a series of perfect capabilities to solve almost all problems that can be solved by computers, like Java belongs to this types. Domain-specific languages ​​only focus on specific tasks, such as SQL only focuses on manipulating databases, and HTML only focuses on expressing hypertext.

Since a general-purpose programming language can solve all problems, why do you need a domain-specific language? Because it can express domain-specific operations using a more compact syntax than equivalent code in a general-purpose programming language. For example, when executing an SQL statement, there is no need to start by declaring a class and its methods.

A tighter syntax means a cleaner API. Every class in an application provides the possibility for other classes to interact with it, and ensuring that these interactions are understandable and expressible concisely is critical to software maintainability.

DSLs have one feature that ordinary APIs don't: DSLs have structures. This 带接收者的lambdamakes it easy to build structured APIs.

lambda with receiver

It's a special kind of lambda that's unique to kotlin. It can be understood as "an anonymous extension function declared for the receiver". (an extension function is a feature that adds functionality to a class outside the class)

The function body of a lambda with a receiver can access all non-private members of the receiver in addition to the members of its class. This feature makes it easy to build structures.

When lambdas with receivers are paired with higher-order functions, building structured APIs becomes a piece of cake.

higher order functions

It is a special kind of function whose argument or return value is another function.

For example, the extension function of a set filter()is a higher-order function:

//The parameter of filter is a lambda
public inline fun Iterable.filter(predicate: (T) -> Boolean): List { return filterTo(ArrayList(), predicate) }

It can be used to filter elements in a collection:

students.filter { age > 18 }

This is a structured API call (not seen in java), although this structure benefits from a convention of kotlin (if the function has only one parameter and it is a lambda, the parentheses of the function parameter list can be omitted). But more critical is the interior of the lambda, thanks to the fact 带接收者的lambdathat age > 18it runs in a context different from its caller 上下文. In this context, Studentmembers that can be easily accessed Student.age(this can be omitted when pointing to age)

Let's use such a trick to improve the readability of the "dynamically build layout" code.

Dynamic Layout DSL

The effect of reconstructing the above layout with DSL is as follows:

private val rootView by lazy {
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent

ImageView {
layout_id = “ivBack”
layout_width = 40
layout_height = 40
margin_start = 20
margin_top = 20
src = R.drawable.ic_back_black
start_toStartOf = parent_id
top_toTopOf = parent_id
onClick = { onBackClick() }
}

TextView {
layout_width = wrap_content
layout_height = wrap_content
text = “commit”
textSize = 30f
textStyle = bold
align_vertical_to = “ivBack”
center_horizontal = true
}

ImageView {
layout_width = 40
layout_height = 40
src = R.drawable.ic_member_more
align_vertical_to = “ivBack”
end_toEndOf = parent_id
margin_end = 20
}

View {
layout_id = “vDivider”
layout_width = match_parent
layout_height = 1
margin_top = 10
background_color = “#eeeeee”
top_toBottomOf = “ivBack”
}

Layer {
layout_id = “layer”
layout_width = wrap_content
layout_height = wrap_content
referenceIds = “ivDiamond,tvTitle,tvContent,ivAvatar,tvTime,tvSub”
background_res = R.drawable.tag_checked_shape
start_toStartOf = “ivDiamond”
top_toTopOf = “ivDiamond”
bottom_toBottomOf = “tvTime”
end_toEndOf = “tvTime”
}

ImageView {
layout_id = “ivDiamond”
layout_width = 40
layout_height = 40
margin_start = 20
margin_top = 40
src = R.drawable.diamond_tag
start_toStartOf = “ivBack”
top_toBottomOf = “vDivider”
}

TextView {
layout_id = “tvTitle”
layout_width = wrap_content
layout_height = wrap_content
margin_start = 5
gravity = gravity_center
text = “gole”
padding = 10
textColor = “#389793”
textSize = 20f
textStyle = bold
align_vertical_to = “ivDiamond”
start_toEndOf = “ivDiamond”
}

TextView {
layout_id = “tvContent”
layout_width = 0
layout_height = wrap_content
margin_top = 5
text = “The changes were merged into release with so many bugs”
textSize = 23f
start_toStartOf = “ivDiamond”
top_toBottomOf = “ivDiamond”
end_toStartOf = “ivAvatar”
}

ImageView {
layout_id = “ivAvatar”
layout_width = 100
layout_height = 100
margin_end = 20
src = R.drawable.user_portrait_gender_female
end_toEndOf = parent_id
start_toEndOf = “tvContent”
top_toTopOf = “tvContent”
}

TextView {
layout_id = “tvSub”
layout_width = wrap_content
layout_height = wrap_content
text = “merge it with mercy”
textColor = “#c4747E8B”
textSize = 18f
start_toStartOf = “ivDiamond”
top_toBottomOf = “tvContent”
}

TextView {
layout_id = “tvTime”
layout_width = wrap_content
layout_height = wrap_content
margin_top = 20
text = “2020.04.30”
end_toEndOf = “ivAvatar”
top_toBottomOf = “ivAvatar”
}

TextView {
layout_id = “tvCancel”
layout_width = wrap_content
layout_height = wrap_content
margin_end = 30
background_res = R.drawable.bg_orange_btn
padding_start = 30
padding_top = 10
padding_end = 30
padding_bottom = 10
text = “cancel”
margin_bottom = 20
textSize = 20f
textStyle = bold
bottom_toBottomOf = parent_id
end_toStartOf = “tvOk”
start_toStartOf = parent_id
horizontal_chain_style = packed
}

TextView {
layout_id = “tvOk”
layout_width = wrap_content
layout_height = wrap_content
background_res = R.drawable.bg_orange_btn
padding_start = 30
padding_top = 10
margin_bottom = 20
padding_end = 30
padding_bottom = 10
text = “Ok”
textSize = 20f
textStyle = bold
bottom_toBottomOf = parent_id
end_toEndOf = parent_id
horizontal_chain_style = packed
start_toEndOf = “tvCancel”
}
}
}

The refactored dynamic layout code is as readable as the static layout, and even more concise than the static layout.

build control

The class name of each control in the code is an extension method. The method of building a container control is as follows:

inline fun Context.ConstraintLayout(init: ConstraintLayout.() -> Unit): ConstraintLayout =
ConstraintLayout(this).apply(init)

The construction of the container control is Contextimplemented through the extension method, and the layout can be built as long as Contextthere is a place.

Extension methods call the constructor directly and apply the lambda that initializes the properties for it. The lambda is one 带接收者的labmda, and its receiver is ConstraintLayout. This unique feature of Kotlin allows the lambda function body to additionally access non-private members of an object . initAll non-private members accessible in the function body of the lambda expression in this example ConstraintLayout, so that it is easy to set control properties in the function body.

With this extension function, you can build a container control like this (you can ignore the attribute assignment logic first, and introduce it in the next section):

ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
}

The above paragraph is equivalent to the following xml:

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”>

Compared with xml, some repetitive information is omitted, which is more concise.

The extension method implemented by building sub-controls ViewGroup:

inline fun ViewGroup.TextView(init: TextView.() -> Unit) =
TextView(context).apply(init).also { addView(it) }

After the child control is built, it needs to be filled in the container control, and the defined ViewGroupextension method can be called conveniently addView().

The construction method of the control inlineis inlined by keywords, and the compiler will inlineflatten the code with the function body to the calling place, thus avoiding a function call, and the function call also has time and space overhead (on the stack create a stack frame). By default, every lambda in Kotlin is compiled into an anonymous class, unless the lambda is inlined. The build method is inlined so that no function calls are made to build the layout and no anonymous inner classes are created.

Now you can add child controls to the container control like this:

ConstraintLayout {
layout_width = match_parent
layout_height = match_parent

TextView {
layout_width = wrap_content
layout_height = wrap_content
}
}

The disadvantage of this definition is that it can only ViewGroupbe built in TextView. If there is a need for a separate build, you can imitate the build method of the container control:

inline fun Context.TextView(init: TextView.() -> Unit) =
TextView(this).apply(init)

Set control properties

Each attribute in xml has a corresponding Java method, and calling the method directly makes the readability of the dynamically constructed code poor.

Is there any way to convert method calls into property assignment statements? - Extended attributes :

inline var View.background_color: String
get() {
return “”
}
set(value) {
setBackgroundColor(Color.parseColor(value))
}

In order to Viewincrease background_colorthe extended attribute named, it is Stringa variable of type, and the value and setting method need to be defined for it. When the property is assigned, set()the method will be called, which is called View.setBackgroundColor()to set the background color.

Now you can set the background color of the control like this:

ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
background_color = “#ffff00”
}

In particular, for the following "or" attributes:

Change to +:

TextView {
layout_width = wrap_content
layout_height = wrap_content
gravity = gravity_center_horizontal + gravity_top
}

Incrementally modify layout properties

In the above example, the background color is an independent property, that is, modifying it will not affect other properties. But modifying the layout properties is done in batches. When you only want to modify one of the attribute values, you must modify it incrementally:

inline var View.padding_top: Int
get() {
return 0
}
set(value) {
setPadding(paddingLeft, value.dp(), paddingRight, paddingBottom)
}

padding_topis defined as Viewan extended attribute, so the original , , set()can be easily accessed in the method , so that these three attributes can be kept as they are and only modified .ViewpaddingLeftpaddingRightpaddingBottompaddingTop

dp()is an extension method used to convert an Int value to a dp value according to the current screen density:

fun Int.dp(): Int =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()

Setting width and height for controls also requires incremental modification:

inline var View.layout_width: Int
get() {
return 0
}
set(value) {
val w = if (value > 0) value.dp() else value
val h = layoutParams?.height ?: 0
layoutParams = ViewGroup.MarginLayoutParams(w, h)
}

When setting the width, read the original height, create a new one ViewGroup.MarginLayoutParams, and layoutParamsreassign it. For generality, select ViewGroup.MarginLayoutParams, which is LayoutParamsthe parent of all others.

A more complex example is ContraintLayoutthe relative layout property in:

inline var View.start_toStartOf: String
get() { return "" } set(value) { layoutParams = layoutParams.append { //'toLayoutId() is the method to generate the control id, which will be introduced in the next section' startToStart = value.toLayoutId () startToEnd = -1 } }








Each relative layout attribute in xml corresponds to ContraintLayout.LayoutParamsan Int value in the instance (control ID is of type Int). So you must get the original LayoutParamsinstance and assign values ​​to the corresponding new properties, like this:

inline var View.start_toStartOf: String
get() { return "" } set(value) { layoutParams = layoutParams.apply { startToStart = Control ID //'-1 means no relative constraints' startToEnd = -1 } }








But when setting the width and height, ViewGroup.MarginLayoutParamsthe instance is constructed, and it has no relative layout attributes. So you need to ViewGroup.MarginLayoutParamscopy the original width, height and margin values, and rebuild one ContraintLayout.LayoutParams:

fun ViewGroup.LayoutParams.append(set: ConstraintLayout.LayoutParams.() -> Unit) =
//'If it is a restricted layout, direct incremental assignment'
(this as? ConstraintLayout.LayoutParams)?.apply(set) ?:
/ /'Otherwise, copy the margin layout parameter value to the constraint layout parameter, and then assign it incrementally'
(this as? ViewGroup.MarginLayoutParams)?.toConstraintLayoutParam()?.apply(set)

//'Convert margin layout parameters to constraint layout parameters'
fun ViewGroup.MarginLayoutParams.toConstraintLayoutParam() =
ConstraintLayout.LayoutParams(width, height).also { it ->
it.topMargin = this.topMargin
it.bottomMargin = this. bottomMargin
it.marginStart = this.marginStart
it.marginEnd = this.marginEnd
}

This solution has a disadvantage: you must first set the width and height for the control, and then set the relative layout properties.

Generate control ID

View.setId(int id)Receives the value of type int, but the value of int has no semantics and cannot play the role of marking control, so the extended property layout_idis of type String:

inline var View.layout_id: String
get() {
return “”
}
set(value) {
id = value.toLayoutId()
}

//'Convert String into corresponding Int value'
fun String.toLayoutId():Int{ var id = java.lang.String(this).bytes.sum() if (id == 48) id = 0 return id }



String must be converted into Int before calling View.setId(). The method adopted is: first convert String into byte array, and then accumulate the array. But String in Kotlin does not getBytes(), so it can only be constructed explicitly java.lang.String.

The reason for hardcoding 48is because:

public class ConstraintLayout extends ViewGroup {
public static class LayoutParams extends MarginLayoutParams {
public static final int PARENT_ID = 0;
}
}

And I redefine the constant as a String type:

val parent_id = “0”

Through toLayoutId()the algorithm, "0"the corresponding value is 48.

A better approach is to find toLayoutId()the inverse of the algorithm, ie what should be the input when the output of the function is 0? Unfortunately, I can't figure out how to do it. Friends who want to know will call~

Now you can set the control ID like this:

ConstraintLayout {
layout_id = “cl”
layout_width = match_parent
layout_height = match_parent
background_color = “#ffff00”

ImageView {
layout_id = “ivBack”
layout_width = 40
layout_height = 40
src = R.drawable.ic_back_black
start_toStartOf = parent_id
top_toTopOf = parent_id
}
}

Rename control properties

In order to make the construction syntax as concise as possible, the original constants with class names are redefined, such as:

val match_parent = ViewGroup.LayoutParams.MATCH_PARENT
val wrap_content = ViewGroup.LayoutParams.WRAP_CONTENT

val constraint_start = ConstraintProperties.START
val constraint_end = ConstraintProperties.END
val constraint_top = ConstraintProperties.TOP
val constraint_bottom = ConstraintProperties.BOTTOM
val constraint_baseline = ConstraintProperties.BASELINE
val constraint_parent = ConstraintProperties.PARENT_ID

Added attribute: Combination attribute

Using extended attributes, you can dynamically add some attributes that are not in the original xml.

If you want to vertically align a control in ConstraintLayout, you need to set the values ​​of the two properties as the target control ID, which are top_toTopOfand respectively bottom_toBottomOf. This step can be simplified by extending the properties:

inline var View.align_vertical_to: String
get() {
return “”
}
set(value) {
top_toTopOf = value
bottom_toBottomOf = value
}

top_toTopOfThe and are similar to those bottom_toBottomOflisted above start_toStartOfand will not be repeated here.

Similarly, it can also be defined align_horizontal_to.

New property: view click listener

The following code sets the click event through the extension property:

var View.onClick: (View) -> Unit
get() {
return {}
}
set(value) {
setOnClickListener { v -> value(v) }
}

For Viewextended attributes onClick, it is 函数类型. Then you can set the click event like this:

private fun buildViewByClDsl(): View =
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent

ImageView {
layout_id = “ivBack”
layout_width = 40
layout_height = 40
margin_start = 20
margin_top = 20
src = R.drawable.ic_back_black
start_toStartOf = parent_id
top_toTopOf = parent_id
onClick = onBackClick
}
}

val onBackClick = { v : View ->
activity?.finish()
}

Thanks to that 函数类型, the click logic can be encapsulated in a lambda and assigned to a variable onBackClick.

Added property: list item click event

RecyclerViewThere is no child control click event listener, and this problem can also be solved by extending properties:

//'Click listener property for RecyclerView extension items'
var RecyclerView.onItemClick: (View, Int) -> Unit
get() { return { _, _ -> } } set(value) { setOnItemClickListener(value) }




//'Click listener for RecyclerView extension table item'
fun RecyclerView.setOnItemClickListener(listener: (View, Int) -> Unit) { //'Set touch listener for RecyclerView child control' addOnItemTouchListener(object : RecyclerView.OnItemTouchListener { / /'Construct a gesture detector for parsing click events' val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener { override fun onShowPress(e: MotionEvent?) { }





override fun onSingleTapUp(e: MotionEvent?): Boolean { //'When the click event occurs, find the child control under the click coordinates and call back the listener' e?.let { findChildViewUnder(it.x, it.y )?.let { child -> listener(child, getChildAdapterPosition(child)) } } return false }







override fun onDown(e: MotionEvent?): Boolean {
return false
}

override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
return false
}

override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
return false
}

override fun onLongPress(e: MotionEvent?) {
}
})

override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {

}

Summarize

It can be seen that the author's work and study mode is composed of the following  "six points"  :

❝ Multi-level work/study plan + Pomodoro technique + quota work method + batch processing + multi-task parallel + layer work method❞

I hope that everyone can integrate these points into their own work and study. I believe that work and study will be more effective.

The following are some book learning maps and systematic learning materials that I used in my study. Each knowledge point has a corresponding map, learning materials, videos, and interview questions.

**For example: I need to learn **Flutter knowledge. (You can refer to my learning method)

Click here to learn more and get it!

  • Flutter's mind map (no matter what you learn, you will get twice the result with half the effort if you have a learning route)

  • Flutter Advanced Learning Complete Manual

  • A full set of videos for advanced learning of Flutter

onEvent?, distanceX: Float, distanceY: Float): Boolean {
return false
}

override fun onLongPress(e: MotionEvent?) {
}
})

override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {

}

Summarize

It can be seen that the author's work and study mode is composed of the following  "six points"  :

❝ Multi-level work/study plan + Pomodoro technique + quota work method + batch processing + multi-task parallel + layer work method❞

I hope that everyone can integrate these points into their own work and study. I believe that work and study will be more effective.

The following are some book learning maps and systematic learning materials that I used in my study. Each knowledge point has a corresponding map, learning materials, videos, and interview questions.

**For example: I need to learn **Flutter knowledge. (You can refer to my learning method)

Click here to learn more and get it!

  • Flutter's mind map (no matter what you learn, you will get twice the result with half the effort if you have a learning route)

[External link image transfer...(img-JodNO1gS-1644995507939)]

  • Flutter Advanced Learning Complete Manual

[External link image transfer...(img-XzHlEApO-1644995507940)]

  • A full set of videos for advanced learning of Flutter

[External link image transfer...(img-zYVjaCax-1644995507941)]

Probably just the above steps, so that learning is not only efficient, but also can learn new knowledge systematically.

Guess you like

Origin blog.csdn.net/m0_66264630/article/details/122964162