美文网首页
从AsyncTask聊到JobIntentService

从AsyncTask聊到JobIntentService

作者: 墨源为水 | 来源:发表于2021-03-01 20:38 被阅读0次

    一.AsyncTask分析

    AsyncTask本质是上只是一个框架,内部封装了Thread+Handler机制,不需要控制线程和Handler,可以轻松实现后台计算,发布进度和结果到主线程。需要注意的是
    1.The task can be executed only once
    2.The task instance must be created on the UI thread
    3.execute must be invoked on the UI thread

    1.AsyncTask用例如下:

     * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     *     protected Long doInBackground(URL... urls) {
     *         int count = urls.length;
     *         long totalSize = 0;
     *         for (int i = 0; i < count; i++) {
     *             totalSize += Downloader.downloadFile(urls[i]);
     *             publishProgress((int) ((i / (float) count) * 100));
     *             // Escape early if cancel() is called
     *             if (isCancelled()) break;
     *         }
     *         return totalSize;
     *     }
     *
     *     protected void onProgressUpdate(Integer... progress) {
     *         setProgressPercent(progress[0]);
     *     }
     *
     *     protected void onPostExecute(Long result) {
     *         showDialog("Downloaded " + result + " bytes");
     *     }
     * }
    
    new DownloadFilesTask().execute(url1, url2, url3);
    

    AsyncTask内部流程如下:

    When an asynchronous task is executed, the task goes through 4 steps:

      1. onPreExecute, invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
      1. doInBackground, invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate step.
      1. onProgressUpdate, invoked on the UI thread after a call to publishProgress. The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
      1. onPostExecute, invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.

    基本如下操作:AsyncTask框架有三个泛型三个,分别是输入的多个参数、处理中的进度、处理后的结果;excute操作时, onPreExecute在主线程做执行前操作,doInBackground做后台操作,在操作的同时发布Progress进度,回调在onProgressUpdate中,这是主线程,doInBackground return的结果回调在onPostExecute中。实例化的AsyncTask通过execute

    2.在基于这样的大致框架下,我们如何自行实现以上功能?

    本质上就是在execute时,起一个工作线程,执行后台计算。工作线程执行前,调用onPreExecute;通过主线程looper起一个Handler,用于主线程与工作线程的通信。执行在publishProgress方法中handler.sendmessage,在handler.handleMessage调用onProgressUpdate,在工作线程结束后handler.sendmessage,计算结果放在obj属性,handler.handleMessage调用onPostExecute即可。

    3.源码分析

    AsyncTask框架内处理后台任务,是要起线程的。其内部线程创建是通过线程池的,即单例的线程池,在第一次AsyncTask类加载时,便会初始化(static方法块);在初始化类时,具体走构造器时,会初始化Handler与一个FutureTask(为什么要用FutureTask执行异步任务,是为了获得异步计算的结果,而且FutureTask也是一个可取消的异步计算,Runable或者继承Thread做不到这些特性)

    这里拓展一下,谈一下线程创建
    Java线程创建本身都是通过实例化Thread去做的,至于如何实例化Thead有以下几种方式(如何实现run内部逻辑代码)

    • 继承Thread类实现多线程
     *     class PrimeThread extends Thread {
     *         long minPrime;
     *         PrimeThread(long minPrime) {
     *             this.minPrime = minPrime;
     *         }
     *
     *         public void run() {
     *             // compute primes larger than minPrime
     *             &nbsp;.&nbsp;.&nbsp;.
     *         }
     *     }
     *     PrimeThread p = new PrimeThread(143);
     *     p.start();
    
    • 实现Runnable()接口实现多线程,而后同样覆写run()
     *     class PrimeRun implements Runnable {
     *         long minPrime;
     *         PrimeRun(long minPrime) {
     *             this.minPrime = minPrime;
     *         }
     *
     *         public void run() {
     *             // compute primes larger than minPrime
     *             &nbsp;.&nbsp;.&nbsp;.
     *         }
     *     }
     *     PrimeRun p = new PrimeRun(143);
     *     new Thread(p).start();
    
    • 实现Callable接口(本质也是Runnable方式,不同的是有后台计算的返回值)
     *     class PrimeRun implements Callable<String> {
     *
     *         public String call() throws Exception{
     *             // compute primes larger than minPrime
     *             &nbsp;.&nbsp;.&nbsp;.
     *             return "demo";
     *         }
     *     }
     *     PrimeRun callable = new PrimeRun();
     *     new Thread(new FutureTask<>(callable)).start();
    

    FutureTask is a cancellable asynchronous computation,即FutureTask是一个可取消的异步计算,本身实现了Runnable、Future接口,实现了Runnable的run方法(有了线程异步的能力),实现了Future接口的get,cancel,isCancelled,isDone方法(有了可取消的异步计算结果的能力)
    AsyncTask有三个状态如下:

    /**
         * Indicates the current status of the task. Each status will be set only once
         * during the lifetime of a task.
         */
        public enum Status {
            /**
             * Indicates that the task has not been executed yet.
             */
            PENDING,
            /**
             * Indicates that the task is running.
             */
            RUNNING,
            /**
             * Indicates that {@link AsyncTask#onPostExecute} has finished.
             */
            FINISHED,
        }
    

    如备注所说,这个状态的功能就是为了让每个任务只执行一次。其中PENDING指的是任务还未被执行,RUNNING指的是任务执行运行中,FINISHED指的是onPostExecute方法已结束

    返回到AsyncTask构造器讲解,源码如下:

        public AsyncTask(@Nullable Looper callbackLooper) {
            mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
                ? getMainHandler()
                : new Handler(callbackLooper);
    
            mWorker = new WorkerRunnable<Params, Result>() {
                public Result call() throws Exception {
                    mTaskInvoked.set(true);
                    Result result = null;
                    try {
                        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                        //noinspection unchecked
                        result = doInBackground(mParams);
                        Binder.flushPendingCommands();
                    } catch (Throwable tr) {
                        mCancelled.set(true);
                        throw tr;
                    } finally {
                        postResult(result);
                    }
                    return result;
                }
            };
    
            mFuture = new FutureTask<Result>(mWorker) {
                @Override
                protected void done() {
                    try {
                        postResultIfNotInvoked(get());
                    } catch (InterruptedException e) {
                        android.util.Log.w(LOG_TAG, e);
                    } catch (ExecutionException e) {
                        throw new RuntimeException("An error occurred while executing doInBackground()",
                                e.getCause());
                    } catch (CancellationException e) {
                        postResultIfNotInvoked(null);
                    }
                }
            };
        }
    

    这里初始化了FutureTask,异步执行的是doInBackground,现将mTaskInvoked愿子变量置为true,代表任务已被调用。doInBackground结束后,send一个MESSAGE_POST_RESULT的Message,handler接受到消息,执行finish方法,任务结束后执行postResultIfNotInvoked,该方法会检查mTaskInvoked是否为false,即任务未被调用的话,也会send一个MESSAGE_POST_RESULT的Message
    finish方法源码如下:

    private void finish(Result result) {
            if (isCancelled()) {
                onCancelled(result);
            } else {
                onPostExecute(result);
            }
            mStatus = Status.FINISHED;
        }
    

    isCancelled的原子变量为true的情况要么是用户调用cancle方法,要么doInBackground发生异常。finsh方法判断AsyncTask是否可取消,如果可取消,调用onCancelled,不能取消则执行onPostExecute。总是将AsyncTask的状态置为Finished。
    以上都初始化好了之后,当用户调用excute方法,excute方法会调用executeOnExecutor,不仅要传入params可变参数,还有传入执行器,为什么要传入执行器呢?其实AsyncTask本质是一个线程,不过是加了Handler的线程,AsyncTask内部维护了一个线程池,当用户起了多个AsyncTask,这个时候,就要考虑AsyncTask内部线程该如何调用;系统默认的是串行执行器,源码如下:

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
        private static class SerialExecutor implements Executor {
            final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
            Runnable mActive;
    
            public synchronized void execute(final Runnable r) {
                mTasks.offer(new Runnable() {
                    public void run() {
                        try {
                            r.run();
                        } finally {
                            scheduleNext();
                        }
                    }
                });
                if (mActive == null) {
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
                if ((mActive = mTasks.poll()) != null) {
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }
    

    由源码可知,系统默认对于多个AsyncTask采用了串行执行器,即一个线程执行完之后执行双端队列的下一个线程

    executeOnExecutor源码如下:

        @MainThread
        public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                Params... params) {
            if (mStatus != Status.PENDING) {
                switch (mStatus) {
                    case RUNNING:
                        throw new IllegalStateException("Cannot execute task:"
                                + " the task is already running.");
                    case FINISHED:
                        throw new IllegalStateException("Cannot execute task:"
                                + " the task has already been executed "
                                + "(a task can be executed only once)");
                }
            }
    
            mStatus = Status.RUNNING;
    
            onPreExecute();
    
            mWorker.mParams = params;
            exec.execute(mFuture);
    
            return this;
        }
    

    在excute执行方法内可知,AsyncTask为了只执行一个Task,通过状态判断,只要状态执行中,或者已结束,就抛出异常。之后将状态置为RUNNING状态,在调用onPreExecute,将可变参数传给workrunbale对象中,之后通过执行器执行在构造器时已初始化的futuretask的线程。

    4.重谈AsyncTask.cancle方法

    AsyncTask类cancle方法,除了将原子变量mCancelled置为true,还执行了FutureTask的cancle方法,FutureTask的cancle源码如下:

        public boolean cancel(boolean mayInterruptIfRunning) {
            if (!(state == NEW &&
                  U.compareAndSwapInt(this, STATE, NEW,
                      mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
                return false;
            try {    // in case call to interrupt throws exception
                if (mayInterruptIfRunning) {
                    try {
                        Thread t = runner;
                        if (t != null)
                            t.interrupt();
                    } finally { // final state
                        U.putOrderedInt(this, STATE, INTERRUPTED);
                    }
                }
            } finally {
                finishCompletion();
            }
            return true;
        }
    

    其实canle方法最终调用了线程的interrupt方法;线程的interrupt()方法,根据字面意思,很容易将该方法理解为中断线程。其实Thread.interrupt()并不会中断线程的运行,它的作用仅仅是为线程设定一个状态而已,即标明线程是中断状态,这样线程的调度机制或我们的代码逻辑就可以通过判断这个状态做一些处理,比如sleep()方法会抛出异常,或是我们根据isInterrupted()方法判断线程是否处于中断状态,然后做相关的逻辑处理。

    由以上分析可知,当用户调用AsyncTask的cancle方法时,onBackgroud所做的耗时任务,只要正在进行中,依然还会进行。所以onBackgroud内还要判断AsyncTask.isCancle方法

    AsyncTask内部是用Thread+Handler实现,那与HandlerThred甚是相似,它与AsyncTask有什么不同呢?

    5.与HandlerThread区别

    HandlerThread本身继承自Thread的类,这个衍生类是方便子线程创建Handler,用于子线程与主线程之间,子线程与子线程之间的通信。
    使用如下:

            /**
              * 步骤①:创建HandlerThread实例对象
              * 传入参数 = 线程名字,作用 = 标记该线程
              */
            mHandlerThread = new HandlerThread("handlerThread");
            /**
             * 步骤②:启动线程
             */
            mHandlerThread.start();
     
            /**
             * 步骤③:创建工作线程Handler & 复写handleMessage()
             * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
             * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
             */
     
            workHandler = new Handler(mHandlerThread.getLooper()){
                @Override
                public void handleMessage(Message msg)
                {
              });
    

    上面使用可知HandlerThread框架免去了Looper的初始化,与loop操作。
    其run方法如下:

        @Override
        public void run() {
            mTid = Process.myTid();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();
            mTid = -1;
        }
    

    在Handler构造器可以传入目标线程不为空的Looper, 其中getLooper源码如下:

    public Looper getLooper() {
            if (!isAlive()) {
                return null;
            }
            
            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    

    这里要注意一点时,HandlerThread框架为了避免在获取Looper时,出现为空的情况,在mLooper的set,与get操作加了同步锁;

    由以上分析可知AsyncTask框架内部Handler机制中子线程是生产者,主线程是消费者。而HandlerThread框架内部Handler机制中目标子线程是消费者,其他线程(可能是主线程)是生产者

    三.延展分析IntentService

    IntentService源码实现很简单,其是Service的衍生类,本身Service组件是运行在主线程中,为了让Service处理耗时任务,在onCreate起了一个HandlerThread与Handler(解决子线程与主线程的通信),在onStartCommand/onStart生命周期中,Handler将Intent包裹在message的obj属性里,send消息,通知到子线程中处理耗时任务,任务结束后结束Service。无需多讲,源码如下:

        private volatile ServiceHandler mServiceHandler;
        private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                onHandleIntent((Intent)msg.obj);
                stopSelf(msg.arg1);
            }
        }
        @Override
        public void onCreate() {
            // TODO: It would be nice to have an option to hold a partial wakelock
            // during processing, and to have a static startService(Context, Intent)
            // method that would launch the service & hand off a wakelock.
    
            super.onCreate();
            HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
    
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }
    
        @Override
        public void onStart(@Nullable Intent intent, int startId) {
            Message msg = mServiceHandler.obtainMessage();
            msg.arg1 = startId;
            msg.obj = intent;
            mServiceHandler.sendMessage(msg);
        }
    
        /**
         * You should not override this method for your IntentService. Instead,
         * override {@link #onHandleIntent}, which the system calls when the IntentService
         * receives a start request.
         * @see android.app.Service#onStartCommand
         */
        @Override
        public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
            onStart(intent, startId);
            return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
        }
    

    但是,但是,但是 安卓源码对IntentService有如下备注:


    Android O对应用在后台运行时可以执行的操作施加了限制,称为后台执行限制(Background Execution Limits),这可以大大减少应用的内存使用和耗电量,提高用户体验。后台执行限制分为两个部分:后台服务限制(Background Service Limitations)、广播限制(BroadcastLimitations)。该文章详解:android O 对Service的限制【Background Execution Limits】对此做了详解.

    对于Android8.0后台执行限制,官方推荐JobIntentService类,用法如下:

    public class SimpleJobIntentService extends JobIntentService {
        /**
         * Unique job ID for this service.
         */
        static final int JOB_ID = 1000;
    
        /**
         * Convenience method for enqueuing work in to this service.
         */
        static void enqueueWork(Context context, Intent work) {
            enqueueWork(context, SimpleJobIntentService.class, JOB_ID, work);
        }
    
        @Override
        protected void onHandleWork(@NonNull Intent intent) {
            // We have received work to do.  The system or framework is already
            // holding a wake lock for us at this point, so we can just go.
            Log.i("SimpleJobIntentService", "Executing work: " + intent);
            String label = intent.getStringExtra("label");
            if (label == null) {
                label = intent.toString();
            }
            toast("Executing: " + label);
            for (int i = 0; i < 5; i++) {
                Log.i("SimpleJobIntentService", "Running service " + (i + 1)
                        + "/5 @ " + SystemClock.elapsedRealtime());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
            Log.i("SimpleJobIntentService", "Completed service @ " + SystemClock.elapsedRealtime());
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            toast("All work complete");
        }
    
        @SuppressWarnings("deprecation")
        final Handler mHandler = new Handler();
    
        // Helper for showing tests
        void toast(final CharSequence text) {
            mHandler.post(new Runnable() {
                @Override public void run() {
                    Toast.makeText(SimpleJobIntentService.this, text, Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
    

    由于Android O的后台限制,创建后台服务需要使用JobScheduler来由系统进行调度任务的执行,而使用JobService的方式比较繁琐,8.0及以上提供了JobIntentService帮助开发者更方便的将任务交给JobScheduler调度,其本质是Service后台任务在他的OnhandleWork()中进行,子类重写该方法即可。使用较简单。

    相关文章

      网友评论

          本文标题:从AsyncTask聊到JobIntentService

          本文链接:https://www.haomeiwen.com/subject/dwcafltx.html