在 AsyncTask 完成之前不会绘制 UI

Posted

技术标签:

【中文标题】在 AsyncTask 完成之前不会绘制 UI【英文标题】:UI is not drawn until AsyncTask completes 【发布时间】:2014-09-15 08:52:42 【问题描述】:

我的活动中有一些片段。每个片段都会调用 AsyncTask,它会在 onCreate() 方法中从本地数据库加载数据,如下所示:

@Override
public void onCreate(Bundle savedInstance) 
    super.onCreate(savedInstance);
    (new DataLoaderTask()).execute();
    ...

我有三个片段可以做到这一点。这样做时,片段在 DataLoaderTask 完成之前不会完成其 UI 的绘制。为什么是这样?我尝试将呼叫更改为此,我不再遇到问题:

@Override
public void onCreate(Bundle savedInstance) 
    super.onCreate(savedInstance);
    (new Handler()).postDelayed(new Runnable() 
        @Override
        public void run() 
            (new DataLoaderTask()).execute();
        
    
    ...

为什么将 Runnable 中的调用传递给 Handler 工作? AsyncTask 不应该在后台运行,因此应该在它完成之前绘制 UI?

感谢您的帮助。

更新:添加更多信息。 下面是 DataLoaderTask() 的构造函数:

public DataLoaderTask(Object object) 
    mObject = object;
    mListeners = new ArrayList<OnUpdateListener>();

DataLoaderTask 确实覆盖onPreExecute() 方法。它确实覆盖了onPostExecute() 方法。我已经为我的onPostExecute() 方法计时,它大约需要 2 毫秒。它所做的只是更新一些对象并在提供的任何侦听器上调用方法。

更新:这是完整的onPostExecute() 方法:

@Override
protected void onPostExecute(Object result) 
    synchronized(mDataManagerLock) 
        if (Config.isLogging()) 
            Log.i(TAG, "*** *** *** *** Finished syncing with database (onPostExecute()).");
        
        if (mObject instanceof Playlist && result instanceof Playlist) 
            if (((Playlist) result).isMyPlaylist()) 
                synchronized(mTmpMyPlaylist) 
                    if (mTmpMyPlaylist != null && !mTmpMyPlaylist.isEmpty()) 
                        ((Playlist) result).addPlaylistActions(mTmpMyPlaylist.getPlaylistActions());
                        mTmpMyPlaylist.clear();
                    
                
            
            if (mergeLocalPlaylistChanges((Playlist) result) && Config.isLogging()) 
                Log.i(TAG, "Local playlist changes during sync merged.");
             else if (Config.isLogging()) 
                Log.i(TAG, "No local playlist changes were made during sync.");
            
            ((Playlist) mObject).replace((Playlist) result);
            putPlaylist((Playlist) mObject, null /*newClips*/);
         else if (mObject instanceof Catalog && result instanceof Catalog) 
            ((Catalog) mObject).replace((Catalog) result);
            putCatalog((Catalog) mObject, null);
         else if (mObject instanceof NotificationsList) 
            // We've synced NotificationsList in Memory with Disk.
            mNotificationsList.setLastNetworkFetchTime(mApp.getNotificationsNetworkFetchTime());
         else if (mObject instanceof SetsList) 
            // We've synced SetsList in Memory with Disk.
            mSetsList.setLastNetworkFetchTime(mApp.getExploreNetworkFetchTime());
        
        if (Config.isLogging()) 
            Log.i(TAG, mObject.getClass().getSimpleName() + " after sync with database: " + mObject);
        
    
    if (Config.isLogging()) 
        Log.i(TAG, "Finished syncing in memory object/list with database.\n *** Time taken: " + (System.currentTimeMillis() - mStartTime)/1000 + " milliseconds.");
    
    if (mListeners != null) 
        for (OnUpdateListener listener : mListeners) 
        if (listener != null) 
            listener.onUpdated();
        
    


【问题讨论】:

发布您的DataLoaderTask 的构造函数(如果适用)。你在onPreExecute()做什么? 显示您的DataLoaderTask。也许您在 ui 线程上运行的方法中执行昂贵的操作。 我已经更新了问题。也许我应该提供实际的 onPostExecute() 方法。然而,就像我提到的,onPostExecute() 方法执行起来并不需要很长时间。 您还在哪里同步 mDataManagerLock? @alanv 我正在同步 mDataManagerLock 在其他几个方法调用以及doInBackground() 【参考方案1】:

正如您所提到的,任何资源密集型或需要一些时间的事情都应该在 doInBackground 中完成。这包括不运行任何可能在 onPostExecute 中阻止它的东西,因为我们知道它在主线程中运行(有一些例外)并且可能会导致这样的意外行为。

我进行了一些测试,显然主线程被 onPostExecute 持有。 我创建了一个虚拟任务:

class MyAsyncTask extends AsyncTask<Void, Void, Void> 

    @Override
    protected Void doInBackground(Void... params) 
        return null;
    

    @Override
    protected void onPostExecute(Void result) 
        try 
            Log.i(TAG, "I'm holding the UI back.");
            Thread.sleep(3000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        super.onPostExecute(result);
    


即使您发布到处理程序,它也会接受任务并持有它。直到我将它添加到线程中,UI 才按时打印:

@Override
protected void onPostExecute(Void result) 
    new Thread(new Runnable() 

        @Override
        public void run() 
            try 
                Log.i(TAG, "I'm NOT holding the UI back.");
                Thread.sleep(3000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    ).start();
    super.onPostExecute(result);

故事的寓意,不要做任何可能占据主线的事情。而且我也不是说你应该在 onPostExecute 中创建一个线程,如果你不以某种方式管理该线程,那将是一种不好的做法。

【讨论】:

这也不起作用。由于某种原因,它仍在等待 AsyncTask 完成。我将尝试等待 UI 绘制(使用 GlobalLayoutListener)然后执行 AsyncTask。如果可行,将发布完整的解决方案! 那么你到底做了什么?我所解释的只是 onPostExecute 推迟了 UI,这就是你所面临的,你必须在 doInBackground 中运行所有资源密集型的东西。另外,请注意,如果您在处理后等待某种 UI 更新,很明显它不会出现。 所以我的onPostExecute() 执行时间不到 90 毫秒。我知道 onPostExecute() 在 UI 线程上运行,但是,问题是我的 UI 应该在 doInBackground() 之前绘制很久,因此运行 onPostExecute()。相反,我所做的是添加OnGlobalLayoutListener。在onGlobalLayout() 回调中,我执行AsyncTask。通过这样做,我确保 UI 被绘制并且不等待 AsyncTask。这当然是一种解决方法。当我有机会时,我需要弄清楚为什么如果在 onCreate() 中调用了 AsyncTask,那么 UI 会等待它完成。 这个问题解释了如何创建一个 GlobalLayoutListener:***.com/questions/7733813/… 嗯,问题是 UI 不绘制的原因。这里 onPostExecute 以某种方式获得了主线程,它让它等待。另外,请记住 onCreate 在绘制任何内容之前运行,队列可能允许任务在进入下一阶段之前运行。 (刚刚确认,onStart 和 onResume 正在执行)

以上是关于在 AsyncTask 完成之前不会绘制 UI的主要内容,如果未能解决你的问题,请参考以下文章

让一个 AsyncTask 等待另一个完成?

AsyncTask onPostExecute()有时不会被调用

等待 Google 在 AsyncTask 中放置照片请求

如何在 Android Studio 中使用 AsyncTask 从可绘制对象中设置图像

Android AsyncTask get()形成另一个AsyncTask()

如何在屏幕旋转期间处理 AsyncTask?