zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    洛谷
    洛谷
    洛谷
    洛谷
    洛谷
    模板
    模板
    模板
    洛谷
    模板
  • 原文地址:https://www.cnblogs.com/mengdd/p/android-weekly-notes-issue-428.html
Copyright © 2011-2022 走看看