diff options
author | George Mount <mount@google.com> | 2015-03-20 13:49:37 -0700 |
---|---|---|
committer | George Mount <mount@google.com> | 2015-03-26 15:57:23 -0700 |
commit | 038ad93e7e7fd52c690bd9a8bd41eb79238ec6d1 (patch) | |
tree | 863a87617faf7c2fc122f92604dc9f5dcf78914d /tools | |
parent | ff5868ed69193fd10caf79f456c4925c8b6d1cf0 (diff) | |
download | frameworks_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')
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(); + } } } }; |