这是在一次Android采访中被问到的。我被问到是否可以从异步任务1的doInBackground()方法启动另一个异步任务(让它成为Task2)(让它成为Task1)。我已经阅读了以下文档:
必须在UI线程上创建任务实例。
必须在UI线程上调用execute(Params ...)。
根据这些陈述,我认为不应该从另一个任务的后台方法启动任务。此外,异步任务有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()方法启动第二个任务。 我想对这里发生的事情提供一些帮助。感谢。
答案 0 :(得分:14)
让我们将您的代码更改为以下内容:
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 2
执行结束后执行Task 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
方法时,它会提供一个新的Runnable
到mTask
,这是ArrayDeque<Runnable>
类的一个实例,它作为不同线程上不同请求的序列化程序{ {3}}。如果没有执行Runnable
(即if (mActive == null)
),则会调用scheduleNext()
,否则scheduleNext()
块中的finally
将在(出于任何原因)当前执行的结束Runnable
。所有Runnable
都在THREAD_POOL_EXECUTOR
的单独线程上执行。
从其他线程执行AsyncTask有什么问题?从Jelly Bean开始,AsyncTask
在UI线程的应用程序启动时加载了类,因此保证在UI线程上发生回调,但是,在Jelly Bean发布之前,如果另一个线程创建了AsyncTask
,则回调可能不会出现在正确的线程上。
因此,应仅在Jelly Bean之前的平台( [more info]和+)上从UI线程调用AsyncTask
实现。
澄清:请考虑以下示例,该示例简单说明了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)
答案 1 :(得分:-1)
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());
}
}
}
}