由于异步 Firebase 调用,主线程做了太多工作?
Posted
技术标签:
【中文标题】由于异步 Firebase 调用,主线程做了太多工作?【英文标题】:Main thread doing too much work because of asynchronous Firebase calls? 【发布时间】:2016-09-28 16:02:47 【问题描述】:我的应用程序不断收到错误消息,上面写着 I/Choreographer: Skipped 252 frames! The application may be doing too much work on its main thread.
我认为这会导致我的 UI 出现一些我不想要的延迟。我认为这是因为当我执行 Firebase 查询时,当我执行 onDataChange()
时,它似乎总是在主 UI 线程中执行。我有大约 5 个与下面类似的 Firebase 查询。结果,我尝试将我的代码从onDataChange()
方法移动到AsyncTask
并更新AsyncTask
的onPostExecute()
方法上的UI 线程。但是,当我尝试这个时,onPostExecute()
方法永远不会完成。这是我的尝试:
public void getPublicPosts(final View progressOverlay, final View fragmentView, final Context context)
//Need to do order by / equal to.
Firebase postsRef = firebaseRef.child("Posts");
Query query = postsRef.orderByChild("privacy").equalTo("Public");
query.keepSynced(true);
query.addListenerForSingleValueEvent(new ValueEventListener()
@Override
public void onDataChange(DataSnapshot dataSnapshot)
for (final DataSnapshot postSnapShot : dataSnapshot.getChildren())
AsyncTask task = new AsyncTask<URL, Integer, Long>()
@Override
protected Long doInBackground(URL... params)
Post post = postSnapShot.getValue(Post.class);
List<Post> publicPosts = application.getPublicAdapter().getPosts();
if (post.getPrivacy().equals("Public") && application.getPublicAdapter().containsId(publicPosts, post.getId()) == null)
application.getPublicAdapter().getPosts().add(0, post);
return null;
@Override
protected void onProgressUpdate(Integer... progress)
@Override
protected void onPostExecute(Long result)
System.out.println("Finished executing public");
populateNewsFeedList(fragmentView, application.getPublicAdapter(), TabEnum.Public, context);
if (progressOverlay.getVisibility() == View.VISIBLE)
System.out.println("getPublicPosts: DONE");
androidUtils.animateView(progressOverlay, View.GONE, 0, 200);
fragmentView.findViewById(R.id.rv_public_feed).setVisibility(View.VISIBLE);
;
`task.execute();`
@Override
public void onCancelled(FirebaseError firebaseError)
);
对此的任何帮助都会有所帮助。如果有人可以帮助我,那就太好了。谢谢!
编辑:添加函数以创建AsyncTask
public AsyncTask asyncTaskWrapper(final DataSnapshot dataSnapshot, final View progressOverlay, final View fragmentView, final Context context)
AsyncTask task = new AsyncTask<URL, Integer, Long>()
@Override
protected Long doInBackground(URL... params)
for (final DataSnapshot postSnapShot : dataSnapshot.getChildren())
Post post = postSnapShot.getValue(Post.class);
List<Post> publicPosts = application.getPublicAdapter().getPosts();
if (post.getPrivacy() == PrivacyEnum.Public && application.getPublicAdapter().containsId(publicPosts, post.getId()) == null)
application.getPublicAdapter().getPosts().add(0, post);
return null;
@Override
protected void onProgressUpdate(Integer... progress)
@Override
protected void onPostExecute(Long result)
System.out.println("Finished executing public");
TabsUtil.populateNewsFeedList(fragmentView, application.getPublicAdapter(), TabEnum.Public, context);
if (progressOverlay.getVisibility() == View.VISIBLE)
System.out.println("getPublicPosts: GONE");
AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
fragmentView.findViewById(R.id.rv_public_feed).setVisibility(View.VISIBLE);
;
return task;
PublicPosts 功能:
public void getPublicPosts(final View progressOverlay, final View fragmentView, final Context context)
//Need to do order by / equal to.
Firebase postsRef = firebaseRef.child("Posts");
Query query = postsRef.orderByChild("privacy").equalTo(PrivacyEnum.Public.toString());
query.keepSynced(true);
query.addListenerForSingleValueEvent(new ValueEventListener()
@Override
public void onDataChange(DataSnapshot dataSnapshot)
asyncTaskWrapper(dataSnapshot, progressOverlay, fragmentView, context);
@Override
public void onCancelled(FirebaseError firebaseError)
TabsUtil.populateNewsFeedList(fragmentView, application.getPublicAdapter(), TabEnum.Public, context);
);
【问题讨论】:
不是在for循环中实现AsyncTask,而是单独定义它,然后从for循环中执行,这样它就可以安全地运行在另一个线程上。 @Vickyexpert 嗨,我明白你想说什么,但我似乎无法找到一种方法来实例化AsyncTask
一次而不重新创建多次。我已经在原帖中发布了我的尝试。
什么是application.getPublicAdapter()??我认为跳帧的一个原因可能是 application.getPublicAdapter().getPosts().add(0, post) 多次触发 notifyDataSetChanged。这也可能是您的应用程序退出的原因,因为无法在后台线程中调用 notifyDataSetChanged。
¿ 有必要在主线程中显示进度吗?我认为 Asynctask 仅在 doInBackground() 函数的后台执行。因此,它将是更高效的线程和处理程序系统。
【参考方案1】:
您的主线程可能很慢,因为您正在运行一个包含许多对象的循环。但是,不知道您到底在执行什么,这只是一个猜测。
但我对您的 AsyncTask 有疑问,我认为它无法与您发布的代码一起使用。
AsyncTask 需要一个 URL 数组。你没有通过任何。如果不需要 URL 作为输入,则只需使用:
AsyncTask task = new AsyncTask<Void, Boolean, Boolean>()
您尝试故障排除如何:
AsyncTask task = new AsyncTask<URL, Boolean, Boolean>()
@Override
protected Boolean doInBackground(URL... params)
for (final DataSnapshot postSnapShot : dataSnapshot.getChildren())
Post post = postSnapShot.getValue(Post.class);
List<Post> publicPosts = application.getPublicAdapter().getPosts();
if (post.getPrivacy() == PrivacyEnum.Public && application.getPublicAdapter().containsId(publicPosts, post.getId()) == null)
application.getPublicAdapter().getPosts().add(0, post);
return true;
@Override
protected void onPostExecute(Boolean result)
if(result)
System.out.println("Finished executing public");
TabsUtil.populateNewsFeedList(fragmentView, application.getPublicAdapter(), TabEnum.Public, context);
if (progressOverlay.getVisibility() == View.VISIBLE)
System.out.println("getPublicPosts: GONE");
AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
fragmentView.findViewById(R.id.rv_public_feed).setVisibility(View.VISIBLE);
;
然后通过触发执行任务:
URL[] urls = new URL[2];
urls[0] = new URL(...);
urls[1] = new URL(...);
task.execute(urls);
如果代码失败,请使用 IDE 中的调试功能并回发确切的位置。
【讨论】:
【参考方案2】:您没有在您创建的 AsyncTask 上调用 execute(),因此它永远不会被触发。
创建 AsyncTask 后,在其上调用 execute(URL...params) 以便执行。
我还看到你没有使用你传递的参数,所以你可以什么都不传递,它会起作用。
【讨论】:
嗯,我在创建AsyncTask
后调用了 execute()
,但我的应用程序现在刚刚退出。任何想法为什么?【参考方案3】:
你最好在 IntentService 上做网络请求。这很容易并防止界面冻结和/或“应用程序可能在其主线程上做太多工作”。
看看:
IntentService on Android Developer Documentation
【讨论】:
【参考方案4】:在您的代码中,您调用 AsynTast n 次更好,您应该调用一次 for:过去的每个循环都应该在 doinbackground() 中,并且在 onProgressUpdate() 中,您可以更新 UI 而不是 onPostExecute(),这将改进代码一点但不完全,您应该在 onPreexecute 上使用进度条并在 onPostEcecute() 关闭进度条,只是为了您的想法,我编写了下面的代码,但不要完全引用它,因为我没有测试这个,但是形成这个会有一些想法。
AsyncTask task = new AsyncTask<URL, Integer, Long>()
@Override
protected Long doInBackground(URL... params)
for (final DataSnapshot postSnapShot : dataSnapshot.getChildren())
Post post = postSnapShot.getValue(Post.class);
List<Post> publicPosts = application.getPublicAdapter().getPosts();
if (post.getPrivacy().equals("Public") && application.getPublicAdapter().containsId(publicPosts, post.getId()) == null)
application.getPublicAdapter().getPosts().add(0, post);
publishProgress((1);
return null;
@Override
protected void onProgressUpdate(Integer... progress)
System.out.println("Finished executing public");
populateNewsFeedList(fragmentView, application.getPublicAdapter(), TabEnum.Public, context);
if (progressOverlay.getVisibility() == View.VISIBLE)
System.out.println("getPublicPosts: DONE");
AndroidUtils.animateView(progressOverlay, View.GONE, 0, 200);
fragmentView.findViewById(R.id.rv_public_feed).setVisibility(View.VISIBLE);
@Override
protected void onPostExecute(Long result)
;
【讨论】:
嘿,我尝试按照您的建议进行操作,但不幸的是效果不佳。还有其他想法吗?【参考方案5】:如果您创建了一个扩展 AsyncTask 的类并在您的 ondatachange 方法中像这样调用它,那将会更加简洁和易于维护:
DoingStuff doTask = new DoingStuff();
doTask.execute(...);
这样,您可以在工作线程中安全地执行 Firebase 连接内容,并使用您检索到的任何结果更新您的 UI。
【讨论】:
【参考方案6】:您可以尝试以下方法:
无需创建和执行多个 N AsyncTask,只需创建并执行一个包含 N 个快照循环的单个任务。 在 onPostExecute() 中检查 populateNewsFeedList 方法是否没有进行密集的数据操作,如果是这样,您也可以在新的 AsyncTask 中执行它并在完成后更新 UI。 您还可以尝试通过将可运行对象传递给post 方法来更新视图,这会将可运行对象排入主线程消息队列中。 您可以在 Service 或 IntentService 中运行长时间运行的操作,并通过 ContentProvider 或 Broadcasts。希望有帮助。
【讨论】:
【参考方案7】:建议:
-
从 UI 主线程中移除繁重的工作,在其他线程中执行它们。
2.当其他线程完成繁重的工作后,如果需要更新UI,使用Handler将msg发送到UI主线程,然后在主线程更新UI。
【讨论】:
以上是关于由于异步 Firebase 调用,主线程做了太多工作?的主要内容,如果未能解决你的问题,请参考以下文章
Flutter WebView 跳过了 30 帧!应用程序可能在其主线程上做了太多工作
Logcat 说 - 应用程序可能在其主线程上做了太多工作,并且错误消息说 - StringtoReal.invalidReal(string.boolean)line:63 [关闭]