从AsyncTask管理ProgressDialog的最佳方法

9
我希望在我的应用程序中使用AsyncTask来管理一些业务逻辑。对于在单独的文件中定义的AsyncTask的onProgressUpdate(...)方法,最佳模式是什么?
我有两个想法:
1. 最简单的方法:在Activity中创建ProgressDialog(使用onCreateDialog(...)方法),并通过构造函数将引用传递给我的AsyncTask子类(在我的AsyncTask子类中覆盖onProgressUpdate(...))。这种解决方案的缺点是在业务逻辑代码中使用UI组件。

FooTask1.java:

public class FooTask1 extends AsyncTask<Void, Integer, Void> {
private ProgressDialog mProgressDialog;

public FooTask1(ProgressDialog progressDialog) {
    super();
    mProgressDialog = progressDialog;
}

@Override
protected Void doInBackground(Void... unused) {
    // time consuming operation
    for (int i=0; i<=100; i++) {
        this.publishProgress(i);
        try {
            Thread.sleep(100);
        } catch (Exception e) {}
    }
    return null;
}

@Override
protected void onProgressUpdate(Integer... progress) {
    mProgressDialog.setProgress(progress[0]);
}

@Override
protected void onPostExecute(Void result) {
    mProgressDialog.dismiss();
}
}

FooActivity1.java:

public class FooActivity1 extends Activity {

  private static final int DIALOG_PROGRESS_ID = 0;
  private ProgressDialog mProgressDialog;

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);

      showDialog(DIALOG_PROGRESS_ID);
      new FooTask(mProgressDialog).execute();
  }

  @Override
  protected Dialog onCreateDialog(int id) {
      switch(id) {
          case DIALOG_PROGRESS_ID:
             mProgressDialog = new ProgressDialog(this);
             mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
             mProgressDialog.setMessage("Loading...");
             mProgressDialog.setCancelable(false);
             return mProgressDialog;
          default:
             return null;
      }
  }
}

2. 更为复杂的方法:在Activity类中重写AsyncTaskonProgressUpdate(...)方法:

FooTask2.java:

public class FooTask2 extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... unused) {
    // time consuming operation
    for (int i=0; i<=100; i++) {
        this.publishProgress(i);
        try {
            Thread.sleep(100);
        } catch (Exception e) {}
    }
    return null;
}
}

FooActivity2.java

public class FooActivity2 extends Activity {

private static final int DIALOG_PROGRESS_ID = 0;
private ProgressDialog mProgressDialog;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    showDialog(DIALOG_PROGRESS_ID);
    new FooTaskLoader().execute();
}

@Override
protected Dialog onCreateDialog(int id) {
    switch(id) {
        case DIALOG_PROGRESS_ID:
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setCancelable(false);
            return mProgressDialog;
        default:
            return null;
    }
}

private class FooTaskLoader extends FooTask2 {
    @Override
    protected void onProgressUpdate(Integer... progress) {
        mProgressDialog.setProgress(progress[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        dismissDialog(DIALOG_PROGRESS_ID);
    }
}
}

你能详细说明一下为什么不想将AsyncTask实现为内部类吗? - yorkw
2
通过将UI与逻辑分离,提高FooTask类的可测试性和可重用性。 - tludek
2个回答

13
我更愿意将业务逻辑与AsyncTask隔离,而不是将AsyncTask与Activity隔离。通常情况下,AsyncTask 在 Android 应用程序生命周期中具有非常特定的设计和用例,即在后台线程中运行一些耗时的任务,一旦完成,则在 UI 线程中更新 Activity 的视图。这就是为什么推荐将其作为 Activity 的内部类使用。我认为更 OO 的设计是将业务逻辑隔离和集中到 POJO 中(以便重用)。为了进行测试,您可以执行以下操作:
1. 定义接口 IBusinessDAO
2. 定义 RealBusinessDAO 实现 IBusinessDAO
3. 定义 MockBusinessDAO 实现 IBusinessDAO
4. 在 AsyncTask.doInBackground() 中调用 IBusinessDAO.foo();
对于您的业务逻辑进行单元测试时,由于它是一个 POJO,您可以使用纯 JUnit 编写您的测试用例。有时我们想要测试 UI 组件,而实际上并不关心底层业务逻辑的实现方式,例如,我的业务逻辑连接到远程 HTTP 服务器下载一些 JSON 数据,当我只想测试 UI 布局时,我不想每次都这样做。对于这种情况,我可以很容易地更改我的 Activity,使用 MockBusinessDAO(类似于 Spring 的 DI 概念)。
public class MyActivity extends Activity {
  IBusinessDAO businessDAO;

  ... ...

  private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    ... ...        

    protected void doInBackground(Void... params) {
      businessDAO.foo();
    }
  }

  ... ...

  public void onCreate(Bundle savedInstanceState) {
    if (runInTest)
      businessDAO = new MockBusinessDAO();
    else
      businessDAO = new RealBusinessDAO();

    new myAsyncTask().execute();
  }


}

做这些的一些优点是:
1. AsyncTask的实现简单清晰(doInBackground()中只需几行代码)
2. 业务逻辑实现纯粹是POJO,提高了可重用性。
3. 隔离测试业务逻辑和UI组件,提高了可测试性。

希望有所帮助。


感谢您提供全面的答案,我已经采用了您的建议。我的改进在于在BusinessDAO类内部实现了进度更新通知机制。 - tludek
这是一个很好的解决方案,yorkw。我现在将研究POJO。我认为它是一种标准化的对象设计方式,因此可以通过像JUnit这样的工具进行测试,就像你提到的那样? - wired00
在下面的链接中,我以类似的方式使用示例代码。 http://www.smartphonebysachin.blogspot.in/2012/11/how-to-return-value-from-async-task-in.html - Sachin Shelke

1
  1. 第一种解决方案可能是我处理它的方式 - 这是Android框架的方式。对于这个解决方案的一个变化(如果AsyncTask无法适应Activity类),我会将Context作为参数传递,然后在onPreExecute中实例化并显示ProgressDialog

  2. 第二种解决方案基本上与将对话框创建为内部类相同 - 因此,如果您选择这个解决方案,也可以这样做。


好的,谢谢你的回答。第一个解决方案确实更符合Android框架的方式,但是这种方法的可测试性如何呢?我必须模拟ProgressDialog类... - tludek
无论您选择哪种解决方案,您都必须这样做,不是吗? - kaspermoerch
1
如果我们选择第二种解决方案,就可以在不使用模拟ProgressDialog的情况下测试FooTask2类。 - tludek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接