【问题标题】:Android AsyncTask doInBackground behavior has changed in Android7Android AsyncTask doInBackground 行为在 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

  1. Android 7. Stalls,即 doInBackground 不执行。

            THREAD_POOL_EXECUTOR.getCorePoolSize ()  : 4
            THREAD_POOL_EXECUTOR.getMaximumPoolSize(): 17
            THREAD_POOL_EXECUTOR.getPoolSize()       : 4
            THREAD_POOL_EXECUTOR.getActiveCount()    : 4
    
  2. 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 属性和之前一样,但是任务没有停止。

  1. 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_EXEC‌​UTOR 调用executeOnExecutor 只是在它本来可以运行的同一个执行器上运行它。尝试在您提供的另一个执行器上运行它。
  • 您的建议已经奏效。我根据developer.android.com/training/multiple-threads/… 中的详细信息创建了一个 ThreadPoolExecutor。现在稍微整理一下。谢谢。

标签: android android-asynctask


【解决方案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。
    猜你喜欢
    • 1970-01-01
    • 2014-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多