@SR1s
2019-03-18T00:45:08.000000Z
字数 4784
阅读 1106
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 WindowManager
val 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 WindowManager
val 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.playWhenReady
player.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()
方法获取值。
@Nullable
private 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
。
以上,便为近期总结。告辞。