This is the third day of my participation in the First Challenge 2022.
0x0, Jetpack introduction
Android 13 has arrived 13 years after Google released Android 1.0 on September 22, 2008.
After 13 years of polishing and sedimentation, the Android system and community ecosystem has been very mature, developers from the initial framework is few, no specification, code had to write their own, to wheel, framework flying everywhere. Thanks to this, we did a lot less of the dirty work (basic code) and spent more time on business logic, leading to faster iterations.
However, developers have no choice but to choose from a variety of technologies, resulting in a mix of good and bad apps, and Android has never officially released a development standard. Some technology communities have gradually introduced application development architectures such as MVP and MVVM for more efficient collaborative development. Applications developed using these architectures are better in terms of project quality, code readability, and maintainability, so these frameworks and technologies are gaining in popularity.
Google has been working hard to build the Android ecosystem. In order to solve the fragmentation of development and make it easier for developers, Google introduced a new Android Jetpack application development architecture at Google I/O 2018. It is a collection of libraries, tools, and guidelines that might be more aptly called the Jetpack Development Toolkit.
Android Jetpack is backward compatible and designed for modern design practices such as separation of concerns, testing capabilities, loose coupling, observer mode, control rollover, Kotlin integration, and more. Designed to make it easier for developers to build robust, high-quality applications with less code.
An old image that’s been circulating on the web breaks down Jetpack components into four categories:
Use Android Jetpack to Accelerate Your App Development
Architecture and Architecture
Help developers design robust, testable, easy to maintain applications.
- Data Binding → Data Binding: interface components in the layout can be declaratively bound to Data sources in the application;
- Lifecycles → Lifecycle awareness, can sense and respond to changes in the life cycle state of activities and fragments;
- LiveData → Observable data holder class, which is life-cycle aware, unlike normal Observables;
- Navigation → In-app Navigation, Fragment management framework, or routing;
- Paging → list Paging, can easily realize Paging preloading to achieve the effect of infinite sliding;
- Room → Lightweight ORM database, which is essentially a SQLite abstraction layer with annotations + auto-generated function classes at compile time;
- ViewModel → Data storage component, with life cycle awareness;
- WorkManager → Hosting delayed tasks, even if the APP is killed or the device is restarted, as long as the TaskRecord still exists in the recent access list, will be executed;
Foundation and Foundation
Horizontal features such as backward compatibility, testing, security, Kotlin language support;
- AppCompat → helps compatibility with lower versions of Android;
- Android KTX → Provides some easy-to-use extensions for Android and Jetpack based on Kotlin features;
- Multidex → support applications with multiple Dex files;
- Test → Android testing framework for unit and runtime interface testing;
- Benchmark(performance testing), Security(Security), etc.
The UI and interface
- Animation & Transition → Built-in Animation and custom Animation effects;
- Emoji → Users can get the latest Emoji even if they haven’t updated Android;
- Auto, TV, WearOS;
- Fragment → Basic unit of componentized interface;
- Layout → Declare UI elements in XML or instantiate UI elements in code;
- Paletee → Extracting useful information from the palette;
Behaviors and Behavior
- Download Manager → System services that handle long-running HTTP downloads and timeout reconnections;
- Media & Playback → Backward-compatible apis for Media Playback and routing, including Google Cast;
- Permissions → Compatibility API for checking and requesting application Permissions;
- Notifications → provides backward-compatible notification apis with support for Wear and Auto;
- Sharing → Provide Sharing operation suitable for application operation bar;
- Slices → UI templates that create flexible interface elements that display application data outside of nutrition;
The above image is not available on Android’s official website, so I guess the official purpose is to strengthen the Architecture component, and the other three are just a collection of existing content. In practice, this part of the component is used more, Jetpack library can be used alone or in combination, developers can choose to use. Explore Jetpack library by Genre
So much for Jetpack’s brief introduction. Knowing why a component exists, and what its responsibility boundaries are, when choosing a model can help you focus on what you want to do. This section starts with a super simple → ViewBinding.
FindViewById to ViewBinding by hand
From the early days of comparing XML handwriting to findViewById, online tools automatically generate:
Automatic generation to AS plug-in:
View injection frame → ButterKnife
Create kotlin-Android-extensions (KAE) from kotlin-Android-Extensions (KAE).
Class findViewById (); class findViewById (); class findViewById (); class findViewById ();
Rough space for time, convenient is very convenient, but there are also the following problems:
In Kotlin 1.4.20-M2, JetBrains scrapped KAE and recommended ViewBinding instead.
0x3 ViewBinding basic usage
ViewBinding replaces findViewById and ensures null and type-safe Java support.
Note: To use ViewBinding, AGP version must >= 3.6
Here’s the basic usage, some of it from the official document: View Binding.
(1) to enable ViewBinding
For modules that need view binding enabled, add the following configuration to their build.gradle:
android {
...
viewBinding {
enabled = true}}Copy the code
Instead of generating the layout XML file for the binding class, you can add the following attributes to the root node:
<LinearLayout
.
tools:viewBindingIgnore="true" >.</LinearLayout>
Copy the code
When compiled, AGP generates a bound class for the XML layout file contained in the Module. The class name rules are:
Convert the XML file name to Pascal case and add a Binding, for example: result_profile. XML → ResultProfileBinding.
② Three class binding apis
// View already exists
fun <T> bind(view : View) : T
// View does not exist
fun <T> inflate(inflater : LayoutInflater) : T
fun <T> inflate(inflater : LayoutInflater, parent : ViewGroup? , attachToParent :Boolean) : T
Copy the code
Next, we will demonstrate a wave of ViewBinding usage in various scenarios, all of which revolve around the above three apis
(3) the Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
// 1. Instantiate the binding instance
binding = ActivityMainBinding.inflate(layoutInflater)
// get a reference to the root view
val view = binding.root
// 3. Make the root view the active view on the screen
setContentView(view)
// 4, reference the view control
binding.tvContent.text = "Modify TextView text"}}Copy the code
(4) fragments
class ContentFragment: Fragment() {
private var _binding: FragmentContentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup? , savedInstanceState:Bundle?).: View {
_binding = FragmentContentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
binding.ivLogo.visibility = View.GONE
}
override fun onDestroyView(a) {
super.onDestroyView()
// Fragment lives longer than View. Be sure to clear all references to bound class instances in this method
// Otherwise, memory leaks will occur
_binding = null}}Copy the code
If you are already at home, another way to write your vehicle is to call bind:
class TestFragment: Fragment(R.layout.fragment_content) {
private var _binding: FragmentContentBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentContentBinding.bind(view)
_binding = binding
binding.ivLogo.visibility = View.VISIBLE
}
override fun onDestroyView(a) {
super.onDestroyView()
// Also need to be empty
_binding = null}}Copy the code
(5) Dialog
The Fragment type is the same as the Fragment type, and the PopupWindow type is similar to the following
class TestDialog(context: Context) : Dialog(context) {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
val binding = DialogTestBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvTitle.text = "Dialog box title"}}Copy the code
6 RecyclerView
class TestAdapter(list: List<String>) : RecyclerView.Adapter<TestAdapter.ViewHolder>() {
private var mList: List<String> = list
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// Need to initialize here to get the parent container
val binding = ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvItem.text = "Adapter"
}
override fun getItemCount(a) = mList.size
// Pass the Binding object
class ViewHolder(binding: ItemTestBinding) : RecyclerView.ViewHolder(binding.root) {
var tvItem: TextView = binding.tvItem
}
}
Copy the code
⑦ Customizing viewGroups
ViewGroup subclasses can only use View binding, but View subclasses cannot, as shown in the following example:
class TestLayout: LinearLayout {
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs) {
val inflater = LayoutInflater.from(this.context)
val binding = ItemLayoutBinding.inflate(inflater, this.true)
binding.tvLayout.text = "Custom ViewGroup"
}
override fun onDraw(canvas: Canvas?). {
super.onDraw(canvas)
}
}
Copy the code
Today the include
Merge the merge XML file is sub_include_test. XML and the id of the merge XML file is include_layout.
Then there is the case with the layout file changed:
Use part of the code unchanged, run the crash message is as follows:
Merge does not load into the layout. Remove the ID of the include tag and bind to the parent layout
Pet-name ruby ViewStub
Basic usage is simple and easy to learn, but there are the following problems:
Repeat: boilerplate code to create and recycle ViewBinding instances, especially fragments, and manually empty them.
So it is necessary to package a wave of optimization
0x4 encapsulation optimization idea
① Generic + superclass implementation template code
The easiest way to think about it is to write the template code in the parent class, along with generics. It is very simple:
abstract class BaseFragment<T : ViewBinding>(layoutId: Int) : Fragment(layoutId) {
private var _binding: T? = null
val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
_binding = initBinding(view)
init()}abstract fun initBinding(view: View): T
abstract fun init(a)
override fun onDestroyView(a) {
_binding = null
super.onDestroyView()
}
}
// Subclass implementation
class TestFragment : BaseFragment<FragmentContentBinding>(R.layout.fragment_content) {
override fun initBinding(view: View) = FragmentContentBinding.bind(view)
override fun init(a) {
binding.ivLogo.visibility = View.VISIBLE
}
}
Copy the code
② Kotlin delegate + Lifecycle component
For those of you who might find writing in a parent class too intrusive, try wrapping it in another way, starting with the original Activity:
To kill the looped code, there is the problem of generics passing. Generics are erased before entering the JVM and can be obtained by reflection at runtime, and class references can be replaced by instantiating class types, such as:
fun <T: Activity> FragmentActivity.startActivity(context: Context, clazz: Class<T>) {
startActivity(Intent(context, clazz))
}
/ / call
startActivity(context, MainActivity::class.java)
Copy the code
In Kotlin, we can also use inline to define an inline function (which is replaced automatically at compile time) and reified to get a Class of a generic type, such as:
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
startActivity(Intent(context, T::class.java))
}
/ / call
startActivity<MainActivity>(context)
Copy the code
Yes, with the reflex invoke() call, add an extension method at hand:
Under the call:
Seemingly in the bag, but collapsing as soon as he ran:
The Activity is initialized without onCreate(), which is not null. You can use the standard delegate -lazy to delay the initialization.
Under the call:
You can also insert setContentView() into the extension:
Lifecycle with the Fragment component will also write the Fragment:
Under the call:
By the way, if you don’t want to use reflection yet, you can use the Kotlin higher-order function as shown in the following example:
Under the call:
You can also override ReadOnlyProperty without the lazy option. This is only a water wrapper, not a production wrapper. For a more complete wrapper you can refer to the following open source libraries:
- Binding
- VBHelper
0 x 5, principle
AGP generates a binding class for each XML in the module, and instances of that class refer directly to the View in the layout that declares the resource ID
① Automatically generated binding classes
Open: the module module name/build/generated/intermediates/javac/channel/package/databinding
As you can see (based on AGP 7.1.1, different AGP versions may vary) :
Automatically generated class file, open one at will:
So essentially it’s still findViewById, just automatically generating the control instances, mapping them one by one, and then a little bit of an overview of the process.
② Generate Java classes
Execute gradlew assembleDebug and find DataBinding instead of ViewBinding in Task build list:
Open the AGP source, and global searching dataBindingMergeGenClasses DataBindingMergeBaseClassLogTask. Kt
With that TaskManager. Kt – createDataBindingTasksIfNecessary
2333 is mixed with DataBinding, so ViewBinding is really only a small part of DataBinding
Look back: DataBindingMergeBaseClassLogTask, incremental and full amount to perform an action:
With the: DataBindingMergeBaseClassLogDelegate
With the: DataBindingMergeBaseClassLogRunnable
Determine whether a file is created, modified, or removed, and then which file is:
A global search for the ending file found it in the following directory:
It is not hard to see is that XML names and ViewBinding mapping, downwards see DataBindingMergeDependencyArtifactsTask, BR, now don’t know why. Then go down: DataBindingGenBaseClassesTask – > CreationAction:
With the: DataBindingGenBaseClassesTask – > @ TaskAction
Look at buildInputArgs(), build the input parameters, again treat incremental and full compilation differently, and then return to the configuration instance
Moving on to the CodeGenerator class, CodeGenerator by name:
Instead of indexing BaseDataBinder directly, rely on databinding- Compiler-common
Implementation 'androidx. Databinding: databinding compiler - common: 7.1.0'Copy the code
MVNResponsitory – Gradle » 7.1.0 depends on 47 runtime libraries
BaseDataBinder → generateAll()
With: ViewBinderGenerateJava. Kt – toJavaFile () – > JavaFileGenerator
Java files are constructed from here, the specific construction process, interested in you can browse this file.
In addition, if you want to understand the logic of collecting layouts and writing layouts, you can refer to the Nature of ViewBinding.
0x6. Some additions
① The difference with DataBinding
You can think of ViewBinding as a subset of DataBinding’s functionality. DataBinding has all of its functionality and does not require DataBinding. You can simply replace findViewById with ViewBinding.
(2) Automatic generation of Java classes without build
Filesystem Events Processor is used to monitor file changes and execute ViewBinding tasks when file changes occur.
③ KAE library is out of date, migrate Parcelable
Module level build.gradle adds the Kotlin-parcelize plugin.
This is all the content of this section, if you have any questions or additional comments, thank you ~
References:
-
It’s time to update your weapons – Jetpack’s most complete overview
-
Android Jetpack Architecture Components
-
Time to embrace the ViewBinding!!
-
Android | ViewBinding and Kotlin entrust shuangjian combination
-
ViewBinding neatly encapsulates the idea, and it also fits BRVAH in this way
-
Android View Binding
-
The end of the Kotlin plugin and the rise of ViewBinding
-
ViewBinding base class
-
Delve into the use of ViewBinding in include, Merge, Adapter, fragment, and activity
-
The essence of ViewBinding