Fixed
Status Update
Comments
ro...@gtempaccount.com <ro...@gtempaccount.com> #2
I tried adding screenshotTestImplementation(libs.androidx.window)
but it didn't fix it.
sa...@gmail.com <sa...@gmail.com> #3
This is likely a duplicate of libs.androidx.window
dependency to implementation
to see if it works?
ne...@gmail.com <ne...@gmail.com> #4
samuh, i just discovered this issue while tracking down a memory leak in one of my apps ... it's definitely a concern in my case, so i coded this class as a work-around:
public class Typefaces{
private static final Hashtable<String, Typeface> cache = new Hashtable<String, Typeface>();
public static Typeface get(Context c, String name){
synchronized(cache){
if(!cache.containsKey(name)){
Typeface t = Typeface.createFromAsset(
c.getAssets(),
String.format("fonts/%s.ttf", name)
);
cache.put(name, t);
}
return cache.get(name);
}
}
}
when you want to load one of your custom fonts, you'd say something like:
Typefaces.get("myfont");
basically this class ensures Android only loads each font once per instance of your app. the tradeoff, of course, is that each requested typeface object will remain in memory until your app is totally stopped by the OS (even though a given activity may not require each font).
this works for my app anyway, my memory leak is gone now.
HTH
public class Typefaces{
private static final Hashtable<String, Typeface> cache = new Hashtable<String, Typeface>();
public static Typeface get(Context c, String name){
synchronized(cache){
if(!cache.containsKey(name)){
Typeface t = Typeface.createFromAsset(
c.getAssets(),
String.format("fonts/%s.ttf", name)
);
cache.put(name, t);
}
return cache.get(name);
}
}
}
when you want to load one of your custom fonts, you'd say something like:
Typefaces.get("myfont");
basically this class ensures Android only loads each font once per instance of your app. the tradeoff, of course, is that each requested typeface object will remain in memory until your app is totally stopped by the OS (even though a given activity may not require each font).
this works for my app anyway, my memory leak is gone now.
HTH
ma...@gmail.com <ma...@gmail.com> #5
Any particular reason this went into the ICS branch instead of Gingerbread?
in...@obdandroid.com <in...@obdandroid.com> #6
Awesome!
gh...@gmail.com <gh...@gmail.com> #7
WOW! that worked perfectly, thanks
br...@gmail.com <br...@gmail.com> #8
I altered HTH's workaround so that the method does not assume the font path or format. The full path of the font asset must be submitted as a parameter. I also wrapped the call to createFromAsset() in a try-catch block so that the get() method will return null if the asset is not found.
public class Typefaces {
private static final String TAG = "Typefaces";
private static final Hashtable<String, Typeface> cache = new Hashtable<String, Typeface>();
public static Typeface get(Context c, String assetPath) {
synchronized (cache) {
if (!cache.containsKey(assetPath)) {
try {
Typeface t = Typeface.createFromAsset(c.getAssets(),
assetPath);
cache.put(assetPath, t);
} catch (Exception e) {
Log.e(TAG, "Could not get typeface '" + assetPath
+ "' because " + e.getMessage());
return null;
}
}
return cache.get(assetPath);
}
}
}
public class Typefaces {
private static final String TAG = "Typefaces";
private static final Hashtable<String, Typeface> cache = new Hashtable<String, Typeface>();
public static Typeface get(Context c, String assetPath) {
synchronized (cache) {
if (!cache.containsKey(assetPath)) {
try {
Typeface t = Typeface.createFromAsset(c.getAssets(),
assetPath);
cache.put(assetPath, t);
} catch (Exception e) {
Log.e(TAG, "Could not get typeface '" + assetPath
+ "' because " + e.getMessage());
return null;
}
}
return cache.get(assetPath);
}
}
}
cg...@gmail.com <cg...@gmail.com> #9
Thanks brian code is working correctly ... helped me ...
ni...@gmail.com <ni...@gmail.com> #10
really aussom Brain this work and reduce my memory 70%......THANKS A LOT
qu...@gmail.com <qu...@gmail.com> #11
To HTH: Could you please give a more detailed example of your implementation of Typefaces in the activity? I'm still having problems (see attached log file).
sa...@gmail.com <sa...@gmail.com> #12
One thing I like to mention if you have to add and remove views or you are assigning typeface multiple times to same textview ,then you need to do this
if(someTextView.getTypeface() !=null && !someTextView.getTypeface().equals(Typefaces.get(somecontext,someassetpath))
someTextView.setTypeface(Typefaces.get(somecontext,someassetpath);
Otherwise you will get more and more memory allocated,when you continuously set typeface to same textview
if(someTextView.getTypeface() !=null && !someTextView.getTypeface().equals(Typefaces.get(somecontext,someassetpath))
someTextView.setTypeface(Typefaces.get(somecontext,someassetpath);
Otherwise you will get more and more memory allocated,when you continuously set typeface to same textview
an...@googlemail.com <an...@googlemail.com> #13
[Comment deleted]
an...@googlemail.com <an...@googlemail.com> #14
#11 This method does not work for Buttons. Is there a way to recognize typeface has already been set for Buttons?
su...@gmail.com <su...@gmail.com> #15
suuuuuuuuuuuu suuuuuuuuuuu super yaar
to...@gmail.com <to...@gmail.com> #16
@ No 14! Please stop posting crap - everyone subscribed to this are getting your crap too which is OFF-TOPIC!
so...@gmail.com <so...@gmail.com> #17
Thank you all for the detailed analysis and providing solution.
ud...@gmail.com <ud...@gmail.com> #18
Thanks for the workaround.
hj...@gmail.com <hj...@gmail.com> #19
Thanks HTH !!
ma...@gmail.com <ma...@gmail.com> #20
No thank you.
Thank you a bounch brian.gi...@gmail.com..
Thank you a bounch brian.gi...@gmail.com..
[Deleted User] <[Deleted User]> #22
I have that 'fix' present in the 4.4.4 framework and still exhibit the signs of this bug...
[Deleted User] <[Deleted User]> #23
I am still seeing this issue also, is it really fixed?
th...@gmail.com <th...@gmail.com> #24
Thank #7 brian.gi...@gmail.com. You save my app.
[Deleted User] <[Deleted User]> #25
This does not seem to be fixed. Can easily reproduce with Android Marshmallow
kn...@gmail.com <kn...@gmail.com> #26
[Comment deleted]
ja...@gmail.com <ja...@gmail.com> #27
Thanks !!! Same problem with Marshmallow ... (~300 mb > 88mb after change)
wk...@gmail.com <wk...@gmail.com> #28
This bug can reproduce in emulator running on api 29.
#8 solution is not working.
I have >100 ttf fonts store in assets or on sdcard on my demo app.
I use LruCache to store Typeface.
Both Typeface.createFromFile and Typeface.createFromAssets have this bug.
The native memory can easily up to 1g when i using TextView.setTypeface in recyclerView.
Neither System.gc or Runtime.getRuntime().exec("kill -10 $pid") can free the native memory.
#8 solution is not working.
I have >100 ttf fonts store in assets or on sdcard on my demo app.
I use LruCache to store Typeface.
Both Typeface.createFromFile and Typeface.createFromAssets have this bug.
The native memory can easily up to 1g when i using TextView.setTypeface in recyclerView.
Neither System.gc or Runtime.getRuntime().exec("kill -10 $pid") can free the native memory.
Description
1. Write an application that calls Typeface.createFromAsset() multiple times.
2. Make sure that the typeface objects are out of scope and the garbage collector has completed at least one pass.
3. Run adb shell dumpsys meminfo <your app package>
Observe: There are multiple opened fonts under "Asset Allocations".
I was able to reproduce this bug on a device with Eclair firmware as well as on a device with Froyo. This is a significant bug as an application with custom fonts can quickly run out of memory.
My first thought of the leak was that the framework may ententionally cache opened asset files. I took a dive into the framework to find out if this was the case. I'm sharing my findings here so it will be easier for the developer fixing the bug to get started.
In the JNI layer, createFromAsset is implemented in Typeface.cpp:
static SkTypeface* Typeface_createFromAsset(JNIEnv* env, jobject,
jobject jassetMgr,
jstring jpath) {
NPE_CHECK_RETURN_ZERO(env, jassetMgr);
NPE_CHECK_RETURN_ZERO(env, jpath);
AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
if (NULL == mgr) {
return NULL;
}
AutoJavaStringToUTF8 str(env, jpath);
Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
if (NULL == asset) {
return NULL;
}
return SkTypeface::CreateFromStream(new AssetStream(asset, true));
}
AssetStream is a local class which derives from SkStream. SkStream in turn derives from SkRefCnt, which is ref counted. The initial ref count is set to 1 in the constructor.
The body of SkTypeface::CreateFromStream() invokes SkFontHost::CreateTypefaceFromStream(). The body of that function is located in SkFontHost_android.cpp:
SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) {
if (NULL == stream || stream->getLength() <= 0) {
return NULL;
}
SkString name;
SkTypeface::Style style = find_name_and_style(stream, &name);
return SkNEW_ARGS(StreamTypeface, (style, false, NULL, stream));
}
Here, the stream is passed in the constructor of a new StreamTypeface object. In the constructor, the StreamTypeface increments the ref count of the stream by 1 to become 2.
The resulting Typeface is returned back to the Java layer. When the typeface gets out of scope, the object is garbage collected and the finalize() method is invoked. This will call the unref function which lowers the ref count:
static void Typeface_unref(JNIEnv* env, jobject obj, SkTypeface* face) {
SkSafeUnref(face);
}
At this point, you would expect the native object to be freed and the asset stream to be closed. However, after lowering the ref count, the value is 1, which is not enough to free the native object.
It seems that the ref count of the Typeface object should be lowered by one in the JNI layer (Typeface_createFromAsset), before returning the object. For reference, another method in the font host does decrement the ref count before returning:
SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) {
SkStream* stream = SkNEW_ARGS(SkMMAPStream, (path));
SkTypeface* face = SkFontHost::CreateTypefaceFromStream(stream);
// since we created the stream, we let go of our ref() here
stream->unref();
return face;
}
Thanks!