如何中断 AsyncTaskLoader 后台线程?

Posted

技术标签:

【中文标题】如何中断 AsyncTaskLoader 后台线程?【英文标题】:How to interrupt AsyncTaskLoader's background thread? 【发布时间】:2014-02-15 10:08:49 【问题描述】:

当我调用cancelLoad() 时,有什么方法可以中断AsyncTaskLoaderloadInBackground() 线程吗?我相信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 更新

AsyncTaskLoader onLoadFinished 有一个挂起的任务和配置更改

并发基础 java线程的中断机制

浏览器页面关闭后台线程会中断吗

在片段分离时中止加载 AsyncTaskLoader

详细分析Java中断机制-转载