从Handler调用的AsyncTask将不会执行doInBackground。

12

我正在开发的应用程序使用后台线程通过API下载图像列表,然后在幻灯片中显示这些图像。

有一个后台任务(当前为AsyncTask),定期获取新的图像。

我没有收到任何关于线程错误等方面的错误消息,只是AsyncTask的第二个实例不会运行doInBackground方法。

以下是Activity中的一些代码:

private DownloadTask mDownloadTask = null;
private Handler mHandler;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if(mDownloadTask != null) {
                mDownloadTask.cancel(true);
            }
            mDownloadTask = new DownloadTask();
            mDownloadTask.execute((Void[]) null);
        }
    };

    mDownloadTask = new DownloadTask();
    mDownloadTask.execute((Void[]) null);
}

DownloadTask 的代码如下:

@Override
protected List<String> doInBackground(Void... voids) {
     // Download list of URLs from server, etc.
}

@Override
protected void onPostExecute(List<String> urls) {
    mHandler.sendEmptyMessageDelayed(111, 5000);
}

处理程序将被调用,AsyncTask中的onPreExecute也将被调用,并且DownloadTask(就在onCreate中)的初始运行也有效。

根据这个问题:Android SDK AsyncTask doInBackground not running (subclass),可能与SDK15有关。

感谢任何提示。


更新:由于我收到了评论,指出处理程序可能不在UI线程中(这很奇怪,因为Thread.currentThread在onCreate和处理程序的handleMessage方法中都是相同的),因此我修改了handleMessage方法:

mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(mDownloadTask != null) {
                    mDownloadTask.cancel(true);
                }
                mDownloadTask = new DownloadTask();
                mDownloadTask.execute((Void[]) null);
            }
        });
    }
};

仍然没有成功。


更新完整的DownloadTask类。

class DownloadTask extends AsyncTask<Void, Void, List<String>> {

    @Override
    protected void onPreExecute() {
        // Cancel the animation.
        if (mSlideshowAnimation != null) {
            mSlideshowAnimation.cancel(true);
        }

        mImageView1.setVisibility(View.GONE);
        mImageView2.setVisibility(View.GONE);
        animate(mProgressBar).alpha(1.0f).setDuration(500).start();

        Log.d(TAG, "Download preparation done.");
    }

    @Override
    protected List<String> doInBackground(Void... voids) {
        Log.d(TAG, "Download");
        SharedPreferences s = getSharedPreferences("access", Context.MODE_PRIVATE);
        String token = s.getString("token", null);

        Log.d(TAG, "Downloading slideshows.");

        List<String> urls = new ArrayList<String>();
        Slideshow[] slideshows = new Api(SlideshowActivity.this).getSlideshows(token);
        for (Slideshow slideshow : slideshows) {
            urls.addAll(slideshow.getAllPhotoUrls());
        }

        Log.d(TAG, "Downloading slideshows: " + slideshows.length);

        for (String url : urls) {
            try {
                url = Api.HOST + url;

                if (!Cache.fileExists(Cache.getCacheFilenameForUrl(SlideshowActivity.this, url))) {
                    Cache.cacheStream(SlideshowActivity.this, HttpHelper.download(SlideshowActivity.this, url), url);
                } else {
                    Log.d(TAG, "Cached: " + url);
                }
            } catch (IOException e) {
                Log.e(TAG, "Error while downloading.", e);
            }
        }

        Log.d(TAG, "Downloading slideshows finished.");

        return urls;
    }

    @Override
    protected void onPostExecute(List<String> urls) {
        Log.d(TAG, "download successful");
        animate(mProgressBar).alpha(0.0f).setDuration(500).start();

        mCurrentImageIndex = -1;
        mImageUrls = urls;

        mSlideshowAnimation = new SlideshowAnimation();
        mSlideshowAnimation.execute((Void[]) null);

        mHandler.sendEmptyMessageDelayed(111, 5000);
    }
}

一个AsyncTask对象只能被执行一次。因此,请尝试更改您的代码,而不是直接使用AsyncTask类的对象,而是将其作为new DownloadTask().execute()使用。 - MKJParekh
@Frankenstein,这就是我在 mDownloadTask = new DownloadTask(); 两次中所做的事情吗? - Sebastian Roth
不要写.execute((Void[]) null),可以写成.execute() - Hauleth
可能是[Android SDK AsyncTask doInBackground不运行(子类)]的重复问题。(https://dev59.com/kWox5IYBdhLWcg3wk1Eg) - Heath Borders
旧的线程,但我认为这可能是解决方案:https://dev59.com/12vXa4cB1Zd3GeqPJ4XZ - Michael
显示剩余7条评论
2个回答

14

感谢与Waqas(谢谢!)的有益讨论,我终于发现了代码中的错误。实际上,以上所述都是正确的,并且按原样工作。我的问题在于第二个任务阻塞了第一个任务,反之亦然。

也许偶然在Google Groups上找到了这篇帖子:http://groups.google.com/group/android-developers/browse_thread/thread/f0cd114c57ceefe3?tvc=2&q=AsyncTask+in+Android+4.0。建议所有涉及线程的人仔细阅读此讨论。

AsyncTask将线程模型切换为串行执行程序(再次),这似乎与我的两个AsyncTasks的方法不兼容。

最后,我将“下载”处理切换为经典的Thread,并使用Handler发布消息以取消幻灯片播放,如果必要的话。使用处理程序的sendEmptyMessageDelayed,我会在一段时间后简单地重新创建下载线程以刷新数据。

感谢所有评论和答案。


你需要创建一个新的ThreadPoolExecutor实例,增加线程池大小,并调用executeOnExecutor来执行异步任务。现在你可以并行运行任务了。 - Vivek MVK

3
Handler 是 Android 中的一种线程,而 AsyncTask 也在不同的线程中运行。当您使用 AsyncTask 时,有几个规则必须遵循。

为了使此类正常工作,必须遵守以下几个线程规则:

任务实例必须在 UI 线程上创建。必须在 UI 线程上调用 execute(Params...)。不要手动调用 onPreExecute()、onPostExecute(Result)、doInBackground(Params...)、onProgressUpdate(Progress...)。任务只能执行一次(如果尝试进行第二次执行,将抛出异常)。

因此,显然 AsyncTask 必须从 UI 线程调用。而您将其从不是 UI 线程的 Handler 中调用...
还可以尝试这个。
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if(mDownloadTask != null) {
                mDownloadTask.cancel(true);
            }
           if([isCancelled()][1]){
            mDownloadTask = new DownloadTask();
            mDownloadTask.execute((Void[]) null);
          } // i assume your task is not getting cancelled before starting it again..
        }
    });
  }
};

同时文档中也说明了这一点..

Handler有两个主要用途:

(1) 安排消息和可运行对象在未来某个时间点执行;(2) 将操作排队以在不同于您自己的线程上执行。


当我在Handler.handleMessage和活动的onCreate函数中添加Log.d(TAG, "Thread: " + Thread.currentThread());时,我将收到THREAD: Thread[main,5,main] - Sebastian Roth
1
@Sandy所说的是,您当前使用来执行异步任务的线程是错误的线程,因为它是处理程序的线程。因此,一个解决方案,虽然不太美观但应该可以工作,就是使用runOnUiThread包装您的execute调用...或者摆脱处理程序调用execute(更好)。 - Snicolas
2
处理程序本身不是一种线程。它是一种机制,允许向其工作的线程提供消息和可运行对象。 - waqaslam
1
如果将其附加到UI线程,则会中断UI线程。在您的onCreate方法中添加以下代码new Handler().post(new Runnable() { @Override public void run() { try { Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } } });,它将使应用程序停止1分钟。 - waqaslam
2
你可以将处理程序附加到另一个 Looper - 与另一个线程连接的 looper。在这种情况下,它将在 UI 线程之外工作。当你在活动中声明 new Handler() 时,它会自动通知为 UI 线程工作,除非你提供一个不同的 Looper。 - waqaslam
显示剩余6条评论

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