异步任务 Android 执行

9 浏览
0 Comments

异步任务 Android 执行

这是一个Android面试中的问题。我被问到是否可以从AsyncTask 1的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()方法来启动第二个任务。

我希望在这里得到一些帮助。谢谢。

0
0 Comments

在这段代码中,出现了一个Async task Android execute的问题。这个问题的原因是在点击loadButton时,会执行LoadIconTask的execute方法,但是在这个方法中,没有传入参数。这导致了LoadIconTask的doInBackground方法中的resId数组为空,从而导致了BitmapFactory.decodeResource方法出现错误。

解决这个问题的方法是在LoadIconTask的execute方法中传入参数,即传入R.drawable.cheetah作为参数。

以下是整理后的文章:

在这段代码中,我们定义了一个MainActivity类,继承自Activity。在onCreate方法中,我们设置了布局文件,并通过findViewById方法获取了一些控件的引用,如ImageView和ProgressBar。同时,我们还为loadButton和otherButton设置了点击事件的监听器。

在LoadIconTask类中,我们定义了一个异步任务。在这个任务中,我们在onPreExecute方法中设置了ProgressBar的可见性为VISIBLE,在doInBackground方法中通过BitmapFactory.decodeResource方法获取了一张图片,并且模拟了一个耗时的操作。在这个操作中,我们通过sleep方法使线程休眠了一段时间,并在每次休眠结束后通过publishProgress方法更新了进度条。最后,在onPostExecute方法中,我们将ProgressBar的可见性设置为INVISIBLE,并将获取到的图片设置为ImageView的图片。

然而,这段代码中存在一个问题,即在点击loadButton时,调用了LoadIconTask的execute方法,但是没有传入任何参数。这导致了LoadIconTask的doInBackground方法中的resId数组为空,从而导致了BitmapFactory.decodeResource方法出现错误。

要解决这个问题,我们需要在LoadIconTask的execute方法中传入参数,即将R.drawable.cheetah作为参数传入。这样,在doInBackground方法中就可以正确地获取到图片资源了。

以下是修改后的代码:

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() {
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "I'm Working",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
    class LoadIconTask extends AsyncTask<Integer, Integer, Bitmap> {
        protected void onPreExecute() {
            mProgressBar.setVisibility(ProgressBar.VISIBLE);
        }
        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;
        }
        protected void onProgressUpdate(Integer... values) {
            mProgressBar.setProgress(values[0]);
        }
        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());
            }
        }
    }
}

0
0 Comments

在上面的代码中,首先定义了一个Task1类和一个Task2类,它们都是AsyncTask的子类。Task1中的doInBackground()方法中创建了一个Task2实例,并调用了task.execute()方法来执行Task2。在Task1的doInBackground()方法中,还使用Thread.sleep()方法来模拟一个5秒钟的等待时间。最后,Task2的doInBackground()方法打印了一些日志信息。

运行上述代码后,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秒钟)才开始执行的。这意味着第二个任务只有在第一个任务完成后才会启动。

为什么会出现这种情况呢?原因在于AsyncTask的源代码中的execute()方法和scheduleNext()方法。在execute()方法中,它将一个新的Runnable对象提供给mTask,而mTask是一个ArrayDeque类的实例,用于在不同的线程上串行执行不同的请求。如果没有正在执行的Runnable(即if (mActive == null)),则会调用scheduleNext()方法。如果有正在执行的Runnable,则会在当前Runnable执行结束后,通过finally块调用scheduleNext()方法。所有的Runnable都由THREAD_POOL_EXECUTOR在一个单独的线程上执行。

那么,为什么从其他线程执行AsyncTask会出错呢?从Jelly Bean开始,AsyncTask在应用程序启动时在UI线程上进行类加载,因此回调方法都保证在UI线程上执行。然而,在Jelly Bean之前的版本中,如果另一个线程创建了AsyncTask,回调方法可能不会在正确的线程上执行。

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

在上述代码中,我们可以看到Task1是在一个后台线程中创建并执行的。如果想要验证它是否在ICS上正常工作,请尝试在Task2的onPostExecute()方法中更改一个TextView的文本。在Jelly Bean之前的版本中,这种情况下是无法更改UI的。

此外,在Android 5.1上运行上述代码是没有问题的,但在Android 2.3上会崩溃并抛出一个异常。这是因为在Android 2.3中,不能在没有调用Looper.prepare()的线程中创建Handler,否则会抛出异常。

需要注意的是,AsyncTask现在已经被弃用了。

0