由于异步 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 并更新AsyncTaskonPostExecute() 方法上的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 方法来更新视图,这会将可运行对象排入主线程消息队列中。 您可以在 ServiceIntentService 中运行长时间运行的操作,并通过 ContentProviderBroadcasts。

希望有帮助。

【讨论】:

【参考方案7】:

建议:

    从 UI 主线程中移除繁重的工作,在其他线程中执行它们。

2.当其他线程完成繁重的工作后,如果需要更新UI,使用Handler将msg发送到UI主线程,然后在主线程更新UI。

【讨论】:

以上是关于由于异步 Firebase 调用,主线程做了太多工作?的主要内容,如果未能解决你的问题,请参考以下文章

应用程序可能在其主线程上做了太多工作

Flutter WebView 跳过了 30 帧!应用程序可能在其主线程上做了太多工作

Logcat 说 - 应用程序可能在其主线程上做了太多工作,并且错误消息说 - StringtoReal.invalidReal(string.boolean)line:63 [关闭]

从 Sqlite 游标创建 Pojo 类花费了太多时间

Firebase ios sdk 异步和线程

SQLite 做了太多的小尺寸磁盘读取