阿里Android开发手册读后感 上篇

上周阿里公布了她的Android开发规范手册,读完一半就有相当多让我感受深刻的,做些简单的思考,记录如下

【推荐】使用Toast时,建议定义一个全局的 Toast 对象,这样可以避免连续显示 Toast 时不能取消上一次 Toast 消息的情况(如果你有连续弹出 Toast 的情况,避免 使用 Toast.makeText)

我的思考:
使用单例而不是静态工具类

不要在 Android 的 Application 对象中缓存数据。基础组件之间的数据共享请使用Intent 等机制,也可使用 SharedPreferences 等数据持久化机制

我的思考:
通过Application缓存对象会带来两个方面的问题:

  1. 大作用域增加了数据管理的成本,注意保证存储数据在前和读取数据顺序在后

  2. App切换到后台,当内存不够时,会回收资源,当从新切回前台时,相当于重新启动App,走App初始化和Activity初始化流程,如果缓存的数据不在生命周期进行,那么这个数据就将不存在,之后用户操作读取数据时报NulllPointException崩溃

【推荐】当前 Activity 的 onPause 方法执行结束后才会执行下一个 Activity 的 onCreate 方法,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率

我的思考:
ActivityA跳到ActivityB,生命周期调用将是A的onPause,A的onSaveInstatnceState方法(一定时在onStop之前,和onPause方法没有先后关系),B的onCreate(), B的onStart(), B的onResume()A的onStop方法能确定的是在onPause方法之后。

【推荐】对于只用于应用内的广播,优先使用LocalBroadcastManager来进行注册和发送,LocalBroadcastManager安全性更好,同时拥有更高的运行效率。

我的思考:
有针对性,便能更好

【推荐】总是使用显式Intent启动或者绑定Service,且不要为服务声明IntentFilter, 保证应用的安全性。如果确实需要使用隐式调用,则可为Service提供Intent Filter并从Intent中排除相应的组件名称,但必须搭配使用Intent#setPackage()方法设置 Intent 的指定包名,这样可以充分消除目标服务的不确定性。

我的思考:
隐私Intent跳转,范围是从前手机系统下所有的App里筛选,有理由推测:android系统维护了一个总的Manifest的清单文件。因此,若要使用隐式跳转,增加筛选条件IntentFilter,比如指定Package包名。

【推荐】如非必须,避免使用嵌套的 Fragment

嵌套 Fragment 是在 Android API 17 添加到 SDK 以及 Support 库中的功能,Fragment 嵌套使用会有一些坑,容易出现 bug,比较常见的问题有如下几种:

1) onActivityResult()方法的处理错乱,内嵌的 Fragment 可能收不到该方法的回调,需要由宿主 Fragment 进行转发处理

2) 突变动画效果;

3) 被继承的setRetainInstance(),导致在 Fragment 重建时多次触发不必要的逻辑。

【推荐】添加Fragment时 ,确保FragmentTransaction#commit()在Activity#onPostResume()或者 FragmentActivity#onResumeFragments()内调用。不要随意使用FragmentTransaction#commitAllowingStateLoss()来代替,任何 commitAllowingStateLoss()的使用必须经过code review,确保无负面影响

说明: https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

https://developer.android.com/reference/android/app/FragmentTransaction.html#commit()

Activity 可能因为各种原因被销毁,Android 支持页面被销毁前通过 Activity#onSaveInstanceState() 保 存 自 己 的 状 态 。 但 如 果FragmentTransaction.commit()发生在 Activity 状态保存之后,就会导致 Activity 重 建、恢复状态时无法还原页面状态,从而可能出错。为了避免给用户造成不好的体验,系统会抛出IllegalStateExceptionStateLoss 异常。推荐的做法是在 Activity 的onPostResume() 或 onResumeFragments() ( 对 FragmentActivity ) 里 执 行 FragmentTransaction.commit(),如有必要也可在 onCreate()里执行。不要随意改用FragmentTransaction.commitAllowingStateLoss()或者直接使用 try-catch 避免 crash,这不是问题的根本解决之道,当且仅当你确认 Activity 重建、恢复状态时,本次commit丢失不会造成影响时才可这么做。

我的思考:
https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html 这个链接的英文文章非常值得一读

【推荐】在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout 刷新:

1) 设置固定的view大小的高宽,如倒计时组件等;

2) 调用view的layout方式修改位置,如弹幕组件等

3)通过修改canvas位置并且调用invalidate(int l, int t, int r, int b)等方式限定刷新区域

4)通过设置一个是否允许requestLayout的变量,然后重写控件的requestLayout,onSizeChanged方法,判断控件的大小没有改变的情况下,当进入requestLayout的时候,直接返回而不调用super的requestLayout方法

我的思考:
手册里出现的好文章Android UI性能优化详解

【推荐】尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。

我的思考:
当项目有这样的需求:加载几十张大图的帧动画,要怎么办? 手册提供了细致的解决方案的文章Android 帧动画OOM问题优化

【强制】不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这 样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图。

说明:
ScrollView 中嵌套 List 或 RecyclerView 的做法官方明确禁止。除了开发过程中遇到的各种视觉和交互问题,这种做法对性能也有较大损耗ListView 等UI组件自身有垂直滚动功能也没有必要在嵌套一层 ScrollView。目前为了较好的UI体验,更贴近Material Design的设计,推荐使用 NestedScrollView

我的思考:
看下NestedScrollView的体系,
public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild2, ScrollingView {}

由此可见,NestedScrollView相比传统的ScrollingView,多了NestedScrollingParent和NestedScrollingChild2赋予的能力