summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorGeorge Mount <mount@google.com>2015-03-20 13:49:37 -0700
committerGeorge Mount <mount@google.com>2015-03-26 15:57:23 -0700
commit038ad93e7e7fd52c690bd9a8bd41eb79238ec6d1 (patch)
tree863a87617faf7c2fc122f92604dc9f5dcf78914d /tools
parentff5868ed69193fd10caf79f456c4925c8b6d1cf0 (diff)
downloadframeworks_base-038ad93e7e7fd52c690bd9a8bd41eb79238ec6d1.zip
frameworks_base-038ad93e7e7fd52c690bd9a8bd41eb79238ec6d1.tar.gz
frameworks_base-038ad93e7e7fd52c690bd9a8bd41eb79238ec6d1.tar.bz2
Don't execute binding when the root view is detached.
Also add tests for memory leaks. The binder should be deleted when the root view is deleted. Change-Id: Ifcb24feb80791e64cdfd7203d071d9b1453f6f70
Diffstat (limited to 'tools')
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java126
-rw-r--r--tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml24
-rw-r--r--tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java41
3 files changed, 189 insertions, 2 deletions
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java
new file mode 100644
index 0000000..bb0576d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.generated.LeakTestBinding;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.widget.FrameLayout;
+
+import java.lang.ref.WeakReference;
+
+public class LeakTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ WeakReference<LeakTestBinding> mWeakReference = new WeakReference<LeakTestBinding>(null);
+
+ public LeakTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ try {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ LeakTestBinding binding = LeakTestBinding.inflate(getActivity());
+ getActivity().setContentView(binding.getRoot());
+ mWeakReference = new WeakReference<LeakTestBinding>(binding);
+ binding.setName("hello world");
+ binding.executePendingBindings();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ } catch (Throwable t) {
+ throw new Exception(t);
+ }
+ }
+
+ public void testBindingLeak() throws Throwable {
+ assertNotNull(mWeakReference.get());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().setContentView(new FrameLayout(getActivity()));
+ }
+ });
+ System.gc();
+ assertNull(mWeakReference.get());
+ }
+
+ // Test to ensure that when the View is detached that it doesn't rebind
+ // the dirty Views. The rebind should happen only after the root view is
+ // reattached.
+ public void testNoChangeWhenDetached() throws Throwable {
+ final LeakTestBinding binding = mWeakReference.get();
+ final AnimationWatcher watcher = new AnimationWatcher();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().setContentView(new FrameLayout(getActivity()));
+ binding.setName("goodbye world");
+ binding.getRoot().postOnAnimation(watcher);
+ }
+ });
+
+ watcher.waitForAnimationThread();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("hello world", binding.getTextView().getText().toString());
+ getActivity().setContentView(binding.getRoot());
+ binding.getRoot().postOnAnimation(watcher);
+ }
+ });
+
+ watcher.waitForAnimationThread();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("goodbye world", binding.getTextView().getText().toString());
+ }
+ });
+ }
+
+ private static class AnimationWatcher implements Runnable {
+ private boolean mWaiting = true;
+
+ public void waitForAnimationThread() throws InterruptedException {
+ synchronized (this) {
+ while (mWaiting) {
+ this.wait();
+ }
+ mWaiting = true;
+ }
+ }
+
+
+ @Override
+ public void run() {
+ synchronized (this) {
+ mWaiting = false;
+ this.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml
new file mode 100644
index 0000000..3dbf2f5
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="name" type="String"/>
+ <TextView
+ android:id="@+id/textView"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{name}"/>
+</LinearLayout> \ No newline at end of file
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java b/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java
index 6cc28571..e4bb2f4 100644
--- a/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java
+++ b/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java
@@ -16,10 +16,13 @@
package android.databinding;
+import android.annotation.TargetApi;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
+import android.util.Log;
import android.util.SparseIntArray;
import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import java.lang.ref.WeakReference;
@@ -71,6 +74,29 @@ public abstract class ViewDataBinding {
}
};
+ private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
+
+ static {
+ if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
+ ROOT_REATTACHED_LISTENER = null;
+ } else {
+ ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
+ @TargetApi(VERSION_CODES.KITKAT)
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ // execute the pending bindings.
+ ViewDataBinding binding = (ViewDataBinding) v.getTag();
+ v.post(binding.mRebindRunnable);
+ v.removeOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ };
+ }
+ }
+
/**
* Runnable executed on animation heartbeat to rebind the dirty Views.
*/
@@ -78,8 +104,19 @@ public abstract class ViewDataBinding {
@Override
public void run() {
if (mPendingRebind) {
- mPendingRebind = false;
- executePendingBindings();
+ boolean rebind = true;
+ if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+ rebind = mRoot.isAttachedToWindow();
+ if (!rebind) {
+ // Don't execute the pending bindings until the View
+ // is attached again.
+ mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
+ }
+ }
+ if (rebind) {
+ mPendingRebind = false;
+ executePendingBindings();
+ }
}
}
};