Android

0 阅读2分钟

1. hooks原理

20251107-110122.jpeg

2. Android 中 UI 的刷新机制

  1. 当界面需要刷新时(View.invalidate()requestLayout() 被调用),最终会触发一次绘制请求。系统会在 下一帧的 VSync 信号到来时 执行 UI 渲染流程
  2. 当调用invalidate时,最终会执行
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 添加handler消息屏障,用于阻塞hannlde执行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 向 Choreographer 注册一个帧回调mTraversalRunnable,等待下一帧 VSync 到来。当下一帧 VSync 到达后,会执行callback.doFrame(frameTimeNanos),触发mTraversalRunnable回调
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

在mTraversalRunnable中移除消息屏障,并依次执行performMeasure() → performLayout() → performDraw()

3.协程

自定义协程作用域

class CustomerScope : CoroutineScope, Closeable {
    /** 让同一个作用域中的协程相互独立,一个失败不会影响其他协程 */
    private val job = SupervisorJob()

    /** 协程上下文 */
    override val coroutineContext: CoroutineContext = Dispatchers.IO + job

    /** 取消该scope中所有正在运行的协程 */
    override fun close() {
        job.cancel()
    }
}

使用

val scope = CustomerScope()
scope.launch{
    doSomething()
}

协程并发执行

runBlocking {
    val start = System.currentTimeMillis()

    val user = async { getUserFromNetwork() }
    val posts = async { getPostsFromNetwork() }

    // 同时发起两个异步任务
    println("Result: ${user.await()} & ${posts.await()}")

    println("Time = ${System.currentTimeMillis() - start}ms")
}

suspend fun getUserFromNetwork(): String {
    delay(1000)
    return "Tom"
}

suspend fun getPostsFromNetwork(): String {
    delay(1000)
    return "Post List"
}

接口合并请求

// viewModelScope 属于ViewModel的生命周期作用域;
// 它的Job是一个SupervisorJob;只作用于不同launch之间
// 所以即使内部某个子协程失败,也不会取消整个 ViewModel;
// 同一个 launch 内部的子协程,还是存在父子取消依赖关系的
viewModelScope.launch {
    // 是一个suspend 函数,可以在协程体内创建一个新的监督作用域;
    // 它会屏蔽子任务间的失败传播;
    supervisorScope {
        val tasks = listOf(
            async { runCatching{api.getUserInfo()} },
            async { runCatching{api.getUserPosts()} }
        )

        val (user, posts) = tasks.awaitAll()
    }
}

协程错误处理:

  1. 使用CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, e ->
    Log.e("Coroutine", "未捕获异常: ${e.message}")
}
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main + handler)
scope.launch {
    launch {
        throw RuntimeException("子任务 A 失败")
    }

    launch {
        delay(1000)
        println("子任务 B 仍然正常执行 ✅")
    }
}
输出
未捕获异常: 子任务 A 失败
子任务 B 仍然正常执行 ✅

2. 使用try catch 例如上方接口合并请求中runCatch

4. LiveData

在fragment onResume时liveData会再次通知更新,修复方案

class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val pending = AtomicBoolean(false)

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner) { t ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    override fun setValue(t: T?) {
        pending.set(true)
        super.setValue(t)
    }
}

5.关于LayoutInflater,它是如何通过 inflate 方法获取到具体View

inflate(resource)
   ↓
getLayout(resource)
   ↓
XmlPullParser -> 解析XML
   ↓
createViewFromTag() -> 反射创建View实例
   ↓
rInflateChildren() -> 递归解析子View
   ↓
addView() -> 挂载到父容器
   ↓
返回根View

6.自定义View

canvas 绘制各种形状View

becf24a2-21a8-443b-9ea3-30f589444415.jpeg

7.接口错误处理

override fun dataOrNull(): T? {
    check(resp)
    return resp.data
}

private fun check(resp: IResponse<T>) {
    val errorCode = resp.errorCode
    val errorMessage = resp.errorMessage
    if (errorCode != 0) {
        throw CustomerException(errorCode, errorMessage, resp)
    }
}
    
ViewModelScope.launch {
    val result = kotlin.runCatching {
        apiServiceKt.useInfo().dataOrNull()
    }
    if (result.isSuccess) {
    
    } else {
        // 判断是否是自定义Exception,不是的话为系统抛出异常,例如JsonException
        if (result.exceptionOrNull() is CustomerException) {

        } else {

        }
    }
}

8. 多个接口同时返回token过期

使用自定义拦截器,在拦截器里面判断errCode

object LoginManager {
    private var isShow = false
    // 打开登录弹框
    fun showLoginDialog() {
        if(isShow) {
            return
        }
        isShow = true
        Dialog.show("token 过期", object: ClickListener{
            override fun onDialogButtonClick() {
                mShowDialog = false
                // 跳转到登录页
            }
        })
    }
}
class UerTokenInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val call = chain.call()
        val request = chain.request()
        val response = chain.proceed(request)
        val responseJson = response.toJSONObject()
        val errorCode = responseJson.get("errCode")
        if(errCode == "400001") { // 弹出登录弹框
            
        }
        return response
    }
}

9. okhttp 内部实现 10. recyclewView 内部实现

  1. handle 消息如何插入
  2. RN 不适合的场景
  3. 目前使用RN遇到的瓶颈 解决方案