@SR1s
2019-03-17T16:45:08.000000Z
字数 4784
阅读 1391
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提示

设计师输出的适配规则见图。
实现方式:监听父布局的onLayout事件,在进行onLayout之后,计算适配之后的子布局区域的大小,调整其高度值。
LinearLayout adaptiveLayout = mActivity.findViewById(R.id.adaptive_layout);adaptiveLayout.addOnLayoutChangeListener(new AdaptiveLayoutHelper(adaptiveLayout));
onMeasure时进行处理,而非onLayout时处理?measure->layout->draw这套机制下,这种适配需求,如何处理才是最优的?OnLayoutChangeListener事件频繁回调,原因是什么?这里需要进一步确定:
1. 测量、布局、绘制这些步骤里,对可见性为GONE的控件的处理逻辑是怎样的?
2. 动画执行过程中,相对位置的计算依赖条件有哪些?
定义方式:
<resources><item name="custom_id_resource" type="id"/></resources>
物理屏幕大小获取:
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerval screenSize = Point().apply { windowManager.defaultDisplay.getRealSize(this) }Log.i(TAG, "screen=(W:${screenSize.x}, H:${screenSize.y})")
可绘制区域大小获取:
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManagerval displaySize = Point().apply { windowManager.defaultDisplay.getSize(this) }LogUtil.i(TAG, "display=(W:${displaySize.x}, H:${displaySize.y})")
抑制局部变量可转换为局部变量的lint提示,使用@SuppressWarnings("FieldCanBeLocal")
其他:
抑制静态变量可能导致内存泄漏:@SuppressWarnings("StaticFieldLeak")
这些字符串,在哪里能快捷找到(除了Google搜索)
相关代码:定义生命周期监听器
/*** 播放器监听进行响应* @author srluo** 离开界面时,暂停播放;* 回到界面时,恢复播放;* 退出界面时,销毁播放器*/class PlayerLifeCycleObserver(private val player: ExoPlayer): LifecycleObserver {private var savedPlayingState: Boolean? = null@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)fun savePlayingState() {LogUtil.i(TAG, "savePlayingState >>> player state: ${player.playWhenReady}")savedPlayingState = player.playWhenReadyplayer.playWhenReady = false}@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)fun resumePlayingState() {LogUtil.i(TAG, "resumePlayingState >>> saved state: $savedPlayingState")savedPlayingState?.let {savedState ->player.playWhenReady = savedState}savedPlayingState = null}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)fun destroyPlayer() {LogUtil.i(TAG, "destroyPlayer")player.stop()player.release()}}private const val TAG = "PlayerLifeCycleObserver"
相关代码:注册监听
mActivity.getLifecycle().addObserver(new PlayerLifeCycleObserver(mExoPlayer));
在Androidsupport包中,AppCompactActivity、Fragment均实现了getLifecycle接口,可通过这个接口,获取生命周期owner,注册生命周期监听器。
生命周期监听器仅需要实现LifecycleObserver接口,此接口不需要实现任何方法。监听器内关心生命周期的函数,通过在方法上添加注解@OnLifecycleEvent以及所关心的生命周期事件:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTROY、ON_ANY,进行监听。
LifeCycleObserver内部运行机制是怎样的?如生命周期的监听和回调的原理。
此Bug的成因复杂,从日志+断点进行分析,出现此种情况时,获取到的封面图片或许为null,或许已被回收,导致无法使用。
此部分逻辑较为复杂,有一套独立的且自己实现的图片缓存机制,且实现代码质量低,不易排查。
考虑到问题场景出现在图片素材的镜头上,遂采用Glide进行图片加载,由Glide进行图片资源的加载、处理和呈现。问题得解,且实现简单。
后续考虑将视频缩略图的加载逻辑也以Glide拓展的形式实现,废弃现有的视频封面图解析、缓存逻辑,进一步提升这部分代码的维护性和可复用性。
Glide具体运作机制的了解(加载、缓存、缓存管理、图片预处理等)Glide拓展(接口的提供、缓存复用、缓存管理复用、预处理逻辑复用)
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的关联:
public Request getRequest() {Object tag = getTag();Request request = null;if (tag != null) {if (tag instanceof Request) {request = (Request) tag;} else {throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting");}}return request;}
获取tag的方式如下。当tagId不为空时,会以tagId为key获取对应的值,当tagId为空时,则直接使用View.getTag()方法获取值。
@Nullableprivate Object getTag() {if (tagId == null) {return view.getTag();} else {return view.getTag(tagId);}}
在这个案例里,异常时tagId为空,使用View.getTag()方式获取对应的Request;而恰好业务上使用View.setTag(Object)设置了业务相关的数据。最终就出现了:获取tag上的数据,而数据类型不是Request,Glide发现后,主动抛出异常。
解决方案有两个方向:
1. 业务不使用View.setTag(Object)
2. Glide不使用View.setTag(Object)
方向1明显是不现实的,在整个工程里,很难去约束全局不使用这种方式;而即便是在工程内实现了这个约束,也无法保证第三方依赖内部也遵循这个约束。
方向2则好得多,变被动为主动。通过ViewTarget.setTagId(int)指定一个setTag使用的key。这个keyGlide建议使用一个Android资源id来指定。为何?因为这样能保证这个id在工程内是唯一的。
定义id方式:
<resources><item name="glide_tag_key" type="id"/></resources>
然后在项目初始化时设置一次这个值:
ViewTarget.setTagId(R.id.glide_tag_key);
当然,Glide也自定义了一个id: glide_custom_view_target_tag,使用这个也是可以的。
ViewTarget这个类,在Glide源码上标记为废弃,引导开发者使用CustomTarget,但并没有在CustomViewTarget上看到和setTagId(int)等价的接口,需要进一步了解一下CustomViewTarget。
以上,便为近期总结。告辞。