避免在Android 4.4中使用列表时应用程序出现RejectedExecutionException异常

12
在Android 4.4中,似乎有一个代码变化导致列表图标使用AsyncTasks加载。结果是我的许多Android 4.4用户会因为超过队列大小限制而收到RejectedExecutionException错误。Code Google上的一位聪明用户发现了这一点,并以以下方式解释:
ResolverActivity在Android 4.4上会抛出RejectedExecutionException异常。
我查看了最新的ResolverActivity代码,并注意到在ResolveListAdapter.bindView方法中它使用new LoadIconTask().execute(info),这应该是根本原因。LoadIconTask是AsyncTask的子类,同时运行太多的AsyncTask将导致RejectedExecutionException异常。
ResolverActivity的更改可以在Android GitHub repo上找到。

我的应用程序目前有82个RejectedExecutionException堆栈跟踪,所有这些都是针对Android 4.4的。堆栈跟踪的示例开始:

java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@41d44580 rejected from java.util.concurrent.ThreadPoolExecutor@41a575c0[Running, pool size = 5, active threads = 5, queued tasks = 128, completed tasks = 140]
 at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2011)
 at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:793)
 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1339)
 at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:590)
 at android.os.AsyncTask.execute(AsyncTask.java:535)
 at com.android.internal.app.ResolverActivity$ResolveListAdapter.bindView(ResolverActivity.java:716)
 at com.android.internal.app.ResolverActivity$ResolveListAdapter.getView(ResolverActivity.java:702)
 at android.widget.AbsListView.obtainView(AbsListView.java:2255)
...

有没有什么方法可以规避或处理这个变化?

1个回答

21

问题在于AsyncTask根据应用程序的targetSdkVersion使用不同的执行器:

1)targetSdkVersion <= 12

AsyncTask.execute()使用AsyncTask.THREAD_POOL_EXECUTOR。AsyncTask.THREAD_POOL_EXECUTOR中的队列仅限于128个项目。如果队列已满,则会抛出RejectedExecutionException异常。这就是此处发生的情况。

2)targetSdkVersion > 12

AsyncTask使用AsyncTask.SERIAL_EXECUTOR。AsyncTask.SERIAL_EXECUTOR具有无限队列。因此,在此方案中,永远不会抛出RejectedExecutionException异常。

解决方案1(也称为“清洁”解决方案)

使用一个单独的APK,其targetSdkVersion>12且具有更高的versionCode,以便在HONEYCOMB_MR2和Android的后续版本中首选。这将导致AsyncTask在HONEYCOMB_MR2和后续版本的Android上使用ThreadPool.SERIAL_EXECUTOR。

解决方案2(也称为肮脏的hack)

使用Reflection将AsyncTask.SERIAL_EXECUTOR设置为默认值。

AsyncTask.class.getMethod("setDefaultExecutor", Executor.class).invoke(null, AsyncTask.SERIAL_EXECUTOR);

1
谢谢。您知道早期版本使用POOL与SERIAL的原因吗?强制使用SERIAL会导致某些手机出现问题吗?此外,源代码 - Halvor Holsten Strand
1
来回切换的原因在AsyncTask.execute()文档中有说明。基本上使用SERIAL_EXECUTOR是更安全的默认选项,因为它会导致较少难以重现的线程问题,所以他们转而使用它。不,我认为你切换到SERIAL_EXECUTOR不会破坏任何东西。 - HHK
我猜测我的理解是正确的,因为SERIAL_EXECUTOR是在API 11级中添加的,所以这个“hack”只适用于minSdkVersion 11或12(或更高版本,但无论如何都要使用“clean”)?虽然您的“clean”解决方案适用于小于11的minSdkVersions,但如果API太低,则实际上不会改变任何内容。 - Halvor Holsten Strand
是的,没错。你最初的错误报告是关于在KitKat上运行你的应用程序。你在其他Android版本上也遇到了RejectedExecutionException的问题吗? - HHK
不,只是澄清何时可以使用这个黑客技巧。设置targetSdkVersion是一个可靠的选择。谢谢。 - Halvor Holsten Strand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接