什么是异步?哈,你可以百度一下看看它的定义。不过我这里就只具体说Android异步加载服务器数据的相关内容。
Android应用程序与服务器交互时,如果数据量比较大,或者网络速度缓慢,会导致加载服务器数据的操作变得缓慢,甚至会出现阻塞等待的情况;如果这些与服务器交互的操作放到Android应用程序的UI线程上去执行,也就是放在Activity上直接执行,那么UI线程也相应的进行等待或阻塞,这时候Android手机的屏幕就会出现卡顿的现象,甚至出现不能响应用户操作的情况,这必定严重影响用户的操作体验。并且手机设备与网络服务器进行交互一般都是有延时的,也就是说,如果那些交互的操作放到UI线程上去执行,必定会有延时和阻塞。
为了解决这样的延时,我们必须要把交互的操作放到另一个线程上面去执行,当线程从服务器加载数据回来了,然后才在UI线程上显示数据。这就是异步操作。Android异步操作,一般是指在后台运行一段代码,进行某些处理,处理完之后,再提醒前台的线程,进行处理后的操作。而在这个处理的过程中不与前台的UI线程发生冲突和阻塞。
异步操作还有其他的好处!
异步操作的实现方法,可以参考以下的链接:
- 异步加载数据的三种实现
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面
- Android异步处理二:使用AsyncTask异步更新UI界面
- Android异步处理三:Handler+Looper+MessageQueue深入详解
- Android异步处理四:AsyncTask的实现原理
异步操作的实现方法主要还是Handler和AsyncTask,其中Handler据说会占有一定的开销,不过Handler实现异步操作,会显得更加灵活和方便。本人用Handler实现了一个异步操作的调度系统,以下这些内容是假设你已经对Handler的使用有一定的了解的情况下提出的。
这个调度系统,主要包括三个类,可以查看src文件夹下面的async文件夹里面的文件:
- AsyncDispatcher.java
- AsyncTask.java
- AsyncListener.java
AsyncDispatcher是异步操作的任务调度者,里面包括任务队列和处理任务的线程;AsyncDispatcher的线程会从任务队列里面按顺序取出任务,然后交处理任务,处理完成后交给handler更新UI。
AsyncTask是表示的是异步操作的内容,其操作的内容重写在AsyncTask的handle()方法里面。
AsyncListener是异步处理的监听器,当异步处理完成并且成功后,就会执行onDone()方法;当异步处理失败之后(可能因为网络异常,或者内部的代码逻辑错误抛出异常),就会执行onException()方法;
怎样传递参数呢?且查看AsyncTask 源码:
1 public abstract class AsyncTask implements Runnable {
2 public int type = ExecuteThread.TYPE_NORMAL;
3 public Object[] objects = null;
4 public AsyncListener listener;
5
6 AsyncTask() {
7 }
8
9 AsyncTask(int type, Object[] objects) {
10 this.type = type;
11 this.objects = objects;
12 }
13
14 public void asyncHandle(AsyncListener listener) {
15 this.listener = listener;
16 AsyncDispatcher.getInstance().put(type, this);
17 }
18
19 abstract void handle();
20
21 public void run() {
22 handle();
23 }
24 }
AsyncDo有一个参数public Object[] objects,当初始化一个AsyncDo对象时,传入objects参数,并且this.objects = objects;,故生成了AsyncTask对象之后就保存了需要传递的参数。
asyncHandle()方法的作用是生成一个AsyncTask任务对象,并把这个任务对象放进调度器AsyncDispatcher对象的任务队列里面;这是由AsyncDispatcher.getInstance().put()方法调用的,我们可以看看put()方法的代码:
1 public synchronized void put(int type, Runnable task) {
2 Object ticket = null;
3 if (type == ExecuteThread.TYPE_NORMAL) {
4 synchronized (taskQueue) {
5 taskQueue.add(task);
6 ticket = nomalTicket;
7 }
8 }
9 //……
10 }
很明显,通过taskQueue.add(task)方法把任务对象添加到taskQueue任务队列里面去。那么,这些任务对象是怎样取出来运行的呢?我们看看AsyncDispatcher的startThreads()方法是怎样的:
1 public void startThreads() {
2 Log.i(TAG, "startThreads()");
3 if (isActive || threads == null) {
4 return;
5 }
6 isActive = true;
7 for (int i = 0; i < threads.size(); i++) {
8 threads.get(i).setDaemon(true);
9 threads.get(i).start();
10 }
11 //……
12 }
startThreads()方法启动了调度器里面所有的线程,而这些线程是怎样定义和执行的呢?这些线程是ExecuteThread的对象,我们看看这个对象的run()方法就明了了:
1 public void run() {
2 isAlive = true;
3 while (isAlive) {
4 AsyncTask task = (AsyncTask) asyncDispatcher.poll(type);
5 if (null != task) {
6 try {
7 task.run();
8 Message msg = asyncDispatcher.handler.obtainMessage();
9 msg.obj = task;
10 msg.what = DOWN_CODE;
11 asyncDispatcher.handler.sendMessage(msg);
12 } catch (Exception ex) {
13 ex.printStackTrace();
14 Message msg = asyncDispatcher.handler.obtainMessage();
15 msg.obj = task;
16 msg.what = EXCEPTION_CODE;
17 asyncDispatcher.handler.sendMessage(msg);
18 }
19 }
20 }
21 }
ExecuteThread线程对象通过asyncDispatcher的poll(type)方法从任务对象中取出任务对象,然后任务对象调用run()方法执行需要的处理(例如加载服务器数据),处理成功或者失败之后就调用handler.sendMessage(msg)方法把处理后的结果交给handler去处理。
那么handler是怎么处理的呢?看看handler对象的代码:
1 Handler handler = new Handler() {
2 @Override
3 public void handleMessage(Message msg) {
4 AsyncTask task = (AsyncTask) msg.obj;
5 if (msg.what == EXCEPTION_CODE) {
6 task.listener.onException();
7 } else if (msg.what == DOWN_CODE) {
8 task.listener.onDone();
9 }
10 // msg.what默认等于0
11 }
12 };
没错!执行成功后或失败后的善后处理就在这里执行,其中AsyncTask 对象是通过msg.obj变量来传递的。
但是还有一个疑问,那就是task.listener变量又是怎么回事?再看看AsyncTask 是怎么实现的:
1 public abstract class AsyncTask implements Runnable {
2 //……
3 public AsyncListener listener;
4 //……
5 public void asyncHandle(AsyncListener listener) {
6 this.listener = listener;
7 AsyncDispatcher.getInstance().put(type, this);
8 }
9 //……
10 }
asyncHandle()函数传入一个监听器对象AsyncListener给AsyncTask对象,然后AsyncTask对象把自己放进AsyncDispatcher调度器的任务队列里面。
且看一个使用这个异步操作的例子:
1 // 加载数据的操作
2 public void load(int paramInt) {
3 System.out.println("load Something!");
4 System.out.println("load Something!");
5 System.out.println("load Something!");
6 System.out.println("load Something!");
7 System.out.println("load Something!");
8 System.out.println("load Something!");
9 }
10 // 异步加载数据方法1
11 public void asyncLoad(int paramInt) {
12 new AsyncTask(TYPE_NORMAL, new Object[] { paramInt }) {
13 @Override
14 void handle() {
15 load((Integer) objects[0]);
16 }
17 }.asyncHandle(new AsyncListener() {
18 @Override
19 public void onDone() {
20 super.onDone();
21 System.out.println("load onDone!, paramInt="
22 + (Integer) objects[0]);
23 }
24
25 @Override
26 public void onException() {
27 super.onException();
28 System.out.println("load onException!, paramInt="
29 + (Integer) objects[0]);
30 }
31 });
32 }
在asyncLoad()方法中首先生成了一个AsyncTask对象,并且重写了handle()方法,其中handle()方法调用了load()加载数据的方法;然后这个AsyncTask对象调用了asyncHandle()方法,并且传入一个AsyncListener监听器,并把加载完数据的操作重写在AsyncListener监听器的onDone()和onException()方法;AsyncTask的使用就是这样的了;
如果执行了asyncLoad()方法,那会有什么样的效果呢?
当前,前提是AsyncDispatcher调度器已经启动,AsyncDispatcher启动的方法是:
AsyncDispatcher.getInstance().startThreads();
AsyncDispatcher调度器启动后,执行asyncLoad()方法后,其整个处理过程是这样的:
- new AsyncTask(),并传入所需的参数;
- 调用asyncHandle(),并传入AsyncListener监听器对象给AsyncTask对象,在asyncHandle()方法里面,AsyncTask对象把自己放进AsyncDispatcher调度器的任务队列里面去;
- 然后AsyncDispatcher的执行线程不断的从任务队列里面取出任务来执行,当取出asyncLoad()方法放进去的AsyncTask对象出来后,就调用AsyncTask对象的run()方法;
- AsyncTask对象的run()方法会执行AsyncTask的抽象方法handle(),这个方法是在生成AsyncTask对象的时候重写了;
- 重写的handle()方法,就会执行真正的处理函数load();
- 如果load()方法执行成功之后就返回到handle()方法,handle()方法又返回到run()方法,然后接着继续执行线程接下来的代码;
- 接下来的代码就是通过AsyncDispatcher的handler对象的sendMessage(msg)方法发送这个消息给这个handler对象;
- 这个handler就会对这个消息进行相应的处理,输出“load onDone!, paramInt=1”;
- 同理,如果load()方法执行失败之后,就会捕捉到异常,然后执行到catch块继续进行;
- 然后handler对象的sendMessage(msg)方法发送这个消息给这个handler对象;
- 这个handler就会对这个消息进行相应的处理,输出“load onException!, paramInt=1”;
这里的异步系统的AsyncTask与android自带的AsyncTask相比,有什么优缺点呢?之前本人看过一些文章,说用android自带的AsyncTask来做后台的异步处理比Handler做后台异步处理更轻量。是吗?那就打开android的AsyncTask类的源代码研究一下,发现,原来android的AsyncTask也是用Handler来做后台处理的,唉唉!兜兜转转,还是Handler,看来做异步处理的,肯定离不开Handler了。(严谨的说,Handler只是做善后的工作而已,真正的后台处理操作是放在后台线程里面进行的。)
不过有一点值得注意的是:在android的AsyncTask中,Handler是以AsyncTask类的静态变量存在的,也就是所有的AsyncTask对象共用一个Handler对象来进行善后的处理。Handler对象是一个有一定消耗的对象,当有多个异步操作同时需要进行的时候,如果每个操作都生成一个Handler对象进行异步操作的话,那这种叠加的消耗确实会不小;而AsyncTask来处理多个异步操作,其使用的只是一个Handler对象而已,所以相对来说,消耗会更小。
那么现在来证明我们的AsyncDispatcher调度器也有这样的优点。我们只允许生成一个AsyncDispatcher对象,是通过getInstance()这个静态方法获取AsyncDispatcher实例的,这个实例也就是调度器的唯一实例。为什么是唯一实例呢?我们可以看看getInstance()的实现代码就可以一目了然了,这里就不罗嗦了。
这个AsyncDispatcher实例,也就是AsyncDispatcher对象,里面只有唯一的一个Handler对象;AsyncDispatcher对象里面有若干的执行线程,执行线程从任务队列里面取出任务来执行,执行成功或失败后,都会把相应的消息发送给这个唯一的Handler对象进行善后的处理。Handler对象会按顺序从自己的消息队列里面取出消息然后进行相应的处理。
事实上,这里的AsyncDispatcher调度器的执行过程和android的AsyncTask的执行过程是很相似的。android的AsyncTask里面同样有类似于任务队列和有线程队列这样的东西。不同的是,这里的AsyncDispatcher调度器实现起来简单很多,并且还对AsyncTask任务进行分类处理。
这里的分类处理主要体现在AsyncTask的一个变量type,这个变量标志了这个任务的类型;当生成一个AsyncTask任务时,可以传入这type参数。当把这个任务放进任务队列时,就会根据这个type变量的值放到对应类型的任务队列里面;然后不同的任务队列就由这个任务队列对应的执行线程来执行。这样分类之后,有什么好处呢?
这样的分类主要是因为与服务器进行交互时,对服务器进行操作的种类不一样,以及加载的数据种类不一样。例如有些操作需要长时间才能返回数据,而有些是迅速的;如果那些长时间的操作阻碍了那些迅速的操作,这肯定会影响用户的体验。例如在一个页面中既要显示文字数据,又要显示图片,显示图片是一个长时间的操作和显示文字相对比较快,如果显示图片的操作是在显示文字的前面,这样就很有可能阻碍了显示文字的操作,使得这个页面一直都显示不完整,这肯定会影响了用户的使用体验。可能你又会说,那多启动几个线程就可以啦!是吗?那到底要启动多少个线程才满足要求呢?一个页面可能需要显示的内容很丰富,可能要加载很多的图片,或者需要加载很多其他的数据,那么难道要加载多少个数据就启动多少个线程?这很明显是不合理的啦。线程启动的数目一般在5个左右就好,或者更少,如果可以的话;毕竟线程是一个消耗不小的家伙呢!
另外,一般在一个android应用中,访问的服务器可能不止一个,不同的数据可能会分到不同的服务器上面,例如普通的数据放在数据库服务器中,上传的文件放在另一个服务器中,而图片相关的数据又放在另一个服务器中等等。这样,各个服务器之间的网络状态也可能会有不同,访问的速度也不一样。如果一个服务器的网络有些异样,例如储存图片的服务器出现网络异常,使得网络传输速度缓慢,如果在应用当中,刚好需要加载大量的图片,这时候,就会阻塞了其他的操作任务(例如加载普通数据的任务),这样肯定会对页面的显示速度造成一定的影响。
所以综合这些想法,个人觉得对这些任务进行分类是有必要的。因为这样做,就可以使得不同的数据服务器不会互相影响,也能使得不同种类的数据不会互相阻碍。例如,本AsyncDispatcher系统中,定义了几个类型的执行线程,用来执行相应类型的任务,如下:
1 public static class ExecuteThread extends Thread {
2 public static final int TYPE_NORMAL = 0;
3 public static final int TYPE_GET_PHOTO = 1;
4 public static final int TYPE_GET_BIG_PHOTO = 2;
5 public static final int TYPE_GET_NOTI_MSG = 3;
6 int type = -1;
7 AsyncDispatcher asyncDispatcher;
8 //……
分别是普通数据的类型、获取小图片的类型、获取大图片的类型、获取通知(推送)消息的类型等等。
分析到这里,差不多了…就这样吧!散……