【问题标题】:Main thread doing too much work because of asynchronous Firebase calls?由于异步 Firebase 调用,主线程做了太多工作?
【发布时间】: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() 函数的后台执行。因此,它将是更高效的线程和处理程序系统。

标签: java android multithreading android-asynctask firebase


【解决方案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】:

          建议:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-01-18
            • 1970-01-01
            相关资源
            最近更新 更多