Android Weekly Notes Issue #428
阿新 • • 發佈:2020-08-31
# [Android Weekly Issue #428](http://androidweekly.net/issues/issue-428)
## [Kotlin Flow Retry Operator with Exponential Backoff Delay](https://blog.mindorks.com/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](https://medium.com/androiddevelopers/fragments-rebuilding-the-internals-61913f8bf48e)
Fragment在Android 10已經廢棄, 現在不在framework中了, 只在AndroidX中有.
這個[Fragment 1.3.0-alpha08](https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha08)版本的釋出, 有一些關於FragmentManager內部狀態的重要更新.
解決了很多issue, 簡化了fragment的生命週期, 還提供了一個FragmentManager多個back stacks的支援.
核心就是這個[FragmentStateManager](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java?ss=androidx)類.
這個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](https://issuetracker.google.com/issues/147749580).
這個issue相關聯的還有好幾個issues.
### 在容器層面解決問題
用一個[SpecialEffectsController](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java)(以後名字可能會改)來處理所有動畫轉場相關的東西.
這樣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](https://doordash.engineering/2020/08/19/speedy-and-scalable-development-of-android-mobile-ui-tests/)
講了一整套的測試實踐.
沒有用Appium, 用的UI Automator和Espresso.
## [Basic Coroutine Level 1](https://proandroiddev.com/basic-coroutine-level-1-fd46c4bccd8f)
Kotlin協程的概念.
## [Android Lint Framework — An Introduction](https://proandroiddev.com/android-lint-framework-an-introduction-36139deedf8b)
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
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](https://medium.com/androiddevelopers/codelabs-for-new-android-game-technologies-486a847eb92e)
關於Android Game新技術的Codelabs:
* 資源打包: [Play Asset Delivery](https://developer.android.com/guide/app-bundle/asset-delivery) -> https://codelabs.developers.google.com/codelabs/unity-gamepad/#0
* 幀率和影象: [Android Performance Tuner](https://developer.android.com/games/sdk/performance-tuner) -> https://codelabs.developers.google.com/codelabs/android-performance-tuner-unity/#0
都是Unity的game.
## [Android Vitals - When did my app start?](https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4)
系列文章之六, 我的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](https://androidessence.com/comparing-three-dependency-injection-solutions)
比較三種依賴注入的解決方案.
* 手寫方式.
* Koin.
* Dagger Hilt.
## [Avoiding memory leaks when using Data Binding and View Binding](https://proandroiddev.com/avoiding-memory-leaks-when-using-data-binding-and-view-binding-3b91d571c150)
使用Data Binding和View Binding的時候, 注意記憶體洩漏問題.
Google[建議](https://developer.android.com/topic/libraries/view-binding#fragments)在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
}
```
有個[部落格](https://proandroiddev.com/make-android-view-binding-great-with-kotlin-b71dd9c87719)中介紹的方法, 可以簡化成這樣:
```
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](https://medium.com/swlh/anti-patterns-of-automated-software-testing-b396283a4cb6)
關於測試的一些anti-patterns.
推薦閱讀.
## [Using bytecode analysis to find unused dependencies](https://dev.to/autonomousapps/dependency-analysis-gradle-plugin-using-bytecode-analysis-to-find-unused-dependencies-509n)
關於這個庫: https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin的說明.
## Code
* https://github.com/jmfayard/refreshVersions: 一個依賴版本管理的gradle外掛.
## 後記
好久沒在部落格園發過這個系列.
其實一直還有在更, 只不過寫得比較散亂隨意, 所以丟在了簡書:
https://www.jianshu.com/c/e51d4d597637
最近有點忙, 不太有時間寫部落格, 積攢了好多話題都是沒有完成的.
看部落格兩個月沒更了, 拿這篇刷一下存在感.
是想多寫點真正厲害有價值的原創的.
先韜光養晦, 積累