[关闭]
@SR1s 2019-03-17T16:45:08.000000Z 字数 4784 阅读 1028

2019-03-17 近期工作日常总结

2019年度承诺


Hello,又是一周结束。汇报下近期进展。
1. 本周无关键目标相关的进展;
2. 近期(这两周)主要忙于项目需求开发,集中于产品需求和编辑器模块的优化/梳理/重构;
3. 因版本发布,上周有相当大一部分时间集中在Bug排查和修复,以及其他临时插入的紧急事项。

贰:需求相关

相关需求有:
1. 【1.5】全面屏以及刘海屏的适配优化
2. 【1.6】滤镜参数优化
3. 【1.6】音乐外露和全局音量
4. 【1.6】滤镜交互优化

遇到和解决的问题有:
1. 根据一定规则动态适应全面屏/普通屏尺寸屏幕(需求1)
2. 全局音量/滤镜/音量面板展示时,首次动画不生效问题(需求3)
3. 如何自定义id资源(需求1)
4. 获取屏幕宽高信息的方式(需求1)
5. 抑制(suppress)变量可转换为局部变量lint提示

根据一定规则动态适应全面屏/普通屏尺寸屏幕

image.png-111.1kB

设计师输出的适配规则见图。
实现方式:监听父布局的onLayout事件,在进行onLayout之后,计算适配之后的子布局区域的大小,调整其高度值。

  1. LinearLayout adaptiveLayout = mActivity.findViewById(R.id.adaptive_layout);
  2. adaptiveLayout.addOnLayoutChangeListener(new AdaptiveLayoutHelper(adaptiveLayout));

遗留问题

  1. 是否可以更加主动,在onMeasure时进行处理,而非onLayout时处理?
  2. measure->layout->draw这套机制下,这种适配需求,如何处理才是最优的?
  3. OnLayoutChangeListener事件频繁回调,原因是什么?

全局音量/滤镜/音量面板展示时,首次动画不生效问题

  1. 现象为:首次点击时,没有动画,而是一次直接闪现的过程
  2. 排查代码,首次并无前置额外操作
  3. 添加动画监听器,输出日志,虽然日志显示开始和结束之间的间隔并非完整的300ms,但首次和往后的其他次数的时间间隔并无差别。
  4. 观察实现代码,发现动画是以目标View的相对位置进行定位,从目标View的地步移动到目标View的顶部
  5. 观察实现代码,发现动画是以目标View的相对位置进行定位,从目标View的地步移动到目标View的顶部
  6. 在此猜测上,进行相应修改:
    开始动画前,设置目标View的可见性为INVISIBLE,使其在界面上有一定的位置,但未呈现;之后再进行动画。问题得解。

遗留问题

这里需要进一步确定:
1. 测量、布局、绘制这些步骤里,对可见性为GONE的控件的处理逻辑是怎样的?
2. 动画执行过程中,相对位置的计算依赖条件有哪些?

自定义id资源

定义方式:

  1. <resources>
  2. <item name="custom_id_resource" type="id"/>
  3. </resources>

获取屏幕宽高信息的方式

物理屏幕大小获取:

  1. val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  2. val screenSize = Point().apply { windowManager.defaultDisplay.getRealSize(this) }
  3. Log.i(TAG, "screen=(W:${screenSize.x}, H:${screenSize.y})")

可绘制区域大小获取:

  1. val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  2. val displaySize = Point().apply { windowManager.defaultDisplay.getSize(this) }
  3. LogUtil.i(TAG, "display=(W:${displaySize.x}, H:${displaySize.y})")

抑制(suppress)变量可转换为局部变量lint提示

抑制局部变量可转换为局部变量的lint提示,使用@SuppressWarnings("FieldCanBeLocal")
其他:
抑制静态变量可能导致内存泄漏:@SuppressWarnings("StaticFieldLeak")

遗留问题

这些字符串,在哪里能快捷找到(除了Google搜索)

叁:BugFix相关

  1. 使用LifeCycle监听生命周期,控制播放器进入后台不播放
  2. 镜头列表偶发缩略图不展示
  3. 引入Glide后提示setTagId异常

使用LifeCycle监听生命周期,控制播放器进入后台不播放

相关代码:定义生命周期监听器

  1. /**
  2. * 播放器监听进行响应
  3. * @author srluo
  4. *
  5. * 离开界面时,暂停播放;
  6. * 回到界面时,恢复播放;
  7. * 退出界面时,销毁播放器
  8. */
  9. class PlayerLifeCycleObserver(private val player: ExoPlayer): LifecycleObserver {
  10. private var savedPlayingState: Boolean? = null
  11. @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
  12. fun savePlayingState() {
  13. LogUtil.i(TAG, "savePlayingState >>> player state: ${player.playWhenReady}")
  14. savedPlayingState = player.playWhenReady
  15. player.playWhenReady = false
  16. }
  17. @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
  18. fun resumePlayingState() {
  19. LogUtil.i(TAG, "resumePlayingState >>> saved state: $savedPlayingState")
  20. savedPlayingState?.let {
  21. savedState ->
  22. player.playWhenReady = savedState
  23. }
  24. savedPlayingState = null
  25. }
  26. @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
  27. fun destroyPlayer() {
  28. LogUtil.i(TAG, "destroyPlayer")
  29. player.stop()
  30. player.release()
  31. }
  32. }
  33. private const val TAG = "PlayerLifeCycleObserver"

相关代码:注册监听

  1. mActivity.getLifecycle().addObserver(new PlayerLifeCycleObserver(mExoPlayer));

在Androidsupport包中,AppCompactActivityFragment均实现了getLifecycle接口,可通过这个接口,获取生命周期owner,注册生命周期监听器。

生命周期监听器仅需要实现LifecycleObserver接口,此接口不需要实现任何方法。监听器内关心生命周期的函数,通过在方法上添加注解@OnLifecycleEvent以及所关心的生命周期事件:ON_CREATEON_STARTON_RESUMEON_PAUSEON_STOPON_DESTROYON_ANY,进行监听。

遗留问题

LifeCycleObserver内部运行机制是怎样的?如生命周期的监听和回调的原理。

引入Glide解决图片镜头偶发缩略图不展示的问题

此Bug的成因复杂,从日志+断点进行分析,出现此种情况时,获取到的封面图片或许为null,或许已被回收,导致无法使用。

此部分逻辑较为复杂,有一套独立的且自己实现的图片缓存机制,且实现代码质量低,不易排查。

考虑到问题场景出现在图片素材的镜头上,遂采用Glide进行图片加载,由Glide进行图片资源的加载、处理和呈现。问题得解,且实现简单。

后续考虑将视频缩略图的加载逻辑也以Glide拓展的形式实现,废弃现有的视频封面图解析、缓存逻辑,进一步提升这部分代码的维护性和可复用性。

遗留问题

  1. Glide具体运作机制的了解(加载、缓存、缓存管理、图片预处理等)
  2. 如何实现Glide拓展(接口的提供、缓存复用、缓存管理复用、预处理逻辑复用)

引入Glide后触发setTagId异常

  1. java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting

异常信息如上,意思是不能开发者不能对交给Glide操作的View设置tag。

问题1:为何不能设置tag?
回答这个问题,需要先解答问题2:Glide利用View的tag做了什么事?
从抛出异常的地方的源码可以看到,Glide会将Request放在View的tag里,以此建立请求和View的关联:

  1. public Request getRequest() {
  2. Object tag = getTag();
  3. Request request = null;
  4. if (tag != null) {
  5. if (tag instanceof Request) {
  6. request = (Request) tag;
  7. } else {
  8. throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");
  9. }
  10. }
  11. return request;
  12. }

获取tag的方式如下。当tagId不为空时,会以tagId为key获取对应的值,当tagId为空时,则直接使用View.getTag()方法获取值。

  1. @Nullable
  2. private Object getTag() {
  3. if (tagId == null) {
  4. return view.getTag();
  5. } else {
  6. return view.getTag(tagId);
  7. }
  8. }

在这个案例里,异常时tagId为空,使用View.getTag()方式获取对应的Request;而恰好业务上使用View.setTag(Object)设置了业务相关的数据。最终就出现了:获取tag上的数据,而数据类型不是RequestGlide发现后,主动抛出异常。

解决方案有两个方向:
1. 业务不使用View.setTag(Object)
2. Glide不使用View.setTag(Object)

方向1明显是不现实的,在整个工程里,很难去约束全局不使用这种方式;而即便是在工程内实现了这个约束,也无法保证第三方依赖内部也遵循这个约束。

方向2则好得多,变被动为主动。通过ViewTarget.setTagId(int)指定一个setTag使用的key。这个keyGlide建议使用一个Android资源id来指定。为何?因为这样能保证这个id在工程内是唯一的。

定义id方式:

  1. <resources>
  2. <item name="glide_tag_key" type="id"/>
  3. </resources>

然后在项目初始化时设置一次这个值:

  1. ViewTarget.setTagId(R.id.glide_tag_key);

当然,Glide也自定义了一个id: glide_custom_view_target_tag,使用这个也是可以的。

遗留问题

ViewTarget这个类,在Glide源码上标记为废弃,引导开发者使用CustomTarget,但并没有在CustomViewTarget上看到和setTagId(int)等价的接口,需要进一步了解一下CustomViewTarget

以上,便为近期总结。告辞。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注