在 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 onPostExecute()有时不会被调用
如何在 Android Studio 中使用 AsyncTask 从可绘制对象中设置图像