如果不设置自己的selector,默认情况下,在按下item时,会使用默认的selector,而这个selector是有border的,导致显示出来四周会多出几像素。解决方法就是设置自己的selector,不要有border。
Android开发:一个Activity半透明引起的BUG(android:windowIsTranslucent)
场景: 两个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”
Android开发:在后台服务中运行-报告工作状态
本节讲运行在后台服务里的工作请求,如何向发送请求者报告状态。推荐用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来响应广播。
Android开发:在后台服务中运行-发送工作请求给后台服务
上节讲了如何创建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实现。
Android开发:在后台服务中运行-创建后台服务
翻自: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的类名。 注意:
Android开发:后台任务最佳实践-在后台服务中运行
翻自:http://developer.android.com/training/run-background-service/index.html 除非特别指定,否则,大多数默认的操作都是在前台(UI线程)运行的。这可能会让UI响应慢,甚至ANR。Android提供了几个类来帮助你把任务分到后台运行的独立线程里。最有用的是IntentService. 本课讲了如何实现IntentService,发送工作请求,把结果反馈给其他组件。
课程
创建一个后台服务
学会如何创建一个IntentService
发送请求到后台服务
学会如何发送工作请求给一个IntentService
报告工作状态
学会如何用Intent和LocalBroadcastManager,在IntentService与Activity之间通讯工作请求的状态
Android开发:性能最佳实践-保持应用响应
翻自: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线程上潜在的耗时操作
Android开发:多线程操作-与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.
Android开发:多线程操作-在线程池里运行代码
翻自: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);
...
Android开发:多线程操作-创建多线程管理器
翻自: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);
}