Add a callback for rendernode parentcount=0
Bug: 27709981
Fixes: 22565656
Change-Id: I1cb4461baf9069dc4e7ca6de10d5862578c107f4
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index a19254f..4ffb3d3 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -763,6 +763,16 @@
return nGetDebugSize(mNativeRenderNode);
}
+ /**
+ * Called by native when the passed displaylist is removed from the draw tree
+ */
+ void onRenderNodeDetached() {
+ discardDisplayList();
+ if (mOwningView != null) {
+ mOwningView.onRenderNodeDetached(this);
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////////////////////////
@@ -795,7 +805,9 @@
// Native methods
///////////////////////////////////////////////////////////////////////////
- private static native long nCreate(String name);
+ // Intentionally not static because it acquires a reference to 'this'
+ private native long nCreate(String name);
+
private static native void nDestroyRenderNode(long renderNode);
private static native void nSetDisplayList(long renderNode, long newData);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index c972476..df774b4 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -794,7 +794,8 @@
}
final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
- int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
+ int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length,
+ mRootNode.mNativeRenderNode);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
@@ -993,7 +994,8 @@
private static native void nSetLightCenter(long nativeProxy,
float lightX, float lightY, float lightZ);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
- private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
+ private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size,
+ long rootRenderNode);
private static native void nDestroy(long nativeProxy, long rootRenderNode);
private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index dd79e62..3ee6a8d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16009,6 +16009,13 @@
}
/**
+ * Called when the passed RenderNode is removed from the draw tree
+ * @hide
+ */
+ public void onRenderNodeDetached(RenderNode renderNode) {
+ }
+
+ /**
* <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
*
* @return A non-scaled bitmap representing this view or null if cache is disabled.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5d4ee874..420c4f2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -774,7 +774,7 @@
* has invoked. If false, the functor may be invoked
* asynchronously.
*/
- public void invokeFunctor(long functor, boolean waitForCompletion) {
+ public static void invokeFunctor(long functor, boolean waitForCompletion) {
ThreadedRenderer.invokeFunctor(functor, waitForCompletion);
}
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 94dc03c..8104f7d 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -86,8 +86,7 @@
*/
public void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor,
boolean waitForCompletion) {
- ViewRootImpl viewRootImpl = containerView.getViewRootImpl();
- viewRootImpl.invokeFunctor(nativeDrawGLFunctor, waitForCompletion);
+ ViewRootImpl.invokeFunctor(nativeDrawGLFunctor, waitForCompletion);
}
/**
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 79b518f..27e2ee8 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -43,6 +43,54 @@
? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \
: false)
+static JNIEnv* getenv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
+ }
+ return env;
+}
+
+static jmethodID gOnRenderNodeDetached;
+
+class RenderNodeContext : public VirtualLightRefBase {
+public:
+ RenderNodeContext(JNIEnv* env, jobject jobjRef) {
+ env->GetJavaVM(&mVm);
+ // This holds a weak ref because otherwise there's a cyclic global ref
+ // with this holding a strong global ref to the view which holds
+ // a strong ref to RenderNode which holds a strong ref to this.
+ mWeakRef = env->NewWeakGlobalRef(jobjRef);
+ }
+
+ virtual ~RenderNodeContext() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mWeakRef);
+ }
+
+ jobject acquireLocalRef(JNIEnv* env) {
+ return env->NewLocalRef(mWeakRef);
+ }
+
+private:
+ JavaVM* mVm;
+ jweak mWeakRef;
+};
+
+// Called by ThreadedRenderer's JNI layer
+void onRenderNodeRemoved(JNIEnv* env, RenderNode* node) {
+ auto context = reinterpret_cast<RenderNodeContext*>(node->getUserContext());
+ if (!context) return;
+ jobject jnode = context->acquireLocalRef(env);
+ if (!jnode) {
+ // The owning node has been GC'd, release the context
+ node->setUserContext(nullptr);
+ return;
+ }
+ env->CallVoidMethod(jnode, gOnRenderNodeDetached);
+ env->DeleteLocalRef(jnode);
+}
+
// ----------------------------------------------------------------------------
// DisplayList view properties
// ----------------------------------------------------------------------------
@@ -59,7 +107,8 @@
return renderNode->getDebugSize();
}
-static jlong android_view_RenderNode_create(JNIEnv* env, jobject clazz, jstring name) {
+static jlong android_view_RenderNode_create(JNIEnv* env, jobject thiz,
+ jstring name) {
RenderNode* renderNode = new RenderNode();
renderNode->incStrong(0);
if (name != NULL) {
@@ -67,6 +116,7 @@
renderNode->setName(textArray);
env->ReleaseStringUTFChars(name, textArray);
}
+ renderNode->setUserContext(new RenderNodeContext(env, thiz));
return reinterpret_cast<jlong>(renderNode);
}
@@ -627,6 +677,9 @@
jclass clazz = FindClassOrDie(env, "android/view/SurfaceView");
gSurfaceViewPositionUpdateMethod = GetMethodIDOrDie(env, clazz,
"updateWindowPositionRT", "(JIIII)V");
+ clazz = FindClassOrDie(env, "android/view/RenderNode");
+ gOnRenderNodeDetached = GetMethodIDOrDie(env, clazz,
+ "onRenderNodeDetached", "()V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 07868c5..8019326 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -120,7 +120,13 @@
std::string mMessage;
};
-class RootRenderNode : public RenderNode, ErrorHandler {
+// TODO: Clean this up, it's a bit odd to need to call over to
+// rendernode's jni layer. Probably means RootRenderNode should be pulled
+// into HWUI with appropriate callbacks for the various JNI hooks so
+// that RenderNode's JNI layer can handle its own thing
+void onRenderNodeRemoved(JNIEnv* env, RenderNode* node);
+
+class RootRenderNode : public RenderNode, ErrorHandler, TreeObserver {
public:
RootRenderNode(JNIEnv* env) : RenderNode() {
mLooper = Looper::getForThread();
@@ -131,12 +137,15 @@
virtual ~RootRenderNode() {}
- virtual void onError(const std::string& message) {
+ virtual void onError(const std::string& message) override {
mLooper->sendMessage(new RenderingException(mVm, message), 0);
}
- virtual void prepareTree(TreeInfo& info) {
+ virtual void prepareTree(TreeInfo& info) override {
info.errorHandler = this;
+ if (info.mode == TreeInfo::MODE_FULL) {
+ info.observer = this;
+ }
// TODO: This is hacky
info.windowInsetLeft = -stagingProperties().getLeft();
info.windowInsetTop = -stagingProperties().getTop();
@@ -145,7 +154,8 @@
info.updateWindowPositions = false;
info.windowInsetLeft = 0;
info.windowInsetTop = 0;
- info.errorHandler = NULL;
+ info.errorHandler = nullptr;
+ info.observer = nullptr;
}
void sendMessage(const sp<MessageHandler>& handler) {
@@ -171,10 +181,27 @@
mPendingAnimatingRenderNodes.clear();
}
+ virtual void onMaybeRemovedFromTree(RenderNode* node) override {
+ mMaybeRemovedNodes.insert(sp<RenderNode>(node));
+ }
+
+ void processMaybeRemovedNodes(JNIEnv* env) {
+ // We can safely access mMaybeRemovedNodes here because
+ // we will only modify it in prepareTree calls that are
+ // MODE_FULL
+
+ for (auto& node : mMaybeRemovedNodes) {
+ if (node->hasParents()) continue;
+ onRenderNodeRemoved(env, node.get());
+ }
+ mMaybeRemovedNodes.clear();
+ }
+
private:
sp<Looper> mLooper;
JavaVM* mVm;
std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
+ std::set< sp<RenderNode> > mMaybeRemovedNodes;
};
class AnimationContextBridge : public AnimationContext {
@@ -473,13 +500,16 @@
}
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
+ jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize, jlong rootNodePtr) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
"Mismatched size expectations, given %d expected %d",
frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
- return proxy->syncAndDrawFrame();
+ int ret = proxy->syncAndDrawFrame();
+ rootRenderNode->processMaybeRemovedNodes(env);
+ return ret;
}
static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz,
@@ -706,7 +736,7 @@
{ "nSetup", "(JIIFII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetLightCenter", "(JFFF)V", (void*) android_view_ThreadedRenderer_setLightCenter },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
- { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
+ { "nSyncAndDrawFrame", "(J[JIJ)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroy", "(JJ)V", (void*) android_view_ThreadedRenderer_destroy },
{ "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
{ "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 1f17851..be816f78 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -252,6 +252,7 @@
tests/unit/LinearAllocatorTests.cpp \
tests/unit/MatrixTests.cpp \
tests/unit/OffscreenBufferPoolTests.cpp \
+ tests/unit/RenderNodeTests.cpp \
tests/unit/SkiaBehaviorTests.cpp \
tests/unit/StringUtilsTests.cpp \
tests/unit/TextDropShadowCacheTests.cpp \
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 61441ce..f6f92f7 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -68,7 +68,7 @@
}
RenderNode::~RenderNode() {
- deleteDisplayList();
+ deleteDisplayList(nullptr);
delete mStagingDisplayList;
#if HWUI_NEW_OPS
LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!");
@@ -88,7 +88,7 @@
// If mParentCount == 0 we are the sole reference to this RenderNode,
// so immediately free the old display list
if (!mParentCount && !mStagingDisplayList) {
- deleteDisplayList();
+ deleteDisplayList(nullptr);
}
}
@@ -462,7 +462,7 @@
}
#endif
-void RenderNode::syncDisplayList() {
+void RenderNode::syncDisplayList(TreeObserver* observer) {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
if (mStagingDisplayList) {
@@ -470,7 +470,7 @@
child->renderNode->incParentRefCount();
}
}
- deleteDisplayList();
+ deleteDisplayList(observer);
mDisplayList = mStagingDisplayList;
mStagingDisplayList = nullptr;
if (mDisplayList) {
@@ -486,15 +486,15 @@
// Damage with the old display list first then the new one to catch any
// changes in isRenderable or, in the future, bounds
damageSelf(info);
- syncDisplayList();
+ syncDisplayList(info.observer);
damageSelf(info);
}
}
-void RenderNode::deleteDisplayList() {
+void RenderNode::deleteDisplayList(TreeObserver* observer) {
if (mDisplayList) {
for (auto&& child : mDisplayList->getChildren()) {
- child->renderNode->decParentRefCount();
+ child->renderNode->decParentRefCount(observer);
}
}
delete mDisplayList;
@@ -526,32 +526,35 @@
}
}
-void RenderNode::destroyHardwareResources() {
+void RenderNode::destroyHardwareResources(TreeObserver* observer) {
if (mLayer) {
destroyLayer(mLayer);
mLayer = nullptr;
}
if (mDisplayList) {
for (auto&& child : mDisplayList->getChildren()) {
- child->renderNode->destroyHardwareResources();
+ child->renderNode->destroyHardwareResources(observer);
}
if (mNeedsDisplayListSync) {
// Next prepare tree we are going to push a new display list, so we can
// drop our current one now
- deleteDisplayList();
+ deleteDisplayList(observer);
}
}
}
-void RenderNode::decParentRefCount() {
+void RenderNode::decParentRefCount(TreeObserver* observer) {
LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
mParentCount--;
if (!mParentCount) {
+ if (observer) {
+ observer->onMaybeRemovedFromTree(this);
+ }
// If a child of ours is being attached to our parent then this will incorrectly
// destroy its hardware resources. However, this situation is highly unlikely
// and the failure is "just" that the layer is re-created, so this should
// be safe enough
- destroyHardwareResources();
+ destroyHardwareResources(observer);
}
}
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 8381925..b0136cf 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -68,6 +68,7 @@
class SaveOp;
class RestoreToCountOp;
class TreeInfo;
+class TreeObserver;
namespace proto {
class RenderNode;
@@ -154,6 +155,14 @@
}
}
+ VirtualLightRefBase* getUserContext() const {
+ return mUserContext.get();
+ }
+
+ void setUserContext(VirtualLightRefBase* context) {
+ mUserContext = context;
+ }
+
bool isPropertyFieldDirty(DirtyPropertyMask field) const {
return mDirtyPropertyFields & field;
}
@@ -187,7 +196,7 @@
}
ANDROID_API virtual void prepareTree(TreeInfo& info);
- void destroyHardwareResources();
+ void destroyHardwareResources(TreeObserver* observer);
// UI thread only!
ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
@@ -232,6 +241,12 @@
mPositionListener.reset(listener);
}
+ // This is only modified in MODE_FULL, so it can be safely accessed
+ // on the UI thread.
+ ANDROID_API bool hasParents() {
+ return mParentCount;
+ }
+
private:
typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
@@ -291,7 +306,7 @@
void syncProperties();
- void syncDisplayList();
+ void syncDisplayList(TreeObserver* observer);
void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
void pushStagingPropertiesChanges(TreeInfo& info);
@@ -302,13 +317,14 @@
#endif
void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
void pushLayerUpdate(TreeInfo& info);
- void deleteDisplayList();
+ void deleteDisplayList(TreeObserver* observer);
void damageSelf(TreeInfo& info);
void incParentRefCount() { mParentCount++; }
- void decParentRefCount();
+ void decParentRefCount(TreeObserver* observer);
String8 mName;
+ sp<VirtualLightRefBase> mUserContext;
uint32_t mDirtyPropertyFields;
RenderProperties mProperties;
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index accd303..a43e544 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -32,6 +32,7 @@
class DamageAccumulator;
class LayerUpdateQueue;
class OpenGLRenderer;
+class RenderNode;
class RenderState;
class ErrorHandler {
@@ -41,6 +42,17 @@
~ErrorHandler() {}
};
+class TreeObserver {
+public:
+ // Called when a RenderNode's parent count hits 0.
+ // Due to the unordered nature of tree pushes, once prepareTree
+ // is finished it is possible that the node was "resurrected" and has
+ // a non-zero parent count.
+ virtual void onMaybeRemovedFromTree(RenderNode* node) {}
+protected:
+ ~TreeObserver() {}
+};
+
// This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN
class TreeInfo {
PREVENT_COPY_AND_ASSIGN(TreeInfo);
@@ -86,6 +98,10 @@
#endif
ErrorHandler* errorHandler = nullptr;
+ // Optional, may be nullptr. Used to allow things to observe interesting
+ // tree state changes
+ TreeObserver* observer = nullptr;
+
// Frame number for use with synchronized surfaceview position updating
int64_t frameNumber = -1;
int32_t windowInsetLeft = 0;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6933b2f..63fa788 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -576,7 +576,7 @@
static void destroyPrefetechedNode(RenderNode* node) {
ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", node->getName());
- node->destroyHardwareResources();
+ node->destroyHardwareResources(nullptr);
node->decStrong(nullptr);
}
@@ -641,7 +641,7 @@
if (mEglManager.hasEglContext()) {
freePrefetechedLayers();
for (const sp<RenderNode>& node : mRenderNodes) {
- node->destroyHardwareResources();
+ node->destroyHardwareResources(nullptr);
}
Caches& caches = Caches::getInstance();
// Make sure to release all the textures we were owning as there won't
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 2d1e2e9..5492035 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -218,7 +218,7 @@
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();
- node->syncDisplayList();
+ node->syncDisplayList(nullptr);
auto displayList = node->getDisplayList();
if (displayList) {
for (auto&& childOp : displayList->getChildren()) {
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
new file mode 100644
index 0000000..7c57a50
--- /dev/null
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "RenderNode.h"
+#include "TreeInfo.h"
+#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(RenderNode, hasParents) {
+ auto child = TestUtils::createNode(0, 0, 200, 400,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
+ });
+ auto parent = TestUtils::createNode(0, 0, 200, 400,
+ [&child](RenderProperties& props, TestCanvas& canvas) {
+ canvas.drawRenderNode(child.get());
+ });
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_TRUE(child->hasParents()) << "Child node has no parent";
+ EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
+
+ TestUtils::recordNode(*parent, [](TestCanvas& canvas) {
+ canvas.drawColor(Color::Amber_500, SkXfermode::kSrcOver_Mode);
+ });
+
+ EXPECT_TRUE(child->hasParents()) << "Child should still have a parent";
+ EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
+
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ EXPECT_FALSE(child->hasParents()) << "Child should be removed";
+ EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
+}