【问题标题】:Async task Android execute异步任务Android执行
【发布时间】:2015-08-07 10:00:44
【问题描述】:

这是在一次 Android 采访中提出的。有人问我是否可以从异步任务 1(让它成为 Task1)的 doInBackground() 方法启动另一个异步任务(让它成为 Task2)。我浏览了文档,其中包含以下内容:

任务实例必须在 UI 线程上创建。

execute(Params...) 必须在 UI 线程上调用。

根据这些陈述,我认为不可能从另一个任务的后台方法启动一个任务。此外,异步任务具有 UI 方法(不能在后台线程上使用),因此加强了我的论点,我回答为不可能。

在查看一个简单的演示应用程序时,我发现确实可以这样做。 一些演示代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        init();
        Log.v ("gaurav", "Thread is : " + Thread.currentThread().getName());
        Task1 task = new Task1();
        task.execute();
    }

class Task1 extends AsyncTask {
    @Override
    protected Object doInBackground(Object... params) {
        // TODO Auto-generated method stub
        Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName());

        Task2 task = new Task2();

        task.execute();
        return null;
    }
}

class Task2 extends AsyncTask {
    @Override
    protected Object doInBackground(Object... params) {
        // TODO Auto-generated method stub
        Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName());

        Log.v ("gaurav", "Task 2 started");
        return null;
    }
}

我得到以下指示成功执行的日志:

> 08-07 09:46:25.564: V/gaurav(2100): Thread is : main 08-07
> 09:46:25.564: V/gaurav(2100): Thread task 1 is : AsyncTask #3 08-07
> 09:46:25.564: V/gaurav(2100): Thread task 2 is : AsyncTask #4 08-07
> 09:46:25.564: V/gaurav(2100): Task 2 started

我已经在 ICS、KK 和 L 设备上进行了检查,它适用于所有人。

我能想到的一个原因是我没有覆盖任何 UI 方法并在我的第二个任务中进行任何 UI 更新,因此它不会导致任何问题,但我不确定。即使是这样,它也违反了开发者指南中提到的线程规则。

作为参考,我也查看了此链接:Start AsyncTask from another AsyncTask doInBackground(),但答案表明使用 doInBackground() 中的 runOnUiThread() 方法启动第二个任务。 我想要一些关于这里发生的事情的帮助。谢谢。

【问题讨论】:

  • 我在面试中使用类似问题的另一个原因是确定候选人是否知道从 Honeycomb 向上破坏 AsyncTasks 中的线程行为变化:stackoverflow.com/questions/21165505/…
  • @adelphus,我不太明白“从HONEYCOMB开始,任务在单线程上执行,以避免并行执行导致的常见应用程序错误”的含义。它确实定义了串行执行,但是在我的问题的上下文中,嵌套任务仍然会让它们的 onPreExecute() / onPostExecute() 在后台线程上运行?或者正如下面的答案所说的关于应用程序启动时的类加载,在这种情况下,所有嵌套任务的 onPre/onPost 方法都在 UI 线程上运行,无论任务从哪里开始?
  • 这里有两个问题:我的评论是在谈论多个 AsyncTask 曾经以每个任务 1 个线程并行运行的事实。自 Honeycomb 以来,现在多个 AsyncTask 排队,因此它们都在单个线程上运行 - 一次只能运行 1 个。由于开发人员假设任务并行运行,因此更改破坏了很多代码。您关于 onPreExecute/onPostExecute 是否在 UI 线程上执行的问题是另一个问题,但 yaa110 在他的回答中很好地描述了它。

标签: android android-asynctask


【解决方案1】:

让我们将您的代码更改为以下内容:

class Task1 extends AsyncTask {
    @Override
    protected Object doInBackground(Object... params) {
        // TODO Auto-generated method stub
        Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName());

        Task2 task = new Task2();
        task.execute();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.v ("gaurav", "Log after sleeping");

        return null;
    }
}

class Task2 extends AsyncTask {
    @Override
    protected Object doInBackground(Object... params) {
        // TODO Auto-generated method stub
        Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName());

        Log.v ("gaurav", "Task 2 Started");
        return null;
    }
}

现在 LogCat 返回:

08-07 06:13:44.208    3073-3073/testapplication V/gaurav﹕ Thread is : main
08-07 06:13:44.209    3073-3091/testapplication V/gaurav﹕ Thread task 1 is : AsyncTask #1
08-07 06:13:49.211    3073-3091/testapplication V/gaurav﹕ Log after sleeping
08-07 06:13:49.213    3073-3095/testapplication V/gaurav﹕ Thread task 2 is : AsyncTask #2
08-07 06:13:49.213    3073-3095/testapplication V/gaurav﹕ Task 2 Started

如您所见,Task 2Task 1 执行结束后执行(即使在休眠 5 秒后)。这意味着在第一个任务完成之前不会开始第二个任务。

为什么? 原因在于source code of AsyncTask 的背后。请考虑execute() 方法:

public synchronized void execute(final Runnable r) {
    mTasks.offer(new Runnable() {
        public void run() {
            try {
                r.run();
            } finally {
                scheduleNext();
            }
        }
    });
    if (mActive == null) {
        scheduleNext();
    }
}

scheduleNext()方法:

protected synchronized void scheduleNext() {
    if ((mActive = mTasks.poll()) != null) {
        THREAD_POOL_EXECUTOR.execute(mActive);
    }
}

这些方法中最重要的关键字是synchronized,它确保这些方法只能同时在一个线程中运行。当您调用execute 方法时,它会为mTask 提供一个新的Runnable,它是ArrayDeque<Runnable> 类的一个实例,它充当不同线程 [more info] 上不同请求的序列化程序。如果没有执行Runnable(即if (mActive == null)),则将调用scheduleNext(),否则,将在(出于任何原因)当前执行的@ 结束之后调用finally 块中的scheduleNext() 987654343@。所有Runnables 都由THREAD_POOL_EXECUTOR 在单独的线程上执行。

从其他线程执行 AsyncTask 有什么问题? 从 Jelly Bean 开始,AsyncTask 在应用程序启动时在 UI 线程上进行类加载,从而保证回调发生但是,在 UI 线程上,在 Jelly Bean 发布之前,如果另一个线程创建 AsyncTask,则回调可能不会在正确的线程上发生。

因此,AsyncTask 实现应该仅在 Jelly Bean 之前的平台上从 UI 线程调用(++)。


澄清:请考虑以下示例,它只是澄清了 Android 不同平台版本之间的差异:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);

    new Thread() {
        @Override
        public void run() {
            Task1 task = new Task1();
            task.execute();
        }
    }.start();
}

class Task1 extends AsyncTask {
    @Override
    protected Object doInBackground(Object... params) {
        return null;
    }
}

它在 Android 5.1 上运行良好,但在 Android 2.3 上崩溃并出现以下异常:

08-07 12:05:20.736      584-591/github.yaa110.testapplication E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-8
    java.lang.ExceptionInInitializerError
            at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21)
     Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
            at android.os.Handler.<init>(Handler.java:121)
            at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
            at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
            at android.os.AsyncTask.<clinit>(AsyncTask.java:152)
            at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21)

【讨论】:

  • 根据您的评论,我推断这确实是可能的。唯一的一点是任务将被排队,因为默认情况下串行执行器用于运行任务。如果是这种情况,文档不应该指定这是可能的。 “必须”让我假设它会导致崩溃。
  • 上面的部分对我来说很清楚,它总结了Android会使用序列化程序mTask对请求进行排队。但是你提到这应该只在 API >= JellyBean 上完成的最后一部分有点棘手,因为我在 ICS 上测试了相同的代码。你能检查一下为什么这适用于 ICS 吗?或者它是一种可能不会每次都成功运行的“可能工作”场景?
  • @gauravjain 不幸的是,我无法访问 ICS(让我获得 ICS 的 Genymotion 模拟器)。但是,如果您想检查它是否在 ICS 上正常工作,请尝试在 Task 2onPostExecute() 方法中更改 TextViewtext。我认为,在这种情况下,不可能在 Jelly Bean 之前更改 UI。
  • 我检查了它并更新了任务 2 的 onPreExecute() 和 onPostExecute() 中的按钮文本。在 ICS 和 KK 上对其进行了测试,没有任何错误,这让我明白虽然任务 2 有从后台线程启动,它的 onPreExecute() / onPostExecute() 在 UI 线程上运行,无论调用如何(这也发生在 ICS 模拟器/设备上)。
  • @gauravjain 用于检查 UI 更新是否在 UI 线程上运行的测试不保证会被调用 - 有时它会工作,有时会抛出异常。这取决于 UI 的当前状态。尝试执行大量 UI 更新,看看它是否真的有效。
【解决方案2】:
public class MainActivity extends Activity {

    private final static String TAG = "ThreadingAsyncTask";
    private ImageView mImageView;
    private ProgressBar mProgressBar;
    private int mDelay = 500;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView = (ImageView) findViewById(R.id.imageView);;
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);

        final Button button = (Button) findViewById(R.id.loadButton);
        button.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                new LoadIconTask().execute(R.drawable.cheetah);
            }
        });
        final Button otherButton = (Button) findViewById(R.id.otherButton);
        otherButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "I'm Working",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }


    class LoadIconTask extends AsyncTask<Integer, Integer, Bitmap> {
        @Override
        protected void onPreExecute() {
            mProgressBar.setVisibility(ProgressBar.VISIBLE);
        }
        @Override
        protected Bitmap doInBackground(Integer... resId) {
            Bitmap tmp = BitmapFactory.decodeResource(getResources(), resId[0]);
            // simulating long-running operation
            for (int i = 1; i < 11; i++) {
                sleep();
                publishProgress(i * 10);
            }
            return tmp;
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            mProgressBar.setProgress(values[0]);
        }
        @Override
        protected void onPostExecute(Bitmap result) {
            mProgressBar.setVisibility(ProgressBar.INVISIBLE);
            mImageView.setImageBitmap(result);
        }
        private void sleep() {
            try {
                Thread.sleep(mDelay);
            } catch (InterruptedException e) {
                Log.e(TAG, e.toString());
            }
        }
    }
}

【讨论】:

  • 请在您的答案中添加描述(如果这是一个答案)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-12
  • 2017-08-21
  • 1970-01-01
  • 1970-01-01
  • 2018-08-18
  • 2016-10-23
  • 1970-01-01
相关资源
最近更新 更多