Android异步任务(AsyncTask)的设计思想

AsyncTask在Android十分经常使用,那为何如此经常使用呢,不用行不行呢,内部又是怎么实现的呢,为何Java的API中没有这个类呢,看完本文后,你将会知道答案。html

这里说有设计思想是我根据查看Android源代码提炼出来的代码逻辑,因此不会跟Google工程师的原始设计思想100%符合(也有多是0%),可是本文必定能够帮助你理解AsyncTask,也可能有一些你之前没有发现的内容。java

你们都知道,Android的主线程(又叫UI线程,线程ID为1)有一些限制策略,使得主线程有些事作不了,好比访问网络就不容许,不然就是报,但在2.3以后的版本,你能够经过添加如下代码更改其限制策略,从而强制使得主线程能够访问网络:android

if (android.os.Build.VERSION.SDK_INT > 9) {
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
}

不过StrictMode是一个开发工具主要用于侦测主线程中的磁盘和网络访问,而不是让你作这“坏”事,其实Android这样限制是有好处的,强制让开发者重视用户体验,一个反面例子是Windows,主线程里什么均可以作,一些很懒的开发者就把全部任务放到主线程里,使得线程常常好卡,好比编辑器UE或Notepad++打开了一个网络上(如samba服务器)的文件,若是忽然网络中断了,那你的整个编辑器都卡住,要等很久才会有反应,不过我不肯定那是否是由于主线程里访问了网络,不过Windows常常由于这个缘由卡。还有一个正面例子是iOS,极其注意响应速度,因此当有用户输入事件时,其内核都有相应的调度,从而优先响应用户操做。服务器

仍是回到正题,就是由于主线程的这些限制使开发者不得不写多个线程,固然,你也能够不用AsyncTask,不过你不用也避免不了多线程,若是你不用,就是可能要用Handler和Thread了,我想不少人初学的时候就是那么干的,包括我,由于那时颇有可能尚未发现有这个类,因而就常常写Handler和Thread的了,写着写着就发现有一些代码是相同的,你写的Handler和Thread匿名类确定是重复代码,以下:网络

final Handler handler = new Handler() {
    public void handleMessage(Message msg) {
    	System.out.println("The hard have done!");
    	// ... front end code
    }
};
new Thread() {
    public void run() {
        doHardWork();
        handler.sendEmptyMessage(0);
    }

    private void doHardWork() {
        // ... back end code
    }
}.start();

你可能想到要复用这些代码,固然,你能够经过Copy的方式来复用这段代码,用的时候只要在省略号处写入你的代码就能够了,但更好的复用是将其用一个类封装起来,好吧,那咱们就简单的封装一下吧,因而,就变成了这样:多线程

public class HandlerAndThread {
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            System.out.println("The hard have done!");
            //...
        }
    };
    
    public void doInBackground() {
        new Thread() {
            public void run() {
                doHardWork();
                handler.sendEmptyMessage(0);
            }


            private void doHardWork() {
                // ...
            }
        };
    }   
    
}

这样好像还不行,由于没法告诉后台线程作什么事,作完了也不知道通知,要复用仍是得Copy代码,咱们能够加两个方法,一个在前台调用一个在后台调用,只要定义一个新类就能够实现复用,因而代码变成这样:架构

public class HandlerAndThread {
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            System.out.println("The hard have done!");
            runInFrontend();    // added
        }
    };
    
    public void doInBackground() {
        new Thread() {
            public void run() {
                doHardWork();
                handler.sendEmptyMessage(0);
            }

            private void doHardWork() {
                runInBackend();    //added
            }
        };
    }
    
    //added
    protected void runInBackend() {
    }
    
    //added
    protected void runInFrontend() {
    }
}

一个可复用的类就出炉了,咱们写一个子类,并用一个Activity来调用一下吧:oracle

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new SubHandlerAndThread().doInBackground();
    }
    
    class SubHandlerAndThread extends HandlerAndThread {
        protected void runInBackend() {
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        protected void runInFrontend() {
            System.out.println("Task has been done");
        }
    }
}

这样是否是比直接写Thread和Handler简洁了许多呢,这里我是用sleep来模似长时间事务的,若是在真实的环境中,咱们多是要下载,若是是下载,咱们可能但愿传入个下载地址的参数到后台线程,来让他按咱们的须要下载,咱们给加doInBackground方法加一个参数,因而HandlerAndThread类的代码就变成这样:编辑器

public class HandlerAndThread {
    ...
    
    public void doInBackground(final String url) {	// added url
        new Thread() {
            public void run() {
                doHardWork();
                handler.sendEmptyMessage(0);
            }

            private void doHardWork() {
                runInBackend(url); 	// added url
            }
        };
    }
    
    protected void runInBackend(String url) {	// added url
    }
    
    ...
}

而调用类的代码变成这样:ide

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String url = "http://path/to/file";
        new SubHandlerAndThread().doInBackground(url);   //added url
    }
    
    class SubHandlerAndThread extends HandlerAndThread {
        @Override
        protected void runInBackend(String url) {    // added url
            System.out.println("Start download from url:" + url);
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        @Override
        protected void runInFrontend() {
            System.out.println("finish download");
        }
    }
}

假如是下一个文件呢,咱们是否是加一个进度更新的方法呢,因而又变成这样:

public class HandlerAndThread {
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {        // added
            case 0:
                runInFrontend();
                break;

            case 1:
                runInFrontendProgress(msg.arg1);
                break;
            }
        }
    };

    ...

    final protected void publishProgress(int progress) {    // added
        handler.obtainMessage(1, progress, 0);
    }
   
    protected void runInFrontendProgress(int progress) {    // added
    }
}

public class MainActivity extends Activity {
    ...
    
    class SubHandlerAndThread extends HandlerAndThread {
        @Override
        protected void runInBackend(String url) {
            System.out.println("Start download from url:" + url);
            for (int i = 0; i < 10; ++i) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrack();
                }
                publishProgress(i*10);    // added
            }
        }
        
        ...
        
        @Override
        protected void runInFrontendProgress(int progress) {	// added
            System.out.println("Progress: " + progress);
        }
    }
}

你可能已经没有耐心一版一版的进化了,那我就跳跃一下吧,一次多加几条须要:1、咱们下载完了可能要获得文件的路径,因此咱们给runInFrontend方法加一个输入参数filePath表示路径;2、把子类必须实现的方法改为抽象方法,类也改为抽象方法;我把代码中的一些方法名改一下,使其更好理解,把doInBackground改成execute,把runInFrontend改成onPostExecute,把runInFrontendProgress改成onProgressUpdate。最终版以下:

public abstract class HandlerAndThread {
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 0:
                onPostExecute((String) msg.obj);
                break;


            case 1:
                onProgressUpdate(msg.arg1);
                break;
            }
        }
    };
    
    public void doInBackground(final String url) {
        new Thread() {
            public void run() {
                String result = runInBackend(url);
                handler.obtainMessage(0, result);
            }


        };
    }
    
    final protected void publishProgress(int progress) {
        handler.obtainMessage(1, progress, 0);
    }


    abstract protected String runInBackend(String url);
    protected void onPostExecute(String filePath) { }
    protected void onProgressUpdate(int progress) {    }
}
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        String url = "http://path/to/file";
        new SubHandlerAndThread().doInBackground(url);
    }
    
    class SubHandlerAndThread extends HandlerAndThread {
        @Override
        protected String runInBackend(String url) {
            System.out.println("Start download from url:" + url);
            for (int i = 0; i < 10; ++i) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                publishProgress(i*10);
            }
            
            return "/path/to/file";
        }
        
        @Override
        protected void onPostExecute(String filePath) {
            System.out.println("Download finished");
        }
        
        @Override
        protected void onProgressUpdate(int progress) {
            System.out.println("Progress: " + progress);
        }
    }
}

这是否是跟Android的AsyncTask很像呢,我想Google就是因为这种需求作出这个类的,Android官网是这样描述AsyncTask的:

This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

意思是这个类使得:不使用Thread和Handler,就能够在后台执行操做而后在发布结果到UI线程。其实他内部的实现就是封装了Thread和Handler,因此你就没必要要直接用这两个低层类了,但他的目的也是代码复用,他的实现跟咱们上面写的类也差很少。主要有这几点不一样:1、AsyncTask使用了线程池而不是单个线程去执行后台任务,该线程池是整个进程共用的,是由于他的线程池对象是一个静态成员变量,这一点不少人搞错,误觉得AsyncTask越来建立的线程越多,这不是绝对正确的,由于线程池会根据负载动态调整的,并且有最大值和空闲超时,AsyncTask的配置是最小5,最大128,空闲超时1秒,固然你也能够配置成线程数根据任务数线程递增,关于线程池,可参考这里,后续我会在博客写文章讨论Java线程池;2、AsyncTask的输入和输出参数使用了泛型;3、AsyncTask支持中断当前任务。

如今知道了AsyncTask的设计思想了吧,是否是很简单呢,因此建议童鞋们去看一下它的源码,反正我写代码时有查看源码的习惯,由于我会好奇它是如何实现的,看源码有不少好处,如能够学到好的API设计思想,软件架构,特别是当你遇到技术问题没法解决,网上也搜索不到时,你能够经过查看源码来找到解决办法,我就常常这么干。

因此提议你们在开发时学会看源码利用源码,必定要耐心,一篇看不懂不要气馁,多看几篇必定会看懂的。


参考资料:

Android官网API说明

相关文章
相关标签/搜索