0%

场景: 两个Activity,Activity1,和Activity2. Activity1上是一个ListView,Activity2带EditText.从Activity1跳到Activity2,在Activity2上点EditText调出软键盘,发现Activity1里的ListView竟然调了getView方法,重绘. 原因: Activity2的theme里加了android:windowIsTranslucent=”true”,导致Activity2透明,而Activity1设置了android:windowSoftInputMode=”adjustPan” ,导致Activity2在弹出软键盘时,Activity1也收到事件,ListView item重绘 解决方法: android:windowIsTranslucent=”false” 或者Activity1设置android:windowSoftInputMode=”adjustNothing”

本节讲运行在后台服务里的工作请求,如何向发送请求者报告状态。推荐用LocalBroadcastManager发送和接收状态,它限制了只有本app才能接收到广播。

从IntentService汇报状态

从IntentService发送工作请求状态给其他组件,先创建一个包含状态和数据的Intent。也可以添加action和URI到intent里。 下一步,调用 LocalBroadcastManager.sendBroadcast()发送Intent,应用中所有注册了接收该广播的接收器都能收到。LocalBroadcastManager.getInstance()获取LocalBroadcastManager实例。

public final class Constants {
    ...
    // Defines a custom Intent action
    public static final String BROADCAST_ACTION =
        "com.example.android.threadsample.BROADCAST";
    ...
    // Defines the key for the status "extra" in an Intent
    public static final String EXTENDED_DATA_STATUS =
        "com.example.android.threadsample.STATUS";
    ...
}
public class RSSPullService extends IntentService {
...
    /*
     * Creates a new Intent containing a Uri object
     * BROADCAST_ACTION is a custom Intent action
     */
    Intent localIntent =
            new Intent(Constants.BROADCAST_ACTION)
            // Puts the status into the Intent
            .putExtra(Constants.EXTENDED_DATA_STATUS, status);
    // Broadcasts the Intent to receivers in this app.
    LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
...
}

下一步是接收广播并处理。

接收来自IntentService的广播

接收方式与普通的Broadcast一样,用一个BroadcastReceiver的子类,实现BroadcastReceiver.onReceive()方法

// Broadcast receiver for receiving status updates from the IntentService
private class ResponseReceiver extends BroadcastReceiver
{
    // Prevents instantiation
    private DownloadStateReceiver() {
    }
    // Called when the BroadcastReceiver gets an Intent it's registered to receive
    @
    public void onReceive(Context context, Intent intent) {
...
        /*
         * Handle Intents here.
         */
...
    }
}

定义好了接收器类以后,定义过滤器,匹配指定的action,categorie,data.

// Class that displays photos
public class DisplayActivity extends FragmentActivity {
    ...
    public void onCreate(Bundle stateBundle) {
        ...
        super.onCreate(stateBundle);
        ...
        // The filter's action is BROADCAST_ACTION
        IntentFilter mStatusIntentFilter = new IntentFilter(
                Constants.BROADCAST_ACTION);
    
        // Adds a data filter for the HTTP scheme
        mStatusIntentFilter.addDataScheme("http");
        ...

注册方式稍有不同,用LocalBroadcastManager.registerReceiver()。

  // Instantiates a new DownloadStateReceiver
        DownloadStateReceiver mDownloadStateReceiver =
                new DownloadStateReceiver();
        // Registers the DownloadStateReceiver and its intent filters
        LocalBroadcastManager.getInstance(this).registerReceiver(
                mDownloadStateReceiver,
                mStatusIntentFilter);
        ...

单个BroadcastReceiver可以处理多种类型的广播,这个特性允许你根据不同的action运行不同的代码,而无需为每个action定义一个BroadcastReceiver。

  /*
         * Instantiates a new action filter.
         * No data filter is needed.
         */
        statusIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE);
        ...
        // Registers the receiver with the new filter
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
                mDownloadStateReceiver,
                mIntentFilter);

发送广播并不会启动或恢复Activity.BroadcastReceiver让Activity能够接收处理数据,包括应用在后台的时候,但不会强制app回到前台。如果你要在app在后台,对用户不可见时,通知用户一个事件发生,用Notification。绝对不要启动一个Activity来响应广播。

上节讲了如何创建IntentService。本节讲如何通过发送Intent触发IntentService执行操作。Intent可以包含任意数据。你可以在Activity或Fragment的任意位置发送Intent给IntentService。

创建发送工作请求传给IntentService

创建一个明确的Intent,添加需要的数据,调用startService()发送给IntentService

/*
 * Creates a new Intent to start the RSSPullService
 * IntentService. Passes a URI in the
 * Intent's "data" field.
 */
mServiceIntent = new Intent(getActivity(), RSSPullService.class);
mServiceIntent.setData(Uri.parse(dataUrl));
//Call startService() 
// Starts the IntentService
getActivity().startService(mServiceIntent);

提示:可以在Activity or Fragment的任意位置发送工作请求。如果你需要先取到用户输入,你可以在点击事件或类似手势的回调方法里发送工作请求。 一旦调用了startService(),IntentService会在onHandleIntent()工作,完了结束自身。 下一步是报告结果给原来的Activity或Fragment,下节讲如何用BroadcastReceiver实现。

翻自:http://developer.android.com/training/run-background-service/create-service.html IntentService提供了在单个后台线程运行操作的简单结构。这允许它操作耗时操作,而不影响UI响应。同样,IntentService也不影响UI生命周期事件,所以,它在某些可能关闭AsyncTask的情况下,仍会继续运行(实测在Activity的onDestory里写AsyncTask无法运行)。 IntentService有如下限制:

  • 它不能直接影响UI。要把结果反映给UI,需要发给Activity
  • 工作请求会顺序运行。如果一个操作未结束,后面发送的操作必须等它结束(单线程)
  • IntentService里运行的操作无法被中断

然而,在大多数情况下,IntentService是简单后台任务的首选方式。 本节展示了如何创建IntentService的子类,如何创建onHandleIntent()回调,如何在AndroidManifest.xml声明IntentService。

创建IntentService

定义一个IntentService的子类,覆盖onHandleIntent()方法:

public class RSSPullService extends IntentService {
    @Override
    protected void onHandleIntent(Intent workIntent) {
        // Gets data from the incoming Intent
        String dataString = workIntent.getDataString();
        ...
        // Do work here, based on the contents of dataString
        ...
    }
}

提示:其他Service正常的回调,像 onStartCommand()在IntentService里会自动调用。在IntentService里,应该避免覆盖这些回调。

在AndroidManifest.xml里定义IntentService

IntentService也是Service),需要在AndroidManifest.xml里注册。

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name">
        ...
        <!--
            Because android:exported is set to "false",
            the service is only available to this app.
        -->
        <service
            android:name=".RSSPullService"
            android:exported="false"/>
        ...
    <application/>

android:name属性指定了IntentService的类名。 注意:节点不能包含intent filter。发送工作请求的Activity使用明确的Intent,会指定哪个IntentService。这也意味着,只有同一个app里的组件,或者另一个有相同user id的应用才能访问IntentService。 现在你有了基础的IntentService类,可以用Intent对象发送工作请求。怎么发,下节讲。

翻自:http://developer.android.com/training/run-background-service/index.html 除非特别指定,否则,大多数默认的操作都是在前台(UI线程)运行的。这可能会让UI响应慢,甚至ANR。Android提供了几个类来帮助你把任务分到后台运行的独立线程里。最有用的是IntentService. 本课讲了如何实现IntentService,发送工作请求,把结果反馈给其他组件。

课程

创建一个后台服务

学会如何创建一个IntentService

发送请求到后台服务

学会如何发送工作请求给一个IntentService

报告工作状态

学会如何用Intent和LocalBroadcastManager,在IntentService与Activity之间通讯工作请求的状态

翻自:http://developer.android.com/training/articles/perf-anr.html App里发生的最糟糕的事是弹出应用无响应”Application Not Responding” (ANR) 对话框.本课讲的是如何保持应用响应,避免ANR。

什么触发ANR

通常,系统会在应用无法对用户输入响应时显示ANR。比如,如果一个应用在I/O操作上阻塞了(频繁请求网络)UI线程,系统无法处理用户输入事件。或者,在UI线程中,app花了大量时间在构建复杂的类,或在游戏中计算下一个动作。保证这些操作高效是很重要的,但最高效的代码也需要花费时间。 在任何情况下,都不要在UI线程执行耗时任务,取而代之的是创建 一个工作线程,在这个线程里操作。这可以保持UI线程运行,阻止系统因为代码卡住而结束应用。 在Android里,Activity Manager和Window Manager系统服务监控着应用的响应能力。Android会在检测到以下情形中之一时,弹出ANR对话框:

  • 未在5秒内对用户输入事件响应
  • BroadcastReceiver未在10秒内执行完

如何避免ANR

Android应用默认运行在单线程里,叫UI线程或主线程。这意味着,你的应用所有工作都在UI线程里,如果花费很长时间才能完成,会触发ANR,因为此时应用无法操控输入事件或广播。 因此,UI 线程里的任何方法都应该尽可能地做轻量的工作,特别是Activity在生命周期方法,像onCreate(),onResume().潜在的耗时操作,像网络,数据库,或昂贵的计算(像改变图片大小)应该在工作线程里完成(或者在数据库操作案例里,通过一个异步请求)。 最高效的方法是为耗时操作使用AsyncTask类创建工作线程。继承AsyncTask实现doInBackground()方法来执行工作。要发送进度给用户,调用 publishProgress(),会触发onProgressUpdate(),例子:

private class DownloadFilesTask extends AsyncTask {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

执行这个工作线程,只需要创建一个实例,调用 execute():

new DownloadFilesTask().execute(url1, url2, url3);

尽管比AsyncTask更复杂,你可能还是想创建自己的线程或者HandlerThread类,如果这么做,你应该调用Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND) 设置线程优先线为”background”.如果没有,线程仍然会拖慢应用,因为它跟UI线程优先级相同。 如果你实现Thread或HandlerThread,确保UI线程没有因为等待工作线程执行完而阻塞。不要调用Thread.wait()或Thread.sleep(),而是提供一个Handler,供任务执行完后回调。如此设计,UI线程会保持响应,避免出现ANR对话框。 特别强调BroadcastReceiver的执行时间,意味着你要:分散工作到后台线程里,像保存设置或者注册Notification。执行密集任务(intensive tasks),应该用IntentService。 提示:你可以用StrictMode帮你找到在UI线程上潜在的耗时操作

翻自:http://developer.android.com/training/multiple-threads/communicate-ui.html 本节向你展示如何在任务中发送数据给UI线程里的对象,这个特性允许你在后台线程工作,完了在UI线程展示结果。

在UI线程定义一个Handler

Handler是Android系统线程管理框架里的一部分。一个Handler对象接收消息,并且运行代码来处理消息。正常情况下,你为新线程创建Handler,但你也可以为已有的线程创建一个Handler.当你连接Handler到UI线程时,处理消息的代码会在UI线程上运行. 在创建线程池的类的构造器里实例化Handler对象,保存在全局变量里。用Handler(Looper)方法实例化,连接到UI线程,构造方法使用Looper对象,也是Android系统线程管理框架里的一部分.Looper类有一个静态方法getMainLooper()可以获取UI线程的Looper对象。如:

private PhotoManager() {
...
    // Defines a Handler object that's attached to the UI thread
    mHandler = new Handler(Looper.getMainLooper()) {
    ...

在Handler里,覆盖handleMessage()。Android系统会在Handler管理的线程收到新消息时,调用该方法。一个指定线程的所有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;
            ...
        }
    ...
    }
}

从任务里移动数据到UI线程

要从后台线程的任务里移动数据到UI线程的对象,先保存引用到数据和任务对象的UI对象里,接下来把任务对象和状态码传给Handler对象。在这个对象里,发送一个包含状态 和任务对象的消息给Handler.因为Handler在UI线程上运行,它可以移动数据给UI对象。

在任务对象里存储数据

如,这是一个Runnable,运行在后台线程,它解析Bitmap,并保存到它的父对象。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引用,用来显示Bitmap.尽管引用Bitmap和ImageView是在同一个对象里,但因为不是在UI线程,你不能直接让ImageView显示Bitmap.

沿对象层次逐级发送状态

PhotoTask持有解码的数据和显示数据的View对象的引用,它从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);
    }
    ...
}

移动数据到UI

PhotoManager从PhotoTask对象接收到状态码和PhotoTask对象的句柄。因为状态是TASK_COMPLETE,创建一个包含状态和任务对象的Message,发送给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()运行在UI线程,它可以安全地为ImageView设置Bitmap.

翻自:http://developer.android.com/training/multiple-threads/run-code.html 本节展示如何在线程池里执行任务。流程是,添加一个任务到线程池的工作队列,当有线程可用时(执行完其他任务,空闲,或者还没执行任务),ThreadPoolExecutor会从队列里取任务,并在线程里运行。 本课同时向你展示了如何停止正在运行的任务。

在线程池里的线程上执行任务

在ThreadPoolExecutor.execute()里传入 Runnable对象启动任务。这个方法会把任务添加到线程池工作队列。当有空闲线程时,管理器会取出等待最久的任务,在线程上运行。

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启动Runnable时,会自动调用run()方法。

中断正在运行的代码

要停止任务,你需要中断任务的进程。你需要在创建任务的时候,保存一个当前线程的handle. 如:

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()就可以了。提示:线程对象是系统控制的,可以在你的app进程外被编辑。因为这个原因,你需要在中断它前加访问锁,放到一个同步块里:

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.interrupt()会马上停止线程。可是,它只会停止在等待的线程,但不会中断cpu或network-intensive任务。为了避免系统变慢,你应该在开始尝试操作前测试等待中断的请求。

/*
 * 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);
...

翻自:http://developer.android.com/training/multiple-threads/create-threadpool.html 如果你要反复执行一个任务,用不同的数据集(参数不同),但一次只要一个执行(任务是单线程的),IntentService符合你的需求。当需要在资源可用时自动执行任务,或允许多任务同时执行,你需要一个线程管理器管理你的线程。ThreadPoolExecutor,会维护一个队列,当它的线程池有空时,从队列里取任务,并执行。要运行任务,你要做的就是把它加到队列里。 线程池可以并联运行一个任务的多个实例,所以你要保存代码线程安全。能被多线程访问的变量需要同步块.更多信息,见Processes and Threads

定义线程池类

在它自己类中实例ThreadPoolExecutor.在类里,如下操作: 为线程池使用static变量 你可能在app里只需要一个单例的线程池,这是为了统一控制限制CPU或网络资源。如果你有不同的Runnable类型,你可能想要每种类型都有各自的线程池,但这些都可以放到一个单一的实例里。比如,你可以把它声明成全局变量:

public class PhotoManager {
    ...
    static  {
        ...
        // Creates a single static instance of PhotoManager
        sInstance = new PhotoManager();
    }
    ...

使用private构造方法 把构造方法声明成private,可以确保单例,这意味着你不需要在同步代码块里封装类访问。

    public class PhotoManager {
        ...
        /**
         * 构建用来下载和decode图片的工作队列和线程池,因为构造方法标记为private,
         * 对其他类不可访问(甚至同包下的类)
         */
        private PhotoManager() {
            ...
        }

调用线程池类里的方法来开始任务 线程池类里定义一个方法,用来添加任务到线程池队列,如:

public class PhotoManager {
    ...
    // 供PhotoView调用获取图片
    static public PhotoTask startDownload(
        PhotoView imageView,
        boolean cacheFlag) {
        ...
        // 添加一个任务到线程池
        sInstance.
                mDownloadThreadPool.
                execute(downloadTask.getHTTPDownloadRunnable());
        ...
    }

实例化一个UI线程的Handler. Handler用于与UI线程通讯,大多数UI控件只允许在UI线程修改。

    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) {
                ...
            }
        ...
        }
    }

判断线程池参数

一旦你有了全部类结构,你就可以开始定义线程池。实例化一个线程池对象,你需要下面的值: 初始池大小,最大池大小。 线程池的线程数量主要依赖于设备的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()返回的是当前活跃的内核数量。 保持活跃时间和时间单位 一个进程在关闭前,保持空闲状态的时间(可以复用进程)。时间单位在TimeUnit里 任务队列 ThreadPoolExecutor的列队保存Runnable对象。在线程中执行代码,线程池管理器会从一个FIFO队列里取出一个Runnable对象,附加到线程里。队列实现BlockingQueue接口,在创建线程池时提供。你可以从现有实现中选一个,适应你的需求,参见ThreadPoolExecutor。下面是使用LinkedBlockingQueue的例子:

public class PhotoManager {
    ...
    private PhotoManager() {
        ...
        // A queue of Runnables
        private final BlockingQueue mDecodeWorkQueue;
        ...
        // Instantiates the queue of Runnables as a LinkedBlockingQueue
        mDecodeWorkQueue = new LinkedBlockingQueue();
        ...
    }
    ...
}

创建线程池

调用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);
    }