通常我们说一个系统不如另一个系统流畅,说的就是前者动画显示不如后者流畅,因此动画显示流畅程度是衡量一个系统流畅性的关键指标。为什么这样说呢?这是因为流畅的动画显示需要60fps的UI刷新速度,然而这却不是一个容易达到的速度。Android 5.0通过引入Render Thread尽最大努力提升动画显示流畅性。本文就分析Render Thread显示动画的过程,以便了解它是如何提高动画显示流畅性的。
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
在前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划一文中,我们提到了Render Thread对动画显示的两个优化。第一个优化是在动画显示期间,临时将动画的目标View的Layer Type设置为LAYER_TYPE_HARDWARE,这样就可以使得目标View以Open GL里面的Frame Buffer Object(FBO)进行渲染。这种优化的效果就如Render Thread直接以Open GL里面的Texture来渲染TextureView一样。第二个优化是在Main Thread不需要参与动画的显示过程时,动画就会被注册到Render Thread中,这样动画的计算和显示过程就完全由Render Thread来负责。这种优化带来的好处就是在动画显示期间,Main Thread可以去处理其它的用户输入,而且动画的显示也会更加流畅。
上面描述的两种动画优化涉及到的Main Thread和Render Thread的交互过程如图1所示:
图1 Main Thread与Render Thread的动画交互模型
接下来,我们就通过代码分析上述的两种动画显示优化过程。
我们通过调用View类的成员函数animate可以获得一个ViewPropertyAnimator对象,如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
......
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/View.java中。
有了这个ViewPropertyAnimator对象之后,就可以调用它的成员函数withLayer将它关联的View的Layer Type设置为LAYER_TYPE_HARDWARE,如下所示:
public class ViewPropertyAnimator {
......
public ViewPropertyAnimator withLayer() {
mPendingSetupAction= new Runnable() {
@Override
public void run() {
mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
if (mView.isAttachedToWindow()) {
mView.buildLayer();
}
}
};
final int currentLayerType = mView.getLayerType();
mPendingCleanupAction = new Runnable() {
@Override
public void run() {
mView.setLayerType(currentLayerType, null);
}
};
......
return this;
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimator.java中。
ViewPropertyAnimator类的成员函数withLayer创建了两个Runnable,分别保存在成员变量mPendingSetupAction和mPendingCleanupAction中。其中,成员变量mPendingSetupAction指向的Runnable在动画开始显示之前执行,而成员变量mPendingCleanupAction指向的Runnable在动画结束显示之后执行。由此我们就可以看到:
1. 在动画开始显示之前,目标View的Layer Type会被设置为LAYER_TYPE_HARDWARE,并且它的成员函数buildLayer会被调用来创建一个Layer。
2. 在动画结束显示之后,目标View的Layer Type会被恢复为它之前的Layer Type。注意,这里调用目标View的成员函数getLayerType获得的是它的Layer Type未被设置为LAYER_TYPE_HARDWARE的Layer Type。
接下来我们就继续分析View类的成员函数buildLayer的实现,以便可以了解为一个View设置一个Layer的过程。
View类的成员函数buildLayer的实现如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
......
public void buildLayer() {
......
final AttachInfo attachInfo = mAttachInfo;
......
switch (mLayerType) {
case LAYER_TYPE_HARDWARE:
updateDisplayListIfDirty();
if (attachInfo.mHardwareRenderer != null && mRenderNode.isValid()) {
attachInfo.mHardwareRenderer.buildLayer(mRenderNode);
}
break;
case LAYER_TYPE_SOFTWARE:
buildDrawingCache(true);
break;
}
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/View.java中。
前面已经将当前正在处理的View的Layer Type设置为LAYER_TYPE_HARDWARE,因此View类的成员函数buildLayer首先是调用我们在前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文中分析过的View类的另外一个成员函数updateDisplayListIfDirty更新它的Display List。更新完毕之后,再调用保存在成员变量mAttachInfo描述的一个AttachInfo对象的成员变量mHardwareRenderer指向的一个ThreadedRenderer对象的成员函数buildLayer为当前正在处理的View创建一个Layer。
从这里还可以看到,如果当前正在处理的View的Layer Type被设置为LAYER_TYPE_SOFTWARE,即该View是以软件方式进行渲染的,那么就会调用另外一个成员函数buildDrawingCache将View上次绘制得到的UI缓存在一个Bitmap中,以便下次可以快速地绘制View动画的下一帧。View类的成员函数buildDrawingCache的实现,同样可以参考前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文。
接下来我们主要关注为一个View创建一个Layer的过程,即ThreadedRenderer对象的成员函数buildLayer的实现,如下所示:
public class ThreadedRenderer extends HardwareRenderer {
......
@Override
void buildLayer(RenderNode node) {
nBuildLayer(mNativeProxy, node.getNativeDisplayList());
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。
ThreadedRenderer对象的成员函数buildLayer调用另外一个成员函数nBuildLayer为参数node描述的一个Render Node关联的View创建一个Layer。
ThreadedRenderer对象的成员函数nBuildLayer是一个JNI函数,由Native层的函数android_view_ThreadedRenderer_buildLayer实现,如下所示:
static void android_view_ThreadedRenderer_buildLayer(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong nodePtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
RenderNode* node = reinterpret_cast<RenderNode*>(nodePtr);
proxy->buildLayer(node);
}
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
参数proxyPtr描述的是一个RenderProxy对象,这里调用它的成员函数buildLayer为参数nodePtr描述的一个Render Node创建一个Layer。
RenderProxy类的成员函数buildLayer的实现如下所示:
CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
args->context->buildLayer(args->node);
return NULL;
}
void RenderProxy::buildLayer(RenderNode* node) {
SETUP_TASK(buildLayer);
args->context = mContext;
args->node = node;
postAndWait(task);
}
这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中。
RenderProxy类的成员函数buildLayer首先是通过宏SETUP_TASK创建一个Task,接下来再调用另外一个成员函数postAndWait将该Task添加到Render Thread的Task Queue去等待执行。最后这个Task由宏CREATE_BRIDGE2声明的函数buildLayer来执行,主要就是调用参数context描述的一个CanvasContext对象的成员函数buildLayer为参数node描述的一个Render Node创建一个Layer。
CanvasContext类的成员函数buildLayer的实现如下所示:
void CanvasContext::buildLayer(RenderNode* node) {
......
TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
......
node->prepareTree(info);
......
mCanvas->flushLayerUpdates();
......
}
这个函数定义在文件frameworks/base/libs/hwui/renderthred/CanvasContext.cpp中。
这里就可以看到CanvasContext类的成员函数buildLayer调用了我们在前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文的关键函数----RenderNode类的成员函数prepareTree,它用来将参数node描述的Render Node的Display List从Main Thread同步到Render Thread中,并且为该Render Node创建了一个Layer,但是这个Layer处理待更新状态。
CanvasContext类的成员函数buildLayer接下来继续调用成员变量mCanvas指向的一个OpenGLRenderer对象的成员函数flushLayerUpdates更新刚才创建的Layer,它的实现如下所示:
void OpenGLRenderer::flushLayerUpdates() {
......
updateLayers();
flushLayers();
......
}
这个函数定义在文件frameworks/base/libs/hwui/OpenGLRenderer.cpp中。
OpenGLRenderer类的成员函数flushLayerUpdates主要是执行了以下两个操作:
1. 调用成员函数updateLayers重排和合并所有设置了Layer的Render Node的Display List的绘制命令,这些Layer包括了我们在前面一步创建的Layer。
2. 调用成员函数flushLayers执行所有设置了Layer的经过了重排和合并的Render Node的Display List的绘制命令,使得每一个这样的Render Node的UI都分别渲染在一个FBO上。
关于OpenGLRenderer类的成员函数updateLayers和flushLayers的实现,可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文。
这样,我们就临时地为一个即将要显示动画的View创建了一个Layer,这个Layer将即将要显示的动画的View的UI渲染在一个FBO,这样以后就可以基于这个FBO来更高效率地显示动画了。
后面的动画显示过程实质上就不断地渲染应用程序窗口的UI,这个过程可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文。不过,再结合前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文,我们可以知道,在这个过程中,并不是应用程序窗口视图中的每一个View的Display List都是需要重建的,而且对于要显示动画的View,我们也只是需要将动画参数应用在前面为它创建的FBO之上即可。当然,为了得到应用程序窗口的UI,在渲染的过程中,需要重新执行一遍应用程序窗口视图中的每一个View的Display List的绘制命令。我们可以将这个过程看作是应用程序窗口的各个View的UI合成过程。相对于应用程序窗口的每一个View的Display List构建,以及对它里面的绘制命令进行重排和合并的过程来说,上述合成过程的成本是低很多的。
以上分析的就是View动画显示过程中的第一种优化,即在View的动画开始显示之前,临时地为它创建一个Layer,使得View的UI渲染在一个FBO上,以后的动画就直接作用在该FBO上。接下来我们继续分析View动画显示过程的第二种优化,即将View动画的计算和显示完全交给Render Thread来负责。
当我们通过View类的成员函数animate获得了一个即将要显示动画的View关联的ViewPropertyAnimator对象之后,就可以通过这个ViewPropertyAnimator对象设置各种动画参数。动画参数设置完毕,就可以调用上述ViewPropertyAnimator对象的成员函数start进行动画显示。
ViewPropertyAnimator类的成员函数start的实现如下所示:
public class ViewPropertyAnimator {
......
public void start() {
......
startAnimation();
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimator.java中。
ViewPropertyAnimator类的成员函数start调用了另外一个成员函数startAnimation来启动动画,后者的实现如下所示:
public class ViewPropertyAnimator {
......
/**
* A RenderThread-driven backend that may intercept startAnimation
*/
private ViewPropertyAnimatorRT mRTBackend;
......
private void startAnimation() {
if (mRTBackend != null && mRTBackend.startAnimation(this)) {
return;
}
......
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
......
animator.start();
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimator.java中。
ViewPropertyAnimator类的成员函数startAnimation首先是检查成员变量mRTBackend的值是否等于null。如果不等于null,那么它指向的就是一个ViewPropertyAnimatorRT对象,因此接下来就调用该ViewPropertyAnimatorRT对象的成员函数startAnimation来执行动画相关的操作。
如果调用ViewPropertyAnimator类的成员变量mRTBackend指向的ViewPropertyAnimatorRT对象的成员函数startAnimation得到的返回值为true,就表示由Render Thread完全负责动画的计算以及显示。否则的话,就需要由Main Thread负责动画的计算,然后将计算好的动画应用在View上,再由Render Thread负责将动画显示出来。
从ViewPropertyAnimator类的成员变量mRTBackend的注释我们也可以看到,ViewPropertyAnimatorRT类是用来拦截ViewPropertyAnimator类负责的动画的,也就是将动画完全交给Render Thread来管理。不过在5.0的代码中,还没有看到任何初始化ViewPropertyAnimator类的成员变量mRTBackend的代码。这就是意味着ViewPropertyAnimator类的成员变量mRTBackend始终为null,因此就不会去调用ViewPropertyAnimatorRT类的成员函数startAnimation。
我们相信以后ViewPropertyAnimatorRT类的功能一定会派上用场的,因此接下来我们就假设ViewPropertyAnimator类的成员变量mRTBackend已经被始化,这样我们就可以更好地理解我们上面提到的动画显示的第二种优化。
ViewPropertyAnimatorRT类的成员函数startAnimation的实现如下所示:
class ViewPropertyAnimatorRT {
......
public boolean startAnimation(ViewPropertyAnimator parent) {
......
if (!canHandleAnimator(parent)) {
return false;
}
doStartAnimation(parent);
return true;
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimatorRT.java中。
ViewPropertyAnimatorRT类的成员函数startAnimation首先是调用成员函数canHandleAnimator判断是否能够让Render Thread来完全负责当前正在处理的动画。如果不能完全负责,那么就返回一个false值给调用者。否则的话,就调用另外一个成员函数doStartAnimation将当前正在处理的动画交给Render Thread来管理。
我们先看什么情况下ViewPropertyAnimatorRT类的成员函数startAnimation不能让Render Thread来完全负责当前正在处理的动画,也就是ViewPropertyAnimatorRT类的成员函数canHandleAnimator的实现,如下所示:
class ViewPropertyAnimatorRT {
......
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;
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimatorRT.java中。
参数parent指向的是一个ViewPropertyAniamtor对象,该ViewPropertyAniamtor对象原先是由它负责显示View的动画的,现在ViewPropertyAnimatorRT类的成员函数canHandleAnimator判断它是否设置了一系列的Listener。如果设置了,就意味着Main Thread需要获得动画显示过程中的通知,这样就不能将一个动画完全地将交给Render Thread来管理。
上面提到的Listener有两个:
1. 用来监听动画更新事件的Listener。这个Listener如果设置有,那么就可以通过调用ViewPropertyAniamtor类的成员函数getUpdateListener获得。
2. 用来监听动画的开始和结束等事件的Listener。这个Listener如果设置有,那么就可以通过调用ViewPropertyAniamtor类的成员函数getListener获得。
此外,如我们前面所述,如果在View的动画显示之前,我们调用了与它关联的ViewPropertyAniamtor对象的成员函数withLayer,那么与它关联的ViewPropertyAniamtor对象就会在内部创建两个Runnable。这两个Runnable用来临时地将要显示动画的View的Layer Type设置为LAYER_TYPE_HARDWARE。在这种情况下,调用与View关联的ViewPropertyAniamtor对象的成员函数hasActions得到的返回值就等于true。这意味着Main Thread需要获得动画显示开始和结束的通知,因此就不能将一个动画完全地将交给Render Thread来管理。
再者,如果要显示动画的View不支持硬件加速渲染,即调用它的成员函数isHardwareAccelerated得到的返回值等于false。很明显,Render Thread是通过硬件加速渲染的方式来显示View的动画的。因此,在这种情况下,也不能将一个动画完全地交给Render Thread来管理。
我们假设ViewPropertyAnimatorRT类的成员函数canHandleAnimator的返回值为true,那么接下来就会调用到ViewPropertyAnimatorRT类的成员函数doStartAnimation,它的实现如下所示:
class ViewPropertyAnimatorRT {
......
private void doStartAnimation(ViewPropertyAnimator parent) {
int size = parent.mPendingAnimations.size();
......
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;
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();
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewPropertyAnimatorRT.java中。
当前要显示的动画保存在参数parent描述的一个ViewPropertyAnimator对象的成员变量mPendingAnimations描述的一个NameValuesHodler对象列表中。ViewPropertyAnimatorRT类的成员函数doStartAnimation为这个列表的每一个NameValuesHodler对象都创建一个RenderNodeAnimator对象,并且将相关的动画参数都设置到新创建的RenderNodeAnimator对象中去。当这些参数设置完毕,就可以调用新创建的RenderNodeAnimator对象的成员函数start,来它们标记为可以执行的状态。
其中,上述新创建的RenderNodeAnimator对象有一个重要的参数需要设置,就是它所关联的View,这是通过调用RenderNodeAnimator类的成员函数setTarget来完成的。RenderNodeAnimator类的成员函数setTarget在执行的过程中,就会将相关的动画注册到Render Thread中去,以便Render Thread可以执行它们。
RenderNodeAnimator类的成员函数setTarget的实现如下所示:
public class RenderNodeAnimator extends Animator {
......
public void setTarget(View view) {
mViewTarget = view;
setTarget(mViewTarget.mRenderNode);
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/RenderNodeAnimator.java中。
参数view描述的就是要显示动画的View,RenderNodeAnimator类的成员函数setTarget首先是将它保存成员变量mTargetView中,接着再获得与它关联的一个Render Node传递给另外一个重载版本的成员函数setTarget处理,如下所示:
public class RenderNodeAnimator extends Animator {
......
private void setTarget(RenderNode node) {
......
mTarget = node;
mTarget.addAnimator(this);
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/RenderNodeAnimator.java中。
RenderNodeAnimator类的重载版本的成员函数setTarget首先是将参数node描述的一个Render Node保存在成员变量mTarget中,接着再通过调用RenderNode类的成员函数addAniamtor将当前正在处理的动画注册到Render Thread中去。
RenderNode类的成员函数addAniamtor的实现如下所示:
public class RenderNode {
......
public void addAnimator(RenderNodeAnimator animator) {
......
nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/RenderNode.java中。
RenderNode类的成员函数addAniamtor首先是调用另外一个成员函数nAddAnimator将参数animator描述的动画注册到Render Thread中去。
RenderNode类的成员变量mOwningView描述的是一个与当前正在处理的Render Node关联的一个View,这个View就是要显示动画的View。每一个View都有一个成员变量mAttachInfo,它指向的是一个AttachInfo对象,该AttachInfo对象描述的是一个View所属的窗口的相关信息。其中,这个AttachInfo对象有一个成员变量mViewRootImpl,它指向的是一个ViewRootImpl对象,该ViewRootImpl对象负责管理一个View所属的窗口,例如用来分发输入事件给窗口,以及用来发起窗口的绘制流程等。
RenderNode类的成员函数addAniamtor接下来要做的事情就是调用上述的ViewRootImpl对象的成员函数registerAnimatingRenderNode告诉Render Thread,当前正在处理的Render Node有动画注册到了它里面,这样Render Node就可以将渲染应用窗口的下一帧时,显示前面已经注册的动画。
接下来我们就先分析RenderNode类的成员函数nAddAnimator的实现,接着再分析ViewRootImpl类的成员函数registerAnimatingRenderNode的实现。
RenderNode类的成员函数nAddAnimator是一个JNI函数,由Native层的函数android_view_RenderNode_addAnimator实现,如下所示:
static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz,
jlong renderNodePtr, jlong animatorPtr) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
renderNode->addAnimator(animator);
}
这个函数定义在文件frameworks/base/core/jni/android_view_RenderNode.cpp中。
参数renderNodePtr指向的是Native层的一个RenderNode对象,该RenderNode对象与要显示动画的View关联。参数animatorPtr指向的Native层的一个RenderPropertyAnimator对象,该RenderPropertyAnimator对象描述的就是要显示的动画。这里调用RenderNode类的成员函数addAnimator将参数animatorPtr描述的动画保存在参数renderNodePtr描述的Render Node的内部。
RenderNode类的成员函数addAnimator的实现如下所示:
void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
mAnimatorManager.addAnimator(animator);
}
这个函数定义在文件frameworks/base/libs/hwui/RenderNode.cpp中。
RenderNode类的成员变量mAnimatorManager描述的是一个AnimatorManager对象。这个AnimatorManager对象用来管理一个RenderNode对象所关联的动画。因此,RenderNode类的成员函数addAnimator所做的事情就是将参数animator描述的动画保存在当前正在处理的RenderNode对象内部的一个AnimatorManager对象中,这是通过调用AnimatorManager类的成员函数addAnimator完成的。
AnimatorManager类的成员函数addAnimator的实现如下所示:
void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
animator->incStrong(0);
animator->attach(&mParent);
mNewAnimators.push_back(animator.get());
}
这个函数定义在文件frameworks/base/libs/hwui/AnimatorMaager.cpp中。
在分析AnimatorManager类的成员函数addAnimator的实现之前,我们首先了解AnimatorManager类的三个成员变量:
1. mParent,它描述的是一个RenderNode对象,该RenderNode对象与当前正在处理的AnimatorManager对象关联。
2. mNewAnimators,它描述的是一个Vector,该Vector用来保存新增加的动画。
AnimatorManager类的成员函数addAnimator首先是增加参数animator描述的一个BaseRenderNodeAnimator对象的引用计数,因为后面要将它添加成员变量mNewAnimators描述的一个Vector中去。此外,AnimatorManager类的成员函数addAnimator还会调用参数animator描述的一个BaseRenderNodeAnimator对象的成员函数attach将该BaseRenderNodeAnimator对象与要显示动画的Render Node进行关联。
这一步执行完成之后,我们就将要显示的动画设置到目标View关联的一个RenderNode对象内部的一个AnimatorManager对象中去了。返回到Java层的RenderNode类的成员函数addAniamtor中,接下来我们继续分析ViewRootImpl类的成员函数registerAnimatingRenderNode的实现,以便可以了解Render Thread知道有Render Node有新的动画需要显示的过程。
ViewRootImpl类的成员函数registerAnimatingRenderNode的实现如下所示:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
public void registerAnimatingRenderNode(RenderNode animator) {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.registerAnimatingRenderNode(animator);
} else {
if (mAttachInfo.mPendingAnimatingRenderNodes == null) {
mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList<RenderNode>();
}
mAttachInfo.mPendingAnimatingRenderNodes.add(animator);
}
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。
当ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mHardwareRenderer的值不等于null的时候,它指向的是一个ThreadedRenderer对象。在这种情况下,ViewRootImpl类的成员函数registerAnimatingRenderNode会直接调用该ThreadedRenderer对象的成员函数registerAnimatingRenderNode将参数animator描述的一个Render Node注册到Render Thread中去,以便Render Thread知道哪些Render Node有新的动画需要显示。
另一方面,当ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mHardwareRenderer的值等于null的时候,这意味着应用程序窗口使用软件渲染或者使用硬件加速渲染但是硬件加速渲染环境还没有初始化好。在这两种情况下,都是先将参数animator描述的Render Node保存在成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mPendingAnimatingRenderNodes描述的一个列表中等待处理。
从前面Android应用程序UI硬件加速渲染的Display List构建过程分析一文可以知道,当使用硬件加速渲染时,应用程序窗口的渲染是从调用ThreadedRenderer类的成员函数draw开始的,它的实现如下所示:
public class ThreadedRenderer extends HardwareRenderer {
......
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
......
updateRootDisplayList(view, callbacks);
......
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
// We don't need this anymore as subsequent calls to
// ViewRootImpl#attachRenderNodeAnimator will go directly to us.
attachInfo.mPendingAnimatingRenderNodes = null;
}
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
recordDuration, view.getResources().getDisplayMetrics().density);
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。
ThreadedRenderer类的成员函数draw在调用成员函数updateRootDisplayList构建应用程序窗口的Display List之后,并且调用成员函数nSyncAndDrawFrame渲染应用程序窗口的Display List之前,会调用成员函数registerAnimatingRenderNode处理保存在上面提到的AttachInfo对象的成员变量mPendingAnimatingRenderNodes描述的一个列表中的每一个Render Node。
ThreadedRenderer类的成员函数registerAnimatingRenderNode的实现如下所示:
public class ThreadedRenderer extends HardwareRenderer {
......
@Override
void registerAnimatingRenderNode(RenderNode animator) {
nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode);
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。
ThreadedRenderer类的成员函数registerAnimatingRenderNode调用另外一个成员函数nRegisterAnimatingRenderNode将参数animator描述的一个Render Node注册到Render Thread中去。
ThreadedRenderer类的成员函数nRegisterAnimatingRenderNode是一个JNI函数,由Native层的函数android_view_ThreadedRenderer_registerAnimatingRenderNode实现,如下所示:
static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz,
jlong rootNodePtr, jlong animatingNodePtr) {
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
RenderNode* animatingNode = reinterpret_cast<RenderNode*>(animatingNodePtr);
rootRenderNode->attachAnimatingNode(animatingNode);
}
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
参数rootNodePtr指向的是一个RootRenderNode对象,该RootRenderNode对象描述的是应用程序窗口的Root Render Node,这里主要就是将参数animatorNodePtr描述的一个Render Node注册在上述的RootRenderNode对象的内部,这是通过调用RootRenderNode类的成员函数attachAnimatingNode实现的。
RootRenderNode类的成员函数attachAnimatingNode的实现如下所示:
class RootRenderNode : public RenderNode, ErrorHandler {
public:
......
void attachAnimatingNode(RenderNode* animatingNode) {
mPendingAnimatingRenderNodes.push_back(animatingNode);
}
......
private:
......
std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
};
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
RootRenderNode类的成员函数attachAnimatingNode将参数animatingNode描述的一个Render Node保存成员变量mPendingAnimatingRenderNodes描述的一个Vector中。保在这个Vector中的Render Node将会在应用程序窗口的下一帧被渲染时得到处理。
从前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文可以知道,Render Thread在渲染应用程序窗口的下一帧时,会调用CanvasContext类的成员函数prepareTree将应用程序窗口的Display List从Main Thread同步到Render Thread,如下所示:
void CanvasContext::prepareTree(TreeInfo& info) {
......
info.renderer = mCanvas;
......
mAnimationContext->startFrame(info.mode);
mRootRenderNode->prepareTree(info);
mAnimationContext->runRemainingAnimations(info);
......
int runningBehind = 0;
// TODO: This query is moderately expensive, investigate adding some sort
// of fast-path based off when we last called eglSwapBuffers() as well as
// last vsync time. Or something.
mNativeWindow->query(mNativeWindow.get(),
NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
info.out.canDrawThisFrame = !runningBehind;
if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
if (!info.out.requiresUiRedraw) {
// If animationsNeedsRedraw is set don't bother posting for an RT anim
// as we will just end up fighting the UI thread.
mRenderThread.postFrameCallback(this);
}
}
}
这个函数定义定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。
CanvasContext类的成员函数prepareTree的详细实现分析可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文,这里我们主要关注与动画相关的两个操作:
1. 第一个操作是在调用RenderNode类的成员函数prepareTree同步应用程序窗口的Display List之前,调用成员变量mAnimationContext描述的一个AnimationContext对象的成员函数startFrame执行一些动画显示的准备工作。
2. 第二个操作是在调用RenderNode类的成员函数prepareTree同步应用程序窗口的Display List之后,调用成员变量mAnimationContext描述的一个AnimationContext对象的成员函数runRemainingAnimations更新剩下的还未完成的动画。
接下来我们就分别分析这两个操作的执行过程,即分析AnimationContext类的成员函数startFrame和runRemainingAnimations的实现。不过,在分析这两个函数的实现之前,我们首先要了解CanvasContext类的成员变量mAnimationContext的初始化过程。
从前面Android应用程序UI硬件加速渲染环境初始化过程分析一文可以知道,在Android应用程序的硬件加速渲染环境的初始化过程中,会创建一个RenderProxy对象,如下所示:
static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
jboolean translucent, jlong rootRenderNodePtr) {
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
ContextFactoryImpl factory(rootRenderNode);
return (jlong) new RenderProxy(translucent, rootRenderNode, &factory);
}
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
在调用RenderProxy类的构造函数创建一个RenderProxy对象的时候,传递进去的第三个参数是一个ContextFactoryImpl对象。
我们继续看RenderProxy类的构造函数的实现,如下所示:
CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
return new CanvasContext(*args->thread, args->translucent,
args->rootRenderNode, args->contextFactory);
}
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance())
, mContext(0) {
SETUP_TASK(createContext);
args->translucent = translucent;
args->rootRenderNode = rootRenderNode;
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
mDrawFrameTask.setContext(&mRenderThread, mContext);
}
这个函数定义在文件frameworks/base/libs/hwui/renderthread/RenderProxy.cpp中。
RenderProxy类的构造函数首先是向Render Thread的Task Queue发送一个Task,并且等待该Task执行完成。上述Task在执行的时候,会调用由宏CREATE_BRIDGE4声明的函数createContext。该函数所做的事情就是创建一个CanvasContext对象。该CanvasContext对象最终保存在RenderProxy类的成员变量mContext中。
在调用CanvasContext类的构造函数创建一个CanvasContext对象的时候,传递进去的第四个参数就是在前面分析的函数android_view_ThreadedRenderer_createProxy声明的一个ContextFactoryImpl对象。
CanvasContext类的构造函数的实现如下所示:
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory)
:...... {
mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord());
......
}
这个函数定义在文件frameworks/base/libs/hwui/renderthread/CanvasContext.cpp中。
从这里就可以看出,CanvasContext的成员变量mAnimationContext指向的一个AnimationContext对象是由参数contextFactory描述的一个IContextFactory接口的成员函数createAnimationContext创建的。
从前面的调用过程可以知道,参数contextFactory指向的实际上是一个ContextFactoryImpl对象,它的成员函数createAnimationContext的实现如下所示:
class ContextFactoryImpl : public IContextFactory {
public:
ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {}
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) {
return new AnimationContextBridge(clock, mRootNode);
}
private:
RootRenderNode* mRootNode;
};
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
ContextFactoryImpl类的成员函数createAnimationContext创建的是一个AnimationContextBridge对象,由此就可见,CanvasContext的成员变量mAnimationContext实际指向的一个AnimationContextBridge对象。
明白了这一点之后,回到前面分析的CanvasContext类的成员函数prepareTree中,它在同步应用程序窗口的Display List之前,调用了AnimationContextBridge类的成员函数startFrame执行一些动画显示的准备工作。
AnimationContextBridge类的成员函数startFrame的实现如下所示:
class AnimationContextBridge : public AnimationContext {
public:
......
// Marks the start of a frame, which will update the frame time and move all
// next frame animations into the current frame
virtual void startFrame(TreeInfo::TraversalMode mode) {
if (mode == TreeInfo::MODE_FULL) {
mRootNode->doAttachAnimatingNodes(this);
}
AnimationContext::startFrame(mode);
}
......
private:
sp<RootRenderNode> mRootNode;
......
};
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
参数mode的值等于TreeInfo::MODE_FULL,表示目前Render Thread是处于同步应用程序窗口的Display List的过程中。在这种情况下,AnimationContextBridge类的成员函数startFrame执行两个操作:
1. 调用成员变量mRootNode描述的一个RootRenderNode对象的成员函数doAttachAnimatingNodes处理它内部保存的有动画显示的Render Node。
2. 调用父类AnimationContext的成员函数startFrame继续执行一些动画显示之前的准备工作。
接下来,我们就先分析RootRenderNode类的成员函数doAttachAnimatingNodes的实现,再分析AnimationContext类的成员函数startFrame的实现。
RootRenderNode类的成员函数doAttachAnimatingNodes的实现如下所示:
class RootRenderNode : public RenderNode, ErrorHandler {
public:
......
void doAttachAnimatingNodes(AnimationContext* context) {
for (size_t i = 0; i < mPendingAnimatingRenderNodes.size(); i++) {
RenderNode* node = mPendingAnimatingRenderNodes[i].get();
context->addAnimatingRenderNode(*node);
}
mPendingAnimatingRenderNodes.clear();
}
private:
......
std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
};
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
RootRenderNode类的成员函数doAttachAnimatingNodes遍历保存在成员变量mPendingAnimatingRenderNodes描述的一个Vector中的每一个RenderNode对象,并且分别调用参数context描述的一个AnimationContextBridge对象的成员函数addAnimatingRenderNode对它们进行处理。
AnimationContextBridge类的成员函数addAnimatingRenderNode是从父类AnimationContext继承下来的,它的实现如下所示:
void AnimationContext::addAnimatingRenderNode(RenderNode& node) {
if (!node.animators().hasAnimationHandle()) {
AnimationHandle* handle = new AnimationHandle(node, *this);
addAnimationHandle(handle);
}
}
这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。
参数node指向的是一个RenderNode对象,调用它的成员函数animators可以获得它内部的一个AnimatorManager对象。如果该AnimatorManager对象还没有设置过一个AnimationHandle对象,那么AnimationContext类的成员函数addAnimatingRenderNode就为其设置一个AnimationHandle对象,并且这个AnimationHandle对象会通过AnimationContext类的成员函数addAnimationHandle保存在当前正在处理的一个AnimationContext对象的内部。
AnimationContext类的成员函数addAnimationHandle的实现如下所示:
void AnimationContext::addAnimationHandle(AnimationHandle* handle) {
handle->insertAfter(&mNextFrameAnimations);
}
这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。
AnimationContext类的成员函数addAnimationHandle所做的事情就是将参数handle描述的一个AnimationHandle对象插入成员变量mNextFrameAnimations描述的一个AnimationHandle列表中。
由此我们就可以推断出,只要AnimationContext类的成员变量NextFrameAnimations描述的一个AnimationHandle列表不为空,那么就意味着Render Thread在渲染应用程序窗口的下一帧的时候,有Render Node需要显示动画。
这一步执先完成之后,返回到AnimationContextBridge类的成员函数startFrame中,接下来它所做的事情就是调用父类AnimationContext的成员函数startFrame继续执行一些动画显示之前的准备工作。
AnimationContext的成员函数startFrame的实现如下所示:
void AnimationContext::startFrame(TreeInfo::TraversalMode mode) {
......
AnimationHandle* head = mNextFrameAnimations.mNextHandle;
if (head) {
mNextFrameAnimations.mNextHandle = NULL;
mCurrentFrameAnimations.mNextHandle = head;
head->mPreviousHandle = &mCurrentFrameAnimations;
}
......
}
这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。
AnimationContext的成员函数startFrame所做的事情就是将成员变量mNextFrameAnimations描述的一个AnimationHandle列表转移到另外一个成员变量mCurrentFrameAnimations中。
由此我们就可以推断出,只要AnimationContext类的成员变量mCurrentFrameAnimations描述的一个AnimationHandle列表不为空,那么就意味着Render Thread在渲染应用程序窗口的当前帧的时候,有Render Node需要显示动画。
这一步执行完成之后,返回到CanvasContext类的成员函数prepareTree中,当它同步同步应用程序窗口的Display List之后,就调用成员变量mAnimationContext描述的一个AnimationContextBridge对象的成员函数runRemainingAnimations更新剩下的还未完成的动画。
AnimationContextBridge类的成员函数runRemainingAnimations的实现如下所示:
class AnimationContextBridge : public AnimationContext {
public:
......
// Runs any animations still left in mCurrentFrameAnimations
virtual void runRemainingAnimations(TreeInfo& info) {
AnimationContext::runRemainingAnimations(info);
......
}
......
};
这个函数定义在文件frameworks/base/core/jni/android_view_ThreadedRenderer.cpp中。
AnimationContextBridge类的成员函数runRemainingAnimations主要是通过调用父类AnimationContext的成员函数runRemainingAnimations更新剩下的还未完成的动画。
AnimationContext类的成员函数runRemainingAnimations的实现如下所示:
void AnimationContext::runRemainingAnimations(TreeInfo& info) {
while (mCurrentFrameAnimations.mNextHandle) {
AnimationHandle* current = mCurrentFrameAnimations.mNextHandle;
AnimatorManager& animators = current->mRenderNode->animators();
animators.pushStaging();
animators.animateNoDamage(info);
......
}
}
这个函数定义在文件frameworks/base/libs/hwui/AnimationContext.cpp中。
前面提到,应用程序窗口当前帧要显示动画都记录在AnimationContext类的成员mCurrentFrameAnimations描述的一个AnimationHandle列表中。因此,AnimationContext类的成员函数runRemainingAnimations就遍历这个列表中的每一个AnimationHandle,并且获得与其关联的一个AnimatorManager。有了这些AnimatorManager之的,就可以调用它们的成员函数pushStaging和animateNoDamage来执行动画,其实就是计算动画的下一帧参数,以便应用在目标Render Node上。
AnimatorManager类的成员函数pushStaging的实现如下所示:
void AnimatorManager::pushStaging() {
if (mNewAnimators.size()) {
......
move_all(mNewAnimators, mAnimators);
}
for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
(*it)->pushStaging(mAnimationHandle->context());
}
}
这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。
从前面的分析可以知道,一开始的时候,应用程序窗口新增加的动画都保存在AnimatorManager类的成员变量mNewAnimators描述的一个Vector中,这里将它们移动至AnimatorManager类的另外一个成员变量mAnimators中,类似于我们在前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文提到的将Display List从Main Thread同步到Render Thread一样。
最后,AnimatorManager类的成员函数pushStaging遍历已经移动至新列表中的每一个动画,并且调用它们的成员函数pushStaging,用来同步它们的开始和结束状态,也就是检查动画是否已经被start或者已经end了。如果已经被start,那么就执行一些动画开始执行前的准备工作,例如计算动画的开始时间。如果已经end,就发出事件通知给侦听者。
这一步执行完成之后,回到前面分析的AnimationContext类的成员函数runRemainingAnimations中,接下来就调用AniamtorManager类的成员函数animateNoDamage执行动画,它的实现如下所示:
void AnimatorManager::animateNoDamage(TreeInfo& info) {
if (!mAnimators.size()) return;
animateCommon(info);
}
这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。
AniamtorManager类的成员函数animateNoDamage通过调用另外一个成员函数animateCommon来执行当前正在处理的AnimatorManager对象关联的动画,也就是某一个Render Node关联的动画。
AniamtorManager类的成员函数animateCommon的实现如下所示:
uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
AnimateFunctor functor(info, mAnimationHandle->context());
std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
mAnimators.erase(newEnd, mAnimators.end());
mAnimationHandle->notifyAnimationsRan();
return functor.dirtyMask;
}
这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。
前面提到,当前正在处理的AnimatorManager对象关联的动画都保存其成员变量mAnimators描述的一个Vector,因此这里就会通过函数std::remove_if来遍历这些动画,并且对于每一个动画,都通过本地变量functor描述的一个AnimatorFunctor对象的操作符重载函数()来执行它的当前帧。
AnimatorFunctor类的操作符重载函数()执行完成动画的当前帧之后,如果动画已经完成,即没有下一帧了,那么它就会返回一个true值给调用者,这样会导致函数std::remove_if将该动画移动至列表的末尾位置。
AnimatorFunctor类的操作符重载函数()的实现如下所示:
class AnimateFunctor {
public:
AnimateFunctor(TreeInfo& info, AnimationContext& context)
: dirtyMask(0), mInfo(info), mContext(context) {}
bool operator() (BaseRenderNodeAnimator* animator) {
dirtyMask |= animator->dirtyMask();
bool remove = animator->animate(mContext);
if (remove) {
animator->decStrong(0);
} else {
if (animator->isRunning()) {
mInfo.out.hasAnimations = true;
}
if (CC_UNLIKELY(!animator->mayRunAsync())) {
mInfo.out.requiresUiRedraw = true;
}
}
return remove;
}
uint32_t dirtyMask;
private:
TreeInfo& mInfo;
AnimationContext& mContext;
};
这个函数定义在文件frameworks/base/libs/hwui/AnimatorManager.cpp中。
AnimatorFunctor类的操作符重载函数()主要就是调用参数animator指向的一个BaseRenderNodeAnimator对象的成员函数animate来执行该BaseRenderNodeAnimator对象所描述的动画的当前帧。
当BaseRenderNodeAnimator类的成员函数animate的返回值等于true的时候,就表示参数animator描述的动画已经显示结束了,这意味着它可以从我们上面�%
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。