Service是Android中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。
Service的运行不依赖于任何用户界面,即使程序被切换到后台或者用户打开了另外一个应用程序,Service仍然能够保持正常运行,这也正是Service的使用场景。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。
Service的生命周期相对Activity来说简单的多,只有3个,分别为 onCreate、onStartCommand、onDestory。
一但在项目的任何位置调用了Context的startService()函数,相应的服务就会启动起来。
首次创建调用 onCreate 函数
然后回调onStartCommand()函数。
服务启动了之后会一直保持运行状态,知道 stopService() 或者 stopSelf()函数被调用。
虽然每调用一次 starService() 函数,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。
所以不管你调用了多少次 startService() 函数,只需调用一个 stopService() 或 stopSelf()函数,服务就会被停止。
通常Service大致如下:
package com.example.administrator.onceuponaday.Service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent,int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
private void doMyJob(Intent intent){
//从Intent中获取数据
//执行相关操作
new Thread(){
@Override
public void run() {
//耗时操作
super.run();
}
}.start();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
与Activity一样,Service也需要在AndroidManifest.xml中进行注册,实例如下:
上述示例表示注册一个在应用包service目录下的MyService服务,注册之后,当用户调用 startService(new Intent(mClass,MyService))时,会调用 onStartCommand 函数,我们在该函数中调用 doMyJob,而在 doMyJob 中我们创建了一个线程来执行耗时操作,以避免阻塞UI线程。
当我们的Service完成使命时,需要调用stopService来停止该服务。
IntentService
完成一个简单的后台任务需要这么麻烦,Android显然早就“洞察”了这一点。因此,提供了一个IntentService,来完成这样的操作,IntentService将用户的请求执行在一个子线程中,用户只需要复写 onHandleIntent函数,并且在该函数中完成自己的耗时操作即可。需要注意的是,在任务执行完毕之后 IntentService 会调用 stopSelf 自我销毁,因此,它适用于完成一些短期的耗时任务。
示例:
package com.example.administrator.onceuponaday.Service;
import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* <p>
* TODO: Customize class - update intent actions, extra parameters and static
* helper methods.
*/
public class MyIntentService extends IntentService {
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_FOO = "com.example.administrator.onceuponaday.Service.action.FOO";
private static final String ACTION_BAZ = "com.example.administrator.onceuponaday.Service.action.BAZ";
// TODO: Rename parameters
private static final String EXTRA_PARAM1 = "com.example.administrator.onceuponaday.Service.extra.PARAM1";
private static final String EXTRA_PARAM2 = "com.example.administrator.onceuponaday.Service.extra.PARAM2";
public MyIntentService() {
super("MyIntentService");
}
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionFoo(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
/**
* Starts this service to perform action Baz with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionBaz(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_BAZ);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
//耗时的操作here
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionFoo(param1, param2);
} else if (ACTION_BAZ.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionBaz(param1, param2);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionFoo(String param1, String param2) {
// TODO: Handle action Foo
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Handle action Baz in the provided background thread with the provided
* parameters.
*/
private void handleActionBaz(String param1, String param2) {
// TODO: Handle action Baz
throw new UnsupportedOperationException("Not yet implemented");
}
}
运行在前台的Service
Service默认是运行在后台的,因此,它的优先级相对比较低,当系统出现内存不足的情况时,它可能就会比回收掉。
如果希望Service可以一直保持运行状态,而不会由于系统内存不足被回收,可以将Service运行在前台。
前台服务不仅不会被系统无情地回收,它还会在通知栏显示一条消息,下拉状态栏后可以看到更加详细的信息。
下面的程序实现了一个类似下拉状态栏,显示天气信息的效果。首先定义一个服务:
package com.example.administrator.onceuponaday.Service; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.app.TaskStackBuilder; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v7.app.NotificationCompat; import com.example.administrator.onceuponaday.UI.MainActivity; public class WeatherService extends Service { private static final int NOTIFY_ID=123; @Override public void onCreate() { super.onCreate(); showNotification(); } /** * 在通知栏显示天气信息 */ private void showNotification() { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.weather) .setContentTitle(getText(R.string.weather)) .setContentText(getText(R.string.weather)); //创建通知被点击时触发的Intent Intent resultIntent = new Intent(this, MainActivity.class); //创建任务栈Builder TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotifyMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //构建通知 final Notification notification = mBuilder.build(); //显示通知 mNotifyMgr.notify(NOTIFY_ID,notification); //启动为前台服务 startForeground(NOTIFY_ID,notification); } 1 @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
我们在 onCreate 函数中调用了 showNotification 函数显示通知,并且在最后调用 startForeground将服务设置为前台服务。在AndroidManifest.xml注册之后我们就可以启动该Service了。
AIDL是一种接口描述语言,通常用于进程间通信。编译器根据AIDL文件生成一个系列对应的Java类,通过预先定义的接口以及Binder机制到达进程间通信的目的。说白了,AIDL就是定义一个接口,客户端(调用端)通过bindService来与远程服务端建立一个连接,在该连接建立时会返回一个IBinder对象,该对象是服务器端Binder的BinderProxy(代理对象),在建立连接时,客户端通过 asInterface 函数将该 BinderProxy对象包装成本地的Proxy,并将远程服务端的BinderProxy对象赋值给Proxy类的mRemote字段,就是通过mRemote执行远程函数调用。
在SsoAuth.aidl文件中,会默认有一个basicTypes函数,我们在程序后面添加一个ssoAuth的函数用于 SSO 授权。下面是其代码:
因为客户端是调用端,因此,只需要定义AIDL文件,此时Rebuild 一下工程就会生成一个 SsoAuth.java 类,该类根据SsoAuth.aidl文件生成,包含了我们在AIDL文件中定义的函数。因为AIDL通常用于进程间通信,因此,我们新建一个被调用端的工程,我们命名为 aidl_server,然后将客户端的AIDL文件夹复制到 aidl_server的 app/src/main目录下。
此时相当于在客户端和被调用端都有同一份SsoAuth.aidl文件,它们 的包名、类名完全一致,生成的SsoAuth.java类也完全一致,这样在远程调用时它们就能拥有一致的类型。Rebuild被调用端工程之后就会生成SsoAuth.java文件,该文件中有一个Stub类实现了SsoAuth接口。我们首先需要定义一个Service子类, 然后再定义一个继承自Stub的子类,并且在Service的onBind函数中返回这个Stub子类的对象。
……