秋天的树叶
时间:2018-02-12 03:42:12来源:杰瑞文章网点击:作文字数:200字
作文导读:
UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识、如何优化 UI 渲染两部分内容。
UI 优化系列专题
UI 渲染背景知识
《View 绘制流程之 setContentView() 到底做了什么?》
《View 绘制流程之 DecorView 添加至窗口的过程》
《深入 Activity 三部曲(3)View 绘制流程》
《Android 之 LayoutInflater 全面解析》
《关于渲染,你需要了解什么?》
《Android 之 Choreographer 详细分析》
如何优化 UI 渲染
《Android 之如何优化 UI 渲染(上)》
《Android 之如何优化 UI 渲染(下)》
在 Android 中使用动画是非常常见的,无论是使用补间动画还是属性动画,都离不开 View 的绘制任务。我们知道 Android UI 绘制任务“都”是在主线程中完成的。那异步绘制是否可行呢?答案是肯定的,其关键就是今天要介绍的 RenderThread,对于 RenderThread 可能很多人对它并不了解,接下来我将教会大家如何利用 RenderThread 实现动画的异步渲染。
什么是 RenderThread ?
大家是否曾注意过,Android 在 5.0 之后对动画的支持更加炫酷了,但是 UI 绘制并没有因此受到影响,反而更加流畅。这其中很大的功劳源自于 RenderThread 的变化。在介绍 RenderThread 之前,我们需要先来了解下 Android 系统 UI 渲染的演进之路。
在 Android 3.0 之前(或者没有启用硬件加速时),系统都会使用软件方式来渲染 UI。但是由于 CPU 在结构设计上的差异,对于图形处理并不是那么高效。这个过程完全没有利用 GPU 的图形高性能。
CPU 和 GPU 结构设计如下:
从结构图可以看出,CPU 的控制器较为复杂,而 ALU 数量较少,因此 CPU 更擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。而 GPU 的设计正是为实现大量数学运算。GPU 的控制器比较简单,但包含大量 ALU。GPU 中的 ALU 使用了并行设计,且具有较多的浮点运算单元。可以帮助我们加快栅格化操作。
所以从 Android 3.0 开始,Android 开始支持硬件加速,但是到 Android 4.0 时才默认开启硬件加速。有关 Android 渲染框架详细内容,你可以参考《关于 UI 渲染,你需要了解什么?》。
优化是无止境的,Google 在 2012 年的 I/O 大会上宣布了 Project Butter 黄油计划,并且在 Android 4.1 中正式开启了这个机制。Project Butter 主要包含三个组成部分,VSYNC、Triple Buffer 和 Choreographer。有关它们的详细分析,你可以参考如下资料:
Android 之 Choreographer 详细分析
Android 之 Project Butter 详细介绍
经过 Android 4.1 的 Project Butter 黄油计划之后,Android 的渲染性能有了很大的改善。不过你有没有注意到这样一个问题,虽然利用了 GPU 的图形高性能运算,但是从计算 DisplayList,到通过 GPU 绘制到 Frame Buffer,整个计算和绘制都在 UI 主线程中完成。
UI 线程“既当爹又当妈”,任务过于繁重。如果整个渲染过程比较耗时,可能造成无法响应用户的操作,进而出现卡顿的情况。GPU 对图形的绘制渲染能力更胜一筹,如果使用 GPU 并在不同线程绘制渲染图形,那么整个流程会更加顺畅。
正因如此,在 Android 5.0 引入两个比较大的改变。一个是引入了 RenderNode 的概念,它对 DisplayList 及一些 View 显示属性都做了进一步封装。另一个是引入了 RenderThread,所有的 GL 命令执行都放到这个线程上,渲染线程在 RenderNode 中存有渲染帧的所有信息,可以做一些 View 的异步渲染任务,这样即便主线程有耗时操作的时候也可以保证渲染的流畅性。
至此,我们已经知道 RenderThread 是 Android 5.0 之后的产物,用于分担主线程绘制任务的渲染线程。UI 可以进行异步绘制,那动画可以异步似乎也成为可能。所以,带着疑问,接下来我们还要对其进行一番探索实践,看如何利用 RenderThread 实现动画的异步渲染。
原理探索
经过查看官方文档,得知 RenderThread 目前仅支持两种动画的完全渲染工作(RenderThread 的文档介绍真的是少之又少)。
ViewPreportyAnimator
CircularReveal
关于 CircularReveal(揭露动画)的使用比较简单且功能较为单一,在此不做过多的探索,今天我们着重探索下 ViewPropertyAnimator。
final View view = findViewById(R.id.button);
final ViewPropertyAnimator animator = view.animate().scaleY(2).setDuration(1000);
animator.start();
通过 View 的 animate() 即可创建 ViewPropertyAnimator 动画,注意它并不是 Animator 的子类。其内部提供了缩放、位移、透明度相关方法。
public class ViewPropertyAnimator {
/**
* A RenderThread-driven backend that may intercept startAnimation
*/
private ViewPropertyAnimatorRT mRTBackend;
public ViewPropertyAnimator scaleX(float value) {
animateProperty(SCALE_X, value);
return this;
}
// ... 省略 scaleY
public ViewPropertyAnimator translationX(float value) {
animateProperty(TRANSLATION_X, value);
return this;
}
// ... 省略 translationY
public ViewPropertyAnimator alpha(float value) {
animateProperty(ALPHA, value);
return this;
}
/**
* 开始动画
*/
private void startAnimation() {
// 是否能够通过 ReanderThread 渲染关键在这里
if (mRTBackend != null && mRTBackend.startAnimation(this)) {
// 使用 RenderThread 异步渲染动画
return;
}
// 否则将会降解为普通熟悉动画
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
// ......
animator.start();
}
}
我们需要重点关注的是 startAnimator 方法,在该方法首先对 mRTBackend 进行了判断,它的实际类型是 ViewPropertyAnimatorRT,如果不为 null,则由它来执行动画。如果 if 条件不成立,也就是此时不支持 RenderThread 完全渲染。很明显 RenderThread 渲染动画和 ViewPropertyAnimatorRT 有直接关系。
class ViewPropertyAnimatorRT {
.....
ViewPropertyAnimatorRT(View view) {
mView = view;
}
public boolean startAnimation(ViewPropertyAnimator parent) {
cancelAnimators(parent.mPendingAnimations);
// 关键在这里判断是否成立
if (!canHandleAnimator(parent)) {
return false;
}
// 执行 RenderThread 异步渲染动画
doStartAnimation(parent);
return true;
}
......
}
可以看到 startAnimation 方法先通过 canHandleAnimator 方法判断是否成立,如果不成立返回 false,此时回到 ViewPropertyAnimator 动画将会退化成普通属性动画。否则执行 doStartAnimation 方法。
我们先看下 canHandleAnimator 的判断条件,它的参数是 ViewPropertyAnimator:
private boolean canHandleAnimator(ViewPropertyAnimator parent) {
if (parent.getUpdateListener() != null) {
return false;
}
if (parent.getListener() != null) {
// TODO support
return false;
}
if (!mView.isHardwareAccelerated()) {
// TODO handle this maybe?
return false;
}
if (parent.hasActions()) {
return false;
}
// Here goes nothing...
return true;
}
可以看出代码逻辑是比较清楚了,① 是否支持硬件加速(Android 在 3.0 开始支持硬件加速,在 4.0 默认开启),② 是否设置了监听 Listener 或 UpdateListener,或者设置了 Action(监听动画开始、结束)都会导致 canHandleAnimator 方法返回 false,从而导致 doStartAnimator 方法无法执行。在此我们得到一个非常重要的条件是不进行任何监听器设置,确保 canHandleAnimator 返回 true。
下面接着看 doStartAnimation 方法,执行 doStartAnimation 方法表示动画将被 RenderThread 执行。
private void doStartAnimation(ViewPropertyAnimator parent) {
int size = parent.mPendingAnimations.size();
// 启动延迟时间
long startDelay = parent.getStartDelay();
// duration 执行时间
long duration = parent.getDuration();
// 插值器
TimeInterpolator interpolator = parent.getInterpolator();
if (interpolator == null) {
// Documented to be LinearInterpolator in ValueAnimator.setInterpolator
// 默认线性插值器
interpolator = sLinearInterpolator;
}
if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) {
interpolator = new FallbackLUTInterpolator(interpolator, duration);
}
for (int i = 0; i < size; i++) {
NameValuesHolder holder = parent.mPendingAnimations.get(i);
int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
final float finalValue = holder.mFromValue + holder.mDeltaValue;
// 对于每个动画属性都创建了RenderNodeAnimaor
RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);
animator.setStartDelay(startDelay);
animator.setDuration(duration);
animator.setInterpolator(interpolator);
animator.setTarget(mView);
animator.start();
mAnimators[property] = animator;
}
parent.mPendingAnimations.clear();
}
ViewPropertyAnimator 的 mPendingAniations 保存了动画的每个属性。doStartAnimation 方法为每个动画属性都创建了一个 RenderNodeAnimator,然后将对应的动画参数也设置给了 RenderNodeAnimator,此处就完成了动画和属性的绑定。
接下来我们要跟踪下 RendernodeAnimator,
public class RenderNodeAnimator extends Animator {
public void setTarget (View view) {
mViewTarget = view;
setTarget (mViewTarget.mRenderNode);
}
private void setTarget (RenderNode node){
......
mTarget = node;
mTarget.addAnimator(this);
}
}
setTarget 方法将当前 View 的 RenderNode 和 RenderNodeAnimator 通过 addAnimator 进行绑定。在 RenderNode 的 addAnimator 方法通过 Native 方法 nAddAnimator 将其注册到 AnimatorManager 中。
public class RenderNode {
public void addAnimator(RenderNodeAnimator animator) {
if (mOwningView == null || mOwningView.mAttachInfo == null) {
throw new IllegalStateException("Cannot start this animator on a detached view!");
}
// Native 方法注册到AnimatorManager
nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
}
}
nAddAnimator 方法实现如下:
static void android_view_RenderNode_addAnimator (JNIEnv* env, jobject clazz, jlong renderNodePtr, jlong animatorPtr ){
RenderNode* renderNode = reinterpret_cast (renderNodePtr);
RenderPropertyAnimator* animator = reinterpret_cast (animatorPtr);
renderNode -> addanimator(animator);
}
void RenderNode :: addAnimator (const sp& animator){
// 添加到 AnimatorManager
mAnimatorManager.addAnimator(animator);
}
至此,我们清楚了动画是如何被添加到 AnimatorManager 中。根据其官方文档的介绍,后续 AnimatorManager 和 RenderThread 的操作交由系统处理,进而让 RenderThread 去完全管理动画,实现由 RenderThread 渲染动画。
代码实践
通过上面原理探索阶段,为了能够让动画顺利交给 RenderThread,除了不能设置任何回调且 View 支持硬件加速(Android 4.0 之后默认支持)之外,还必须必须满足 ViewPropertyAnimatorRT 不为 null,它是让动画交由 RenderThread 的关键。
但是翻阅源码,并未发现任何创建该对象的地方。此时我们需要一些特殊的操作以达到预期的效果。通过查看源码,发现 ViewPropertyAnimatorRT 属于包保护级别,而且没有被 @hide(Android P 之后也没有关系),所以我们直接采用反射的方式完成。
ps:国外一篇博客中介绍:每个组件都是隐藏的,因此要使用它们,必须通过反射获得对所有需要类 / 方法的引用。
为 View 创建 ViewPropertyAnimatorRT 对象:
private static Object createViewPropertyAnimatorRT(View view) {
try {
final Class> animRtCalzz = Class.forName("android.view.ViewPropertyAnimatorRT");
final Constructor> animRtConstructor = animRtCalzz.getDeclaredConstructor(View.class);
animRtConstructor.setAccessible(true);
return animRtConstructor.newInstance(view);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
然后将 ViewPropertyAnimatorRT 设置到对应的 ViewPropertyAnimator:
private static void setViewPropertyAnimatorRT(ViewPropertyAnimator animator, Object rt) {
try {
final Class> animClazz = Class.forName("android.view.ViewPropertyAnimator");
final Field animRtField = animClazz.getDeclaredField("mRTBackend");
animRtField.setAccessible(true);
animRtField.set(animator, rt);
} catch (Exception e) {
e.printStackTrace();
}
}
在动画执行前需要先执行上述步骤以满足相关条件:
final View view = findViewById(R.id.button);
final ViewPropertyAnimator animator = view.animate().scaleY(2).setDuration(1000);
// 必须在 start 之前
AsyncAnimHelper.onStartBefore(animator, view);
animator.start();
设置两种动画分别在执行 1s 后,让主线程休眠 2s(模拟主线程卡顿)。可以很明显看到普通属性动画,在主线程阻塞的时候,会出现丢帧卡顿现象。而使用 RenderThread 渲染的动画即使阻塞了主线程仍然不受影响,如下图所示(上面控件为普通属性动画):
以上便是关于 RenderThread 实现动画的异步渲染的探索和实践,文中如果不妥或有更好的分析结果,欢迎您的分享留言或指正。
Android 渲染框架非常庞大,并且演进的非常快,更多 Android 渲染框架的知识,感兴趣的朋友可以参考如下资料:
Android 之如何优化 UI 渲染(上)
Android 之如何优化 UI 渲染(下)
Android 之理解 VSYNC 信号
Android 之 ViewTreeObserver 全面解析
深入 Activity 三部曲(3)之 View 绘制流程
最后,如果你有更好的分析结果或实践方案,欢迎您的分享留言或指正。
文章如果对你有帮助,请留个赞吧!
其他系列专题
Android 存储优化系列专题
Android 对象序列化之追求完美的 Serial
Android 之不要滥用 SharedPreferences(上)
Android 存储选项之 SQLite 优化那些事儿
Android 对象序列化之你不知道的 Serializable
UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识、如何优化 UI 渲染两部分内容。
UI 优化系列专题
UI 渲染背景知识
《View 绘制流程之 setContentView() 到底做了什么?》
《View 绘制流程之 DecorView 添加至窗口的过程》
《深入 Activity 三部曲(3)View 绘制流程》
《Android 之 LayoutInflater 全面解析》
《关于渲染,你需要了解什么?》
《Android 之 Choreographer 详细分析》
如何优化 UI 渲染
《Android 之如何优化 UI 渲染(上)》
《Android 之如何优化 UI 渲染(下)》
在 Android 中使用动画是非常常见的,无论是使用补间动画还是属性动画,都离不开 View 的绘制任务。我们知道 Android UI 绘制任务“都”是在主线程中完成的。那异步绘制是否可行呢?答案是肯定的,其关键就是今天要介绍的 RenderThread,对于 RenderThread 可能很多人对它并不了解,接下来我将教会大家如何利用 RenderThread 实现动画的异步渲染。
什么是 RenderThread ?
大家是否曾注意过,Android 在 5.0 之后对动画的支持更加炫酷了,但是 UI 绘制并没有因此受到影响,反而更加流畅。这其中很大的功劳源自于 RenderThread 的变化。在介绍 RenderThread 之前,我们需要先来了解下 Android 系统 UI 渲染的演进之路。
在 Android 3.0 之前(或者没有启用硬件加速时),系统都会使用软件方式来渲染 UI。但是由于 CPU 在结构设计上的差异,对于图形处理并不是那么高效。这个过程完全没有利用 GPU 的图形高性能。
CPU 和 GPU 结构设计如下:
从结构图可以看出,CPU 的控制器较为复杂,而 ALU 数量较少,因此 CPU 更擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。而 GPU 的设计正是为实现大量数学运算。GPU 的控制器比较简单,但包含大量 ALU。GPU 中的 ALU 使用了并行设计,且具有较多的浮点运算单元。可以帮助我们加快栅格化操作。
所以从 Android 3.0 开始,Android 开始支持硬件加速,但是到 Android 4.0 时才默认开启硬件加速。有关 Android 渲染框架详细内容,你可以参考《关于 UI 渲染,你需要了解什么?》。
优化是无止境的,Google 在 2012 年的 I/O 大会上宣布了 Project Butter 黄油计划,并且在 Android 4.1 中正式开启了这个机制。Project Butter 主要包含三个组成部分,VSYNC、Triple Buffer 和 Choreographer。有关它们的详细分析,你可以参考如下资料:
Android 之 Choreographer 详细分析
Android 之 Project Butter 详细介绍
经过 Android 4.1 的 Project Butter 黄油计划之后,Android 的渲染性能有了很大的改善。不过你有没有注意到这样一个问题,虽然利用了 GPU 的图形高性能运算,但是从计算 DisplayList,到通过 GPU 绘制到 Frame Buffer,整个计算和绘制都在 UI 主线程中完成。
UI 线程“既当爹又当妈”,任务过于繁重。如果整个渲染过程比较耗时,可能造成无法响应用户的操作,进而出现卡顿的情况。GPU 对图形的绘制渲染能力更胜一筹,如果使用 GPU 并在不同线程绘制渲染图形,那么整个流程会更加顺畅。
正因如此,在 Android 5.0 引入两个比较大的改变。一个是引入了 RenderNode 的概念,它对 DisplayList 及一些 View 显示属性都做了进一步封装。另一个是引入了 RenderThread,所有的 GL 命令执行都放到这个线程上,渲染线程在 RenderNode 中存有渲染帧的所有信息,可以做一些 View 的异步渲染任务,这样即便主线程有耗时操作的时候也可以保证渲染的流畅性。
至此,我们已经知道 RenderThread 是 Android 5.0 之后的产物,用于分担主线程绘制任务的渲染线程。UI 可以进行异步绘制,那动画可以异步似乎也成为可能。所以,带着疑问,接下来我们还要对其进行一番探索实践,看如何利用 RenderThread 实现动画的异步渲染。
原理探索
经过查看官方文档,得知 RenderThread 目前仅支持两种动画的完全渲染工作(RenderThread 的文档介绍真的是少之又少)。
ViewPreportyAnimator
CircularReveal
关于 CircularReveal(揭露动画)的使用比较简单且功能较为单一,在此不做过多的探索,今天我们着重探索下 ViewPropertyAnimator。
final View view = findViewById(R.id.button);
final ViewPropertyAnimator animator = view.animate().scaleY(2).setDuration(1000);
animator.start();
通过 View 的 animate() 即可创建 ViewPropertyAnimator 动画,注意它并不是 Animator 的子类。其内部提供了缩放、位移、透明度相关方法。
public class ViewPropertyAnimator {
/**
* A RenderThread-driven backend that may intercept startAnimation
*/
private ViewPropertyAnimatorRT mRTBackend;
public ViewPropertyAnimator scaleX(float value) {
animateProperty(SCALE_X, value);
return this;
}
// ... 省略 scaleY
public ViewPropertyAnimator translationX(float value) {
animateProperty(TRANSLATION_X, value);
return this;
}
// ... 省略 translationY
public ViewPropertyAnimator alpha(float value) {
animateProperty(ALPHA, value);
return this;
}
/**
* 开始动画
*/
private void startAnimation() {
// 是否能够通过 ReanderThread 渲染关键在这里
if (mRTBackend != null && mRTBackend.startAnimation(this)) {
// 使用 RenderThread 异步渲染动画
return;
}
// 否则将会降解为普通熟悉动画
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
// ......
animator.start();
}
}
我们需要重点关注的是 startAnimator 方法,在该方法首先对 mRTBackend 进行了判断,它的实际类型是 ViewPropertyAnimatorRT,如果不为 null,则由它来执行动画。如果 if 条件不成立,也就是此时不支持 RenderThread 完全渲染。很明显 RenderThread 渲染动画和 ViewPropertyAnimatorRT 有直接关系。
class ViewPropertyAnimatorRT {
.....
ViewPropertyAnimatorRT(View view) {
mView = view;
}
public boolean startAnimation(ViewPropertyAnimator parent) {
cancelAnimators(parent.mPendingAnimations);
// 关键在这里判断是否成立
if (!canHandleAnimator(parent)) {
return false;
}
// 执行 RenderThread 异步渲染动画
doStartAnimation(parent);
return true;
}
......
}
可以看到 startAnimation 方法先通过 canHandleAnimator 方法判断是否成立,如果不成立返回 false,此时回到 ViewPropertyAnimator 动画将会退化成普通属性动画。否则执行 doStartAnimation 方法。
我们先看下 canHandleAnimator 的判断条件,它的参数是 ViewPropertyAnimator:
private boolean canHandleAnimator(ViewPropertyAnimator parent) {
if (parent.getUpdateListener() != null) {
return false;
}
if (parent.getListener() != null) {
// TODO support
return false;
}
if (!mView.isHardwareAccelerated()) {
// TODO handle this maybe?
return false;
}
if (parent.hasActions()) {
return false;
}
// Here goes nothing...
return true;
}
可以看出代码逻辑是比较清楚了,① 是否支持硬件加速(Android 在 3.0 开始支持硬件加速,在 4.0 默认开启),② 是否设置了监听 Listener 或 UpdateListener,或者设置了 Action(监听动画开始、结束)都会导致 canHandleAnimator 方法返回 false,从而导致 doStartAnimator 方法无法执行。在此我们得到一个非常重要的条件是不进行任何监听器设置,确保 canHandleAnimator 返回 true。
下面接着看 doStartAnimation 方法,执行 doStartAnimation 方法表示动画将被 RenderThread 执行。
private void doStartAnimation(ViewPropertyAnimator parent) {
int size = parent.mPendingAnimations.size();
// 启动延迟时间
long startDelay = parent.getStartDelay();
// duration 执行时间
long duration = parent.getDuration();
// 插值器
TimeInterpolator interpolator = parent.getInterpolator();
if (interpolator == null) {
// Documented to be LinearInterpolator in ValueAnimator.setInterpolator
// 默认线性插值器
interpolator = sLinearInterpolator;
}
if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) {
interpolator = new FallbackLUTInterpolator(interpolator, duration);
}
for (int i = 0; i < size; i++) {
NameValuesHolder holder = parent.mPendingAnimations.get(i);
int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
final float finalValue = holder.mFromValue + holder.mDeltaValue;
// 对于每个动画属性都创建了RenderNodeAnimaor
RenderNodeAnimator animator = new RenderNodeAnimator(property, finalValue);
animator.setStartDelay(startDelay);
animator.setDuration(duration);
animator.setInterpolator(interpolator);
animator.setTarget(mView);
animator.start();
mAnimators[property] = animator;
}
parent.mPendingAnimations.clear();
}
ViewPropertyAnimator 的 mPendingAniations 保存了动画的每个属性。doStartAnimation 方法为每个动画属性都创建了一个 RenderNodeAnimator,然后将对应的动画参数也设置给了 RenderNodeAnimator,此处就完成了动画和属性的绑定。
接下来我们要跟踪下 RendernodeAnimator,
public class RenderNodeAnimator extends Animator {
public void setTarget (View view) {
mViewTarget = view;
setTarget (mViewTarget.mRenderNode);
}
private void setTarget (RenderNode node){
......
mTarget = node;
mTarget.addAnimator(this);
}
}
setTarget 方法将当前 View 的 RenderNode 和 RenderNodeAnimator 通过 addAnimator 进行绑定。在 RenderNode 的 addAnimator 方法通过 Native 方法 nAddAnimator 将其注册到 AnimatorManager 中。
public class RenderNode {
public void addAnimator(RenderNodeAnimator animator) {
if (mOwningView == null || mOwningView.mAttachInfo == null) {
throw new IllegalStateException("Cannot start this animator on a detached view!");
}
// Native 方法注册到AnimatorManager
nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
}
}
nAddAnimator 方法实现如下:
static void android_view_RenderNode_addAnimator (JNIEnv* env, jobject clazz, jlong renderNodePtr, jlong animatorPtr ){
RenderNode* renderNode = reinterpret_cast (renderNodePtr);
RenderPropertyAnimator* animator = reinterpret_cast (animatorPtr);
renderNode -> addanimator(animator);
}
void RenderNode :: addAnimator (const sp& animator){
// 添加到 AnimatorManager
mAnimatorManager.addAnimator(animator);
}
至此,我们清楚了动画是如何被添加到 AnimatorManager 中。根据其官方文档的介绍,后续 AnimatorManager 和 RenderThread 的操作交由系统处理,进而让 RenderThread 去完全管理动画,实现由 RenderThread 渲染动画。
代码实践
通过上面原理探索阶段,为了能够让动画顺利交给 RenderThread,除了不能设置任何回调且 View 支持硬件加速(Android 4.0 之后默认支持)之外,还必须必须满足 ViewPropertyAnimatorRT 不为 null,它是让动画交由 RenderThread 的关键。
但是翻阅源码,并未发现任何创建该对象的地方。此时我们需要一些特殊的操作以达到预期的效果。通过查看源码,发现 ViewPropertyAnimatorRT 属于包保护级别,而且没有被 @hide(Android P 之后也没有关系),所以我们直接采用反射的方式完成。
ps:国外一篇博客中介绍:每个组件都是隐藏的,因此要使用它们,必须通过反射获得对所有需要类 / 方法的引用。
为 View 创建 ViewPropertyAnimatorRT 对象:
private static Object createViewPropertyAnimatorRT(View view) {
try {
final Class> animRtCalzz = Class.forName("android.view.ViewPropertyAnimatorRT");
final Constructor> animRtConstructor = animRtCalzz.getDeclaredConstructor(View.class);
animRtConstructor.setAccessible(true);
return animRtConstructor.newInstance(view);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
然后将 ViewPropertyAnimatorRT 设置到对应的 ViewPropertyAnimator:
private static void setViewPropertyAnimatorRT(ViewPropertyAnimator animator, Object rt) {
try {
final Class> animClazz = Class.forName("android.view.ViewPropertyAnimator");
final Field animRtField = animClazz.getDeclaredField("mRTBackend");
animRtField.setAccessible(true);
animRtField.set(animator, rt);
} catch (Exception e) {
e.printStackTrace();
}
}
在动画执行前需要先执行上述步骤以满足相关条件:
final View view = findViewById(R.id.button);
final ViewPropertyAnimator animator = view.animate().scaleY(2).setDuration(1000);
// 必须在 start 之前
AsyncAnimHelper.onStartBefore(animator, view);
animator.start();
设置两种动画分别在执行 1s 后,让主线程休眠 2s(模拟主线程卡顿)。可以很明显看到普通属性动画,在主线程阻塞的时候,会出现丢帧卡顿现象。而使用 RenderThread 渲染的动画即使阻塞了主线程仍然不受影响,如下图所示(上面控件为普通属性动画):
以上便是关于 RenderThread 实现动画的异步渲染的探索和实践,文中如果不妥或有更好的分析结果,欢迎您的分享留言或指正。
Android 渲染框架非常庞大,并且演进的非常快,更多 Android 渲染框架的知识,感兴趣的朋友可以参考如下资料:
Android 之如何优化 UI 渲染(上)
Android 之如何优化 UI 渲染(下)
Android 之理解 VSYNC 信号
Android 之 ViewTreeObserver 全面解析
深入 Activity 三部曲(3)之 View 绘制流程
最后,如果你有更好的分析结果或实践方案,欢迎您的分享留言或指正。
文章如果对你有帮助,请留个赞吧!
其他系列专题
Android 存储优化系列专题
Android 对象序列化之追求完美的 Serial
Android 之不要滥用 SharedPreferences(上)
Android 存储选项之 SQLite 优化那些事儿
Android 对象序列化之你不知道的 Serializable

秋天的树叶一文由杰瑞文章网免费提供,本站为公益性作文网站,此作文为网上收集或网友提供,版权归原作者所有,如果侵犯了您的权益,请及时与我们联系,我们会立即删除!
杰瑞文章网友情提示:请不要直接抄作文用来交作业。你可以学习、借鉴、期待你写出更好的作文。
说说你对这篇作文的看法吧