Android学习总结之kotlin篇(一)

1. open 关键字的用法和作用深入源码分析

  • 类的 open 修饰:在 Kotlin 字节码层面,对于一个open类,编译器会在生成的字节码中添加ACC_SUPERACC_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为例,在字节码层面,属性的gettersetter方法(如果是可变属性)会带有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 lazyby lazy的实现依赖于kotlin.Lazy接口及其实现类kotlin.LazyKt__LazyJVMKt.LazyImplby lazy本质上是通过委托给Lazy对象来实现延迟初始化。例如:
val lazyValue: String by lazy { "Hello" }

这里lazy是一个函数,其定义如下:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

SynchronizedLazyImplLazy接口的一个实现类,它会在getValue方法被调用时(即第一次访问属性时),调用initializer函数进行初始化,并将结果缓存起来,后续访问直接返回缓存值。在 Android 开发中,这种机制利用了对象的延迟初始化特性,减少了不必要的资源消耗,提升了应用性能。

  • lateinitlateinit主要是 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.displayMetrics
    return displayMetrics.widthPixels
}

/**
 * 获取屏幕高度
 * @return 屏幕高度,单位为像素
 */
fun Context.screenHeight(): Int {
    // 获取屏幕显示指标
    val displayMetrics = resources.displayMetrics
    return 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 的 Intent
    val intent = Intent(this, T::class.java)
    // 配置 Intent
    intent.init()
    // 启动 Service
    startService(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.ContextCompat

fun 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.ContextCompat

fun 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)
    // 关闭 Activity
    finish()
}

// 示例调用
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() {
    // 获取当前焦点 View
    val view = currentFocus
    if (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) {
    // 获取 FragmentManager
    val 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.FragmentTransaction

fun 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.ViewBinding

inline 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? = null

    init {
        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 T
        thisRef.view = invokeLayout.root
        return 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)
        // 使用 binding
        binding.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()
            // 根据数据数量显示或隐藏空视图和 RecyclerView
            showHideEmptyView()
        }

        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.VISIBLE
                this@setEmptyView.visibility = View.GONE
            } else {
                emptyView.visibility = View.GONE
                this@setEmptyView.visibility = View.VISIBLE
            }
        }
    })
    // 初始时显示或隐藏空视图和 RecyclerView
    showHideEmptyView()
}

// 示例调用
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.RecyclerView

fun RecyclerView.setup(
    layoutManager: RecyclerView.LayoutManager,
    adapter: RecyclerView.Adapter<*>,
    hasFixedSize: Boolean = true
) {
    this.layoutManager = layoutManager
    this.adapter = adapter
    this.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.RecyclerView

fun RecyclerView.addDivider() {
    val divider = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
    addItemDecoration(divider)
}

// 示例调用
val recyclerView: RecyclerView = findViewById(R.id.recycler_view)
recyclerView.addDivider()

扩展函数在 Android 开发中能够显著提高代码的复用性和可维护性,让开发过程更加高效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值