• Android-多线程AsyncTask


    http://www.cnblogs.com/plokmju/p/android_AsyncTask.html

    AsyncTask,异步任务,可以简单进行异步操作,并把执行结果发布到UI主线程。AsyncTask是一个抽象类,它的内部其实也是结合了Thread和Handler来实现异步线程操作,但是它形成了一个通用线程框架,更清晰简单。AsyncTask应该被用于比较简短的操作(最多几秒钟)。如果需要保持长时间运行的线程,可以使用ThreadPooExecutor或者FutureTask

    首先来看一下AsyncTask的基本用法,由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:

    1. Params

    在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。

    2. Progress

    后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。

    3. Result

    当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

    AsyncTask的使用规则

      使用AsyncTask必须遵循以下规则:

    • AsyncTask必须声明在UI线程上。
    • AsyncTask必须在UI线程上实例化。
    • 必须通过execute()方法执行任务。
    • 不可以直接调用onPreExecute()、onPostExecute(Resut)、doInBackground(Params...)、onProgressUpdate(Progress...)方法。
    • 可以设置任务只执行一次,如果企图再次执行会报错。

    第一个因为AsyncTask内置了handler,所以在主线程运行最好。不过调用Looper.prepare后似乎也可以在子线程运行

    使用方法在第一个链接里很清楚,下面来看一下源码:

    http://blog.csdn.net/guolin_blog/article/details/11711405

    • 可以看到源码execute时调用了executeOnExecutor()方法,在这里调用了onPreExecute(),保证了他第一个执行
    • 接着调用了Executor的execute()方法,并将前面初始化的mFuture对象传了进去
    • 接着就来到FutureTask类的run()方法,这里开启了一个子线程,调用call方法,而call方法调用了doInBackground()方法,这保证了方法在子线程里执行
    • postResult()方法,源码如下:
    private Result postResult(Result result) {  
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
                new AsyncTaskResult<Result>(this, result));  
        message.sendToTarget();  
        return result;  
    }  

      可以看到,实际上就是用handler处理返回的结果,回到主线程

    private static class InternalHandler extends Handler {  
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})  
        @Override  
        public void handleMessage(Message msg) {  
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
            switch (msg.what) {  
                case MESSAGE_POST_RESULT:  
                    // There is only one result  
                    result.mTask.finish(result.mData[0]);  
                    break;  
                case MESSAGE_POST_PROGRESS:  
                    result.mTask.onProgressUpdate(result.mData);  
                    break;  
            }  
        }  
    }  

      InernalHandler接受两种信息,如果这是一条MESSAGE_POST_RESULT消息,就会去执行finish()方法,如果这是一条MESSAGE_POST_PROGRESS消息,就会去执行onProgressUpdate()方法。那么finish()方法的源码如下所示:

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

      可以看到,如果当前任务被取消掉了,就会调用onCancelled()方法,如果没有被取消,则调用onPostExecute()方法,这样当前任务的执行就全部结束了。

      从上面还可以看到,运行中可以随时调用cancel(boolean)方法取消任务,如果成功调用isCancelled()会返回true,并且不会执行 onPostExecute() 方法了,取而代之的是调用 onCancelled() 方法。

      而且从源码看,如果这个任务已经执行了这个时候调用cancel是不会真正的把task结束,而是继续执行,只不过改变的是执行之后的回调方法是 onPostExecute还是onCancelled。

      onProgressUpdate()方法源码如下:

    protected final void publishProgress(Progress... values) {  
        if (!isCancelled()) {  
            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,  
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();  
        }  
    }  

      同样是发给handler处理,所以是在主线程运行的

      SerialExecutor详细:

       其实SerialExecutor也是AsyncTask在3.0版本以后做了最主要的修改的地方,它在AsyncTask中是以常量的形式被使用的,因此在整个应用程序中的所有AsyncTask实例都会共用同一个SerialExecutor

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

    可以看到,SerialExecutor是使用ArrayDeque这个队列来管理Runnable对象的,如果我们一次性启动了很多个任务,首先在第一次运行execute()方法的时候,会调用ArrayDeque的offer()方法将传入的Runnable对象添加到队列的尾部,然后判断mActive对象是不是等于null,第一次运行当然是等于null了,于是会调用scheduleNext()方法。在这个方法中会从队列的头部取值,并赋值给mActive对象,然后调用THREAD_POOL_EXECUTOR去执行取出的取出的Runnable对象。之后如何又有新的任务被执行,同样还会调用offer()方法将传入的Runnable添加到队列的尾部,但是再去给mActive对象做非空检查的时候就会发现mActive对象已经不再是null了,于是就不会再调用scheduleNext()方法。

    那么后面添加的任务岂不是永远得不到处理了?当然不是,看一看offer()方法里传入的Runnable匿名类,这里使用了一个try finally代码块,并在finally中调用了scheduleNext()方法,保证无论发生什么情况,这个方法都会被调用。也就是说,每次当一个任务执行完毕后,下一个任务才会得到执行,SerialExecutor模仿的是单一线程池的效果,如果我们快速地启动了很多任务,同一时刻只会有一个线程正在执行,其余的均处于等待状态。Android照片墙应用实现,再多的图片也不怕崩溃 这篇文章中例子的运行结果也证实了这个结论。

     

    在Android 3.0之前是并没有SerialExecutor这个类的,那个时候是直接在AsyncTask中构建了一个sExecutor常量,并对线程池总大小,同一时刻能够运行的线程数做了规定,代码如下所示:

    private static final int CORE_POOL_SIZE = 5;  
    private static final int MAXIMUM_POOL_SIZE = 128;  
    private static final int KEEP_ALIVE = 10;  
    ……  
    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);  

    可以看到,这里规定同一时刻能够运行的线程数为5个,线程池总大小为128。也就是说当我们启动了10个任务时,只有5个任务能够立刻执行,另外的5个任务则需要等待,当有一个任务执行完毕后,第6个任务才会启动,以此类推。而线程池中最大能存放的线程数是128个,当我们尝试去添加第129个任务时,程序就会崩溃。

    因此在3.0版本中AsyncTask的改动还是挺大的,在3.0之前的AsyncTask可以同时有5个任务在执行,而3.0之后的AsyncTask同时只能有1个任务在执行。为什么升级之后可以同时执行的任务数反而变少了呢?这是因为更新后的AsyncTask已变得更加灵活,如果不想使用默认的线程池,还可以自由地进行配置。比如使用如下的代码来启动任务:

    Executor exec = new ThreadPoolExecutor(15, 200, 10,  
            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());  
    new DownloadTask().executeOnExecutor(exec);  

    这样就可以使用我们自定义的一个Executor来执行任务,而不是使用SerialExecutor。上述代码的效果允许在同一时刻有15个任务正在执行,并且最多能够存储200个任务。

     

    需要注意的地方:

    http://www.open-open.com/lib/view/open1434802647364.html

    上面提到了那么多的注意点,还有其他需要注意的吗?当然有!我们开发App过程中使用AsyncTask请求网络数据的时候,一般都是习惯在onPreExecute显示进度条,在数据请求完成之后的onPostExecute关闭进度条。这样做看似完美,但是如果您的App没有明确指定屏幕方向和configChanges时,当用户旋转屏幕的时候Activity就会重新启动,而这个时候您的异步加载数据的线程可能正在请求网络。当一个新的Activity被重新创建之后,可能由重新启动了一个新的任务去请求网络,这样之前的一个异步任务不经意间就泄露了,假设你还在onPostExecute写了一些其他逻辑,这个时候就会发生意想不到异常。

    一般简单的数据类型的,对付configChanges我们很好处理,我们直接可以通过onSaveInstanceState()和onRestoreInstanceState()进行保存与恢复。 Android会在销毁你的Activity之前调用onSaveInstanceState()方法,于是,你可以在此方法中存储关于应用状态的数据。然后你可以在onCreate()或onRestoreInstanceState()方法中恢复。

    但是,对于AsyncTask怎么办?问题产生的根源在于Activity销毁重新创建的过程中AsyncTask和之前的Activity失联,最终导致一些问题。那么解决问题的思路也可以朝着这个方向发展。Android官方文档 也有一些解决问题的线索。

    这里介绍另外一种使用事件总线的解决方案,是国外一个安卓大牛写的。中间用到了Square开源的EventBus类库http://square.github.io/otto/。首先自定义一个AsyncTask的子类,在onPostExecute方法中,把返回结果抛给事件总线,代码如下:

    @Override
        protected String doInBackground(Void... params) {
            Random random = new Random();
            final long sleep = random.nextInt(10);
            try {
                Thread.sleep(10 * 6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Slept for " + sleep + " seconds";
        }
    
        @Override
        protected void onPostExecute(String result) {
            MyBus.getInstance().post(new AsyncTaskResultEvent(result));
        }

      在Activity的onCreate中注册这个事件总线,这样异步线程的消息就会被otta分发到当前注册的activity,这个时候返回结果就在当前activity的onAsyncTaskResult中了,代码如下:

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.otto_layout);
    
            findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View v) {
                    new MyAsyncTask().execute();
                }
            });
    
            MyBus.getInstance().register(this);
        }
    
        @Override
        protected void onDestroy() {
            MyBus.getInstance().unregister(this);
            super.onDestroy();
        }
    
        @Subscribe
        public void onAsyncTaskResult(AsyncTaskResultEvent event) {
            Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show();
        }

      个人觉的这个方法相当好,当然更简单的你也可以不用otta这个库,自己单独的用接口回调的方式估计也能实现,大家可以试试。

     

  • 相关阅读:
    Rancher 中 Traefik 负载均衡 Initializing 状态
    【音视频】YUV、RGB视频像素处理
    Debian WSL 2 安装使用 Docker
    CentOS 7 切换 Java 版本到 Java 11
    阿里云 CentOS 8.2 停服后 yum / dnf 无法安装更新
    CentOS 8 Stream 报错处理 Faild to start Load Kernel Modules. Failed to insert 'ipmi_si': No such device
    System.getProperty()获取系统变量
    简单科普私钥、地址、助记词、Keystore的区别
    synchronized实现原理及锁升级过程
    H3C 策略路由原理介绍
  • 原文地址:https://www.cnblogs.com/qlky/p/5658070.html
Copyright © 2020-2023  润新知