1. 程式人生 > >Android Weekly Notes Issue #428

Android Weekly Notes Issue #428

# [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 最近有點忙, 不太有時間寫部落格, 積攢了好多話題都是沒有完成的. 看部落格兩個月沒更了, 拿這篇刷一下存在感. 是想多寫點真正厲害有價值的原創的. 先韜光養晦, 積累