Android AsyncTask doInBackground 行为在 Android7 中发生了变化
Posted
技术标签:
【中文标题】Android AsyncTask doInBackground 行为在 Android7 中发生了变化【英文标题】:Android AsyncTask doInBackground behavior has changed in Android7 【发布时间】:2017-11-30 19:50:52 【问题描述】:我的应用程序有一个带有 AsyncTask 的 Fragment,它从数据库中获取记录并使用 ListView 显示它们。几年来一直运行良好,但现在在 android 7 上它停滞不前,没有显示任何记录。但是,在离开应用程序(例如进入 Android 设置)然后返回时,会显示记录。 调试显示最初执行的是onPreExecute,但是直到离开应用程序的那一刻才执行doInBackground。
谁能提出 Android 7 中的哪些变化可以解释这一点?
// 1. ==== Fragment containing AsyncTask ====
public class AuditFragment extends ListFragment implements OnClickListener
// Using beep for debugging until I can get LogCat in Eclipse restored for Android 7
public static void beep ( final int times )
...
private class UpdateAuditTask extends AsyncTask<Void, RecordEntry, SQLException>
@Override
protected SQLException doInBackground ( Void... parameters )
beep ( 5 ); // Debug, in the absence of LogCat
...
@Override
protected void onProgressUpdate ( RecordEntry... values )
L.logMethodCall ( (Object[]) values );
if ( values.length == 1 )
auditListAdapter.add ( values[0] );
auditListAdapter.notifyDataSetChanged ();
@Override
protected void onPreExecute ()
L.logMethodCall ();
beep ( 2 ); // Debug, in the absence of LogCat
auditListAdapter.clear ();
@Override
protected void onPostExecute ( SQLException result )
L.logMethodCall ( result );
...
private void updateAuditList ()
L.logMethodCall ();
beep (1); // Debug, in the absence of LogCat
new UpdateAuditTask ().execute ();
auditListAdapter.notifyDataSetChanged ();
public AuditFragment()
@Override
public void onClick ( View view )
...
@Override
public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
...
@Override
public void onStart ()
L.logMethodCall ();
super.onStart ();
getListView ().setAdapter ( auditListAdapter );
updateFragmentGui ();
@Override
public void onResume ()
L.logMethodCall ();
super.onResume ();
...
private void updateFragmentGui ()
L.logMethodCall ();
...
private class AuditListAdapter extends ArrayAdapter<RecordEntry>
...
// 2. ==== Activity which executes Fragment ====
public class AuditActivity extends Activity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
L.logMethodCall(savedInstanceState);
setContentView(R.layout.audit);
// Add "static" fragments
if (savedInstanceState == null)
FragmentTransaction ft = getFragmentManager().beginTransaction();
AuditFragment audit = new AuditFragment();
ft.add(R.id.kstation_audit_audit_frag, audit);
ft.commit();
@Override
public void finish()
super.finish();
overridePendingTransition(R.anim.donothing, R.anim.collapse_righttoleft);
// 3. ==== Method in main Activity ====
public void showAudit()
Intent intent = new Intent(C.getActivity(), AuditActivity.class);
C.getActivity().startActivity(intent);
我正在三星 SM-T580 上进行测试。 当 onPreExecute 运行后应用程序“停止”时,以下任何操作都会导致 doInBackground 立即执行: - 触摸“最近”按钮 - 按主页键 - 向下滚动并选择设置图标
看起来 AuditFragment 或其父 Activity 的生命周期状态的变化正在解除对 doInBackground 执行的阻塞。
更新:我已设法恢复 Android 7 的 LogCat 可见性(使用 sdk 工具 Monitor 而不是 Eclipse),因此获得了一些调试信息。
实验: - 恢复 updateAuditTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) (而不是使用我自己的执行器) 并在调用 executeOnExecutor 之前将 THREAD_POOL_EXECUTOR 属性输出到 LogCat
Android 7. Stalls,即 doInBackground 不执行。
THREAD_POOL_EXECUTOR.getCorePoolSize () : 4
THREAD_POOL_EXECUTOR.getMaximumPoolSize(): 17
THREAD_POOL_EXECUTOR.getPoolSize() : 4
THREAD_POOL_EXECUTOR.getActiveCount() : 4
Android 6. 不会停止,即 doInBackground 会执行。
THREAD_POOL_EXECUTOR.getCorePoolSize () : 5
THREAD_POOL_EXECUTOR.getMaximumPoolSize(): 9
THREAD_POOL_EXECUTOR.getPoolSize() : 5
THREAD_POOL_EXECUTOR.getActiveCount() : 5
我对此感到困惑:在每种情况下,当前活动的线程都不少于核心池大小; 新任务在 Android6 中运行,但在 Android7 中不运行。
实验 2。 禁用一个较早开始的 AsyncTask。这一次,THREAD_POOL_EXECUTOR 属性和之前一样,但是任务没有停止。
Android 7. 不会停止,即 doInBackground 会执行。
THREAD_POOL_EXECUTOR.getCorePoolSize () : 4
THREAD_POOL_EXECUTOR.getMaximumPoolSize(): 17
THREAD_POOL_EXECUTOR.getPoolSize() : 4
THREAD_POOL_EXECUTOR.getActiveCount() : 4
所以看起来池大小等与任务是否执行无关?
(澄清。早些时候,我错误地报告说我在禁用早期任务的情况下尝试过它;相反,我禁用了一些在单独线程上运行的任务。)
【问题讨论】:
发布您的代码。我有一种预感,问题出在您的Activity
,而不是AsyncTask
本身。
您是否正在运行任何其他异步任务?
是的,还有其他几个 AsyncTask 正在运行。但是,昨天我尝试了以下更改但没有成功: // 27/06/17 这应该是一个普遍的改进 //new UpdateAuditTask ().execute (); UpdateAuditTask updateAuditTask = new UpdateAuditTask(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) updateAuditTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);否则 updateAuditTask.execute((Void[])null);
用AsyncTask.THREAD_POOL_EXECUTOR
调用executeOnExecutor
只是在它本来可以运行的同一个执行器上运行它。尝试在您提供的另一个执行器上运行它。
您的建议已经奏效。我根据developer.android.com/training/multiple-threads/… 中的详细信息创建了一个 ThreadPoolExecutor。现在稍微整理一下。谢谢。
【参考方案1】:
我正在开发的应用程序遇到了同样的问题。这真的很奇怪,因为相同的代码正在其他一些设备上运行,具有相同的版本、配置...... 但是,一种解决方法可以帮助您:使用 Rx 而不是 AsyncTask。我无法解释主要原因,但它解决了我的阻塞问题。
【讨论】:
【参考方案2】:默认情况下,AsyncTask.execute()
使用AsyncTask.SERIAL_EXECUTOR
,因此单个长时间运行的任务将阻止任何其他任务运行。我通过查看Marshmallow AsyncTask
source 发现了这一点,并在我的 6.0.1 设备上进行了验证。
new AsyncTask<Void, Void, Void>()
@Override
protected Void doInBackground(final Void... voids)
Log.d("DERP", "bad task starting");
try
Thread.sleep(5000);
catch(InterruptedException e)
// ignore
finally
Log.d("DERP", "bad task exiting");
return null;
.execute();
new AsyncTask<Void, Void, Void>()
@Override
protected Void doInBackground(final Void... voids)
Log.d("DERP", "nice task running");
return null;
;
.execute();
输出:
06-30 15:43:23.777 8761-8792/com.chalcodes.rogueasynctask D/DERP: bad task starting
06-30 15:43:28.777 8761-8792/com.chalcodes.rogueasynctask D/DERP: bad task exiting
06-30 15:43:28.779 8761-9084/com.chalcodes.rogueasynctask D/DERP: nice task running
我建议使用executeOnExecutor(...)
并提供您自己的执行程序。如果您无法控制的两个任务相互干扰,您可以使用此 hack 更改默认执行程序。 AsyncTask
有一个公共静态 setDefaultExecutor
方法,但它被标记为 @hide
所以你必须通过反射来访问它。
try
final Method method = AsyncTask.class.getMethod("setDefaultExecutor", Executor.class);
method.invoke(null, executor);
catch(Exception e)
Log.e("AsyncTask", "error setting default executor", e);
但这并不能解释 Android 6 和 7 之间发生了什么变化。也许与 AsyncTask
无关的某些变化导致 AsyncTask
阻止串行执行程序。
这些是在 Android 7 中更改了 AsyncTask
的提交。我没有看到确凿证据。
【讨论】:
谢谢。我将尝试禁用其他 AsyncTask 的版本,看看是否会产生影响。 禁用其他 AsyncTasks 没有区别。我的“哔”调试确认 onPreExecute 已执行,但 doInBackground 未执行。但是 AsyncTask 的文档说:doInBackground(Params...),在 onPreExecute() 完成执行后立即在后台线程上调用。 // 我们希望核心池中至少有 2 个线程,最多 4 个线程。 Android7 中将线程数限制为 4 的上述更改可能会导致该问题;但我无法根据我的调试报告的 THREAD_POOL_EXECUTOR 值来解释行为:Android 6 和 7 之间没有一致性。我现在提供自己的执行程序作为 Executors.newSingleThreadExecutor () 而不是我第一次尝试的更精细的执行程序,并且这似乎很好。提供自己的执行人可能有什么缺点吗? @user5997813 我不认为它使用THREAD_POOL_EXECUTOR
,所以这种改变不应该有什么不同。奇怪的是,它可以与单线程执行器一起工作,但不能与默认的SERIAL_EXECUTOR
一起工作,这实际上是一回事。
我刚刚确认,对于 Android7,它不能使用 task.execute() 或 task.execute (AsyncTask.THREAD_POOL_EXECUTOR),而只能使用我已经测试过的 task.execute (Executors.newSingleThreadExecutor())前两个适用于 Android 6。以上是关于Android AsyncTask doInBackground 行为在 Android7 中发生了变化的主要内容,如果未能解决你的问题,请参考以下文章
android:如果一个 AsyncTask 已经在运行,则阻止另一个 AsyncTask 执行