这是讲协程Flow系列文章中的一篇.
对于重试的两个操作符:
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
}
}
Fragment在Android 10已经废弃, 现在不在framework中了, 只在AndroidX中有.
这个Fragment 1.3.0-alpha08版本的发布, 有一些关于FragmentManager内部状态的重要更新.
解决了很多issue, 简化了fragment的生命周期, 还提供了一个FragmentManager多个back stacks的支持.
核心就是这个FragmentStateManager类.
这个FragmentStateManager负责:
关于状态的确定, 有一个case是一个难点: postponed fragments.
这是一个以前就有的东西, 通常跟shared element transition动画有关系.
postponed fragment有两个特点:
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中状态不一致的问题.
原先: 一个FragmentManager
总管所有.
现在: FragmentManager
和各个FragmentStateManager
的实例交流.
FragmentManager
only has state that applies to all fragments.FragmentStateManager
manages the state at the fragment level.SpecialEffectsController
manages the state at the container level.这个改动新发布, 实验阶段, 总体来说是应该没有行为改变的.
如果有行为改变, 对你的程序造成了影响, 也可以暂时关闭(FragmentManager.enableNewStateManager(false)
), 并且报告个issue.
讲了一整套的测试实践.
没有用Appium, 用的UI Automator和Espresso.
Kotlin协程的概念.
Android Lint的介绍.
创建一个Lint规则, 保证每个人都用项目自定义的ImageView, 而不是原生的ImageView.
具体做法:
首先从创建一个叫做custom-lint
的module. 需要依赖lint-api
和lint-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
}
关于Android Game新技术的Codelabs:
都是Unity的game.
系列文章之六, 我的app啥时候启动的?
看个结论吧:
Here's how we can most accurately measure the app start time when monitoring cold start:
Process.getStartUptimeMillis()
.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.比较三种依赖注入的解决方案.
使用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.
推荐阅读.
关于这个库: https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin的说明.
好久没在博客园发过这个系列.
其实一直还有在更, 只不过写得比较散乱随意, 所以丢在了简书:
https://www.jianshu.com/c/e51d4d597637
最近有点忙, 不太有时间写博客, 积攒了好多话题都是没有完成的.
看博客两个月没更了, 拿这篇刷一下存在感.
是想多写点真正厉害有价值的原创的.
先韬光养晦, 积累一下.
手机扫一扫
移动阅读更方便
你可能感兴趣的文章