如何中断AsyncTaskLoader的后台线程? [英] How to interrupt AsyncTaskLoader's background thread?

查看:107
本文介绍了如何中断AsyncTaskLoader的后台线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我调用 AsyncTaskLoader 的 loadInBackground()线程> cancelLoad()?我相信 AsyncTask.cancel()可以做到这一点,但是任务变量是私有的,无法访问。

Is there some way to interrupt AsyncTaskLoader's loadInBackground() thread when I call cancelLoad()? I believe that AsyncTask.cancel() does this but the task variables are private and cannot be accessed.

推荐答案

我已经检查了可用的方法,似乎没有一个方法真正中断在后台运行的线程。

I've checked the available methods, and it seems none really interrupts the thread that runs in the background.

查看代码,似乎在后台使用了AsyncTask。我尝试过找到一个可用来中断asyncTask的空洞,但我却找不到(除非您能反射)。
我想您可以使自己的装载机具有此功能。

Looking at the code, it seems that AsyncTask is being used under the hood. I've tried finding a "hole" that I could use to interrupt the asyncTask, but I couldn't find (unless you are fine with reflection). I think you could make your own loader that will have this functionality.

我认为您可以根据需要修改代码。也许添加 forceCancel,它将检查asyncTask的值,如果它不为null,则在其上调用 cancel(true)。这是我看过的代码:

I think you can modify the code to your needs. Maybe add "forceCancel", which will check the value of the asyncTask, and if it's not null, call "cancel(true)" on it. Here's the code I've looked at:

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();
        }
    }
}






编辑:三年后,我决定为此发布解决方案:


after 3 years, I've decided to post my solution for this:

/**
 * 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的后台线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆