Skip to content

Commit cdd3c94

Browse files
author
Dmytro Voronkevych
committed
Added implementation of WeakHandler
1 parent 5ce5ef9 commit cdd3c94

File tree

7 files changed

+886
-1
lines changed

7 files changed

+886
-1
lines changed

README.md

+67-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,70 @@
1-
android-weak-handler
1+
Android Weak Handler
22
====================
33

44
Memory safer implementation of android.os.Handler
5+
6+
Problem
7+
-------
8+
9+
Original implementation of Handler always keeps hard reference to handler in queue of execution.
10+
Any object in Message or Runnable posted to `android.os.Handler` will be hard referenced for some time.
11+
If you create anonymous Runnable and call to `postDelayed` with large timeout, that Runnable will be held
12+
in memory until timeout passes. Even if your Runnable seems small, it indirectly references owner class,
13+
which is usually something as big as Activity or Fragment.
14+
15+
Very soon we will post article with much more details.
16+
17+
Solution
18+
--------
19+
20+
`WeakHandler` is trickier then `android.os.Handler` , it will keep `WeakReferences` to runnables and messages,
21+
and GC could collect them once `WeakHandler` instance is not referenced any more.
22+
23+
![Screenshot](WeakHandler.png)
24+
25+
Usage
26+
-----
27+
Clone project to your local machine, then run
28+
```shell
29+
cd android-weak-handler
30+
gradle uploadArchives
31+
```
32+
33+
Add reference to your build.gradle:
34+
```groovy
35+
repositories {
36+
mavenLocal()
37+
}
38+
39+
dependencies {
40+
compile 'com.badoo.mobile:android-weak-handler:1.0'
41+
}
42+
```
43+
44+
Use WeakHandler as you normally would use Handler
45+
46+
```java
47+
import com.badoo.mobile.util.WeakHandler;
48+
49+
public class ExampleActivity extends Activity {
50+
51+
private WeakHandler mHandler; // We still need at least one hard reference to WeakHandler
52+
53+
protected void onCreate(Bundle savedInstanceState) {
54+
mHandler = new WeakHandler();
55+
...
56+
}
57+
58+
private void onClick(View view) {
59+
mHandler.postDelayed(new Runnable() {
60+
view.setVisibility(View.INVISIBLE);
61+
}, 5000);
62+
}
63+
}
64+
```
65+
66+
Credits
67+
-------
68+
Weak Handler is brought to you by [Badoo Trading Limited](http://corp.badoo.com) and it is released under the [MIT License](http://opensource.org/licenses/MIT).
69+
70+
Created by [Dmytro Voronkevych](https://github.com/dmitry-voronkevich)

WeakHandler.png

27.1 KB
Loading

build.gradle

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2014 Badoo Trading Limited
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
* THE SOFTWARE.
20+
*/
21+
project.ext.set('VERSION_CODE', 1)
22+
project.ext.set('VERSION_NAME', '1.0')
23+
24+
buildscript {
25+
repositories {
26+
mavenCentral()
27+
}
28+
29+
dependencies {
30+
classpath 'com.android.tools.build:gradle:0.12.+'
31+
}
32+
}
33+
34+
apply plugin: 'com.android.library'
35+
36+
dependencies {
37+
compile "com.android.support:support-annotations:19.1.0"
38+
}
39+
40+
41+
android {
42+
compileSdkVersion 19
43+
buildToolsVersion "19.1.0"
44+
45+
defaultConfig {
46+
versionCode project.VERSION_CODE
47+
versionName project.VERSION_NAME
48+
minSdkVersion 8
49+
targetSdkVersion 19
50+
}
51+
}
52+
53+
if (project.hasProperty('badooMavenScript')) {
54+
apply from: project.badooMavenScript
55+
} else {
56+
apply from: 'mvn-local-support.gradle'
57+
}
58+

mvn-local-support.gradle

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2014 Badoo Trading Limited
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
* THE SOFTWARE.
20+
*/
21+
apply plugin: 'maven'
22+
23+
uploadArchives {
24+
repositories {
25+
mavenDeployer {
26+
repository(url: 'file://' + System.properties['user.home'] + '/.m2/repository')
27+
28+
pom.project {
29+
name = "Android Weak Handler"
30+
groupId = 'com.badoo.mobile'
31+
artifactId = 'android-weak-handler'
32+
version = project.VERSION_NAME
33+
description = 'Memory safer implementation of android Handler'
34+
url = 'http://github.com/badoo/android-weak-handler'
35+
36+
scm {
37+
connection 'scm:git:http://github.com/badoo/android-weak-handler/'
38+
developerConnection 'scm:git:http://github.com/badoo/android-weak-handler/'
39+
url 'http://github.com/badoo/android-weak-handler'
40+
}
41+
42+
licenses {
43+
license {
44+
name 'MIT License'
45+
url 'http://opensource.org/licenses/MIT'
46+
}
47+
}
48+
49+
developers {
50+
developer {
51+
id 'zloy'
52+
name 'Dmytro Voronkevych'
53+
54+
}
55+
}
56+
}
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright (c) 2014 Badoo Trading Limited
3+
* Permission is hereby granted, free of charge, to any person obtaining a copy
4+
* of this software and associated documentation files (the "Software"), to deal
5+
* in the Software without restriction, including without limitation the rights
6+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
* copies of the Software, and to permit persons to whom the Software is
8+
* furnished to do so, subject to the following conditions:
9+
*
10+
* The above copyright notice and this permission notice shall be included in
11+
* all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
* THE SOFTWARE.
20+
*/
21+
package com.badoo.mobile.util;
22+
23+
import android.os.HandlerThread;
24+
import android.os.SystemClock;
25+
26+
import junit.framework.TestCase;
27+
28+
import java.util.concurrent.CountDownLatch;
29+
import java.util.concurrent.TimeUnit;
30+
import java.util.concurrent.atomic.AtomicBoolean;
31+
32+
/**
33+
* Unit tests for {@link com.badoo.mobile.util.WeakHandler}
34+
*
35+
* Created by Dmytro Voronkevych on 17/06/2014.
36+
*/
37+
public class WeakHandlerTest extends TestCase {
38+
39+
public void testChainedRef() {
40+
final Runnable runHead = new DummyRunnable();
41+
final Runnable runFirst = new DummyRunnable();
42+
final Runnable runSecond = new DummyRunnable();
43+
44+
WeakHandler.ChainedRef refHead = new WeakHandler.ChainedRef(runHead) {
45+
@Override
46+
public String toString() {
47+
return "refHead";
48+
}
49+
};
50+
WeakHandler.ChainedRef first = new WeakHandler.ChainedRef(runFirst) {
51+
@Override
52+
public String toString() {
53+
return "first";
54+
}
55+
};
56+
WeakHandler.ChainedRef second = new WeakHandler.ChainedRef(runSecond) {
57+
@Override
58+
public String toString() {
59+
return "second";
60+
}
61+
};
62+
63+
refHead.insertAbove(first);
64+
refHead.insertAbove(second);
65+
66+
assertSame(second, refHead.next);
67+
assertSame(first, refHead.next.next);
68+
assertNull(refHead.next.next.next);
69+
70+
assertNull(refHead.prev);
71+
assertSame(second, first.prev);
72+
assertSame(refHead, second.prev);
73+
74+
assertSame(second, refHead.findForward(runSecond));
75+
assertSame(first, refHead.findForward(runFirst));
76+
assertSame(refHead, refHead.findForward(runHead));
77+
assertNull(refHead.findForward(new DummyRunnable()));
78+
79+
second.remove();
80+
assertNull(second.prev);
81+
assertNull(second.next);
82+
assertNull(refHead.prev);
83+
assertNull(first.next);
84+
assertSame(first, refHead.next);
85+
assertSame(refHead, first.prev);
86+
87+
assertSame(first, refHead.findForward(runFirst));
88+
assertSame(refHead, refHead.findForward(runHead));
89+
assertNull(refHead.findForward(runSecond));
90+
91+
first.remove();
92+
assertSame(WeakHandler.ChainedRef.sPool, first); // It was put in pull
93+
assertSame(second, first.next);
94+
assertNotSame(refHead, first.next);
95+
assertNull(first.prev);
96+
assertNull(refHead.next);
97+
}
98+
99+
public void testChainedRefAlloc() {
100+
WeakHandler.ChainedRef.sPool = null;
101+
WeakHandler.ChainedRef.sPoolSize = 0;
102+
103+
WeakHandler.ChainedRef ref1 = WeakHandler.ChainedRef.obtain(null);
104+
assertNotNull(ref1);
105+
assertEquals(0, WeakHandler.ChainedRef.sPoolSize);
106+
WeakHandler.ChainedRef ref2 = WeakHandler.ChainedRef.obtain(null);
107+
assertNotNull(ref2);
108+
assertNotSame(ref1, ref2);
109+
assertEquals(0, WeakHandler.ChainedRef.sPoolSize);
110+
ref1.remove();
111+
assertEquals(1, WeakHandler.ChainedRef.sPoolSize);
112+
ref2.remove();
113+
assertEquals(2, WeakHandler.ChainedRef.sPoolSize);
114+
assertSame(ref2, WeakHandler.ChainedRef.obtain(null));
115+
assertEquals(1, WeakHandler.ChainedRef.sPoolSize);
116+
assertSame(ref1, WeakHandler.ChainedRef.obtain(null));
117+
assertEquals(0, WeakHandler.ChainedRef.sPoolSize);
118+
}
119+
120+
public void testPostDelayed() throws InterruptedException {
121+
HandlerThread thread = new HandlerThread("test");
122+
thread.start();
123+
124+
final CountDownLatch latch = new CountDownLatch(1);
125+
126+
WeakHandler handler = new WeakHandler(thread.getLooper());
127+
128+
long startTime = SystemClock.elapsedRealtime();
129+
final AtomicBoolean executed = new AtomicBoolean(false);
130+
handler.postDelayed(new Runnable() {
131+
@Override
132+
public void run() {
133+
executed.set(true);
134+
latch.countDown();
135+
}
136+
}, 300);
137+
138+
latch.await(1, TimeUnit.SECONDS);
139+
assertTrue(executed.get());
140+
141+
long elapsedTime = SystemClock.elapsedRealtime() - startTime;
142+
assertTrue(elapsedTime <= 305 && elapsedTime >= 300);
143+
thread.getLooper().quit();
144+
}
145+
146+
public void testRemoveCallbacks() throws InterruptedException {
147+
HandlerThread thread = new HandlerThread("test");
148+
thread.start();
149+
150+
final CountDownLatch latch = new CountDownLatch(1);
151+
152+
WeakHandler handler = new WeakHandler(thread.getLooper());
153+
154+
long startTime = SystemClock.elapsedRealtime();
155+
final AtomicBoolean executed = new AtomicBoolean(false);
156+
Runnable r = new Runnable() {
157+
@Override
158+
public void run() {
159+
executed.set(true);
160+
latch.countDown();
161+
}
162+
};
163+
handler.postDelayed(r, 300);
164+
handler.removeCallbacks(r);
165+
latch.await(1, TimeUnit.SECONDS);
166+
assertFalse(executed.get());
167+
168+
long elapsedTime = SystemClock.elapsedRealtime() - startTime;
169+
assertTrue(elapsedTime > 300);
170+
thread.getLooper().quit();
171+
}
172+
173+
private class DummyRunnable implements Runnable {
174+
@Override
175+
public void run() {
176+
}
177+
}
178+
}

src/main/AndroidManifest.xml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<manifest package="com.badoo.mobile.util">
2+
3+
<application />
4+
5+
</manifest>

0 commit comments

Comments
 (0)