Android Weekly Notes Issue #428
- 2020 年 8 月 31 日
- 筆記
- Android Weekly
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-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
}
Codelabs for new Android game technologies
關於Android Game新技術的Codelabs:
- 資源打包: Play Asset Delivery -> //codelabs.developers.google.com/codelabs/unity-gamepad/#0
- 幀率和圖像: Android Performance Tuner -> //codelabs.developers.google.com/codelabs/android-performance-tuner-unity/#0
都是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 toApplication.onCreate()
) and fallback to the timeContentProvider.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
關於這個庫: //github.com/autonomousapps/dependency-analysis-android-gradle-plugin的說明.
Code
- //github.com/jmfayard/refreshVersions: 一個依賴版本管理的gradle插件.
後記
好久沒在博客園發過這個系列.
其實一直還有在更, 只不過寫得比較散亂隨意, 所以丟在了簡書:
//www.jianshu.com/c/e51d4d597637
最近有點忙, 不太有時間寫博客, 積攢了好多話題都是沒有完成的.
看博客兩個月沒更了, 拿這篇刷一下存在感.
是想多寫點真正厲害有價值的原創的.
先韜光養晦, 積累一下.