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



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

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

大家都知道,Android的主线程(又叫UI线程,线程ID为1)有一些限制策略,使得主线程有些事做不了,比如访问网络就不允许,否则就是报,但在2.3之后的版本,你可以通过添加以下代码更改其限制策略,从而强制使得主线程可以访问网络:

?
1
2
3
4
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匿名类肯定是重复代码,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的方式来复用这段代码,用的时候只要在省略号处写入你的代码就可以了,但更好的复用是将其用一个类封装起来,好吧,那我们就简单的封装一下吧,于是,就变成了这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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代码,我们可以加两个方法,一个在前台调用一个在后台调用,只要定义一个新类就可以实现复用,于是代码变成这样:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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来调用一下吧:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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类的代码就变成这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
     }
     
     ...
}
而调用类的代码变成这样:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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" );
         }
     }
}
假如是下一个文件呢,我们是不是加一个进度更新的方法呢,于是又变成这样:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);
         }
     }
}
你可能已经没有耐心一版一版的进化了,那我就跳跃一下吧,一次多加几条需要:一、我们下载完了可能要得到文件的路径,所以我们给runInFrontend方法加一个输入参数filePath表示路径;二、把子类必须实现的方法改成抽象方法,类也改成抽象方法;我把代码中的一些方法名改一下,使其更好理解,把doInBackground改为execute,把runInFrontend改为onPostExecute,把runInFrontendProgress改为onProgressUpdate。最终版如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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,所以你就不必要直接用这两个低层类了,但他的目的也是代码复用,他的实现跟我们上面写的类也差不多。主要有这几点不同:一、AsyncTask使用了线程池而不是单个线程去执行后台任务,该线程池是整个进程共用的,是因为他的线程池对象是一个静态成员变量,这一点很多人搞错,误以为AsyncTask越来创建的线程越多,这不是绝对正确的,因为线程池会根据负载动态调整的,而且有最大值和空闲超时,AsyncTask的配置是最小5,最大128,空闲超时1秒,当然你也可以配置成线程数根据任务数线程递增,关于线程池,可参考这里,后续我会在博客写文章讨论Java线程池;二、AsyncTask的输入和输出参数使用了泛型;三、AsyncTask支持中断当前任务。

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

所以提议大家在开发时学会看源码利用源码,一定要耐心,一篇看不懂不要气馁,多看几篇一定会看懂的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值