Android学习总结之kotlin篇(一)
1. open 关键字的用法和作用深入源码分析
- 类的 open 修饰:在 Kotlin 字节码层面,对于一个
open
类,编译器会在生成的字节码中添加ACC_SUPER
和ACC_OPEN
标志。例如,定义一个open class TestOpenClass
,反编译其字节码可以看到类似如下信息:
public class TestOpenClass {// 类的成员
}
// 字节码中会包含这些标志
ACC_PUBLIC, ACC_SUPER, ACC_OPEN
这表明该类是可继承的,ACC_OPEN
标志告知虚拟机这个类可以被其他类继承。当有类继承这个open
类时,如class SubTestOpenClass : TestOpenClass()
,在生成的SubTestOpenClass
字节码中会体现出对TestOpenClass
的继承关系。
- 成员函数的 open 修饰:当一个函数被
open
修饰,在字节码层面,函数会带有ACC_OPEN
标志。例如:
open class OpenFunctionClass {open fun openFunction() {// 函数体}
}
反编译字节码,openFunction
函数的字节码会有ACC_PUBLIC, ACC_VIRTUAL, ACC_OPEN
标志。ACC_VIRTUAL
表示这是一个虚方法,支持多态调用,ACC_OPEN
则表示该方法可以在子类中被重写。子类重写该方法时,会在字节码中体现重写关系,并遵循 Java 虚拟机规范中的方法重写规则。
- 属性的 open 修饰:对于
open
修饰的属性,以open var openProperty: String
为例,在字节码层面,属性的getter
和setter
方法(如果是可变属性)会带有ACC_OPEN
标志。例如反编译字节码后,getOpenProperty
方法会有ACC_PUBLIC, ACC_OPEN
标志,这意味着子类可以重写该属性的访问器方法来改变属性的获取或设置行为。
2. 为什么 Kotlin 要用 open 关键字来标志当前类可以继承深入源码分析
- 默认 final 类的实现:Kotlin 默认类是
final
的,这在字节码层面体现为类的字节码标志只有ACC_PUBLIC, ACC_SUPER
(没有ACC_OPEN
)。例如:
class FinalClass {// 类成员
}
反编译字节码,不会出现ACC_OPEN
标志,这使得该类不能被继承。这种设计的好处是,从字节码角度保证了类的稳定性和安全性,防止在运行时被意外继承而破坏原有逻辑。
- open 关键字改变继承性原理:当使用
open
关键字修饰类时,如前所述,字节码中会添加ACC_OPEN
标志。这是 Kotlin 编译器在编译过程中根据open
关键字做出的处理,告知虚拟机该类可被继承。这种设计使得开发者可以在需要时,通过添加open
关键字,有针对性地开放类的继承,符合 Kotlin 对代码安全性和可维护性的追求。从语言设计角度看,Kotlin 编译器在处理继承关系时,会根据类和成员的open
修饰情况,生成符合 Java 虚拟机规范的字节码,保证继承关系的正确实现。
3. by lazy 和 lateinit 深入源码分析(回答 Android 面试官问题)
- by lazy:
by lazy
的实现依赖于kotlin.Lazy
接口及其实现类kotlin.LazyKt__LazyJVMKt.LazyImpl
。by lazy
本质上是通过委托给Lazy
对象来实现延迟初始化。例如:
val lazyValue: String by lazy { "Hello" }
这里lazy
是一个函数,其定义如下:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
SynchronizedLazyImpl
是Lazy
接口的一个实现类,它会在getValue
方法被调用时(即第一次访问属性时),调用initializer
函数进行初始化,并将结果缓存起来,后续访问直接返回缓存值。在 Android 开发中,这种机制利用了对象的延迟初始化特性,减少了不必要的资源消耗,提升了应用性能。
- lateinit:
lateinit
主要是 Kotlin 编译器层面的一种处理机制。当使用lateinit
声明属性时,编译器会进行特殊处理。例如lateinit var myProperty: MyType
,编译器会在字节码中标记该属性为延迟初始化属性。在使用该属性时,编译器会生成代码检查属性是否已经初始化,如果未初始化则抛出UninitializedPropertyAccessException
异常。在 Android 开发中,这种机制方便了视图绑定等场景,让开发者可以先声明属性,在合适时机进行初始化,同时通过编译器的检查保证了代码的健壮性。
kotlin的扩展函数使用
1. View
相关扩展
1.1 设置 View
透明度动画
import android.animation.ObjectAnimator
import android.view.View/*** 为 View 设置透明度动画* @param targetAlpha 目标透明度,取值范围 0f 到 1f* @param duration 动画持续时间,单位为毫秒*/
fun View.fadeTo(targetAlpha: Float, duration: Long = 300L) {// 创建透明度动画,从当前透明度过渡到目标透明度val animator = ObjectAnimator.ofFloat(this, "alpha", alpha, targetAlpha)// 设置动画持续时间animator.duration = duration// 启动动画animator.start()
}// 示例调用
val myView: View = findViewById(R.id.my_view)
myView.fadeTo(0.5f)
1.2 测量 View
的尺寸
import android.view.View/*** 测量 View 的尺寸* @return 包含 View 测量宽度和高度的 Pair*/
fun View.measureSize(): Pair<Int, Int> {// 获取 View 的布局参数val layoutParams = layoutParams// 计算测量规格val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(layoutParams.width,if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT) {View.MeasureSpec.AT_MOST} else {View.MeasureSpec.EXACTLY})val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(layoutParams.height,if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {View.MeasureSpec.AT_MOST} else {View.MeasureSpec.EXACTLY})// 进行测量measure(widthMeasureSpec, heightMeasureSpec)// 返回测量结果return measuredWidth to measuredHeight
}// 示例调用
val myView: View = findViewById(R.id.my_view)
val (width, height) = myView.measureSize()
2. Context
相关扩展
2.1 获取屏幕宽度和高度
import android.content.Context
import android.util.DisplayMetrics/*** 获取屏幕宽度* @return 屏幕宽度,单位为像素*/
fun Context.screenWidth(): Int {// 获取屏幕显示指标val displayMetrics = resources.displayMetricsreturn displayMetrics.widthPixels
}/*** 获取屏幕高度* @return 屏幕高度,单位为像素*/
fun Context.screenHeight(): Int {// 获取屏幕显示指标val displayMetrics = resources.displayMetricsreturn displayMetrics.heightPixels
}// 示例调用
val context: Context = this
val screenWidth = context.screenWidth()
val screenHeight = context.screenHeight()
2.2 从 Context
启动 Service
import android.content.Context
import android.content.Intent/*** 从 Context 启动 Service* @param T 要启动的 Service 类* @param init 用于配置 Intent 的 Lambda 表达式*/
inline fun <reified T : Service> Context.startServiceEx(noinline init: Intent.() -> Unit = {}) {// 创建启动 Service 的 Intentval intent = Intent(this, T::class.java)// 配置 Intentintent.init()// 启动 ServicestartService(intent)
}// 示例调用
class MyService : Service() {// Service 实现代码
}val context: Context = this
context.startServiceEx<MyService> {putExtra("key", "value")
}
2.3 获取 Drawable
资源
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompatfun Context.getDrawableCompat(resId: Int): Drawable? {return ContextCompat.getDrawable(this, resId)
}// 示例调用
val context: Context = this
val drawable = context.getDrawableCompat(R.drawable.my_drawable)
2.4 获取颜色资源
import android.content.Context
import androidx.core.content.ContextCompatfun Context.getColorCompat(resId: Int): Int {return ContextCompat.getColor(this, resId)
}// 示例调用
val context: Context = this
val color = context.getColorCompat(R.color.my_color)
3. Activity
相关扩展
3.1 关闭当前 Activity
并返回结果
import android.app.Activity
import android.content.Intent/*** 关闭当前 Activity 并返回结果* @param resultCode 结果码* @param data 要返回的数据*/
fun Activity.finishWithResult(resultCode: Int, data: Intent? = null) {// 设置返回结果setResult(resultCode, data)// 关闭 Activityfinish()
}// 示例调用
val activity: Activity = this
val resultIntent = Intent()
resultIntent.putExtra("result", "success")
activity.finishWithResult(Activity.RESULT_OK, resultIntent)
3.2 隐藏软键盘
import android.app.Activity
import android.view.inputmethod.InputMethodManager/*** 隐藏当前 Activity 的软键盘*/
fun Activity.hideSoftKeyboard() {// 获取当前焦点 Viewval view = currentFocusif (view != null) {// 获取输入法管理器val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager// 隐藏软键盘imm.hideSoftInputFromWindow(view.windowToken, 0)}
}// 示例调用
val activity: Activity = this
activity.hideSoftKeyboard()
4. Fragment
相关扩展
4.1 获取 Fragment
的父 Activity
并进行类型转换
import androidx.fragment.app.Fragment/*** 获取 Fragment 的父 Activity 并进行类型转换* @param T 要转换的 Activity 类型* @return 转换后的 Activity 实例,如果转换失败则返回 null*/
inline fun <reified T : Activity> Fragment.getParentActivity(): T? {return activity as? T
}// 示例调用
class MyFragment : Fragment() {fun doSomething() {val parentActivity = getParentActivity<MainActivity>()parentActivity?.let {// 使用父 Activity 进行操作}}
}
4.2 为 Fragment
添加回退栈
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction/*** 将 Fragment 添加到指定容器并添加到回退栈* @param containerId 容器 ID* @param tag Fragment 的标签*/
fun Fragment.addToBackStack(containerId: Int, tag: String? = null) {// 获取 FragmentManagerval fragmentManager = requireActivity().supportFragmentManager// 开始事务fragmentManager.beginTransaction().add(containerId, this, tag).addToBackStack(tag).commit()
}// 示例调用
val myFragment = MyFragment()
myFragment.addToBackStack(R.id.fragment_container, "MyFragment")
4.3 替换 Fragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransactionfun FragmentManager.replaceFragment(containerId: Int, fragment: Fragment) {beginTransaction().replace(containerId, fragment).commit()
}// 示例调用
val fragmentManager = supportFragmentManager
val newFragment = MyFragment()
fragmentManager.replaceFragment(R.id.fragment_container, newFragment)
4.4 获取 Activity
的视图绑定
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBindinginline fun <reified T : ViewBinding> Fragment.viewBinding() =FragmentViewBindingDelegate(T::class.java, this)// 自定义委托类
class FragmentViewBindingDelegate<T : ViewBinding>(private val bindingClass: Class<T>,fragment: Fragment
) : ReadOnlyProperty<Fragment, T> {private var binding: T? = nullinit {fragment.lifecycle.addObserver(object : LifecycleEventObserver {override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {if (event == Lifecycle.Event.ON_DESTROY) {binding = null}}})}override fun getValue(thisRef: Fragment, property: KProperty<*>): T {binding?.let { return it }val inflateMethod = bindingClass.getMethod("inflate", LayoutInflater::class.java)val invokeLayout = inflateMethod.invoke(null, thisRef.layoutInflater) as TthisRef.view = invokeLayout.rootreturn invokeLayout.also { binding = it }}
}// 在 Fragment 中使用
class MyFragment : Fragment(R.layout.fragment_my) {private val binding by viewBinding<FragmentMyBinding>()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 使用 bindingbinding.textView.text = "Hello, World!"}
}
5. RecyclerView
相关扩展
5.1 为 RecyclerView
设置空视图
import androidx.recyclerview.widget.RecyclerView
import android.view.View/*** 为 RecyclerView 设置空视图* @param emptyView 空视图*/
fun RecyclerView.setEmptyView(emptyView: View) {// 监听 RecyclerView 的数据变化adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {override fun onChanged() {super.onChanged()// 根据数据数量显示或隐藏空视图和 RecyclerViewshowHideEmptyView()}override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {super.onItemRangeInserted(positionStart, itemCount)showHideEmptyView()}override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {super.onItemRangeRemoved(positionStart, itemCount)showHideEmptyView()}private fun showHideEmptyView() {if (adapter?.itemCount == 0) {emptyView.visibility = View.VISIBLEthis@setEmptyView.visibility = View.GONE} else {emptyView.visibility = View.GONEthis@setEmptyView.visibility = View.VISIBLE}}})// 初始时显示或隐藏空视图和 RecyclerViewshowHideEmptyView()
}// 示例调用
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
val emptyView: View = findViewById(R.id.empty_view)
recyclerView.setEmptyView(emptyView)
5.2 滚动到 RecyclerView
的顶部
import androidx.recyclerview.widget.RecyclerView/*** 滚动 RecyclerView 到顶部*/
fun RecyclerView.scrollToTop() {// 平滑滚动到顶部smoothScrollToPosition(0)
}// 示例调用
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
recyclerView.scrollToTop()
5.3 快速设置 RecyclerView
import androidx.recyclerview.widget.RecyclerViewfun RecyclerView.setup(layoutManager: RecyclerView.LayoutManager,adapter: RecyclerView.Adapter<*>,hasFixedSize: Boolean = true
) {this.layoutManager = layoutManagerthis.adapter = adapterthis.setHasFixedSize(hasFixedSize)
}// 示例调用
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
val layoutManager = LinearLayoutManager(this)
val adapter = MyAdapter()
recyclerView.setup(layoutManager, adapter)
5.2 添加分割线
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerViewfun RecyclerView.addDivider() {val divider = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)addItemDecoration(divider)
}// 示例调用
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
recyclerView.addDivider()
扩展函数在 Android 开发中能够显著提高代码的复用性和可维护性,让开发过程更加高效。