I'm writing a chatty net enabled application and for an improved user experience I'm using all kinds of concurrency constructs. The information coming in from the network contains textual items and URLs for images. I'm currently fetching the images in a blocking manner without spawning a new thread to fetch the images. This works ok but I realized that I could make things a lot snappier by spawning another thread to fetch the images. I settled on a thread pool with 2 worker threads for handling this task but now I'm having intermittent issues with the app crashing. It seems to happen randomly and I don't know what exactly is going wrong. So here's how I currently do things:
User taps search and a new AsyncTask is spawned to fetch the required information.
In the new AsyncTask information starts to trickle in. As soon as enough information is available for a single item we add the item to a list adapter, jump onto the main thread and call
notifyDataSetChanged()
to update the list. We also submit a task to the thread pool to fetch the image and callnotifyDataSetChanged()
as soon as the image is ready.Continue to update the list until there are no more items to process.
I think my setup is pretty good but I would like some suggestions for improving how I do things and maybe some pointers for tracking down what's causing the crashes.
Here's a logcat output:
02-23 21:30:20.303: DEBUG/AndroidRuntime(1209): Shutting down VM 02-23 21:30:20.313: WARN/dalvikvm(1209): threadid=1: thread exiting with uncaught exception (group=0x4001d800) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): FATAL EXCEPTION: main 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI t开发者_JAVA技巧hread. [in ListView(16908298, class android.widget.ListView) with Adapter(class android.widget.HeaderViewListAdapter)] 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.ListView.layoutChildren(ListView.java:1492) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.AbsListView.onLayout(AbsListView.java:1147) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.View.layout(View.java:7035) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1249) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1125) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.LinearLayout.onLayout(LinearLayout.java:1042) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.View.layout(View.java:7035) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.FrameLayout.onLayout(FrameLayout.java:333) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.View.layout(View.java:7035) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1249) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1125) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.LinearLayout.onLayout(LinearLayout.java:1042) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.View.layout(View.java:7035) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.widget.FrameLayout.onLayout(FrameLayout.java:333) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.View.layout(View.java:7035) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.ViewRoot.performTraversals(ViewRoot.java:1045) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.view.ViewRoot.handleMessage(ViewRoot.java:1727) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.os.Handler.dispatchMessage(Handler.java:99) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.os.Looper.loop(Looper.java:123) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at android.app.ActivityThread.main(ActivityThread.java:4627) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at java.lang.reflect.Method.invokeNative(Native Method) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at java.lang.reflect.Method.invoke(Method.java:521) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626) 02-23 21:30:20.342: ERROR/AndroidRuntime(1209): at dalvik.system.NativeStart.main(Native Method) 02-23 21:30:20.402: WARN/ActivityManager(72): Force finishing activity com.daveco.pricewatcher/.SearchActivity
It sounds like when an item becomes available you are updating the list adapter from the worker thread in the AsyncTask. The same adapter is traversed in the UI thread to render the list. This can cause the random crashes because you might be updating the list adapter for the next item while it is still responding to an earlier notifyDataSetChanged()
.
A better way to update the list adapter is to use a handler to post a runnable into the UI thread that will update the list adapter and call notifyDataSetChanged()
.
There may be a similar thing going on with the image updates.
As a general strategy, any data structure on which the UI depends should be updated on the UI thread. It tends to be safer all around. An alternative strategy is to carefully synchronize critical sections of code, but that is much harder to get right.
精彩评论