android 多线程
本章讲述在android开发中,多线程的应用。多线程能够处理耗时的操作并优化程序的性能。本章主要介绍知识点,AsyncTask,Java线程池,ThreadPoolExecutor线程池类。本章案例只是抛砖引玉,在多线程应用方面,推荐研究大文件断点续传下载文件方面的应用。
1.功能需求
用AsyncTask来实现文件下载,要求:
- 可在文本框中输入请求路径,点击按钮开始下载
- 在界面上实时更新下载进度
- 如果文件已存在,则删除原文件再进行下载
2.软件实现
图1 AsyncTask下载文件
图2 导航界面
图3 线程池下载
3.相关知识
(1)线程池类 java.util.concurrent.ThreadPoolExecutor
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程.每个线程都使用默认的堆栈大小, 以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙. 如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线 程可以排队,但他们要等到其他线程完成后才启动。
- ThreadPoolExcutor 为一些Executor提供了基本的实现,这些Executor是由Executors中的工厂 newCahceThreadPool、newFixedThreadPool和newScheduledThreadExecutor返回的。
- ThreadPoolExecutor是一个灵活的健壮的池实现,允许各种各样的用户定制。
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
1.参数说明
- corePoolSize: 线程池维护线程的最少数量
- maximumPoolSize:线程池维护线程的最大数量
- keepAliveTime: 线程池维护线程所允许的空闲时间
- unit: 线程池维护线程所允许的空闲时间的单位
- workQueue: 线程池所使用的缓冲队列
- handler: 线程池对拒绝任务的处理策略
2.运行机制
一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。当一个任务通过execute(Runnable)方法欲添加到线程池时:
- 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
处理任务的优先级为:
- 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
- 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。
workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue
handler有四个选择:
- 抛出java.util.concurrent.RejectedExecutionException异常:ThreadPoolExecutor.AbortPolicy()
- 重试添加当前的任务,他会自动重复调用execute()方法:ThreadPoolExecutor.CallerRunsPolicy()
- 抛弃旧的任务:ThreadPoolExecutor.DiscardOldestPolicy()
- 抛弃当前的任务:ThreadPoolExecutor.DiscardPolicy()
线程池原生使用案例代码如下:
1 int corePoolSize = 1; 2 int maximumPoolSize = 2; 3 int keepAliveTime = 10; 4 BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(5); 5 ThreadFactory threadFactory = Executors.defaultThreadFactory(); 6 //线程池和队列满了之后的处理方式 7 //1.抛出异常 8 RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); 9 RejectedExecutionHandler handler2 = new ThreadPoolExecutor.CallerRunsPolicy(); 10 RejectedExecutionHandler handler3 = new ThreadPoolExecutor.DiscardPolicy(); 11 RejectedExecutionHandler handler4 = new ThreadPoolExecutor.DiscardOldestPolicy(); 12 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, threadFactory, handler2); 13 for (int j = 1; j < 15; j++) { 14 threadPoolExecutor.execute(new Runnable() { 15 public void run() { 16 17 try { 18 System.out.println(Thread.currentThread().getName()); 19 TimeUnit.SECONDS.sleep(1); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 24 25 } 26 }); 27 } 28 29 System.out.println(threadPoolExecutor); 30 31 }
3.线程池定制
1 public static ExecutorService newFixedThreadPool(int nThreads) { 2 return new ThreadPoolExecutor(nThreads, nThreads, 3 0L, TimeUnit.MILLISECONDS, 4 new LinkedBlockingQueue<Runnable>()); 5 } 6 public ThreadPoolExecutor(int corePoolSize, 7 int maximumPoolSize, 8 long keepAliveTime, 9 TimeUnit unit, 10 BlockingQueue<Runnable> workQueue) { 11 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 12 Executors.defaultThreadFactory(), defaultHandler); 13 }
newCachedThreadPool比较适合没有固定大小并且比较快速就能完成的小任务,没必要维持一个Pool,这比直接new Thread来处理的好处是能在60秒内重用已创建的线程。
1 public static ExecutorService newCachedThreadPool() { 2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3 60L, TimeUnit.SECONDS, 4 new SynchronousQueue<Runnable>()); 5 } 6 public ThreadPoolExecutor(int corePoolSize, 7 int maximumPoolSize, 8 long keepAliveTime, 9 TimeUnit unit, 10 BlockingQueue<Runnable> workQueue) { 11 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 12 Executors.defaultThreadFactory(), defaultHandler); 13 }
newSingleThreadExecutor单线程原理:
1 public static ExecutorService newSingleThreadExecutor() { 2 return new FinalizableDelegatedExecutorService 3 (new ThreadPoolExecutor(1, 1, 4 0L, TimeUnit.MILLISECONDS, 5 new LinkedBlockingQueue<Runnable>())); 6 }
(2) Java线程池
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
1. newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调 用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。示例代码如下:
1 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 2 for (int i = 0; i < 10; i++) { 3 final int index = i; 4 try { 5 Thread.sleep(index * 1000); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 10 cachedThreadPool.execute(new Runnable() { 11 12 @Override 13 public void run() { 14 System.out.println(index); 15 } 16 }); 17 }
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
2. newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失 败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。案例代码如下:
1 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); 2 for (int i = 0; i < 10; i++) { 3 final int index = i; 4 fixedThreadPool.execute(new Runnable() { 5 6 @Override 7 public void run() { 8 try { 9 System.out.println(index); 10 Thread.sleep(2000); 11 } catch (InterruptedException e) { 12 // TODO Auto-generated catch block 13 e.printStackTrace(); 14 } 15 } 16 }); 17 }
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
3. newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
1 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); 2 scheduledThreadPool.schedule(new Runnable() { 3 @Override 4 public void run() { 5 System.out.println("delay 3 seconds"); 6 } 7 }, 3, TimeUnit.SECONDS);
表示延迟3秒执行。
4.newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行 后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。示例代码如下:
1 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); 2 for (int i = 0; i < 10; i++) { 3 final int index = i; 4 singleThreadExecutor.execute(new Runnable() { 5 @Override 6 public void run() { 7 try { 8 System.out.println(index); 9 Thread.sleep(2000); 10 } catch (InterruptedException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 }); 16 }
结果依次输出,相当于顺序执行各个任务。
(3)AsyncTask
AsyncTask是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
- Params 启动任务执行的输入参数,比如HTTP请求的URL。
- Progress 后台任务执行的百分比。
- Result 后台执行任务最终返回的结果,比如String。
使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:
- doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以 调用publicProgress(Progress…)来更新任务的进度。
- onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话你还得重写以下这三个方法,但不是必须的:
- onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
- onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
- onCancelled() 用户调用取消时,要做的操作
使用AsyncTask类,以下是几条必须遵守的准则:
- Task的实例必须在UI thread中创建;
- execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
- 该task只能被执行一次,否则多次调用时将会出现异常;
4.项目核心代码解析
1.AsyncTask文件下载逻辑函数:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 private class DownloadByAsyncTask extends AsyncTask<String, Integer, String> { 2 //onPreExecute方法用于在执行后台任务前做一些UI操作 3 @Override 4 protected void onPreExecute() { 5 Log.i(TAG, "onPreExecute() called"); 6 progressDialog.show(); 7 result.setText("loading..."); 8 } 9 10 //doInBackground方法内部执行后台任务,不可在此方法内修改UI 11 @Override 12 protected String doInBackground(String... params) { 13 Log.i(TAG, "doInBackground(Params... params) called"); 14 String mark=""; 15 try 16 { 17 URL url = new URL(params[0]); 18 URLConnection urlConnection = url.openConnection(); 19 InputStream inputStream = urlConnection.getInputStream(); 20 int contentLength = urlConnection.getContentLength(); // 要下载的文件的大小 21 Log.i(TAG, "the download file's content length: " + contentLength); 22 // File.separator 表示文件路径分割线// 23 String downloadFoldersName = Environment.getExternalStorageDirectory() + File.separator + GEEK_BAND + File.separator; 24 File file = new File(downloadFoldersName); 25 if (!file.exists()) { 26 file.mkdir(); 27 } 28 29 String fileName = downloadFoldersName + "test.apk"; 30 File apkFile = new File(fileName); 31 if (apkFile.exists()) { 32 apkFile.delete(); 33 } 34 int downloadSize = 0; 35 byte[] bytes = new byte[1024]; 36 int length = 0; 37 OutputStream outputStream = new FileOutputStream(fileName); 38 while ((length = inputStream.read(bytes)) != -1) { 39 outputStream.write(bytes, 0, length); 40 downloadSize += length; 41 int progress = downloadSize * 100 / contentLength; 42 mark=String.valueOf(progress); 43 publishProgress(progress); 44 Log.i(TAG, "download progress: " + progress); 45 } 46 Log.i(TAG, "download success"); 47 inputStream.close(); 48 outputStream.close(); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 Log.i(TAG, "download failure"); 52 } 53 return mark; 54 } 55 56 //onProgressUpdate方法用于更新进度信息 57 @Override 58 protected void onProgressUpdate(Integer... progresses) { 59 Log.i(TAG, "onProgressUpdate(Progress... progresses) called"); 60 // 更新ProgressDialog的进度条 61 progressDialog.setProgress(progresses[0]); 62 result.setText("loading..." + progresses[0] + "%"); 63 } 64 65 //onPostExecute方法用于在执行完后台任务后更新UI,显示结果 66 @Override 67 protected void onPostExecute(String results) { 68 Log.i(TAG, "onPostExecute(Result result) called"); 69 progressDialog.dismiss(); 70 result.setText("下载进度:"+results); 71 } 72 73 //onCancelled方法用于在取消执行中的任务时更改UI 74 @Override 75 protected void onCancelled() { 76 Log.i(TAG, "onCancelled() called"); 77 result.setText("cancelled"); 78 } 79 }
2.线程池定义及执行代码:
1 newFixedThreadPool = Executors.newFixedThreadPool(1); 2 download.setOnClickListener(new View.OnClickListener() { 3 @Override 4 public void onClick(View arg0) { 5 if (HttpUtil.IsOnline(mContext)) { 6 result.setText("开始文件下载"); 7 progressDialog.show(); 8 newFixedThreadPool.execute(new Runnable() { 9 @Override 10 public void run() { 11 download(url_text.getText().toString()); 12 } 13 }); 14 } else { 15 result.setText("网络未连接"); 16 } 17 } 18 });
5.项目源代码下载
本项目源代码在360云盘上,开发环境为 Android Studio 2.0 。
https://yunpan.cn/cYamkZG3sq9jf 访问密码 f3d5。文件名称:android多线程操作。
喜欢请赞赏一下啦^_^
微信赞赏
支付宝赞赏