【问题标题】:Android robust threading Architecture? Asynctask vs thread/handler vs service/threadAndroid 强大的线程架构? Asynctask vs 线程/处理程序 vs 服务/线程
【发布时间】:2014-07-09 07:29:34
【问题描述】:

假设您正在为应用程序设计线程架构 -> 主要目的是您的应用程序将有很多任务需要在后台线程上完成,有时还需要在 UI 线程上完成任务,或者不是(虽然更多时候,结果需要在 UI 线程上运行)。为简单起见,假设任务如下:下载文件并显示弹出窗口,登录用户并转到不同的页面,处理图像并将结果存储在数据库中(很多流行的任务应用程序)

我对细微差别进行了很多研究,但我真的很想深入解释/了解哪种架构更好,以及有哪些考虑因素。

以下是考虑的三个模型:

  1. AsyncTask 模型:每个操作(例如下载文件和显示弹出窗口)都是一个 AsyncTask,或者是抽象出通用功能的父类的派生类。
  2. 线程/处理程序模型:我总是创建一个new Handler(Looper.getMainLooper());,每次我需要执行任务时,我使用线程工厂来分离任务,处理程序位于 UI 线程(或任何自定义处理程序)上。
  3. 服务/线程模型:我使用一个通用的Service类,它负责基于一些操作代码的操作。有一堆 ServiceTask 派生对象做某些事情,但是当任务开始/完成时,Service 类与每个 ServiceTask 通信。

我稍微倾向于使用整个服务/线程模型,只是因为我读到了 AsyncTask/Threads 的一些非常尴尬的细微差别:

  • AsyncTask 有一个private static handler,如果类加载器在错误的时间调用它(例如在你的应用程序之前包含一个使用它的库),那么你所有的onPostExecute 将发生在错误的时间,因为您的处理程序不是主要处理程序
  • 很容易忘记检查onPostExecute 中的一堆东西,例如是否有配置更改,或者您的活动被破坏,或者在调用onPostExecute 时应用程序被后台/暂停(导致崩溃)
  • AsyncTask 更改了其在不同 API 上的串行/并行执行行为
  • 如果您使用线程/处理程序模型,在旧设备上,线程优先级实际上非常低。我听说有 1-15 的优先级,这样您的线程会自动获得低优先级,如果系统资源不足,您的线程将停止运行(而由于服务独立于您的活动运行线程优先级更高?)

设计一个不会轻易导致崩溃/意外行为同时保持良好性能的强大线程架构的最佳方法是什么??

如果这个问题太含糊,如果您需要实际代码,也请在 cmets 中告诉我(我害怕发布代码,因为它会使问题的长度变得比现在更夸张)。

【问题讨论】:

    标签: java android multithreading service android-asynctask


    【解决方案1】:

    您也可以查看 Needle;它是一个开源、简单但功能强大的 Android 多线程库。有了它,你可以说:

    Needle.onMainThread().execute(new Runnable() {
        @Override
        public void run() {
            // e.g. change one of the views
        }
    });
    

    Needle.onBackgroundThread().execute(new UiRelatedTask<Integer>() {
        @Override
        protected Integer doWork() {
            int result = 1+2;
            return result;
        }
    
        @Override
        protected void thenDoUiRelatedWork(Integer result) {
            mSomeTextView.setText("result: " + result);
        }
    });
    
    • 非常简单的 API
    • 固定线程池大小
    • 可自定义的线程池大小
    • 支持 UI 交互(“做工作,然后在 UI 线程上使用结果”)
    • 安卓1.5+
    • 在所有平台版本上表现相同

    在 GitHub 上查看:https://github.com/ZsoltSafrany/needle

    【讨论】:

      【解决方案2】:

      我认为 AsyncTask 是用于列出目的的好工具。但它需要包装 AsyncTask 以便于使用。我的这种包装的变体(带有进度指示器)如下:

      用于在应用程序活动中扩展它的主类 AsyncActivity:

      public abstract class AsyncActivity extends Activity{
      // Поле нужно обязательно объявить как статическое!
      private static AsyncConnect asyncConnect =  null;
      
      protected void runBackgroundTask(String progressInscription, RequestTask task){
          asyncConnect = new AsyncConnect(this, responseListener, progressInscription, task);
          asyncConnect.execute();
      }
      
      protected abstract void onBackgroundTaskEnd(boolean result);
      
      @Override
      protected void onResume(){
          super.onResume();
      
          // Перерегистрируем текущий контекст этой формы 
          // для корректной работы слушателя ответа с сервера
          responseListener.registerCurrentContext( this );
      
          if (asyncConnect != null){
              asyncConnect.onResume(this);
          }
      }
      
      @Override
      protected void onPause(){
          super.onPause();
      
          if (asyncConnect != null){
              asyncConnect.onPause();
          }
      }
      
      /**
       * Чтобы диалоги не вызывались из устаревшего контекста 
       * и по этой причине не исчезали при повороте экрана,
       * слушателя ответа с сервера необходимо сделать статическим полем класса,
       * в котором должен быть зарегистрирован текущий контекст
       */
      private static final OnServerResponseListener responseListener = new OnServerResponseListener(){
          private AsyncActivity context = null;
      
          @Override
          public void registerCurrentContext(AsyncActivity context){this.context = context; }
      
          @Override
          public void onResponse(boolean result){
              // Если никакой контекст не был зарегистрирован, ничего не делаем
              if (context == null) return;
      
              // Освождаем статическое поле для сборщика мусора (но делать это не обязательно!)
              asyncConnect = null;
      
              // Вызываем колбэк о завершении фоновой задачи
              context.onBackgroundTaskEnd(result); 
          }
      };
      }
      

      附加类和一对接口:

      public class AsyncConnect {
      private final Activity context;
      private final RequestTask task;
      private final String progressInscription;
      private final OnServerResponseListener responseListener;
      private boolean isDone = false;
      
      private ProgressDialog progressDialog;
      
      public AsyncConnect(Activity context, OnServerResponseListener responseListener,
              String progressInscription, RequestTask task){
          this.context = context;
          this.task = task;
          this.progressInscription = progressInscription;
          this.responseListener = responseListener;
      
          progressDialog = null;
      
          isDone = false;
      }
      
      public void execute(){
          if (isDone) return;
      
          new ConnectTask().execute();
      }
      
      public void onPause(){
          if (isDone) return;
      
          if (progressDialog != null){
              if (progressDialog.isShowing()){
                  progressDialog.dismiss();
                  progressDialog = null;
              } 
          }
      }
      
      public void onResume(Activity context){
          if (isDone) return;
      
          progressDialog = ProgressDialog.show( context, null, (CharSequence)progressInscription, 
              true, false);
      }
      
      private class ConnectTask extends AsyncTask<Object, Void, Boolean> {
      
          @Override
          protected void onPreExecute( ) {
              super.onPreExecute();
      
              progressDialog = ProgressDialog.show( context, null, 
                  (CharSequence)progressInscription, true, false);
          }
      
          @Override
          protected Boolean doInBackground(Object... messages) {
              return task.call();
          }
      
          @Override
          protected void onPostExecute(Boolean result) {
              super.onPostExecute(result);
      
              if (progressDialog != null){
                  if (progressDialog.isShowing()){
                      progressDialog.dismiss();
                      progressDialog = null;
                  } 
              }
      
              // Делаем невозможным повторное использование этого объекта
              isDone = true; 
      
              responseListener.onResponse(result);
          }
      }
      
      }
      
      public interface OnServerResponseListener {
          public void registerCurrentContext(AsyncActivity context);
          public void onResponse(boolean result);
      }
      
      public interface RequestTask {
          public boolean call();
      }
      

      使用 AsyncActivity 我们只需要调用 runBackgroundTask 并在目标活动中实现 onBackgroundTaskEnd 即可。可以根据这个想法创建不同类型的 AsyncTask 包装。

      【讨论】:

        【解决方案3】:

        我认为您不会在这里找到一种万能的方法。

        • 下载文件?使用DownloadManager
        • 登录用户并转到下一个屏幕? AsyncTask 可能是最好的。
        • 处理图像并存储它? Service 在这里可能是一个不错的选择,因为您不希望将操作附加到任何特定的 Activity
        • Handlers 更棘手,如果它们附加到在后台线程上运行的 Looper,您需要在完成后调用 Looper 上的 quit()。当您需要延迟操作时,处理程序很好,postDelayed() 非常适合。当您需要从后台线程与 UI 线程进行通信时,它们也很适用。

        但是,你说得对,正如你提到的,每个人都有陷阱。 Android 是一头复杂的野兽,似乎他们可以做得更好,防止开发人员自爆,尤其是在 Activity 被销毁后调用 AsyncTask 方面!

        【讨论】:

        • 我认为您的答案对于一个较小且相对易于由几个人管理的代码库是正确的,但是如果有大约 50 个任务,您是否仍会选择为每个任务使用不同的机制一?还是您更愿意开发和使用通用架构(也易于测试的架构)?
        • 我怀疑通用架构是否适用于所有情况,服务和线程非常不同,可以满足不同的需求。如果您担心崩溃,您可能需要使用更安全的包装 AsyncTask 类,例如此处推荐的 androiddesignpatterns.com/2013/04/…
        【解决方案4】:

        我通过创建从 Java 的 Thread 派生的类(我称之为 ThreadRunner)来使用 Java 的老派方法。构造函数看起来像:

        public ThreadRunner (Object [] params, AbstractCallback callBack) {...}
        

        AbstractCallback 是一个实现单个“onCall”方法的类,主要用于向调用方通知“任务执行完成”等事件。

        我用它从 Internet 获取内容并运行其他耗时的操作。它没有引起任何问题并且按预期工作。

        但是,我多次听说 AsyncTask 是一种类似 Android 的处理方式。我不知道为什么,也没有任何改变的打算,因为我在鼓吹“如果没有损坏就不要修复它”的方法。

        我还看到您需要使用 AsyncTask 编写更少的代码,但是在我使用传统 Java 威胁的方法中,编码量也很小,所以我认为这只是您个人喜好的问题,并且经验。

        关于您的第三种方法 - 我认为您应该在编写始终运行、侦听请求且永不停止的服务时使用它。当您只需要异步执行单个任务时,应使用 Java Threads 或 AsyncTask。

        【讨论】:

          猜你喜欢
          • 2014-08-30
          • 1970-01-01
          • 2017-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多