I am working on an internal app that displays the daily schedule for a given conference room on a timeline. Every minute, I hit a web service and get info back for all of the day’s appointments, and I use that data to create custom “timeline block” views that show the time of the meeting, the client, etc. I then remove all appointments from the timeline and refresh it with the new data. The custom views I am using consist of nothing more than a LinearLayout with 3 TextViews inside it. I suppose I should note that the layout for the activity is pretty complex, and some views are nested about 7 layers deep.
Everything runs great for about 2-3 days, and then the app will crash. I made it so that it would hit the web service every 3 seconds, and now the crash will happen about every 2.5 hours. What gets me is that the exception that is thrown does not even point to anything in my code. Exception is below:
FATAL EXCEPTION: main
java.lang.ClassCastException: java.lang.String
at android.text.Styled.drawDirectionalRun(Styled.java:283)
at android.text.Styled.measureText(Styled.java:430)
at android.text.Layout.measureText(Layout.java:1655)
at android.text.Layout.getLineMax(Layout.java:689)
at android.text.Layout.getLineWidth(Layout.java:671)
at android.widget.TextView.desired(TextView.java:5037)
at android.widget.TextView.onMeasure(TextView.java:5083)
at android.view.View.measure(View.java:8171)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:578)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:362)
at android.view.View.measure(View.java:8171)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:578)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:362)
at android.view.View.measure(View.java:8171)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:3132)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:245)
at android.view.View.measure(View.java:8171)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:3132)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:245)
at android.view.View.measure(View.java:8171)
at android.view.ViewRoot.performTraversals(ViewRoot.java:801)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1727)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
If I am incorrectly casting a String (which I’m pretty sure I’m not), how is it that it wouldn’t be caught until it’s that deep in native code? I’m at a loss here. Is it possible this is an internal Android bug?
Another thing that might help: another time when this app crashed, it gave the same ClassCastException, but in a different spot. It happened in the BoringLayout.isBoring function, which is also something that is never touched in my code.
So, it turns out that the problem all along was that Android leaks memory when disposing (improperly) of TypeFace objects. I was creating a new TypeFace for every text label, and each minute the timeline blocks were being repopulated as the timeline was updated.
The solution is to create the TypeFaces that are reused so that they are globally accessible. I put them in the Application class since all my Activities can access it. That way Android only has to dispose of a few objects instead of hundreds. It doesn’t fix the internal issue of course, but it is a workaround. The issue may be fixed as of ICS, but I’m not sure.