Android Weekly Notes Issue #428

Android Weekly Issue #428

Kotlin Flow Retry Operator with Exponential Backoff Delay

这是讲协程Flow系列文章中的一篇.

对于重试的两个操作符:

  • retryWhen
  • retry

retryWhen的使用:

.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(2000)
return@retryWhen true
} else {
return@retryWhen false
}
}

retry:

.retry(retries = 3) { cause ->
if (cause is IOException) {
delay(2000)
return@retry true
} else {
return@retry false
}
}

可以把时间指数延长:

viewModelScope.launch {
var currentDelay = 1000L
val delayFactor = 2
doLongRunningTask()
.flowOn(Dispatchers.Default)
.retry(retries = 3) { cause ->
if (cause is IOException) {
delay(currentDelay)
currentDelay = (currentDelay * delayFactor)
return@retry true
} else {
return@retry false
}
}
.catch {
// error
}
.collect {
// success
}
}

Fragments: Rebuilding the Internals

Fragment在Android 10已经废弃, 现在不在framework中了, 只在AndroidX中有.

这个Fragment 1.3.0-alpha08版本的发布, 有一些关于FragmentManager内部状态的重要更新.
解决了很多issue, 简化了fragment的生命周期, 还提供了一个FragmentManager多个back stacks的支持.

核心就是这个FragmentStateManager类.

这个FragmentStateManager负责:

  • 转换Fragment的生命周期状态.
  • 跑动画和转场.
  • 处理延迟转换.

Postponed fragments

关于状态的确定, 有一个case是一个难点: postponed fragments.
这是一个以前就有的东西, 通常跟shared element transition动画有关系.

postponed fragment有两个特点:

  • view创建了, 但是不可见.
  • lifecycle顶多到STARTED.

只有调用这个方法: startPostponedEnterTransition()之后, fragment的transition才会跑, view会变成可见, fragment会移动到RESUMED.

所以有这个bug: Postponed Fragments leave the Fragments and FragmentManager in an inconsistent state bug.

这个issue相关联的还有好几个issues.

在容器层面解决问题

用一个SpecialEffectsController(以后名字可能会改)来处理所有动画转场相关的东西.

这样FragmentManager就被解放出来, 不需要处理postponed的逻辑, 而是交给了container, 这样就避免了FragmentManager中状态不一致的问题.

新的StateManager构架

原先: 一个FragmentManager总管所有.

现在: FragmentManager和各个FragmentStateManager的实例交流.

  • The FragmentManager only has state that applies to all fragments.
  • The FragmentStateManager manages the state at the fragment level.
  • The SpecialEffectsController manages the state at the container level.

总体

这个改动新发布, 实验阶段, 总体来说是应该没有行为改变的.

如果有行为改变, 对你的程序造成了影响, 也可以暂时关闭(FragmentManager.enableNewStateManager(false)), 并且报告个issue.

A Framework For Speedy and Scalable Development Of Android UI Tests

讲了一整套的测试实践.

没有用Appium, 用的UI Automator和Espresso.

Basic Coroutine Level 1

Kotlin协程的概念.

Android Lint Framework — An Introduction

Android Lint的介绍.

创建一个Lint规则, 保证每个人都用项目自定义的ImageView, 而不是原生的ImageView.

具体做法:

  • 首先从创建一个叫做custom-lint的module. 需要依赖lint-apilint-checks:
compileOnly "com.android.tools.lint:lint-api:$androidToolsVersion"
compileOnly "com.android.tools.lint:lint-checks:$androidToolsVersion"

这里用了compileOnly是因为不想lint API在runtime available.

  • 之后创建自定义规则. 每个lint check的实现都叫一个detector. 需要继承Detector, 并且利用Scanners来做扫描. 报告错误需要定义Issue. 还可以创建LintFx, 作为quick fix.
class ImageViewUsageDetector : LayoutDetector() {
// Applicable elements
override fun visitElement(context: XmlContext, element: Element) {
context.report(
issue = ISSUE,
location = context.getElementLocation(element),
message = REPORT_MESSAGE,
quickfixData = computeQuickFix()
)
}
private fun computeQuickFix(): LintFix {
return LintFix.create()
.replace().text(SdkConstants.IMAGE_VIEW)
.with(TINTED_IMAGE_VIEW)
.build()
}
// Issue, implementation, and other constants
}
  • 然后把定义好的自定义规则注册.
class Registry: IssueRegistry() {
override val issues: List<Issue>
get() = listOf(ImageViewUsageDetector.ISSUE)
override val api: Int = CURRENT_API
}
  • 创建入口, 在build.gradle文件中:
// Configure jar to register our lint registry
jar {
manifest {
attributes("Lint-Registry-v2": "com.tintedimagelint.lint.Registry")
}
}
  • 加上依赖和一些配置.
android {
// Configurations above
lintOptions {
lintConfig file('../analysis/lint/lint.xml')
htmlOutput file("$project.buildDir/reports/lint/lint-reports.html")
xmlOutput file("$project.buildDir/reports/lint/lint-reports.xml")
abortOnError false
}
//Configurations below
}
dependencies {
// Dependencies above
// Include custom lint module as a lintCheck
lintChecks project(":custom-lint")
// Dependencies below
}

Codelabs for new Android game technologies

关于Android Game新技术的Codelabs:

都是Unity的game.

Android Vitals - When did my app start?

系列文章之六, 我的app啥时候启动的?

看个结论吧:

Here's how we can most accurately measure the app start time when monitoring cold start:

  • Up to API 24: Use the class load time of a content provider.
  • API 24 - API 28: Use Process.getStartUptimeMillis().
  • API 28 and beyond: Use Process.getStartUptimeMillis() but filter out weird values (e.g. more than 1 min to get to Application.onCreate()) and fallback to the time ContentProvider.onCreate() is called.

Comparing Three Dependency Injection Solutions

比较三种依赖注入的解决方案.

  • 手写方式.
  • Koin.
  • Dagger Hilt.

Avoiding memory leaks when using Data Binding and View Binding

使用Data Binding和View Binding的时候, 注意内存泄漏问题.

Google建议在Fragment中使用binding时, 要在onDestroyView中置为null:

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

有个博客中介绍的方法, 可以简化成这样:

private val binding: FragmentFirstBinding by viewBinding()

Fragment还有一个参数的构造, 可以传入布局id:

class FirstFragment : Fragment(R.layout.fragment_first) {
private val binding: FragmentFirstBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Any code we used to do in onCreateView can go here instead
}
}

冷知识: DataBinding实现了ViewBinding.

public abstract class ViewDataBinding extends BaseObservable implements ViewBinding

所以ViewBinding和DataBinding方法通用.

Anti-patterns of automated software testing

关于测试的一些anti-patterns.

推荐阅读.

Using bytecode analysis to find unused dependencies

关于这个库: https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin的说明.

Code

后记

好久没在博客园发过这个系列.
其实一直还有在更, 只不过写得比较散乱随意, 所以丢在了简书:
https://www.jianshu.com/c/e51d4d597637

最近有点忙, 不太有时间写博客, 积攒了好多话题都是没有完成的.
看博客两个月没更了, 拿这篇刷一下存在感.
是想多写点真正厉害有价值的原创的.
先韬光养晦, 积累一下.

posted @   圣骑士wind  阅读(468)  评论(0编辑  收藏  举报
编辑推荐:
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
历史上的今天:
2016-08-31 commit(), commitNow()和commitAllowingStateLoss()
点击右上角即可分享
微信分享提示