整理一下自己过完年的两次面试经验

93 阅读32分钟

这个是DXC一个奔驰外包面试 结果没过,奔驰那块对源码理解的要求很高

说一下activity和fragment的生命周期

面试官考察点

    对基础概念的掌握:是否能准确描述Activity和Fragment的核心生命周期方法

    理解生命周期关联性:是否清楚Activity与Fragment生命周期的协作关系

    实际应用能力:是否能说明不同生命周期阶段应该处理哪些业务逻辑

    异常场景处理:是否了解配置变更(如屏幕旋转)时的特殊处理


回答思路

    分层次说明:先独立描述Activity和Fragment的生命周期,再说明二者的协作关系

    对比关键差异:突出Fragment特有的生命周期方法(如onAttach/onDetach)

    关联实际场景:结合常见开发场景解释生命周期方法的用途

    注意弃用方法:特别说明已弃用的方法(如Fragment的onActivityCreated)


完整回答

Activity生命周期(7个核心方法)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MainActivity : AppCompatActivity() {
    // 创建时调用(必须实现)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) // 初始化UI
    }

    // 即将进入前台
    override fun onStart() {
        super.onStart() // 恢复需要界面可见的资源
    }

    // 进入交互状态
    override fun onResume() {
        super.onResume() // 启动动画/传感器等
    }

    // 失去焦点(仍部分可见)
    override fun onPause() {
        super.onPause() // 提交数据/停止动画
    }

    // 完全不可见
    override fun onStop() {
        super.onStop() // 释放占用资源
    }

    // 销毁前最后调用
    override fun onDestroy() {
        super.onDestroy() // 清理线程/注销监听
    }

    // 从stopped状态恢复时调用(非首次创建)
    override fun onRestart() {
        super.onRestart()
    }
}

Fragment生命周期(核心方法+特有方法)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MyFragment : Fragment() {
    // 绑定到Activity时调用(最早的生命周期方法)
    override fun onAttach(context: Context) {
        super.onAttach(context)
        // 获取Activity传递的参数
    }

    // 初始化非UI组件
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        retainInstance = true // 配置变更时保留实例
    }

    // 创建视图(返回rootView)
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_my, container, false)
    }

    // 视图创建完成后调用(替代已弃用的onActivityCreated)
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 初始化UI组件
        button.setOnClickListener { ... }
    }

    // 与Activity的onDestroy解绑
    override fun onDetach() {
        super.onDetach()
        // 清除对Activity的引用
    }

    // 其他方法与Activity类似(onStart/onResume等)
}

关键差异对比

场景ActivityFragment
创建入口AndroidManifest.xml注册通过FragmentManager动态添加
视图层级根容器必须依附于Activity
特有方法-onAttach/onDetach
回退栈管理自动管理需要手动addToBackStack()
布局复用困难可多Activity复用

生命周期联动示例

当Activity创建并添加Fragment时:
Activity.onCreate()Fragment.onAttach()Fragment.onCreate()Fragment.onCreateView()Fragment.onViewCreated()Activity.onStart()Fragment.onStart()Activity.onResume()Fragment.onResume()

开发注意事项

    避免在onCreateView做耗时操作:会导致界面卡顿,应使用后台线程

    onDestroyView的特殊性:Fragment可能保留实例但视图被销毁(ViewBinding需置空)

    状态保存时机:应在onSaveInstanceState保存数据,而不是onStop

    正确处理配置变更:使用ViewModel + SavedStateHandle组合方案

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// ViewBinding在Fragment中的正确用法
class MyFragment : Fragment() {
    private var _binding: FragmentMyBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(...): View {
        _binding = FragmentMyBinding.inflate(...)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null // 避免内存泄漏
    }
}

activity的四种启动模式

面试官考察点

    对启动模式原理的理解:是否清楚四种模式在任务栈(Task)中的表现差异

    实际应用能力:是否能正确匹配业务场景选择合适的启动模式

    特殊场景处理:是否了解与FLAG_ACTIVITY_*标志位的配合使用

    问题排查能力:是否知晓错误使用启动模式导致的常见问题(如返回栈异常)


回答思路

    分类说明:按standard → singleTop → singleTask → singleInstance顺序解释

    核心特征对比:用表格突出关键差异

    场景化解释:结合典型使用案例说明

    注意事项补充:说明实际开发中的常见陷阱


完整回答

四种启动模式详解(代码示例)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

<!-- AndroidManifest.xml中声明 -->
<activity
    android:name=".StandardActivity"
    android:launchMode="standard"/> <!-- 默认值可不写 -->

<activity
    android:name=".SingleTopActivity"
    android:launchMode="singleTop"/>

<activity
    android:name=".SingleTaskActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.example.customtask"/>

<activity
    android:name=".SingleInstanceActivity"
    android:launchMode="singleInstance"/>

1. Standard(标准模式)

  • 行为特点

    • 每次启动创建新实例(即使Activity已存在)

    • 允许同个Activity多次叠加

    • 默认模式

  • 代码验证

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 连续启动3次StandardActivity
startActivity(Intent(this, StandardActivity::class.java)) 
// 返回栈状态:[MainActivity, StandardActivity, StandardActivity, StandardActivity]
  • 适用场景

    • 常规页面跳转

    • 需要多个实例的页面(如商品详情页)


2. SingleTop(栈顶复用)

  • 行为特点

    • 当目标Activity位于栈顶时,复用实例并触发onNewIntent()

    • 不在栈顶时表现与standard相同

  • 生命周期变化

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 已有SingleTopActivity在栈顶时再次启动
onPause() → onNewIntent() → onResume() // 不会走onCreate
  • 代码验证

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val intent = Intent(this, SingleTopActivity::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_SINGLE_TOP // 动态设置方式(优先级高于Manifest)
}
startActivity(intent)
  • 适用场景

    • 防止快速重复点击创建多个实例(如支付页面)

    • 通知栏跳转统一页面


3. SingleTask(栈内单例)

  • 核心机制

    • 指定任务栈中保持唯一实例

    • 启动时会清空该实例之上的所有Activity

    • 通过taskAffinity指定任务栈(默认与包名相同)

  • 典型流程

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 现有栈:[MainActivity, A, B, SingleTaskActivity]
// 再次启动SingleTaskActivity时:
// 1. 清除上方Activity → [MainActivity, A, B]
// 2. 将SingleTaskActivity带到前台 → [MainActivity, A, B, SingleTaskActivity]
// 触发B.onDestroy → SingleTaskActivity.onNewIntent()
  • 特殊配置

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

<!-- 指定独立的任务栈 -->
<activity
    android:name=".HomeActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.example.home"/>
  • 适用场景

    • 应用主页(保证唯一性)

    • 登录页(清空其他页面)


4. SingleInstance(全局单例)

  • 独占特性

    • 单独占用一个任务栈

    • 该栈中只能存在该Activity

    • 其他Activity不能共享此栈

  • 启动效果

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 从MainActivity启动SingleInstanceActivity:
// Task1: [MainActivity] 
// Task2: [SingleInstanceActivity]

// 在SingleInstanceActivity中启动其他Activity:
// Task1: [MainActivity, OtherActivity]
// Task2: [SingleInstanceActivity] 
  • 适用场景

    • 系统级独占页面(如来电界面)

    • 与其他应用共享的Activity(需要独立任务栈)


对比表格

模式实例数量任务栈规则典型场景
standard多实例默认栈叠加普通页面
singleTop栈顶单例检测当前栈顶防重复点击
singleTask栈内单例清空目标栈上方Activity主页/登录页
singleInstance全局单例独占独立任务栈系统级独占界面

开发注意事项

    慎用singleInstance

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 错误使用可能导致返回逻辑异常:
    // 从SingleInstance页面返回时直接回到桌面
    

    FLAG优先级

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // Intent设置的flag会覆盖Manifest声明:
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    

    跨进程问题

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    <!-- 不同应用的singleTask需要指定相同的taskAffinity -->
    <activity android:taskAffinity="com.android.contacts"/>
    

    生命周期验证

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // singleTask复用时的特殊处理:
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        // 必须手动处理新intent的数据
        handleIntent(intent)
    }
    

这个回答通过代码示例、流程图解和对比表格,系统性地展示了不同启动模式的特点,同时结合实际开发中的正反例说明,能够展现候选人对底层机制的理解和工程实践经验。

service的两种启动方式

面试官考察点

    基础概念掌握:能否准确区分两种启动方式的核心差异

    生命周期理解:是否清楚不同启动方式对应的生命周期方法调用顺序

    实际应用能力:能否根据业务场景选择合适的启动方式

    异常处理意识:是否了解服务泄漏风险及正确释放资源的方法


回答思路

    明确分类:直接说明两种方式(startService/bindService)

    对比差异:从生命周期、通信能力、使用场景三个维度对比

    代码示例:给出两种方式的典型代码模板

    注意事项:强调服务泄漏风险及Android版本差异


完整回答

两种启动方式的核心定义

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 方式1:启动式服务(生命周期独立)  
startService(Intent(this, MyService::class.java))  

// 方式2:绑定式服务(与组件生命周期关联)  
bindService(
    Intent(this, MyService::class.java),  
    serviceConnection,  
    Context.BIND_AUTO_CREATE  
)  

方式1:startService(启动式服务)

生命周期流程
onCreate()onStartCommand() → (running) → onDestroy()

关键特点

    服务与调用组件(如Activity)完全解耦

    必须显式调用stopSelf()stopService()才会停止

    多次调用startService会触发多次onStartCommand()

    适用场景:后台音乐播放、文件下载等需要长期运行的操作

代码模板

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MyService : Service {  
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {  
        // 执行具体任务(如开启线程下载文件)  
        return START_STICKY // 服务被杀死后的重启策略  
    }  

    override fun onBind(intent: Intent?): IBinder? = null  
}  

// 停止服务  
stopService(Intent(this, MyService::class.java))  
// 或在服务内部调用 stopSelf()  

方式2:bindService(绑定式服务)

生命周期流程
onCreate()onBind() → (running) → onUnbind()onDestroy()

关键特点

    通过ServiceConnection实现双向通信

    当所有绑定组件解绑后自动销毁(除非同时被startService启动)

    绑定期间可获取IBinder实现组件与服务的交互

    适用场景:音乐播放控制、传感器数据交互等需要实时通信的场景

代码模板

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

private val serviceConnection = object : ServiceConnection {  
    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {  
        val myBinder = binder as MyService.LocalBinder  
        myBinder.getService().doSomething()  
    }  

    override fun onServiceDisconnected(name: ComponentName?) {  
        // 异常断开处理  
    }  
}  

// 在Activity中绑定  
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)  

// 解绑服务(必须调用!否则内存泄漏)  
unbindService(serviceConnection)  

混合模式(同时start + bind)

生命周期特性

    必须同时调用stopService()unbindService()才会触发onDestroy()

    典型应用场景:音乐播放器(start保持后台运行,bind实现播放控制)

执行顺序示例

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

startService() → onCreate()  
bindService() → onBind()  
unbindService() → onUnbind()  
stopService() → onDestroy()  

关键差异对比表

特性startServicebindService
生命周期关联独立于组件与绑定组件共存亡
通信能力单向通信(通过Intent)双向通信(通过IBinder)
停止条件显式调用stop所有组件解绑后自动停止
多次调用影响触发多次onStartCommand()仅首次绑定触发onBind()
典型使用场景长期后台任务进程间通信/实时交互

开发注意事项

    内存泄漏风险

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 错误示例:在Activity中绑定服务但忘记解绑  
    override fun onDestroy() {  
        super.onDestroy()  
        // 必须添加 unbindService(connection)  
    }  
    

    Android 8.0+限制

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    <!-- 后台服务需转换为前台服务 -->  
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>  
    

    粘性服务处理

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // onStartCommand返回值策略:  
    START_STICKY    // 系统会重建服务但intent为null  
    START_NOT_STICKY// 系统不会自动重建  
    

    跨进程通信

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 使用Messenger实现安全跨进程通信  
    val messenger = Messenger(handler)  
    binder.send(message)  
    

service和thread有什么不一样的地方

面试官考察点

    基础概念理解:能否准确区分系统组件(Service)与线程(Thread)的本质差异

    运行时机制认知:是否清楚两者在进程/线程模型中的位置关系

    使用场景判断:是否能根据业务需求正确选择技术方案

    生命周期管理:是否了解两者的资源释放机制差异


回答思路

    层级定位:从操作系统层面解释组件与线程的本质差异

    对比维度:生命周期管理、运行环境、通信方式、系统优先级等

    典型误区:明确纠正"Service=后台线程"的错误认知

    组合使用:说明实际开发中如何配合使用二者


完整回答

本质差异对比表

维度ServiceThread
类型Android系统组件Java并发编程单元
运行环境主线程(默认)独立线程
生命周期由系统管理(需显式停止)随代码执行结束自动终止
跨进程能力支持(通过AIDL)仅限同一进程内
系统优先级较高(可设置为前台服务)与应用进程同级
资源消耗较重(需声明在Manifest)轻量
主要用途长期后台任务(如音乐播放)异步/耗时操作(如网络请求)

自定义view的绘制流程

面试官考察点

    核心流程掌握:是否理解onMeasureonLayoutonDraw的三阶段流程

    测量机制理解:能否正确处理MeasureSpec的三种模式(EXACTLY/AT_MOST/UNSPECIFIED)

    性能优化意识:是否知晓避免在绘制过程中进行耗时操作

    实际编码能力:能否通过代码示例说明关键环节实现


回答思路

    流程总述:先整体说明绘制流程的三个阶段

    分步详解:逐个阶段说明作用、典型代码实现及注意事项

    补充机制:说明requestLayout()invalidate()的区别

    实战示例:给出一个简单自定义View的完整实现示例


完整回答

自定义View绘制三阶段流程图

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

Constructor  
   ↓  
onMeasure() ←───── requestLayout()  
   ↓  
onLayout()  
   ↓  
onDraw() ←──────── invalidate()  

阶段1:onMeasure(测量尺寸)

核心任务:确定View自身尺寸(含处理wrap_content)
关键对象MeasureSpec(32位int值,高2位模式,低30位尺寸)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {  
    // 解析测量规格  
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)  
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)  
      
    // 计算期望尺寸(需考虑padding)  
    var desiredWidth = calculateDesiredWidth() + paddingLeft + paddingRight  
      
    // 根据模式调整最终尺寸  
    val finalWidth = when (widthMode) {  
        MeasureSpec.EXACTLY -> widthSize // 父容器强制尺寸  
        MeasureSpec.AT_MOST -> min(desiredWidth, widthSize) // 不能超过父容器限制  
        else -> desiredWidth // UNSPECIFIED(少见)  
    }  
      
    // 高度计算同理  
    setMeasuredDimension(finalWidth, finalHeight)  
}  

常见问题

  • 忘记处理wrap_content(表现为和match_parent效果相同)

  • 未考虑padding导致内容被截断


阶段2:onLayout(确定位置)

核心任务:确定View在父容器中的位置(对ViewGroup还需确定子View位置)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 普通View的默认实现(位置由父容器决定)  
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {  
    // 通常无需重写,除非需要特殊布局逻辑  
}  

// ViewGroup示例(需遍历布局子View)  
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {  
    for (i in 0 until childCount) {  
        val child = getChildAt(i)  
        // 根据测量结果布局  
        child.layout(childLeft, childTop, childRight, childBottom)  
    }  
}  

关键点

  • 只有ViewGroup需要处理子View布局

  • 使用getMeasuredWidth()而非getWidth()(测量结果 vs 最终尺寸)


阶段3:onDraw(绘制内容)

核心任务:使用Canvas进行视觉呈现

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

override fun onDraw(canvas: Canvas) {  
    super.onDraw(canvas)  
      
    // 绘制圆角矩形背景  
    val paint = Paint().apply {  
        color = Color.BLUE  
        isAntiAlias = true  
    }  
    canvas.drawRoundRect(  
        paddingLeft.toFloat(),  
        paddingTop.toFloat(),  
        (width - paddingRight).toFloat(),  
        (height - paddingBottom).toFloat(),  
        16f, 16f,  
        paint  
    )  
      
    // 绘制文本(注意基线计算)  
    val text = "Hello Custom View"  
    val textX = (width - paint.measureText(text)) / 2  
    val textY = (height - paint.descent() - paint.ascent()) / 2  
    canvas.drawText(text, textX, textY, paint)  
}  

优化要点

    避免在onDraw中创建对象(Paint/Bitmap等应提前初始化)

    使用clipRect减少过度绘制

    考虑使用Canvas.saveLayer()时的性能损耗


刷新机制对比

方法触发阶段使用场景
requestLayout()onMeasure+onLayout尺寸/位置变化(如动态修改布局参数)
invalidate()onDraw视觉变化(如数据更新需要重绘)
postInvalidate()onDraw(线程安全)非UI线程触发重绘

完整自定义View示例(带点击动画)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class CustomButton @JvmOverloads constructor(  
    context: Context,  
    attrs: AttributeSet? = null,  
    defStyleAttr: Int = 0  
) : View(context, attrs, defStyleAttr) {  

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {  
        color = Color.parseColor("#6200EE")  
        style = Paint.Style.FILL  
    }  
    private var clickProgress = 0f  
      
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {  
        val minW = paddingLeft + paddingRight + suggestedMinimumWidth  
        val minH = paddingTop + paddingBottom + suggestedMinimumHeight  
        setMeasuredDimension(  
            resolveSize(minW, widthMeasureSpec),  
            resolveSize(minH, heightMeasureSpec)  
        )  
    }  
      
    override fun onDraw(canvas: Canvas) {  
        // 绘制背景  
        canvas.drawRoundRect(  
            paddingLeft.toFloat(),  
            paddingTop.toFloat(),  
            (width - paddingRight).toFloat(),  
            (height - paddingBottom).toFloat(),  
            16f, 16f,  
            paint  
        )  
          
        // 点击涟漪效果  
        if (clickProgress > 0) {  
            val ripplePaint = Paint(paint).apply {  
                alpha = (255 * (1 - clickProgress)).toInt()  
                color = Color.WHITE  
            }  
            canvas.drawCircle(  
                latestX,  
                latestY,  
                width * clickProgress,  
                ripplePaint  
            )  
        }  
    }  
      
    override fun performClick(): Boolean {  
        // 启动动画  
        ValueAnimator.ofFloat(0f, 1f).apply {  
            duration = 300  
            addUpdateListener {  
                clickProgress = animatedValue as Float  
                invalidate()  
            }  
            start()  
        }  
        return super.performClick()  
    }  
}  

高级优化技巧

    硬件加速

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    <!-- 在Manifest中全局启用 -->  
    <application android:hardwareAccelerated="true">  
    

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 特定View控制  
    view.setLayerType(LAYER_TYPE_HARDWARE, null)  
    

    Partial Invalidate

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 仅重绘需要更新的区域  
    invalidate(dirtyRect)  
    

    DisplayList复用

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 使用RenderNode(API 29+)  
    val renderNode = RenderNode("customNode")  
    renderNode.beginRecording().apply {  
        // 录制绘制操作  
    }  
    renderNode.endRecording()  
    canvas.drawRenderNode(renderNode)  
    

这个回答系统性地覆盖了自定义View的核心知识点,通过代码示例展示了关键环节的实现方式,同时给出了性能优化方案和典型问题的规避方法,能够全面体现候选人对Android视图系统的理解深度。

事件的分发机制

面试官考察点

    核心流程理解:能否准确描述事件分发的U型传递机制(Activity → ViewGroup → View → ViewGroup → Activity)

    拦截机制掌握:是否清楚onInterceptTouchEvent在ViewGroup中的关键作用

    消费逻辑判断:能否正确处理事件消费的返回值(true/false)

    实战问题解决:是否知晓如何处理滑动冲突等复杂场景


回答思路

    三级分发模型:Activity → ViewGroup → View 的逐级传递

    关键方法解析dispatchTouchEventonInterceptTouchEventonTouchEvent

    消费优先级链OnTouchListeneronTouchEventOnClickListener

    滑动冲突方案:通过坐标差值判断拦截时机的典型实现


完整回答

事件分发核心流程图

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

Activity.dispatchTouchEvent  
     
ViewGroupA.dispatchTouchEvent  
   ├── ViewGroupA.onInterceptTouchEvent?  
           No  
          ViewChild.dispatchTouchEvent  
              ├── ViewChild.onTouchEvent  
              └── 消费判断  
   └── ViewGroupA.onTouchEvent(若拦截)  
     
未消费则回传到上层onTouchEvent  

关键方法解析(代码示例)

1. Activity层分发入口

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MainActivity : AppCompatActivity() {  
    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {  
        // 可在此拦截全局事件(慎用)  
        return super.dispatchTouchEvent(ev)  
    }  
}  

2. ViewGroup拦截机制

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class CustomViewGroup @JvmOverloads constructor(  
    context: Context,  
    attrs: AttributeSet? = null  
) : ViewGroup(context, attrs) {  

    private var startY = 0f  

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {  
        when (ev.actionMasked) {  
            MotionEvent.ACTION_DOWN -> {  
                startY = ev.y  
                return false // 必须放行DOWN事件  
            }  
            MotionEvent.ACTION_MOVE -> {  
                val deltaY = abs(ev.y - startY)  
                // 纵向滑动超过阈值则拦截  
                if (deltaY > touchSlop) return true  
            }  
        }  
        return super.onInterceptTouchEvent(ev)  
    }  
}  

3. View消费逻辑

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class CustomView @JvmOverloads constructor(  
    context: Context,  
    attrs: AttributeSet? = null  
) : View(context, attrs) {  

    override fun onTouchEvent(event: MotionEvent): Boolean {  
        when (event.actionMasked) {  
            MotionEvent.ACTION_DOWN -> {  
                // 必须返回true表示消费后续事件  
                return true  
            }  
            MotionEvent.ACTION_UP -> performClick()  
        }  
        return super.onTouchEvent(event)  
    }  

    override fun performClick(): Boolean {  
        super.performClick()  
        // 处理点击事件  
        return true  
    }  
}  

事件处理优先级链

    OnTouchListener(最高优先级)

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    view.setOnTouchListener { v, event ->  
        // 返回true将阻断onTouchEvent和OnClickListener  
        false  
    }  
    

    onTouchEvent

    OnClickListener(最低优先级)


滑动冲突解决方案(以ScrollView内嵌ListView为例)

方案1:外部拦截法(推荐)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class ParentScrollView @JvmOverloads constructor(  
    context: Context,  
    attrs: AttributeSet? = null  
) : ScrollView(context, attrs) {  

    private var lastY = 0f  

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {  
        when (ev.action) {  
            MotionEvent.ACTION_DOWN -> {  
                lastY = ev.y  
                super.onInterceptTouchEvent(ev) // 必须调用父类方法  
                return false  
            }  
            MotionEvent.ACTION_MOVE -> {  
                val deltaY = ev.y - lastY  
                // 判断纵向滑动是否超过阈值  
                if (abs(deltaY) > touchSlop) {  
                    // 根据业务逻辑决定是否拦截  
                    return if (isListViewAtTop() && deltaY > 0) {  
                        true // 拦截给ScrollView  
                    } else {  
                        false  
                    }  
                }  
            }  
        }  
        return super.onInterceptTouchEvent(ev)  
    }  

    private fun isListViewAtTop(): Boolean {  
        val listView = getChildAt(0) as ListView  
        return listView.firstVisiblePosition == 0 &&  
                listView.getChildAt(0).top >= listView.paddingTop  
    }  
}  

方案2:内部拦截法

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class ChildListView @JvmOverloads constructor(  
    context: Context,  
    attrs: AttributeSet? = null  
) : ListView(context, attrs) {  

    private var lastX = 0f  
    private var lastY = 0f  

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {  
        when (ev.action) {  
            MotionEvent.ACTION_DOWN -> {  
                parent.requestDisallowInterceptTouchEvent(true)  
                lastX = ev.x  
                lastY = ev.y  
            }  
            MotionEvent.ACTION_MOVE -> {  
                val deltaX = abs(ev.x - lastX)  
                val deltaY = abs(ev.y - lastY)  
                // 横向滑动主导时允许父容器拦截  
                if (deltaX > deltaY) {  
                    parent.requestDisallowInterceptTouchEvent(false)  
                }  
            }  
        }  
        return super.dispatchTouchEvent(ev)  
    }  
}  

事件分发核心规则

    DOWN事件裁决:若某个View未消费ACTION_DOWN,后续事件(MOVE/UP)将直接跳过它

    拦截反悔机制:ViewGroup一旦拦截某个事件序列,后续事件将直接调用onTouchEvent

    嵌套滚动优先:子View可通过requestDisallowInterceptTouchEvent临时禁止父容器拦截

    多点触控处理:使用actionIndexpointerId跟踪多指操作


性能优化技巧

    减少事件处理层级:使用getParent().requestDisallowInterceptTouchEvent(true)阻断事件传递

    避免过度绘制:在onTouchEvent中使用invalidate(Rect)局部刷新

    重用MotionEvent:在频繁触发的MOVE事件中避免创建新对象

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    val recycledEvent = MotionEvent.obtain(originalEvent)  
    // 处理完成后回收  
    recycledEvent.recycle()  
    

这个回答通过分层解析、代码示例和典型场景解决方案,系统性地阐释了Android事件分发机制的核心要点,既涵盖了基础原理又提供了工程实践指导,能够充分展现候选人对该知识点的深入理解。

handler的机制

面试官考察点

    核心组件理解:是否清楚Handler、Looper、MessageQueue的关系及各自作用。

    线程通信机制:能否解释Handler如何实现线程间通信。

    内存管理意识:是否了解Handler可能引发的内存泄漏及解决方案。

    实际应用能力:能否举例说明Handler的使用场景及注意事项。


回答思路

    核心组件关系:通过流程图说明Handler、Looper、MessageQueue的协作。

    消息传递流程:从发送到处理的全流程解析。

    内存泄漏防范:结合代码示例说明静态内部类+弱引用的使用。

    高级机制补充:如HandlerThread、同步屏障(Sync Barrier)等。


完整回答

Handler机制核心流程图

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

Thread(主/子线程)
   │
   ↓ 创建并绑定
Looper.prepare() → MessageQueue(消息队列)
   │
   ↓ 启动循环
Looper.loop()(轮询取消息)
   │
   ↓ 消息分发
Handler.dispatchMessage() → handleMessage() / Runnable.run()

四大核心组件职责

    Handler

    • 消息发送:通过sendMessage()post(Runnable)将消息/任务插入队列

    • 消息处理:在目标线程的handleMessage()Runnable中执行逻辑

    Looper

    • 轮询器:通过loop()方法循环从MessageQueue取消息

    • 线程绑定:每个线程最多一个Looper(主线程默认创建,子线程需手动初始化)

    MessageQueue

    • 消息存储:按时间优先级排列的队列(单链表实现)

    • 消息复用:通过Message.obtain()从对象池复用Message实例

    Message

    • 数据载体:包含what(标识)、arg1/arg2(简单数据)、obj(对象)、target(目标Handler)

    • 回调支持:可通过callback字段直接执行Runnable


消息传递全流程(代码示例)

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 子线程发送消息
new Thread(() -> {
    Message msg = Message.obtain();
    msg.what = 1;
    msg.obj = "Data from background";
    handler.sendMessage(msg);
}).start();

// 主线程Handler处理
private Handler handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == 1) {
            String data = (String) msg.obj;
            textView.setText(data); // 安全更新UI
        }
    }
};

内存泄漏解决方案

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 静态内部类+弱引用
private static class SafeHandler extends Handler {
    private final WeakReference<Activity> activityRef;

    SafeHandler(Activity activity) {
        super(Looper.getMainLooper());
        activityRef = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        Activity activity = activityRef.get();
        if (activity != null && !activity.isFinishing()) {
            // 处理消息
        }
    }
}

// 在Activity中正确释放
@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null); // 清除所有未处理消息
}

高级机制与应用场景

    HandlerThread(自带Looper的子线程)

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    HandlerThread workerThread = new HandlerThread("Worker");
    workerThread.start();
    Handler workerHandler = new Handler(workerThread.getLooper());
    workerHandler.post(() -> {
        // 在后台线程执行耗时操作
    });
    

    同步屏障(Sync Barrier)

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 插入同步屏障(API 23+)
    ViewRootImpl.getRunQueue().postSyncBarrier();
    
    // 发送异步消息(优先处理)
    Message asyncMsg = Message.obtain();
    asyncMsg.setAsynchronous(true);
    handler.sendMessageAtFrontOfQueue(asyncMsg);
    

    IdleHandler(队列空闲时执行)

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    Looper.myQueue().addIdleHandler(() -> {
        // 在消息队列空闲时执行
        return false; // true表示保留,false执行后移除
    });
    

开发注意事项

    子线程初始化Looper

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    new Thread(() -> {
        Looper.prepare(); // 创建Looper
        Handler handler = new Handler();
        Looper.loop();    // 启动消息循环
    }).start();
    

    避免ANR

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    // 错误示例:在主线程Handler执行耗时操作
    handler.post(() -> {
        try { Thread.sleep(5000); } // 导致ANR
        catch (InterruptedException e) { e.printStackTrace(); }
    });
    

    消息优先级控制

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    handler.sendMessageAtFrontOfQueue(msg); // 插队到队列头部
    handler.postDelayed(runnable, 1000);    // 延迟执行
    

该回答系统性地阐述了Handler机制的核心原理、实现细节及工程实践中的关键点,通过代码示例和高级技巧展示了候选人对Android消息机制的深入理解。

内存泄漏和内存溢出有什么区别

内存泄漏 不该用的内存被用了

内存溢出 没有内存可以用了

java的内存这部分

引用计数:最简单的寻找无用对象的机制,当一个对象被引用一次引用计数+1 当失去引用时引用计数-1 当此对象引用计数为0时可以直接回收。这种方法有一个显而易见的问题:无法回收被循环引用的对象。 可达性分析: 从一个根对象(GC Root)开始向下搜寻,可以认为搜寻到的所有有强引用的对象都是活跃对象,所有找不到的对象都是无用对象,无用对象可能会被即刻回收也可能进行其他操作(比如执行对象的finalize( 方法) 何时开始GC? 任何时候都可能,当系统觉得你内存不足了就会开始回收常见的比如分配对象内存不足时 (这里的内存不足有可能不是占用真的很高,可能是内存足够, 但是没有连续内存空间去放这个对象),当前堆内存占用超过闯值时,手动调用 System.gc0 建议开始GC时,系统整体内存不足时等 这里举个例子: 你要分配一个大对象(比如一张图片),如果内存不足,可能会发生下面这个情况: 1.首先开始一次GC,这次GC不会回收被软引用引用的对 象2.如果内存仍然不足,做点其他事情尝试让内存足够,常见的比如: 堆整理:前面说过,内存不足有可能不是占用真的很高,可能是内存足够,但是没有连续内存空间去放这个对象,那就试试整理一下堆看看出来的空间够不够 增大堆内存:尝试申请更大的堆来放对象 1.如果内存依然不足,再发起一次GC,这次GC会回收仅 被软引用或更弱引用引用的对象 (这句话好像有点乱),然后再次尝试分配对象 2.如果内存还是不够,那没办法了,抛出OutOfMemoryError

内存抖动(Memory Churn)

  • 特征:频繁GC导致界面卡顿

  • 排查工具:Android Profiler的Memory Viewer

  • 优化方法:避免在循环中创建临时对象

aidl这部分

binder的机制

问题:Binder 机制中,客户端和服务端是如何相互调用的?

回答:

在 Binder 机制中,客户端和服务端的相互调用是通过 Binder 驱动和 Binder 代理对象来实现的。以下是详细的调用过程:

服务端的实现

    创建 Binder 对象

    • 服务端需要创建一个继承自 Binder 类的子类,并实现 onTransact 方法。onTransact 方法是 Binder 机制的核心,用于处理来自客户端的请求。

    • onTransact 方法中,服务端根据客户端传递的请求码(requestCode)来判断需要执行的具体业务逻辑,并进行相应的处理。

    注册服务

    • 服务端将自己创建的 Binder 对象注册到系统的服务管理器(Service Manager)中。这样,客户端就可以通过服务管理器找到并连接到服务端。

客户端的实现

    获取 Binder 代理对象

    • 客户端通过服务管理器获取到服务端注册的 Binder 对象的代理(Proxy)。这个代理对象是系统自动生成的,它实现了与服务端 Binder 对象相同的接口。

    • 客户端通过调用服务管理器的 getService 方法来获取服务端的 Binder 代理对象。

    调用远程方法

    • 客户端通过 Binder 代理对象调用服务端的方法。实际上,客户端调用的是代理对象的本地方法,这个方法会将请求打包成一个数据包,并通过 Binder 驱动发送到服务端。

    • 在调用远程方法时,客户端需要传递必要的参数,包括请求码(requestCode)、数据(data)和回复(reply)等。

Binder 驱动的作用

    数据传递

    • Binder 驱动运行在内核空间,负责在客户端和服务端之间传递数据和消息。当客户端调用远程方法时,Binder 驱动会将客户端的请求打包成一个数据包,并将其发送到服务端。

    • 服务端的 Binder 驱动会接收到该数据包,并将其解包后传递给服务端的 onTransact 方法进行处理。

    跨进程通信

    • Binder 驱动实现了不同进程之间的通信,解决了安卓系统中的进程隔离问题。它能够高效地在客户端和服务端之间传递数据和消息,确保通信的安全性和可靠性。

服务端回调客户端

    传递 Binder 代理对象

    • 如果服务端需要回调客户端的方法,客户端需要将自己创建的 Binder 代理对象传递给服务端。这通常是在客户端调用服务端的方法时,将 Binder 代理对象作为参数传递给服务端。

    服务端调用客户端方法

    • 服务端在收到客户端传递的 Binder 代理对象后,可以通过该代理对象调用客户端的方法。服务端调用客户端方法的过程与客户端调用服务端方法的过程类似,也是通过 Binder 驱动来实现的。

示例代码

以下是一个简单的示例代码,展示了 Binder 机制中客户端和服务端的相互调用:

服务端代码:

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

public class MyBinder extends Binder {
    @Override
    public boolean onTransact(int requestCode, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (requestCode) {
            case 1:
                // 处理客户端的请求
                String str = data.readString();
                String result = "Server received: " + str;
                reply.writeString(result);
                return true;
            default:
                return super.onTransact(requestCode, data, reply, flags);
        }
    }
}

客户端代码:

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

IBinder binder = ServiceManager.getService("my_service");
MyBinder myBinder = (MyBinder) binder;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeString("Hello from client");
myBinder.transact(1, data, reply, 0);
reply.readString(); // 获取服务端的回复

总之,Binder 机制通过 Binder 驱动和 Binder 代理对象实现了客户端和服务端的相互调用,使得不同进程能够安全、高效地共享数据和调用方法。

flow这部分的了解

以下是针对 Kotlin Flow 的核心概念和面试常见问题的总结,帮助你快速掌握其核心要点:


1. Flow 的核心定义与用途

  • 异步数据流:Flow 是 Kotlin 协程库中用于处理异步数据流的工具,支持按顺序发射多个值(如网络请求分页、实时数据更新)。

  • 冷流特性:默认是冷流(Cold Flow),只有调用 collect 时才会触发数据发射,多次 collect 会重复执行流的逻辑。

  • 与协程集成:Flow 的生命周期与协程绑定,协程取消时 Flow 自动停止。

示例

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

fun fetchData(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(1000)
        emit(i) // 发射数据
    }
}

// 收集数据
runBlocking {
    fetchData().collect { value -> println(value) }
}

2. Flow 的创建方式

  • flow { ... } :通过 emit 手动发射数据,适用于复杂逻辑。

  • flowOf() :直接发射固定值集合,如 flowOf(1, 2, 3)

  • .asFlow() :将集合(List、Array)转换为 Flow,如 listOf(1, 2, 3).asFlow()

适用场景
网络请求分页、数据库查询结果流、实时传感器数据流。


3. 关键操作符与使用场景

(1) 转换操作符

  • map / filter:数据转换与过滤。

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    flowOf(1, 2, 3)
        .map { it * 2 } // 转换为 2, 4, 6
        .filter { it > 3 } // 过滤出 4, 6
        .collect { println(it) }
    
  • transform:复杂转换(如一个输入发射多个输出)。

  • zip:合并两个 Flow 的对应元素(如合并用户信息与订单信息)。

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    val flowA = flowOf("A", "B")
    val flowB = flowOf(1, 2)
    flowA.zip(flowB) { a, b -> "$a-$b" } // 输出 "A-1", "B-2"
    

(2) 终端操作符

  • collect:触发数据收集。

  • toList / toSet:将流转换为集合。

  • reduce / fold:聚合数据(如累加)。

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    (1..5).asFlow().reduce { acc, value -> acc + value } // 输出 15
    

(3) 异常处理

  • catch:捕获流中的异常。

  • retry:在异常时重试(如网络请求失败自动重试)。

    --javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

    flow { emitData() }
        .retry(2) // 最多重试2次
        .catch { e -> println("Error: $e") }
        .collect { ... }
    

(4) 生命周期监听

  • onStart:流开始时的回调(如显示加载动画)。

  • onCompletion:流结束时的回调(如隐藏加载动画)。


4. 热流与冷流的区别

  • 冷流(Cold Flow) :默认模式,每次 collect 都会重新执行流的逻辑(如数据库查询)。

  • 热流(Hot Flow) :数据发射独立于收集者,如 StateFlowSharedFlow

    • StateFlow:用于状态管理(如 UI 状态),保留最新值,新订阅者直接获取最新状态。

    • SharedFlow:用于事件传递(如按钮点击),可配置重放次数(replay)处理历史事件。

示例

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// StateFlow 更新 UI 状态
val _uiState = MutableStateFlow("")
val uiState: StateFlow<String> = _uiState

// SharedFlow 处理一次性事件
val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _events

5. 线程调度与性能优化

  • flowOn:指定上游操作的执行线程(如 flowOn(Dispatchers.IO))。

  • buffer:缓存数据以避免生产者与消费者速度不匹配(如处理背压问题)。

  • launchIn:在指定协程作用域中启动流,实现并发执行。

示例

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

flow { emitData() }
    .flowOn(Dispatchers.IO) // 上游在 IO 线程执行
    .buffer(50) // 缓存50个元素
    .collect { ... } // 下游在主线程处理

6. 实际应用场景

    网络请求:结合 Retrofit 的挂起函数返回 Flow,支持分页和实时数据更新。

    UI 状态管理:使用 StateFlow 替代 LiveData,实现响应式 UI。

    数据库观察:监听 Room 数据库变化并自动刷新 UI。

    事件处理:用 SharedFlow 处理用户交互事件(如按钮点击)。


7. 面试常见问题与回答

    Flow 与 RxJava 的区别?
    Flow 更轻量,与协程深度集成,语法更简洁;RxJava 操作符更丰富,但学习成本高。

    如何处理 Flow 的背压(Backpressure)?
    使用 bufferconflate(丢弃中间值)或 collectLatest(取消未完成的任务处理最新值)。

    StateFlow 和 LiveData 的异同?
    相同:均用于状态管理,支持生命周期感知。
    不同:StateFlow 需初始值,LiveData 无需;StateFlow 可结合协程实现更复杂的异步逻辑。


总结回答示例

“Flow 是 Kotlin 协程中处理异步数据流的工具,通过 collect 触发冷流的数据发射。常用操作符如 mapretrycatch 简化数据处理与异常处理。实际开发中,用 StateFlow 管理 UI 状态,SharedFlow 处理事件。需注意线程调度(flowOn)和背压优化(buffer)。相比 RxJava,Flow 更轻量且与协程无缝集成。”

通过结合具体操作符和场景,能清晰展示对 Flow 的理解和实际应用能力。

lateinit

问题:Kotlin 的关键字 lateinit 的使用情况和原理

回答:

lateinit 是 Kotlin 中用于声明可延迟初始化的非空属性的关键字。它允许你在对象创建后稍后再初始化这些属性,适用于那些无法在初始化时就赋值的属性。

使用场景

    依赖注入

    • 在依赖注入框架中,依赖项通常由框架在对象创建后注入。使用 lateinit 可以确保这些依赖项在使用前被正确初始化。

    UI 元素初始化

    • 在 Android 开发中,UI 元素(如 View 对象)通常在 onCreate 方法之后才被初始化。使用 lateinit 可以避免在对象初始化时就对这些 UI 元素进行赋值。

    异步操作

    • 当某些属性需要通过异步操作来初始化时,lateinit 可以确保这些属性在异步操作完成后才被使用。

使用方法

    声明 lateinit 属性

    • 使用 lateinit 关键字修饰 var 声明的属性。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MyViewModel {
    lateinit var myView: View
}

    初始化 lateinit 属性

    • 在对象创建后,通过赋值语句对 lateinit 属性进行初始化。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val myViewModel = MyViewModel()
myViewModel.myView = findViewById(R.id.my_view)

原理

lateinit 的实现原理相对简单,它通过在后台存储一个额外的布尔值来记录属性是否已经初始化。当访问属性时,它会检查这个布尔值,如果发现属性还没有初始化,就会抛出 UninitializedPropertyAccessException 异常。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MyViewModel {
    private var _myView: View? = null
    private var _myViewInitialized = false

    var myView: View
        get() {
            if (!_myViewInitialized) {
                throw UninitializedPropertyAccessException("myView has not been initialized")
            }
            return _myView!!
        }
        set(value) {
            _myView = value
            _myViewInitialized = true
        }
}

注意事项

    只能用于 var 声明的属性

    • lateinit 只能用于 var 声明的属性,不能用于 val 声明的属性。因为 val 声明的属性是不可变的,一旦赋值就不能更改。

    不能用于基本类型

    • lateinit 不能用于基本类型(如 IntDouble 等),因为基本类型不能为 null

    初始化前的检查

    • 在使用 lateinit 属性之前,需要确保它已经被正确初始化,否则会抛出 UninitializedPropertyAccessException 异常。

    线程安全性

    • lateinit 的初始化状态检查不是线程安全的。如果在多线程环境中使用 lateinit,需要自己保证线程安全性。

示例代码

以下是一个完整的示例代码,展示了 lateinit 的使用:

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

class MyActivity : AppCompatActivity() {
    lateinit var myView: View

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 初始化 myView
        myView = findViewById(R.id.my_view)
    }

    fun onClick(view: View) {
        // 使用 myView
        myView.setBackgroundColor(Color.RED)
    }
}

总之,lateinit 是 Kotlin 中一个非常有用的特性,它允许你延迟初始化非空属性,适用于依赖注入、UI 元素初始化和异步操作等场景。

data class 和普通class有什么区别

问题:Kotlin 中 data class 和普通 class 有什么区别?

回答:

在 Kotlin 中,data class 和普通 class 有一些重要的区别,主要体现在它们的用途、生成的成员函数以及性能等方面。

1. 用途

  • 普通 class

    • 普通的类可以用于表示任何类型的对象,没有特定的限制。它可以包含属性、方法、初始化块等。

    • 用于定义具有复杂行为和状态的对象,例如业务逻辑类、控制器类等。

  • data class

    • 主要用于存储数据,它会自动生成一些有用的成员函数,如 toString()equals()hashCode() 等。

    • 适用于表示数据模型的对象,例如实体类、数据传输对象(DTO)等。

2. 生成的成员函数

  • 普通 class

    • 不会自动生成任何成员函数。你需要手动实现 toString()equals()hashCode() 等方法。

  • data class

    • 自动生成 toString() 方法,返回对象的字符串表示,包含类名和所有属性的值。

    • 自动生成 equals() 方法,用于比较两个对象是否相等,基于所有属性的值。

    • 自动生成 hashCode() 方法,返回对象的哈希码,基于所有属性的值。

    • 自动生成 copy() 方法,用于创建对象的副本,并允许修改某些属性的值。

    • 自动生成 componentN() 方法,用于解构对象,将对象的属性拆解为单独的变量。

3. 数据操作

  • 普通 class

    • 如果需要对对象进行数据操作,例如比较、复制等,需要手动实现相关逻辑。

  • data class

    • 由于自动生成了 equals()hashCode() 等方法,可以方便地对对象进行数据操作。

    • 使用 copy() 方法可以轻松地创建对象的副本,并修改某些属性的值。

4. 性能

  • 普通 class

    • 性能与普通对象相同,没有额外的性能开销。

  • data class

    • 由于自动生成了一些成员函数,可能会有轻微的性能开销,但在大多数情况下可以忽略不计。

5. 使用场景

  • 普通 class

    • 适用于需要定义具有复杂行为和状态的对象的场景,例如业务逻辑类、控制器类等。

  • data class

    • 适用于表示数据模型的对象的场景,例如实体类、数据传输对象(DTO)等。

示例代码

以下是一个简单的示例代码,展示了 data class 和普通 class 的区别:

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

// 普通 class
class Person(val name: String, var age: Int) {
    override fun toString(): String {
        return "Person(name=$name, age=$age)"
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || other::class != this::class) return false
        other as Person
        return name == other.name && age == other.age
    }

    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + age
        return result
    }
}

// data class
data class DataPerson(val name: String, var age: Int)

在上面的示例中,Person 类需要手动实现 toString()equals()hashCode() 等方法,而 DataPerson 数据类会自动生成这些方法。

总之,data class 是 Kotlin 中一个非常有用的特性,它通过自动生成一些常用的成员函数,简化了数据模型对象的开发。

?. ?:

问题:在 Kotlin 中 ?. ?: 分别代表什么?

回答:

在 Kotlin 中,?.?: 是两个用于处理可空类型的实用操作符。它们分别代表:

1. ?.(安全调用操作符)

  • 作用 :用于在调用方法或访问属性时,避免对 null 值进行操作而导致的空指针异常。

  • 用法 :当调用的对象可能为 null 时,在方法名或属性名前加上 ?.,如果对象为 null,则返回 null,否则执行对应的方法或访问属性。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val name: String? = null
val length: Int? = name?.length // length 的值为 null

在这个例子中,name 是一个可空类型的字符串,name?.length 的意思是:如果 name 不为 null,则调用 length 属性,否则返回 null

2. ?:(Elvis 操作符)

  • 作用 :用于提供默认值,当左边的表达式为 null 时,返回右边的表达式。

  • 用法 :在表达式中使用 ?:,如果左边的表达式为 null,则返回右边的表达式,否则返回左边的表达式。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val name: String? = null
val defaultName: String = name ?: "Unknown" // defaultName 的值为 "Unknown"

在这个例子中,name 是一个可空类型的字符串,name ?: "Unknown" 的意思是:如果 name 不为 null,则返回 name,否则返回 "Unknown"

总结

  • ?. 用于在调用方法或访问属性时避免空指针异常,如果对象为 null,则返回 null

  • ?: 用于提供默认值,当左边的表达式为 null 时,返回右边的表达式。

这两个操作符在处理可空类型时非常有用,可以简化代码逻辑,提高代码的可读性和安全性。

kotlin的open关键字

open 关键字在 Kotlin 中用于标记一个类或类的成员可以被继承或重写。这是 Kotlin 中实现继承和多态的重要机制之一

by关键字的作用

【有道云笔记】委托的理解.md note.youdao.com/s/GHQKQ8nY

inline的原理 内联函数**

问题:Kotlin 的 inline 关键字是什么意思?

回答:

在 Kotlin 中,inline 是一个关键字,用于将函数体直接嵌入到调用它的位置,而不是像普通函数那样在运行时进行函数调用。这种机制称为“内联函数”(inline function)。inline 的主要目的是提高性能,减少函数调用的开销。

工作原理

当一个函数被标记为 inline 时,Kotlin 编译器会将该函数的代码直接复制到调用它的位置,而不是生成一个单独的函数调用指令。这样可以避免函数调用的栈帧开销,提高程序的运行效率。

使用场景

inline 通常用于以下场景:

    性能优化

    • 对于频繁调用的小函数,使用 inline 可以减少函数调用的开销,提高性能。

    高阶函数

    • 在 Kotlin 中,高阶函数(接受函数作为参数的函数)通常使用 inline 来提高性能,因为高阶函数的调用开销较大。

示例代码

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

inline fun log(message: String) {
    println(message)
}

fun main() {
    log("Hello, inline function!")
}

在上面的示例中,log 函数被标记为 inline,当它在 main 函数中被调用时,Kotlin 编译器会将 log 函数的代码直接嵌入到 main 函数中,生成的代码类似于:

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

fun main() {
    println("Hello, inline function!")
}

限制

虽然 inline 可以提高性能,但它也有一些限制:

    函数体不能包含循环

    • 如果一个函数被标记为 inline,它的函数体不能包含循环(如 forwhile 等),否则编译器会报错。

    返回类型不能是 Unit

    • 如果一个函数被标记为 inline,它的返回类型不能是 Unit,否则编译器会报错。

    不能用于递归函数

    • 如果一个函数被标记为 inline,它不能是递归函数,否则编译器会报错。

总之,inline 是 Kotlin 中一个用于提高性能的关键字,通过将函数体直接嵌入到调用它的位置,可以减少函数调用的开销。

协程的概念和使用

【有道云笔记】协程.md note.youdao.com/s/OPPyQ7K1

kotlin创建的mutablist和普通list的区别

问题:Kotlin 中创建的 MutableList 和普通 List 有什么区别?

回答:

在 Kotlin 中,MutableList 和普通 List 是两种不同的集合类型,它们的主要区别在于是否可以修改集合中的元素。

1. List(普通列表)

  • 定义List 是一个只读的列表,一旦创建后,不能添加、删除或修改其中的元素。

  • 接口方法List 提供了基本的集合操作方法,如 get()sizecontains() 等,但不提供修改集合的方法。

  • 使用场景 :适用于需要存储一组数据并进行只读操作的场景,例如显示数据列表、数据传递等。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val numbers: List<Int> = listOf(1, 2, 3, 4, 5)
println(numbers) // 输出:[1, 2, 3, 4, 5]

2. MutableList(可变列表)

  • 定义MutableList 是一个可变的列表,可以在创建后添加、删除或修改其中的元素。

  • 接口方法 :除了 List 提供的方法外,MutableList 还提供了修改集合的方法,如 add()remove()set() 等。

  • 使用场景 :适用于需要动态修改集合内容的场景,例如数据的增删改操作。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val numbers: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5)
numbers.add(6)
numbers.removeAt(0)
numbers[2] = 10
println(numbers) // 输出:[2, 3, 10, 4, 5, 6]

3. 性能

  • List :由于 List 是只读的,它的性能通常比 MutableList 更高,特别是在多线程环境中。

  • MutableList :由于 MutableList 允许修改元素,它的性能可能会受到一些影响,特别是在频繁修改的情况下。

4. 内存占用

  • List :由于 List 是只读的,它的内存占用通常比 MutableList 更小。

  • MutableList :由于 MutableList 允许修改元素,它的内存占用可能会更大,特别是在频繁修改的情况下。

5. 线程安全性

  • List :由于 List 是只读的,它在多线程环境中是线程安全的。

  • MutableList :由于 MutableList 允许修改元素,它在多线程环境中可能不是线程安全的,需要额外的同步机制。

总结

List 是只读的列表,适用于需要存储一组数据并进行只读操作的场景;而 MutableList 是可变的列表,适用于需要动态修改集合内容的场景。根据实际需求选择合适的集合类型,可以提高程序的性能和可维护性。

kotlin里创建一个空list该怎么操作

问题:Kotlin 中如何创建一个空的 List

回答:

在 Kotlin 中,创建一个空的 List 可以使用 emptyList() 函数。emptyList() 是 Kotlin 标准库提供的一个函数,用于创建一个不可变的空列表。

示例代码

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val emptyList: List<Int> = emptyList()
println(emptyList) // 输出:[]

说明

  • emptyList() 函数返回一个不可变的空列表,这意味着你不能向其中添加或删除元素。

  • 如果你需要一个可变的空列表,可以使用 mutableListOf() 函数。

--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

val emptyMutableList: MutableList<Int> = mutableListOf()
println(emptyMutableList) // 输出:[]

性能

  • emptyList() 返回的空列表是一个单例对象,因此性能非常高。

  • mutableListOf() 返回的空列表是一个可变的列表,性能与普通列表相同。

总之,如果你需要一个不可变的空列表,可以使用 emptyList();如果你需要一个可变的空列表,可以使用 mutableListOf()