谷歌电子市场开发流程(9)-线程,线程池

来源:互联网 发布:廖雪峰的javascript 编辑:程序博客网 时间:2024/06/08 02:29
线程在android中是一个很重要的概念,从用途上来说,线程分为主线程和子线程。主线程主要处理和界面相关的事,而子线程主要处理耗时操作。处理Thread之外,在android中可以扮演线程的角色还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。其中AsyncTask底层使用到了线程池,而HandlerThread和IntentService则直接使用了线程。

AsyncTask:
AsyncTask对线程池和Handler进行了良好的封装,所以它可以方便开发者在子线程中更新UI。
public abstract class AsyncTask<Params, Progress, Result>  这是AsyncTask的定义,可以看到,AsyncTask是一个抽象的泛型类,它提供了Params,Progress和Result三个泛型参数,其中Params表示参数的类型(Integer,URL,String等),Progress表示后台任务的执行进度的类型(一般为Integer),Result则表示后台任务的返回结果类型(String等)。
AsyncTask提供了四个核心方法,
(1)onPreExecute(),在主线程执行,在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。比如说在下载之前,让TextView的文字表示为“等待中”之类的情况,因为此方法是在主线程执行,所以在其中更新UI是可以的。
(2)doInBackground(Params... values),在线程池中执行,在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。此方法也是抽象方法,要使用AsyncTask则必须要实现此方法。
(3)onProgressUpdate(Progress... values),在主线程执行,在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。
(4)onPostExecute(Result result)在主线程执行,当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
其中,在使用时,要注意以下5点:

1.异步任务的实例必须在UI线程中创建。

2.execute(Params... params)方法必须在UI线程中调用。

3.不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。

4.不能在doInBackground(Params... params)中更改UI组件的信息。

5.一个任务实例只能执行一次,如果执行第二次将会抛出异常。

下面,用一个例子来演示AsyncTask的使用,这个例子中,在MainActivity的Button点击事件中,调用了AsyncTask的execute方法,自定义了一个MyTask类继承了AsyncTask,在onPreExecute()中,对UI界面进行了一些设置,在doInBackground(Params... params)中执行了访问网页的任务,在onProgressUpdate(Progress... values)更新了UI中进度条的进度,在onPostExecute(Result result)中将访问的结果设置给了UI的TextView。

  1. public class MainActivity extends Activity {  
  2.   
  3.     private static final String TAG = "ASYNC_TASK";  
  4.   
  5.     private Button execute;  
  6.     private Button cancel;  
  7.     private ProgressBar progressBar;  
  8.     private TextView textView;  
  9.   
  10.     private MyTask mTask;  
  11.   
  12.     @Override  
  13.     public void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         setContentView(R.layout.main);  
  16.   
  17.         execute = (Button) findViewById(R.id.execute);  
  18.         execute.setOnClickListener(new View.OnClickListener() {  
  19.             @Override  
  20.             public void onClick(View v) {  
  21.                 //注意每次需new一个实例,新建的任务只能执行一次,否则会出现异常  
  22.                 mTask = new MyTask();  
  23.                 mTask.execute("http://www.baidu.com");  
  24.   
  25.                 execute.setEnabled(false);  
  26.                 cancel.setEnabled(true);  
  27.             }  
  28.         });  
  29.         cancel = (Button) findViewById(R.id.cancel);  
  30.         cancel.setOnClickListener(new View.OnClickListener() {  
  31.             @Override  
  32.             public void onClick(View v) {  
  33.                 //取消一个正在执行的任务,onCancelled方法将会被调用  
  34.                 mTask.cancel(true);  
  35.             }  
  36.         });  
  37.         progressBar = (ProgressBar) findViewById(R.id.progress_bar);  
  38.         textView = (TextView) findViewById(R.id.text_view);  
  39.   
  40.     }  
  41.   
  42.     private class MyTask extends AsyncTask<String, Integer, String> {  
  43.         //onPreExecute方法用于在执行后台任务前做一些UI操作  
  44.         @Override  
  45.         protected void onPreExecute() {  
  46.             Log.i(TAG, "onPreExecute() called");  
  47.             textView.setText("loading...");  
  48.         }  
  49.   
  50.         //doInBackground方法内部执行后台任务,不可在此方法内修改UI  
  51.         @Override  
  52.         protected String doInBackground(String... params) {  
  53.             Log.i(TAG, "doInBackground(Params... params) called");  
  54.             try {  
  55.                 HttpClient client = new DefaultHttpClient();  
  56.                 HttpGet get = new HttpGet(params[0]);  
  57.                 HttpResponse response = client.execute(get);  
  58.                 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
  59.                     HttpEntity entity = response.getEntity();  
  60.                     InputStream is = entity.getContent();  
  61.                     long total = entity.getContentLength();  
  62.                     ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  63.                     byte[] buf = new byte[1024];  
  64.                     int count = 0;  
  65.                     int length = -1;  
  66.                     while ((length = is.read(buf)) != -1) {  
  67.                         baos.write(buf, 0, length);  
  68.                         count += length;  
  69.                         //调用publishProgress公布进度,最后onProgressUpdate方法将被执行  
  70.                         publishProgress((int) ((count / (float) total) * 100));  
  71.                         //为了演示进度,休眠500毫秒  
  72.                         Thread.sleep(500);  
  73.                     }  
  74.                     return new String(baos.toByteArray(), "gb2312");  
  75.                 }  
  76.             } catch (Exception e) {  
  77.                 Log.e(TAG, e.getMessage());  
  78.             }  
  79.             return null;  
  80.         }  
  81.   
  82.         //onProgressUpdate方法用于更新进度信息  
  83.         @Override  
  84.         protected void onProgressUpdate(Integer... progresses) {  
  85.             Log.i(TAG, "onProgressUpdate(Progress... progresses) called");  
  86.             progressBar.setProgress(progresses[0]);  
  87.             textView.setText("loading..." + progresses[0] + "%");  
  88.         }  
  89.   
  90.         //onPostExecute方法用于在执行完后台任务后更新UI,显示结果  
  91.         @Override  
  92.         protected void onPostExecute(String result) {  
  93.             Log.i(TAG, "onPostExecute(Result result) called");  
  94.             textView.setText(result);  
  95.   
  96.             execute.setEnabled(true);  
  97.             cancel.setEnabled(false);  
  98.         }  
  99.   
  100.         //onCancelled方法用于在取消执行中的任务时更改UI  
  101.         @Override  
  102.         protected void onCancelled() {  
  103.             Log.i(TAG, "onCancelled() called");  
  104.             textView.setText("cancelled");  
  105.             progressBar.setProgress(0);  
  106.   
  107.             execute.setEnabled(true);  
  108.             cancel.setEnabled(false);  
  109.         }  
  110.     }  
  111. }  

至于AsyncTask的工作原理,概括来说,当我们调用execute(Params... params)方法后,execute方法会调用onPreExecute()方法,然后由ThreadPoolExecutor实例sExecutor执行一个FutureTask任务,这个过程中doInBackground(Params... params)将被调用,如果被开发者覆写的doInBackground(Params... params)方法中调用了publishProgress(Progress... values)方法,则通过InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress... values)方法将被调用;如果遇到异常,则发送一条MESSAGE_POST_CANCEL的消息,取消任务,sHandler处理消息时onCancelled()方法将被调用;如果执行成功,则发送一条MESSAGE_POST_RESULT的消息,显示结果,sHandler处理消息时onPostExecute(Result result)方法被调用。也就是对线程池和Handler的封装。


HandlerThread:

HandlerThread 继承自Thread,内部封装了Looper。

首先Handler和HandlerThread的主要区别是:Handler与Activity在同一个线程中,HandlerThread与Activity不在同一个线程,而是别外新的线程中(Handler中不能做耗时的操作)。它的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。

从HandlerThread的实现来看,他和普通的Thread有显著地不同,普通的Thread主要在run方法中执行一个耗时的任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息机制来通知HandlerThread执行一个具体的任务。其具体的使用场景在IntentService,要注意的是,HandlerThread的run方法是一个无限循环,因此当明确不需要在使用HandlerThread时,可以通过它的quit或者quitSafely方法来终止线程的执行。


IntentService:

在Android的开发中,凡是遇到耗时的操作尽可能的会交给Service去做,比如我们上传多张图,上传的过程用户可能将应用置于后台,然后干别的去了,我们的Activity就很可能会被杀死,所以可以考虑将上传操作交给Service去做,如果担心Service被杀,还能通过设置startForeground(int, Notification)方法提升其优先级。

那么,在Service里面我们肯定不能直接进行耗时操作,一般都需要去开启子线程去做一些事情,自己去管理Service的生命周期以及子线程并非是个优雅的做法;好在android给我们提供了一个类,叫做IntentService,IntentService是一种特殊的Service,他继承了Service并且是一个抽象类,它可以用于执行后台耗时的任务,执行后会自动停止,同时由于它是服务的原因,优先级会比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务,在实现上来看,IntentService封装了HandlerThread和Handler。我们使用了IntentService最起码有两个好处,一方面不需要自己去new Thread了;另一方面不需要考虑在什么时候关闭该Service了。

IntentService的逻辑就是每次调用onStartCommand的时候,通过mServiceHandler发送一个消息,消息中包含我们的intent。然后在该mServiceHandler的handleMessage中去回调onHandleIntent(intent);就可以了。

那么我们具体看一下源码,果然是这样,onStartCommand中回调了onStart,onStart中通过mServiceHandler发送消息到该handler的handleMessage中去。最后handleMessage中回调onHandleIntent(intent)。

注意下:回调完成后回调用 stopSelf(msg.arg1),注意这个msg.arg1是个int值,相当于一个请求的唯一标识。每发送一个请求,会生成一个唯一的标识,然后将请求放入队列,当全部执行完成(最后一个请求也就相当于getLastStartId == startId),或者当前发送的标识是最近发出的那一个(getLastStartId == startId),则会销毁我们的Service.

如果传入的是-1则直接销毁。

那么,当任务完成销毁Service回调onDestory,可以看到在onDestroy中释放了我们的Looper:mServiceLooper.quit()。

线程池:

我们都知道,当一个项目中要实现多项下载任务时,如果遇到一个下载任务就new一个Thread,不仅会让项目本身运行效率变慢,而且可能会带来线程安全的问题,导致一些难以觉察的bug。那么运用面向对象的思想,我们可以猜想,可不可以用一个类来统一管理项目中所有的线程呢?
java中提供了线程池的概念,线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。
使用线程池有以下三点好处:
(1)重用线程池的线程,避免线程创建和销毁所带来的性能开销
(2)能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占资源而导致的阻塞现象
(3)能够对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能
android线程池的概念来源于Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。线程池主要分为4类,

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

首先要知道ThreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory)
ThreadPoolExecutor的构造方法有6个参数:
corePoolSize:核心线程数,默认情况下,核心线程数会在线程池中一直存活,即使他们处于闲置状态
maximumPoolSize:最大线程数,当活动线程超过此值后,后序新任务会被阻塞
keepAliveTime:非核心线程闲置的超时时长,超过这个时长,非核心线程会被回收
unit:keepAliveTime的单位,毫秒,秒,分钟
workQueue:线程池的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这里
threadFactory:线程工厂,为线程池提供创建新线程的功能
线程池几个参数的理解:
比如去火车站买票, 有10个售票窗口, 但只有5个窗口对外开放. 那么对外开放的5个窗口称为核心线程数,
而最大线程数是10个窗口.
如果5个窗口都被占用, 那么后来的人就必须在后面排队, 但后来售票厅人越来越多, 已经人满为患, 就类似于线程队列已满.
这时候火车站站长下令, 把剩下的5个窗口也打开, 也就是目前已经有10个窗口同时运行. 后来又来了一批人,
10个窗口也处理不过来了, 而且售票厅人已经满了, 这时候站长就下令封锁入口,不允许其他人再进来, 这就是线程异常处理策略.
而线程存活时间指的是, 允许售票员休息的最长时间, 以此限制售票员偷懒的行为.

线程池的分类:
1.FixedThreadPool
线程数量固定,线程处于闲置时,不会被回收,除非线程池关闭。只有核心线程,没有最大线程,也就是最大线程就是核心线程,没有超时机制,keepAliveTime=0;
  1. public static void main(String[] args) {  
  2.   ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
  3.   for (int i = 0; i < 10; i++) {  
  4.    final int index = i;  
  5.    fixedThreadPool.execute(new Runnable() {  
  6.     public void run() {  
  7.      try {  
  8.       System.out.println(index);  
  9.       Thread.sleep(2000);  
  10.      } catch (InterruptedException e) {  
  11.       e.printStackTrace();  
  12.      }  
  13.     }  
  14.    });  
  15.   }  
  16.  }  
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。

2.CachedThreadPool
线程数量不固定,只有非核心线程,最大线程数maximumPoolSize=Integer.MAX_VALUE为无限大,有超时机制,超时时长为60s,超过60s的闲置线程会被回收。这类线程池比较适合执行大量的耗时较少的任务。
  1. public static void main(String[] args) {  
  2.   ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
  3.   for (int i = 0; i < 10; i++) {  
  4.    final int index = i;  
  5.    try {  
  6.     Thread.sleep(index * 1000);  
  7.    } catch (InterruptedException e) {  
  8.     e.printStackTrace();  
  9.    }  
  10.    cachedThreadPool.execute(new Runnable() {  
  11.     public void run() {  
  12.      System.out.println(index);  
  13.     }  
  14.    });  
  15.   }  
  16.  }  
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

3.ScheduledThreadPool
核心线程数不固定,非核心线程数没有限制,当费核心线程闲置时会被立即回收。主要用于执行定时任务和具体固定周期的重复任务。
  1. public static void main(String[] args) {  
  2.   ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
  3.   scheduledThreadPool.schedule(new Runnable() {  
  4.    public void run() {  
  5.     System.out.println("delay 3 seconds");  
  6.    }  
  7.   }, 3, TimeUnit.SECONDS);  
  8.  }  
表示延迟3秒执行。

4.SingleThreadPool
只有一个核心线程,确保所有任务都在同一线程中按顺序执行,它的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题
  1. public static void main(String[] args) {  
  2.   ExecutorService singleThreadExecutor = Executors.newCachedThreadPool();  
  3.   for (int i = 0; i < 100; i++) {  
  4.    final int index = i;  
  5.    singleThreadExecutor.execute(new Runnable() {  
  6.     public void run() {  
  7.      try {  
  8.       while(true) {  
  9.        System.out.println(index);  
  10.        Thread.sleep(10 * 1000);  
  11.       }  
  12.      } catch (InterruptedException e) {  
  13.       e.printStackTrace();  
  14.      }  
  15.     }  
  16.    });  
  17.    try {  
  18.     Thread.sleep(500);  
  19.    } catch (InterruptedException e) {  
  20.     e.printStackTrace();  
  21.    }  
  22.   }  
  23.  }  
下面是一个完整的线程池工具类:
public class ThreadPoolManager {    private static ThreadPool mThreadPool = null;    private static ThreadPoolExecutor executor;    public static ThreadPool getmThreadPool() {        if (mThreadPool == null) {            synchronized (ThreadPoolManager.class) {                if (mThreadPool == null) {                    int cpuCount=Runtime.getRuntime().availableProcessors();                    System.out.println("=========Cpu个数======="+"        "+cpuCount);                    mThreadPool = new ThreadPool(10,10,1L);                }            }        }        return mThreadPool;    }    public static class ThreadPool {        private int corePoolSize;//核心线程数        private int maximumPoolSize;//最大线程数        private long keepAliveTime;//休息时间        private ThreadPool(int corePoolSize,int maximumPoolSize,long keepAliveTime) {            this.corePoolSize=corePoolSize;            this.maximumPoolSize=maximumPoolSize;            this.keepAliveTime=keepAliveTime;        }        public void execute(Runnable r) {            if (executor==null){                executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,                        keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),                        Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());            }            executor.execute(r);        }        public void cancle(Runnable r){            if (executor!=null){                //从线程队列中移除对象                executor.getQueue().remove(r);            }        }    }}
可以直接使用,这里使用了单例模式。

原创粉丝点击