`

Android性能优化系列---Sending Operations to Multiple Threads

 
阅读更多

 

        本文源自:http://developer.android.com/training/multiple-threads/index.html

 

        当你将一个需要长时间运行的,数据量大的操作,分割成一些小的操作,并且在多线程中运行的话,那么这个长时间运行的操作的速度和效率将会提升不少。对于有一个有多个处理器(多核)的CPU的设备,系统可以并发的运行多个线程,而不是让每个子操作等到被执行。比如,当你要解码多张图片,以在屏幕上显示的话,你如果将每一个图片的解码操作放在每一个独立的线程的话,那么速度将会很快。

 

        本课程将会向你展示,在Android里如何通过一个线程池对象,来创立和使用多线程。你同样也会学到如何使得代码在线程中运行,以及这些线程如何与主线程通信。

 

        Specifying the Code to Run on a Thread

        学习怎么写代码让其运行在一个单一的线程(通过定义一个实现Runnable接口的类)。

 

 

        Creating a Manager for Multiple Threads

        学习如何产生一个管理线程池的类和Runnable队列。该管理线程池的对象叫ThreadPoolExecutor。

 

        Running Code on a Thread Pool Thread

        怎么在线程池里在Thread上运行Runnable任务

 

        Communicating with the UI Thread

        如何让线程池里的线程和UI线程通信

 

 

         Specifying the Code to Run on a Thread

         Thread和Runnable是java里的基本类,但在Android里仅仅使用它们处理和解决问题有限。但它们是Android里例如HandlerThread、AsyncTask和IntentService等这些功能强大的类的基础。Thread和Runnable也是ThreadPoolExecutor类的基础。这个类自动的管理线程和任务队列,并且能并发的运行多线程。

 

          Define a Class that Implements Runnable

          定义一个类实现Runnable接口是直接明了的方式,例如:

 

         public class PhotoDecodeRunnable implements Runnable {

              ...

             @Override

             public void run() {

                     /*

                      * Code you want to run on the thread goes here

                      */

                       ...

             }

            ...

          }

 

 

         Implement the run() Method

        在这个类里,Runnable.run()方法包含被执行的代码。通常,任何执行代码都被运行在Runnable里。但请记住,因为Runnable不被运行在UI thread,因此,不要直接的更新UI对象(例如像View对象)。为了通信和UI线程,你必须使用在Communicate with the UI Thread章节介绍的技术。

        在run()方法里,通过调用Process.setThreadPriority()设置Thread的级别为background priority( THREAD_PRIORITY_BACKGROUND)。这种方式能降低运行Runnable的线程和UI线程之间的资源竞争。你也应该在Runnble对象里持有运行该Runnable对象的线程的引用。通过调用Thread.currentThread()。

        下面的代码片段告诉你怎么写run()方法。

         class PhotoDecodeRunnable implements Runnable {

                     ...

                     /*

                      * Defines the code to run for this task.

                      */

                   @Override

                   public void run() {

                   // Moves the current Thread into the background                                                                                         android.os.Process.setThreadPriority

                              (android.os.Process.THREAD_PRIORITY_BACKGROUND);

                  ...

                   /*

                    * Stores the current Thread in the PhotoTask instance,

                    * so that the instance

                    * can interrupt the Thread.

                    */

                  mPhotoTask.setImageDecodeThread(Thread.currentThread());

                  ...

                 }

                 ...

            }

 

 

         Creating a Manager for Multiple Threads

        前面告诉你如何定义一个运行在单一线程上的任务。如果你仅仅想运行该任务一次,上面的方式就能满足你的要求。如果你想在不同的数据集上多次运行该任务。但是一次又仅仅运行一次,IntentService能满足你的需求。为了资源可用时自动运行任务或者同时运行多个任务,你需要你个管理线程线程的集合。为了做这,可用使用ThreadPoolExecutor,ThreadPoolExecutor依次的从任务队列中取一个任务运行。为了运行一个任务,所有你必须做的就是将该任务加入到任务队列里。

        线程池能并发运行多个任务,因此你必须保证你的代码是线程安全的。确保把能被多个线程访问的变量放在同步代码块里。这能避免一个线程对某个变量读的同时另一个线程对该变量写。特别地,当该变量是静态变量时这种情况更容易出现,当然,这种情况也发生在仅仅实例一次的对象上。如果想了解更多,请阅读Processes and Threads 

 

        Define the Thread Pool Class

        在某个类里(管理线程池的类)实例化ThreadPoolExecutor。在这个类,做如下事情:

        为线程池使用静态变量

        在你的应用里,为了对于有限的CPU和网络资源有单一的控制点,你可能需要该线程池是单例的。如果你有许多不同类型的任务,你就不得不对于每一个类型的Runnable实例不同的线程池,但每一种类型的Runnable只对应有一个ThreadPoolExecutor实例。例如,你可以定义该类作为你全局属性声明的一部分:

 

        public class PhotoManager {

                 ...

                static  {

                       ...

                       // Creates a single static instance of PhotoManager

                      sInstance = new PhotoManager();

                }

                ...

         用一个私有构造器确保该类单例,那意思是你不必在同步代码块里封装对该类的访问。

 

       public class PhotoManager {

               ...

              /**

               * Constructs the work queues and thread pools used to download

               * and decode images. Because the constructor is marked private,

               * it's unavailable to other classes, even in the same package.

               */

               private PhotoManager() {

                            ...

       }

 

         通过调用线程池里的方法开始你的任务

         在线程里定义一个方法,该方法加一个任务到线程池队列里。例如:

             public class PhotoManager {

                     ...

                     // Called by the PhotoView to get a photo

                     static public PhotoTask startDownload(PhotoView imageView,boolean cacheFlag) {

                              ...

                              // Adds a download task to the thread pool for execution sInstance.

                             mDownloadThreadPool.execute(downloadTask.getHTTPDownloadRunnable());

                             ...

             }

 

          在Manager类的构造器里实例化一个Handler,让该Handler附加到UI线程上。Handler运行你放心的调用UI对象例如View里的方法。大多数的UI对象仅仅在UI线程里才是线程安全的。UI线程和非UI线程通信,更多的信息请参考Communicate with the UI Thread。例如:

          private PhotoManager() {

                     ...

                     // Defines a Handler object that's attached to the UI thread

                     mHandler = new Handler(Looper.getMainLooper()) {

                                /*

                                 * handleMessage() defines the operations to perform when

                                 * the Handler receives a new Message to process.

                                 */

                                @Override

                                public void handleMessage(Message inputMessage) {

                                            ...

                                }

                               ...

                   }

            }

 

          Determine the Thread Pool Parameters

          一旦你有了总体的类架构,你能开始定义线程池。为了实例一个线程池对象,你需要如下的值:

          初始化池大小和最大池大小

         分配给线程池的初始化线程个数和最大能运行的线程个数。在线程池里能有的线程的数量主要取决于你的设备的CPU核数。你可以从系统环境里获取该有效数:

 

           public class PhotoManager {

                  ...

                 /*

                  * Gets the number of available cores

                  * (not always the same as the maximum number of cores)

                  */

                private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

           }

 

       这个数量可能并不反映你的设备的CPU的物理核数。有些设备的CPU一些处理器可能并不是活跃的。那即是有些核可能并没有使用。对应这些设备, availableProcessors() 返回活动的核数,这可能比总核数少。

 

        保持活动时间和时间单位

        线程关闭之前可以处于idle状态持续的时间。时间单位取值于TimeUnit类的常量之一。定义了Keep alive time的单位。

 

         任务队列:

         任务队列将传入ThreadPoolExecutor里,ThreadPoolExecutor从该任务队列里去取Runnable对象运行。为了在一个线程上运行代码,线程池管理器从一个先进先出队列取Runnable对象。然后分配其到一个线程运行。当你产生线程池的时候,你能传入该队列对象。任何实现了BlockingQueue接口的对象都可以作为任务队列对象。

        为了了解更多,可以参考类ThreadPoolExecutor。使用LinkedBlockingQueue类的示例如下: 

         public class PhotoManager {

                    ...

                    private PhotoManager() {

                            ...

                            // A queue of Runnables

                            private final BlockingQueue<Runnable> mDecodeWorkQueue;

                            ...

                             // Instantiates the queue of Runnables as a LinkedBlockingQueue

                             mDecodeWorkQueue = new LinkedBlockingQueue<Runnable>();

                              ...

                    }

                ...

           }

 

 

        Create a Pool of Threads

        为了产生一个线程池,通过调用ThreadPoolExecutor()方法实例化一个线程池管理器。其产生和管理一个线程组。因为初始化线程组和最大线程数是相同的,ThreadPoolExecutor会在它实例化的时候就产生所有的线程对象。例如:

              private PhotoManager() {

                         ...

                         // Sets the amount of time an idle thread waits before terminating

                        private static final int KEEP_ALIVE_TIME = 1;

                        // Sets the Time Unit to seconds

                        private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

                        // Creates a thread pool manager

                        mDecodeThreadPool = new ThreadPoolExecutor(

                                          NUMBER_OF_CORES,       // Initial pool size

                                          NUMBER_OF_CORES,       // Max pool size

                                          KEEP_ALIVE_TIME,

                                          KEEP_ALIVE_TIME_UNIT,

                                          mDecodeWorkQueue);

                          }

 

 

        Running Code on a Thread Pool Thread

        前面论述了怎么定义一个线程池管理类及如何定义运行在里的任务。现在我们将讨论如何在一个线程池里运行一个任务。为了实现这,你需要将你的任务加到一个线程池工作队列。当线程变得有效时ThreadPool   -Executor从该队列取一个任务然后运行它。

        这课也告诉你如何停止一个正在运行的任务。你可能在一个任务开始的时候你想要停止它。为了不浪费CPU时间,你能取消正在运行的任务。例如,你正从网络下载图片,并使用了缓存。你可能当发现该图片已缓存时需要停止该线程任务。由于你的应用代码实现的原因。你可能在发生了网络请求时才知道该图片是否已缓存。

 

        Run a Task on a Thread in the Thread Pool

        为了在线程池里运行一个任务,传递一个Runnable对象到ThreadPoolExecutor.execute()即可。执行execute()会将该任务加到线程池的任务队列。当一个处于Idle的线程可以运行时,线程池管理器取等待时间最长的任务运行:

 

        public class PhotoManager {

                    public void handleState(PhotoTask photoTask, int state) {

                              switch (state) {

                                        // The task finished downloading the image

                                        case DOWNLOAD_COMPLETE:

                                        // Decodes the image

                                        mDecodeThreadPool.execute(photoTask.getPhotoDecodeRunnable());

                                        ...

                              }

                   ...

                   }

                   ...

        }

        当ThreadPoolExecutor在一个Thread运行Runnable时,它会自动的调用Runnable的run()方法。

 

        为了停止一个任务。你需要中断运行该任务的线程。为了能这样做。当你产生一个任务的时候,你需要持有该任务线程的句柄。例如:

        class PhotoDecodeRunnable implements Runnable {

                 // Defines the code to run for this task

                 public void run() {

                        /*

                         * Stores the current Thread in the

                         * object that contains PhotoDecodeRunnable

                         */

                        mPhotoTask.setImageDecodeThread(Thread.currentThread());

                        ...

                 }

                 ...

          }

 

        为了中断该线程,调用Thread.interrupt().注意,线程是由系统控制的。系统能在你的应用进程之外修改该线程。因此,在你中断该线程之前,你需要在该线程上对你的取消操作加锁:将代码放入同步代码块。例如:

        public class PhotoManager {

                   public static void cancelAll() {

                              /*

                               * Creates an array of Runnables that's the same size as the

                               * thread pool work queue

                               */

                            Runnable[] runnableArray = new Runnable[mDecodeWorkQueue.size()];

                             // Populates the array with the Runnables in the queue

                             mDecodeWorkQueue.toArray(runnableArray);

                            // Stores the array length in order to iterate over the array

                            int len = runnableArray.length;

                            /*

                             * Iterates over the array of Runnables and interrupts each one's Thread.

                             */

                            synchronized (sInstance) {

                                      // Iterates over the array of tasks

                                      for (int runnableIndex = 0; runnableIndex < len; runnableIndex++) {

                                                 // Gets the current thread

                                                Thread thread = runnableArray[taskArrayIndex].mThread;

                                                 // if the Thread exists, post an interrupt to it

                                                if (null != thread) {

                                                            thread.interrupt();

                                                }

                                     }

                        }

                }

                ...

         }

 

        大多数情况下,Thread.interrupte()立即的停止线程。然而,该方法仅仅停止处于waiting状态的线程,不中断CPU或者网络任务。为了避免系统变慢或者系统被锁,你应该在试图执行该操作前测试该中断请求。

        /*

         * Before continuing, checks to see that the Thread hasn't been interrupted

         */

          if (Thread.interrupted()) {

                     return;

          }

          ...

          // Decodes a byte array into a Bitmap (CPU-intensive)

         BitmapFactory.decodeByteArray(imageBuffer, 0, imageBuffer.length, bitmapOptions);

         ...

 

 

 

        Communicating with the UI Thread

        上一节你知道了怎么在一个线程上开始一个任务。该线程通过ThreadPoolExecutor管理。最后,你需要在UI线程上更新在子线程里运行的结果。每一个app都有一个专门运行UI对象例如View的线程。只有在UI线程里才能进行UI操作。因为运行在线程里的任务并不运行在UI线程,不能在该线程里访问UI对象。为了让后台运行的任务的数据结果更新到UI上,可以使用一个运行在UI线程上的Handler。

 

        Define a Handler on the UI Thread

        Handler是Android系统框架提供的管理Thread的一个类。Handler收到消息并且运行相关代码处理该消息。正常地,你可以为一个新线程产生一个Handler。你也可以将该handler连接到一个已存在的线程。当你连接一个Handler到UI线程时,该Handler将处理消息到UI线程。

        在产生线程池类(将该类定义为全局变量,例如通过单例)的构造方法里实例化Handler。通过Handler (Looper)构造方法连接该Handler到UI线程。该构造器使用Looper对象。当你基于特定的Looper对象实例化你的Handler时,该Handler运行于和Looper一样的线程,例如:

 

        private PhotoManager() {

                     ...

                    // Defines a Handler object that's attached to the UI thread

                    mHandler = new Handler(Looper.getMainLooper()) {

                    ...

 

        在Handler内部,你要重写handleMessage()方法。当Handler接收到来自它附属的线程发来的消息时,系统会自动调用handleMessage方法。在Handler内部,你要重写handleMessage()方法。当Handler接收到来自它附属的线程发来的消息时,系统会自动调用handleMessage方法。一个线程里所有的Handler对象将会同时接收到来自于该线程的消息。例如:

 

          /*

           * handleMessage() defines the operations to perform when

           * the Handler receives a new Message to process.

           */

          @Override

          public void handleMessage(Message inputMessage) {

                     // Gets the image task from the incoming Message object.

                    PhotoTask photoTask = (PhotoTask) inputMessage.obj;

                    ...

           }

          ...

         }

      }

 

 

 

        Move Data from a Task to the UI Thread

        如果想将工作线程运行的数据传递给主线程中的某个对象,可以在任务中存储这个数据和UI对象的引用。接下来,将这个任务还有执行这个任务后的状态码传递给创建handler的对象。在这个对象中,将包含这个状态码以及任务对象的消息发送给handler。由于handler运行在主线程中,因此它可以将任务产生的结果传递给主线程。

 

        Store data in the task object

        例如,下面是一个运行在后台非UI线程,用于解码Bitmap和在它的父类PhotoTask里存储Bitmap的Runnable。该Runnable也存储状态码:DECODE_STATE_COMPLETED。

 

        // A class that decodes photo files into Bitmaps

        class PhotoDecodeRunnable implements Runnable {

                  ...

                  PhotoDecodeRunnable(PhotoTask downloadTask) {

                           mPhotoTask = downloadTask;

                  }

                  ...

                  // Gets the downloaded byte array

                  byte[] imageBuffer = mPhotoTask.getByteBuffer();

                  ...

                  // Runs the code for this task

                  public void run() {

                             ...

                          // Tries to decode the image buffer

                           returnBitmap = BitmapFactory.decodeByteArray(

                                                               imageBuffer,

                                                               0,

                                                               imageBuffer.length,

                                                               bitmapOptions

                                                     );

                            ...

                            // Sets the ImageView Bitmap

                            mPhotoTask.setImage(returnBitmap);

                            // Reports a status of "completed"

                            mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);

                            ...

               }

             ...

          }

         ...

 

        PhotoTask类也拥有ImageView的引用。该ImageView用于展示Bitmap。虽然Bitmap和ImageView在相同的对象里都有引用。但你不能在该对象里直接将Bitmap的值赋给ImageView。因为该类并不运行在UI线程。接下来,发送状态码给PhoteTask对象。

 

        Send status up the object hierarchy

        PhotoTask维护着图片的引用,也持有显示这个图片的imageview的引用。他会PhotoDecodeRunnable接受一个状态码,再将它传递给创建了线程池和初始化了handler的对象:

 

public class PhotoTask {

   ...

   // Gets a handle to the object that creates the thread pools

   sPhotoManager = PhotoManager.getInstance();

   ...

   public void handleDecodeState(int state) {

int outState;

// Converts the decode state to the overall state.

switch(state) {

   case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:

outState = PhotoManager.TASK_COMPLETE;

break;

   ...

}

...

// Calls the generalized state method

handleState(outState);

   }

   ...

   // Passes the state to PhotoManager

   void handleState(int state) {

/*

* Passes a handle to this task and the

* current state to the class that created

* the thread pools

*/

sPhotoManager.handleState(this, state);

   }

   ...

}

 

        Move data to the UI

        PhotoManager从PhotoTask接受状态码和PhotoTask对象的引用。因为该状态码TASK_COMPLETE,产生一个包含该状态码和任务对象的消息,然后发送该消息给Handler:

 

public class PhotoManager {

   ...

   // Handle status messages from tasks

   public void handleState(PhotoTask photoTask, int state) {

switch (state) {

   ...

   // The task finished downloading and decoding the image

   case TASK_COMPLETE:

/*

* Creates a message for the Handler

* with the state and the task object

*/

Message completeMessage =

mHandler.obtainMessage(state, photoTask);

completeMessage.sendToTarget();

break;

   ...

}

...

   }

 

        最后,Handler.handleMessage()检查每一个接受到的Message的状态码。如果状态码是TASK_COMPLETE,意味着任务结束。在Message里的PhotoTask对象也包含这Bitmap和ImageView对象。

这时,因为Handler.handleMessage()运行在主线程。你能放心的将Bitmap展示在ImageView上。

 

    private PhotoManager() {

...

   mHandler = new Handler(Looper.getMainLooper()) {

@Override

public void handleMessage(Message inputMessage) {

   // Gets the task from the incoming Message object.

   PhotoTask photoTask = (PhotoTask) inputMessage.obj;

   // Gets the ImageView for this task

   PhotoView localView = photoTask.getPhotoView();

   ...

   switch (inputMessage.what) {

...

// The decoding is done

case TASK_COMPLETE:

   /*

    * Moves the Bitmap from the task

    * to the View

    */

   localView.setImageBitmap(photoTask.getImage());

   break;

...

default:

   /*

    * Pass along other messages from the UI

    */

   super.handleMessage(inputMessage);

   }

   ...

}

...

   }

   ...

   }

...

}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics