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_EXEC‌​UTOR 调用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 的提交。我没有看到确凿证据。

Tweak AsyncTask#THREAD_POOL_EXECUTOR settings More maybe fix issue #22765972: Binder transactions running out of address space causing package manager to fail Fix AsyncTask to handle exceptions in doInBackground AsyncTask terminating with exception calls onCancelled

【讨论】:

谢谢。我将尝试禁用其他 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

android:如果一个 AsyncTask 已经在运行,则阻止另一个 AsyncTask 执行

android asynctask怎么用

Android AsyncTask分析

android中 如果要你来设计AsyncTask,你怎么设计

Android - 强制取消 AsyncTask