`

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);

   }

   ...

}

...

   }

   ...

   }

...

}

分享到:
评论

相关推荐

    android developer官网例子threadSample.zip

    android developer-&gt;training-&gt;sending operations to multiple threads 的例子。demo的例子叫threadSample.zip。这个例子请结合官网文档看,对android多线程处理给出了一个解决方案,写的相当好。

    android-serialport-api

    The current Android SDK does not provide any API for reading and writing to the Linux TTY serial ports. You may find such serial ports on the connector of HTC Android phones. This project wants to ...

    Android代码-Wi-Fi Privacy Police

    It prevents your smartphone from sending out the names of Wi-Fi networks it wants to connect to over the air. This makes sure that other people in your surroundings can not see the networks you've ...

    Android代码-quitnow-email-suggester

    We're doing it like everyone does: sending an e-mail to the user, and waiting for the user to click a link. The only validation we do is to check the e-mail with usual regex... but we need to go a ...

    Mobile-based-Advertising-without-sending-any-SMS-_advertising

    Document for project manage

    Sending Email using MAPI - A COM DLL(sending email demo and soucecode)

    I remember, once I was to develop an application, which sends emails at the specific time to specific people with the customized messages. For that I developed a COM service that used MAPI. MAPI, i.e...

    USB4 1.0 ECN - Adding Timeout for Sending LFPS on CLx Exit.pdf

    USB4 1.0 ECN - Adding Timeout for Sending LFPS on CLx Exit USB4 1.0 ECN(Engineering Change Notice,工程变更通知)是一种用于改进 USB4 1.0 规范的技术文档。该文档的主要内容是添加超时机制,以避免在 CLx ...

    Android代码-Open-Mam

    but without sending your application binaries on the cloud, you will handle their storage. API The api has been thinked to be used on any server, even in : a hosted server (PHP) a mutualised server ...

    Android代码-quitnow-sleep-time

    and lots of apps were sending notifications when the user was sleeping... and HEY! That makes people angry. So, before setting the sound of that notifications and asking the phone to vibrate arround ...

    AndroidXMPP-master

    Android XMPP(Extensible Messaging and Presence Protocol)是一种基于XML的开放标准协议,常用于实现即时通讯(Instant Messaging, IM)和在线状态管理。这个名为"AndroidXMPP-master"的项目显然是一个针对...

    Android代码-dev-summit-architecture-demo

    Sample Application for The Android Architecture Talk @ Android Dev Summit 2015. video This is a simple social sharing application where users can post text messages and also list other users' messages...

    Best Practices for Performance

    Best Practices for Performance Sending Operations to Multiple Threads

    信息安全_数据安全_spo1-r04-sending_a_human_to_do_a.pdf

    预防层的性能限制在于其静态分析(如模式匹配和信誉匹配)速度较慢,无法有效应对快速变化的高级恶意软件。 4. **高级恶意软件的挑战** 高级恶意软件通过不断改变其行为特征,使得预防层无法匹配已知模式。它可能...

    calo-landing-page-with-mail-sending:https

    【文件名称列表】"calo-landing-page-with-mail-sending-master" 暗示这是一个GitHub仓库的主分支,可能包含以下关键文件和目录: 1. `index.php`:这个文件可能是项目的入口点,包含了HTML结构和PHP代码,用于处理...

    Asynchronous Android Programming

    Enhance UI performance and responsiveness by sending work to a service running in the background Defer, schedule, and batch work on the Android system without compromising the battery life and user ...

    Android Service被关闭后自动重启,解决被异常kill 服务

    Android Service被关闭后自动重启,解决被异常kill 服务

    chrome-extension-tab-message-sending-demo

    使用KotlinJs的Chrome扩展程序用KotlinJs编写的简单的chrome扩展名,用于显示当前url。 建造: ./gradlew compileKotlin2Jscd extensionnpm install安装: Chrome-&gt; Extensions -&gt;启用Developer Mode -&gt; Load ...

    VB编程用ICON图标修改器

    use it you are obligated to register it with the author by sending $29.95 Add $9.95 for CD, or free delivery if email/download. Order Page: http://www.exeicon.com/piture-to-icon/buy.htm -------- ...

    Android代码-PhotoBackup

    silently in background and sending photos to the associated server as soon as you take them. An upload journal allows you to see the status of each photo in your device. It does not backup videos. It ...

Global site tag (gtag.js) - Google Analytics