如何中断 AsyncTaskLoader 后台线程?
Posted
技术标签:
【中文标题】如何中断 AsyncTaskLoader 后台线程?【英文标题】:How to interrupt AsyncTaskLoader's background thread? 【发布时间】:2014-02-15 10:08:49 【问题描述】:当我调用cancelLoad()
时,有什么方法可以中断AsyncTaskLoader
的loadInBackground()
线程吗?我相信AsyncTask.cancel()
会这样做,但任务变量是私有的,无法访问。
【问题讨论】:
【参考方案1】:为此使用stopLoading()
或abandon()
或reset()
文章:https://plus.google.com/117981280628062796190/posts/8b9RmQvxudb
【讨论】:
它并没有说它会中断线程。 @androiddeveloper 看一下图表,X - 它通常用于标记线程的“停止/中断/关闭”。 似乎我已经检查了所有可用的方法(根据我很久以前写的):***.com/a/28794783/878126。您能否使用您所说的任何功能来展示一个示例,证明线程被中断并且不会继续它正在做的事情? @androiddeveloper 证明?!它的开源 - 只需导航到代码并检查 我不明白,但是我已经尝试了你提到的所有功能,再次,它们仍然没有中断加载器的线程,就像我 4 年前写的那样.他们不应该这样做,甚至没有写在您显示描述的文档上。我已经更新了我的答案,以包括如何做到这一点的工作示例。【参考方案2】:添加该功能相对简单,这是它的Gist:
public abstract class InterruptibleAsyncTaskLoader<D> extends AsyncTaskLoader<D>
private volatile Thread thread;
public InterruptibleAsyncTaskLoader(Context context)
super(context);
public boolean isLoadInBackgroundRunning()
return thread != null;
@Override
public void cancelLoadInBackground()
Thread t = thread;
if (t != null) t.interrupt();
@Override
public final D loadInBackground()
try
thread = Thread.currentThread();
return doLoadInBackground();
catch (InterruptedException e)
OperationCanceledException oce = new OperationCanceledException(e.toString());
oce.initCause(e);
throw oce;
finally
Thread.interrupted();
thread = null;
public abstract D doLoadInBackground() throws InterruptedException;
【讨论】:
【参考方案3】:我检查了可用的方法,似乎没有一个真正中断在后台运行的线程。
查看代码,似乎在后台使用了 AsyncTask。我试图找到一个可以用来中断 asyncTask 的“洞”,但我找不到(除非你对反射很好)。 我认为您可以制作具有此功能的自己的加载程序。
我认为您可以根据需要修改代码。也许添加“forceCancel”,它将检查 asyncTask 的值,如果它不为 null,则在其上调用“cancel(true)”。这是我看过的代码:
AsyncTaskLoader.java
package android.support.v4.content;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.util.TimeUtils;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.CountDownLatch;
/**
* Static library support version of the framework's @link android.content.AsyncTaskLoader.
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
public abstract class AsyncTaskLoader<D> extends Loader<D>
static final String TAG = "AsyncTaskLoader";
static final boolean DEBUG = false;
final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable
D result;
boolean waiting;
private CountDownLatch done = new CountDownLatch(1);
/* Runs on a worker thread */
@Override
protected D doInBackground(Void... params)
if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
result = AsyncTaskLoader.this.onLoadInBackground();
if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
return result;
/* Runs on the UI thread */
@Override
protected void onPostExecute(D data)
if (DEBUG) Log.v(TAG, this + " onPostExecute");
try
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
finally
done.countDown();
@Override
protected void onCancelled()
if (DEBUG) Log.v(TAG, this + " onCancelled");
try
AsyncTaskLoader.this.dispatchOnCancelled(this, result);
finally
done.countDown();
@Override
public void run()
waiting = false;
AsyncTaskLoader.this.executePendingTask();
volatile LoadTask mTask;
volatile LoadTask mCancellingTask;
long mUpdateThrottle;
long mLastLoadCompleteTime = -10000;
Handler mHandler;
public AsyncTaskLoader(Context context)
super(context);
/**
* Set amount to throttle updates by. This is the minimum time from
* when the last @link #onLoadInBackground() call has completed until
* a new load is scheduled.
*
* @param delayMS Amount of delay, in milliseconds.
*/
public void setUpdateThrottle(long delayMS)
mUpdateThrottle = delayMS;
if (delayMS != 0)
mHandler = new Handler();
@Override
protected void onForceLoad()
super.onForceLoad();
cancelLoad();
mTask = new LoadTask();
if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
executePendingTask();
/**
* Attempt to cancel the current load task. See @link android.os.AsyncTask#cancel(boolean)
* for more info. Must be called on the main thread of the process.
*
* <p>Cancelling is not an immediate operation, since the load is performed
* in a background thread. If there is currently a load in progress, this
* method requests that the load be cancelled, and notes this is the case;
* once the background thread has completed its work its remaining state
* will be cleared. If another load request comes in during this time,
* it will be held until the cancelled load is complete.
*
* @return Returns <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally, or
* because @link #startLoading() hasn't been called; returns
* <tt>true</tt> otherwise.
*/
public boolean cancelLoad()
if (DEBUG) Log.v(TAG, "cancelLoad: mTask=" + mTask);
if (mTask != null)
if (mCancellingTask != null)
// There was a pending task already waiting for a previous
// one being canceled; just drop it.
if (DEBUG) Log.v(TAG,
"cancelLoad: still waiting for cancelled task; dropping next");
if (mTask.waiting)
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
mTask = null;
return false;
else if (mTask.waiting)
// There is a task, but it is waiting for the time it should
// execute. We can just toss it.
if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
mTask = null;
return false;
else
boolean cancelled = mTask.cancel(false);
if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
if (cancelled)
mCancellingTask = mTask;
mTask = null;
return cancelled;
return false;
/**
* Called if the task was canceled before it was completed. Gives the class a chance
* to properly dispose of the result.
*/
public void onCanceled(D data)
void executePendingTask()
if (mCancellingTask == null && mTask != null)
if (mTask.waiting)
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
if (mUpdateThrottle > 0)
long now = SystemClock.uptimeMillis();
if (now < (mLastLoadCompleteTime+mUpdateThrottle))
// Not yet time to do another load.
if (DEBUG) Log.v(TAG, "Waiting until "
+ (mLastLoadCompleteTime+mUpdateThrottle)
+ " to execute: " + mTask);
mTask.waiting = true;
mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
return;
if (DEBUG) Log.v(TAG, "Executing: " + mTask);
mTask.executeOnExecutor(ModernAsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
void dispatchOnCancelled(LoadTask task, D data)
onCanceled(data);
if (mCancellingTask == task)
if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
rollbackContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mCancellingTask = null;
executePendingTask();
void dispatchOnLoadComplete(LoadTask task, D data)
if (mTask != task)
if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
dispatchOnCancelled(task, data);
else
if (isAbandoned())
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
else
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Log.v(TAG, "Delivering result");
deliverResult(data);
/**
*/
public abstract D loadInBackground();
/**
* Called on a worker thread to perform the actual load. Implementations should not deliver the
* result directly, but should return them from this method, which will eventually end up
* calling @link #deliverResult on the UI thread. If implementations need to process
* the results on the UI thread they may override @link #deliverResult and do so
* there.
*
* @return Implementations must return the result of their load operation.
*/
protected D onLoadInBackground()
return loadInBackground();
/**
* Locks the current thread until the loader completes the current load
* operation. Returns immediately if there is no load operation running.
* Should not be called from the UI thread: calling it from the UI
* thread would cause a deadlock.
* <p>
* Use for testing only. <b>Never</b> call this from a UI thread.
*
* @hide
*/
public void waitForLoader()
LoadTask task = mTask;
if (task != null)
try
task.done.await();
catch (InterruptedException e)
// Ignore
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)
super.dump(prefix, fd, writer, args);
if (mTask != null)
writer.print(prefix); writer.print("mTask="); writer.print(mTask);
writer.print(" waiting="); writer.println(mTask.waiting);
if (mCancellingTask != null)
writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
writer.print(" waiting="); writer.println(mCancellingTask.waiting);
if (mUpdateThrottle != 0)
writer.print(prefix); writer.print("mUpdateThrottle=");
TimeUtils.formatDuration(mUpdateThrottle, writer);
writer.print(" mLastLoadCompleteTime=");
TimeUtils.formatDuration(mLastLoadCompleteTime,
SystemClock.uptimeMillis(), writer);
writer.println();
编辑:3 年后,我决定为此发布我的解决方案:
/**
* makes it a bit easier to use AsyncTaskLoader. based on https://github.com/alexjlockwood/AppListLoader
*/
abstract class AsyncTaskLoaderEx<T>(context: Context) : AsyncTaskLoader<T>(context)
@JvmField
var hasResult = false
@Suppress("MemberVisibilityCanBePrivate")
@JvmField
var isCanceled = false
var result: T? = null
private set
private var _currentThread: Thread? = null
init
onContentChanged()
override fun onStartLoading()
if (takeContentChanged())
forceLoad()
override fun deliverResult(data: T?)
result = data
hasResult = true
super.deliverResult(data)
override fun onLoadInBackground(): T?
_currentThread = Thread.currentThread()
return super.onLoadInBackground()
open fun interrupt()
isCanceled = true
_currentThread?.interrupt()
override fun onReset()
super.onReset()
onStopLoading()
if (hasResult)
onReleaseResources(result)
result = null
hasResult = false
protected open fun onReleaseResources(data: T?)
//nothing to do.
companion object
private val sCurrentUniqueId = AtomicInteger(0)
@JvmStatic
val newUniqueLoaderId: Int
get() = sCurrentUniqueId.getAndIncrement()
以及使用示例:
class MainActivity : AppCompatActivity()
companion object
val TASK_LOADER_ID = AsyncTaskLoaderEx.newUniqueLoaderId
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loaderManager = LoaderManager.getInstance(this)
loaderManager.initLoader(TASK_LOADER_ID, null, object : LoaderManager.LoaderCallbacks<Boolean?>
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Boolean?>
return ImageLoadingTask(this@MainActivity)
override fun onLoadFinished(loader: Loader<Boolean?>, result: Boolean?)
Log.d("AppLog", "finished without being interrupted?:$result")
if (result == null)
return
//TODO use result
override fun onLoaderReset(loader: Loader<Boolean?>)
)
val runnable = Runnable
Log.d("AppLog", "trying to stop loader")
(loaderManager.getLoader<Loader<Boolean?>>(TASK_LOADER_ID) as AsyncTaskLoaderEx?)?.interrupt()
val handler = Handler()
handler.postDelayed(runnable, 2000L)
Log.d("AppLog", "will try to interrupt in 2 seconds")
lifecycle.addObserver(object : LifecycleObserver
@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy()
handler.removeCallbacks(runnable)
)
private class ImageLoadingTask(context: Context) : AsyncTaskLoaderEx<Boolean?>(context)
override fun loadInBackground(): Boolean?
try
for (i in 0..10)
Log.d("AppLog", "loadInBackground: $i")
Thread.sleep(1000L)
return true
catch (e: InterruptedException)
Log.d("AppLog", "INTERRUPTED!!!")
return false
【讨论】:
以上是关于如何中断 AsyncTaskLoader 后台线程?的主要内容,如果未能解决你的问题,请参考以下文章
Android ListView 由 AsyncTaskLoader 更新