Android 笔记 Working in the Background

[/code][b]主要内容[/b]
1 创建,启动和停止Services
2 绑定Services到Activities
3 设定Services的优先级为前端
4 使用AsyncTasks来管理后台进程
5 创建后台线程和使用Handlers使其与UI线程同步
6 显示Toasts
7 使用Notification Manager通知应用事件的使用者(以下部分待完成)
8 Creating insistent and ongoing Notifications
9 使用Alarms来计划应用事件

Service是用来跑不可见,没有用户界面的操作和功能的。
通常来说Service的优先级在inactive Activities之上,不过也可以通过配置使资源变得足够时候重新启动被终止的Service,极端情况下,甚至可以让Service有和foreground Activity一样的优先级。

通过Service的使用,可以保持应用运行和对事件的响应,即使应用在不活动的状态。
通过Notifications和Toasts,我们可以在不打断当前应用的情况下和用户沟通。

Toasts 是个短暂的非模式的对话框,可以在不获得当前应用焦点的情况下向用户显示信息。

Notifications则是一种用来提醒用户的更加健壮的机制。某些情况下,可能手机在口袋里或者不在用户身边,用户会错过铃声,闪屏,震动等提醒。这时需要状态栏出现相应的图标来说明这些事件发生过。这种功能可以通过Notifications来实现。

[b](一)介绍SERVICES[/b]
和现实图形界面的Activity不同,SERVICES是跑在后台的,可以用来更新Content Providers,启动Intents,以及触发Notifications。当应用的Activities处于不可见,不活动甚至被关闭的状态,SERVICES是用来执行进行中或者常规的进程以及处理事件的完美方式。
SERVICES 是由其他的组件(Activities,Broadcast Receivers以及其他的Services)来控制的,包括启动,停止。如果你的应用执行动作不直接依赖于用户的输入,应该选择Services。

已经开始的Services总是拥有比不活动和不可见的Activities更高的优先级,所以他们被运行时资源管理终止的可能性要小一些。Services被过早停止的唯一原因是额外的资源被分配给了前端组件,不过当资源重新可用的时候,这些Services会被自动重新启动。

如果你的Service和用户直接交互(比如播放音乐),你有必要把你的Service的优先级提高到和前端Activity一样。这样做可以使你的Service不到极端情况下不会被终止,但也有可能因为减小了运行时管理资源的能力而造成整体的用户体验下降。

定义一个Service,需要建立一个继承Service的新类,并重写onCreate()和onBind()方法。

通常情况下也需要重写onStartCommand,无论Service是不是通过调用startService来启动,onStartCommand方法都会被调用,所以这个方法可能在Service的生命周期被多次调用。

介绍几个Service中的常量,用于控制重新启动的行为。
START_STICKY
描述标准行为。如果返回这个值,onStartCommand只要在Service在被运行时终止后重新启动都会被调用。注意在重新启动的时候,传给onStartCommand的Intent参数为null。 一般显式的启动和终止,使用这个模式。譬如播放音乐就可以使用这个。

START_NOT_STICKY
这个模式用在启动起来处理特定的动作和命令的Service。比较典型的是使用stopSelf在命令结束时终止。
在被运行时终止以后,设为这种模式的Service只会在有挂起的启动调用才会重新启动。如果在Service被终止后没有startService调用,Service会停止,不会调用onStartCommand。
这种模式是处理特定请求的Service的理想选择,特别是更新和网络投票这类的有规律的处理。相比在一段时期内重启来争夺资源,让Service停止然后在下一个的日程安排的时间点再重试常常是更明智的选择。

START_REDELIVER_INTENT
某些情况下,你会希望确保从你的Service请求的命令已经结束。
这个模式是前两种的结合,如果是运行时终止了Service,有挂起的启动调用或者进程在调用stopSelf之前被杀掉,Service都会重启。在后面这种情况会调用onStartCommand,并且穿进去一个没有处理完全的初始的Intent

需要注意的是,每一种模式都要求你在处理完全的时候通过stopService或者stopSelf显式的停止Service。

在onStartCommand中庸返回值指定的重启模式会影响后来的调用的传入的参数。

最初,Intent会是启动Service时传进startService的参数。在基于系统的重启之后,Intent在START_STICKY模式下会是null,在START_REDELIVER_INTENT.模式下会是最初的Intent。

flag参数可以用来知道Service是怎样启动的。

START_FLAG_REDELIVERY
表示Intent参数是Service在用stopSelf显式终止前被系统运行时终止导致的转交(redelivery)
START_FLAG_RETRY
表示Service在不正常终止后已经被重新启动。当Service之前被设为START_STICKY被传入。

下面的这段代码判断了是哪一种情况

[code="java"]
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if ((flags & START_FLAG_RETRY) == 0) {
// TODO If it’s a restart, do something.
}
else {
// TODO Alternative background process.
}
return Service.START_STICKY;
}



[b]在Manifest中注册Service[/b]

<service android:enabled="true" android:name=".MyService"/>



[b]自终止(Self-Terminating)Service[/b]
当你的Service做的动作结束的时候,应该调用stopSelf方法。如果不使用参数,会强行停止Service。如果使用参数startId可以确保目前startService调用的每一个实例都处理完毕。

stopSelf(startId);


显式停止Service的方式允许系统回收资源,否则资源是被要求保留的。因为Services是高优先级的,通常不会被运行时杀掉,所以自终止Service避免了不必要的资源占用。

[b]启动,控制以及和Service交互[/b]

启动一个Service用startService方法。可以选择用适当的已注册的Intent Receiver隐式的启动也可以选择用一个Service的类来显式的启动。如果一个Service要求某个许可,而你的应用没有这个许可,startService会抛出一个SecurityException。

两种情况都可以用给Intent加extras的方式给Service的onStart传值。


// Implicitly start a Service
Intent myIntent = new Intent(MyService.ORDER_PIZZA);
myIntent.putExtra("TOPPING", "Margherita");
startService(myIntent);
// Explicitly start a Service
startService(new Intent(this, MyService.class));


使用stopService停止一个Service,传入一个定义了要停止的Service的Intent。
下面的代码先启动一个service,然后再停止这个service,都是用显式的方式,在停止的时候用了startService返回的一个组件名。


ComponentName service = startService(new Intent(this, BaseballWatch.class));
// Stop a service using the service name.
stopService(new Intent(this, service.getClass()));
// Stop a service explicitly.
try {
Class serviceClass = Class.forName(service.getClassName());
stopService(new Intent(this, serviceClass));
} catch (ClassNotFoundException e) {}


如果startService调用了正在运行的Service,这个service的onStartCommand会被再执行一次。startService的调用无法嵌套,所以不管调用了多少次startService,一个单独的stopService就可以终止一个Service。


[b]把Activities绑定到Services上[/b]

当一个Activity被绑定到一个service上,那这个activity会保持这个Service实例的引用。为了支持绑定一个service,需要实现一个onBind方法。

private final IBinder binder = new MyBinder();
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}


ServiceConnection用来表示Service和Activity之间的连接,我们需要实现一个ServiceConnection,重写onServiceConnected和onServiceDisconnected来得到一个Service实例的引用,这样来建立一个连接。


// Reference to the service
private MyService serviceBinder;
// Handles the connection between the service and activity
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// Called when the connection is made.
serviceBinder = ((MyService.MyBinder)service).getService();
}
public void onServiceDisconnected(ComponentName className) {
// Received when the service unexpectedly disconnects.
serviceBinder = null;
}
};



要执行一个绑定,需要调用bindService方法,传入一个选择了要绑定的Service的Intent,以及一个新的ServiceConnection的实现。

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Bind to the service
Intent bindIntent = new Intent(MyActivity.this, MyService.class);
bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE);
}


一旦一个service被绑定,这个service所有的公共方法都可以被通过onServiceConnected获得的对象所用。

Android应用通常不共享内存,但在某些情况下,你的应用会希望和泡在其他应用进程的service交互。

你可以使用broadcast Intents或者用来启动service的Intent中的extras Bundle来和跑在另一个进程中的service通信。如果你需要更紧耦合的连接,你可以用AIDL来使Service可以跨应用绑定。AIDL用OS级的术语来定义了Service的接口,允许android跨进程传输对象。


[b]区分后台Services的优先级[/b]

通常情况下Services的优先级是低于前端Activities的,极端情况下,你需要提升某些Services使其拥有和前端Activities拥有同样的优先级,譬如某些和用户直接交互的Services。这时你可以使用startForeground方法来设定你的Service跑在前端。
为了让用户知道这个Service跑在前端,需要指定一个Notification。


int NOTIFICATION_ID = 1;
Intent intent = new Intent(this, MyActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 1, intent, 0));
Notification notification = new Notification(R.drawable.icon,
"Running in the Foreground", System.currentTimeMillis());
notification.setLatestEventInfo(this, "Title", "Text", pi);
notification.flags = notification.flags |
Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_ID, notification);


以上代码使用了setLatestEventInfo来更新notification使用默认状态窗口布局。
后面还会讲到如何定制notification的布局,应用这个技术可以把你的service的更多细节提供给用户。

当你不再要求service跑在前端的时候可以使用stopForeground方法把它重新移到的后台,也可以可选择的移除notification。

// Move to the background and remove the Notification
stopForeground(true);



[b]使用后台线程[/b]

为了确保你的应用保持响应,把慢的耗时的操作从应用主线程移到子线程是一个好的做法。
android提供了两种选择让你的处理放到后台。AsyncTask可以让你定义一个执行在后台的操作,然后提供一个你可以用来监听进程并且通知结果给GUI线程的事件处理器。
另外你还可以实现你自己的Threads并且用Handler类来在更新UI之前保持和GUI线程的同步。

使用后台线程对避免强行关闭的对话框的出现至关重要。(本书是针对android2.0的,实际上在android3.0之后,在主线程访问网络会抛出NetworkOnMainThreadException,不管是否耗时超过5秒)

[b]使用AsyncTask运行异步任务[/b]

AsyncTask类提供了一个简单方便的机制来把耗时的操作移到后台线程。 它提供了方便的事件处理器来和GUI线程同步,这样当你的任务完成时可以更新views和其他UI组件报告进展或者发布结果。

AsyncTask 处理这个线程的创建,管理和同步,让你可以建立一个异步任务,这个异步任务由两部分组成,一个在后台做的处理,一个是当处理结束时UI的更新。

建立一个新的异步任务
建立一个新的异步任务需要继承AsyncTask,也需要指定输入的参数,进展报告和结果值的类型。如果你不需要指定,使用Void来作为类型。


private class MyAsyncTask extends AsyncTask<String, Integer, Integer> {
@Override
protected void onProgressUpdate(Integer... progress) {
// [... Update progress bar, Notification, or other UI element ...]
}
@Override
protected void onPostExecute(Integer... result) {
// [... Report results via UI update, Dialog, or notification ...]
}
@Override
protected Integer doInBackground(String... parameter) {
int myProgress = 0;
// [... Perform background processing task, update myProgress ...]
PublishProgress(myProgress)
// [... Continue performing background processing task ...]
// Return the value to be passed to onPostExecute
return result;
}
}

你通常需要重写这几个方法:
doInBackground 这个方法会在后台线程执行,所以耗时的操作应该放在这个方法里,使用publishProgress方法来允许onProgressUpdate更新UI。

onProgressUpdate 这个方法会收到在doInBackground 中传入publishProgress的参数。这个方法执行的时候是和GUI线程同步的。

onPostExecute 当doInBackground结束的时候,doInBackground的返回值会传入到这个方法,可以在这个方法中更新UI。

运行一个异步任务,使用execute方法,传入相应的参数。


new MyAsyncTask().execute("inputString1", "inputString2");


[b]自建一个线程和GUI同步[/b]

你可以使用Android的Handler类来自己建立和管理子线程。


// This method is called on the main GUI thread.
private void mainProcessing() {
// This moves the time consuming operation to a child thread.
Thread thread = new Thread(null, doBackgroundThreadProcessing,
"Background");
thread.start();
}
// Runnable that executes the background processing method.
private Runnable doBackgroundThreadProcessing = new Runnable() {
public void run() {
backgroundThreadProcessing();
}
};
// Method which does some processing in the background.
private void backgroundThreadProcessing() {
[ ... Time consuming operations ... ]
}


如果是运行在activity中,也可以是用runOnUiThread方法使方法在GUI线程中运行。


runOnUiThread(new Runnable() {
public void run() {
// TODO Update a View.
}
});



在其他情况(譬如Toasts和Notifications),你可以使用Handler类把方法放到创建Handler的线程。


// Initialize a handler on the main thread.
private Handler handler = new Handler();
private void mainProcessing() {
Thread thread = new Thread(null, doBackgroundThreadProcessing,
"Background");
thread.start();
}
private Runnable doBackgroundThreadProcessing = new Runnable() {
public void run() {
backgroundThreadProcessing();
}
};
// Method which does some processing in the background.
private void backgroundThreadProcessing() {
[ ... Time consuming operations ... ]
handler.post(doUpdateGUI);
}
// Runnable that executes the update GUI method.
private Runnable doUpdateGUI = new Runnable() {
public void run() {
updateGUI();
}
};
private void updateGUI() {
[ ... Open a dialog or modify a GUI element ... ]
}


还可以用Handler的postDelayed和postAtTime方法来实现延时或者在特定时间运行。

[b]TOAST[/b]
Toasts是个短暂的对话框,只有几秒钟保持可见。它不会抢夺焦点,所以也不会打断当前活动的应用。比较适合用于作为后台service事件的提醒。


Context context = getApplicationContext();
String msg = "To health and happiness!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, msg, duration);
toast.show();


可以使用makeText这个静态方法来创建一个标准的的Toast显示窗口,传入要显示的信息和显示的时间(通常用常数LENGTH_SHORT或LENGTH_LONG)。.LENGTH_SHORT大概会显示2秒钟。显示时,后面的应用依然保持响应。

自定义Toasts

你也可以自定义toast,用定义特定的布局。

Context context = getApplicationContext();
String msg = "To the bride and groom!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, msg, duration);
int offsetX = 0;
int offsetY = 0;
toast.setGravity(Gravity.BOTTOM, offsetX, offsetY);
toast.show();


这里用setGravity方法是toast保持显示在底部。
你也可以用setView方法把一个View加到Toast里面。用Toast来显示这个view。

在工作线程中使用Toasts

Toasts必须在GUI线程中打开。下面的代码显示了工作线程中如何使用Toasts,并且确保其在GUI线程打开。


private void mainProcessing() {
Thread thread = new Thread(null, doBackgroundThreadProcessing,
"Background");
thread.start();
}
private Runnable doBackgroundThreadProcessing = new Runnable() {
public void run() {
backgroundThreadProcessing();
}
};
private void backgroundThreadProcessing() {
handler.post(doUpdateGUI);
}
// Runnable that executes the update GUI method.
private Runnable doUpdateGUI = new Runnable() {
public void run() {
Context context = getApplicationContext();
String msg = "To open mobile development!";
int duration = Toast.LENGTH_SHORT;
Toast.makeText(context, msg, duration).show();
}
};




[b]介绍NOTIFICATIONS[/b]

你的应用可以使用Notifications来提醒用户,在不需要activity的时候。Notification 是由 Notification Manager来处理的,Notification 的功能有

建立新的状态栏图标
在扩展栏窗口显示附加信息并且能启动一个Intent
可以闪动LED或者灯光
使手机震动
使手机发出铃声
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值