学习Android的同学注意了!!!
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Android学习交流群,群号码:364595326 我们一起学Android!
Services
服务是一个应用程序组件,可以在后台执行长时间运行的操作,不提供用户界面。一个应用程序组件可以启动一个服务,它将继续在后台运行,即使用户切换到另一个应用程序。此外,一个组件可以绑定到一个服务与它交互,甚至执行进程间通信(IPC)。例如,一个服务可能处理网络通信,播放音乐,执行文件I/O,或与一个内容提供者交互,都在后台执行。
一个服务本质上讲有两种形式:
Started 启动的方式
started形式的服务是指当一个应用组件(比如activity)通过startService()方法开启的服务。一旦开启,该服务就可以无限期地在后台运行,哪怕开启它的组件被销毁掉。
通常,开启的服务执行一个单独的操作且并不向调用者返回一个结果。
比如,可能从网络进行下载或者上传一个文件。当任务完成,服务就该自我停止。
Bound 绑定的方式
bound形式的服务是指一个应用组件通过调用 bindService() 方法与服务绑定。一个绑定的服务提供一个客户-服务端接口,以允许组件与服务交互,发送请求,获得结果,甚至执行进程间通信。一个绑定的服务只和与其绑定的组件同时运行。多个组件可以同时绑定到一个服务,但当全部接触绑定后,服务就被销毁。
虽然分这两类,但是一个服务可以同时使用这两种方式——可以用started无限期的运行,同时允许绑定。只需要在服务中实现两个回调方法:onStartCommand()允许组件开启服务,onBind()允许绑定。
不论应用程序是怎么起服务的,任何应用程序都可以用这个服务。同样的,任何组件可以使用一个Activity通过传递Intent开启服务。你也可以在配置文件设置服务为私有来防止其他应用访问该服务。
注意:一个服务在进程中的主线程运行——一个服务不会创建自己的线程,也不会在另外的进程运行(除非另外指定)。这意味着,如果服务需要做一些频繁占用CPU的工作或者会发生阻塞的操作,你需要在服务中另开线程执行任务。这可以降低产生ANR的风险,提高用户体验。
Service的基础生命周期
创建一个服务需要建立一个Service相关的子类,然后需要实现一些回调方法,好在不同的生命周期内做对应处理和绑定服务,比较重要的方法如下:
onStartCommand()
当其他组件,如 activity 请求服务启动时,系统会调用这个方法。一旦这个方法执行,服务就开始并且无限期的执行。如果实现这个方法,当这个服务完成任务后,需要你来调用 stopSelf() 或者 stopService() 停掉服务。如果只想提供绑定,不需要自己实现这个方法。
onBind()
当有其他组件想通过 bindService() 方法绑定这个服务时系统就会调用此方法。在实现的方法里面,必须添加一个供客户端使用的接口通过返回一个IBinder来与服务通信,这个方法必须实现。当然不想允许绑定的话,返回null即可。
onCreate()
服务第一次建立的时候会调用这个方法,执行一次性设置程序,在上面2个方法执行前调用。如果服务已存在,则不执行该方法。
onDestroy()
服务不再使用则使用该方法。服务应该实现这个方法来清理诸如线程,注册的监听器等资源。这是最后调用的方法。
安卓系统只会在内存占用很高,必须恢复系统资源供当前运行程序的情况下强制停掉一个运行中的服务。如果服务绑定在当前运行的程序中,就几乎不会被杀掉,如果服务声明了在前台运行(其实在后台,只是给系统一个错的信息来提高优先级),就几乎不会被杀掉。另外,如果一个服务正在运行,且运行了很久,系统就会根据运行时间把其排在后台任务列表的后面,则这个服务很容易被杀掉。根据onStartCommand() 的返回值设置,服务被杀掉后仍可以在资源充足的条件下立即重启。
是用一个服务好还是开一个线程好?
一个服务就是一个可以忽略交互,在后台独立运行的组件,如果你需要这样就用服务如果你需要在用户与程序交互时在主线程外执行任务,那就开个线程吧。比如想播放音乐,但只在程序运行时播放,你可能在 onCreate() 开一个线程,在 onStart() 中开启它,在 onStop() 停止它。也可以考虑使用AsyncTask或者HandlerThread取代一般的线程。记住,如果使用一个服务,它还是默认在主线程中运行,如果会发生阻塞,还是要在服务中另开线程的。
服务就必须在 manifest 文件声明。
<application>
<service android:name=".ExampleService"
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:icon="drawable resource"
android:permission="string"
android:process="string">
</service>
</application>
service标签属性
1.android:name
你所编写的服务类的类名,可填写完整名称,包名+类名,如com.example.test.ServiceA,也可以忽略包名,用.开头,如.ServiceA,因为在manifest文件开头会定义包名,它会自己引用。一旦你发布应用,你就不能改这个名字(除非设置android:exported="false"),另外name没有默认值,必须定义。
2.android:enabled
是否可以被系统实例化,默认为true。因为父标签也有enable属性,所以必须两个都为默认值true的情况下服务才会被激活,否则不会激活。
3.android:exported
其他应用能否访问该服务,如果不能,则只有本应用或有相同用户ID的应用能访问。当然除了该属性也可以在下面permission中限制其他应用访问本服务。这个默认值与服务是否包含意图过滤器intent filters有关。如果一个也没有则为false。
4.android:isolatedProcess
设置true意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(binding and starting)。
5.android:label
可以显示给用户的服务名称。如果没设置,就用的lable。不管怎样,这个值是所有服务的意图过滤器的默认lable。定义尽量用对字符串资源的引用。
6.android:icon
类似label,是图标,尽量用drawable资源的引用定义。
7.android:permission
是一个实体必须要运行或绑定一个服务的权限。如果没有权限,startService(),bindService()或stopService()方法将不执行,Intent也不会传递到服务。如果属性未设置,会由权限设置情况应用到服务。如果两者都未设置,服务就不受权限保护。
8.android:process
服务运行所在的进程名。通常为默认为应用程序所在的进程,与包名同名。元素的属性process可以设置不同的进程名,当然组件也可设置自己的进程覆盖应用的设置。
如果名称设置为冒号:开头,一个对应用程序私有的新进程会在需要时和运行到这个进程时建立。如果名称为小写字母开头,服务会在一个相同名字的全局进程运行,如果有权限这样的话。这允许不同应用程序的组件可以分享一个进程,减少了资源的使用。
创建“启动的”服务
启动的(started)服务由startService(Intent)方法启动,在服务中的onStartCommand()方法里获得Intent信息。关闭则由服务自己的方法stopSelf()或者由启动服务的地方调用stopService(Intent)方法来关闭。并不会因为启动服务的应用程序销毁而关闭。
示例,一个应用需要保存数据到远程数据库,这时启动一个服务,通过创建启动的服务给服务传递数据,由服务执行保存行为,行为结束再自我销毁。因为服务跟启动它的应用在一个进程的主线程中,所以对于耗时的操作要起一个新的线程去做。
/**Activity*/
Intent intent = new Intent(MainActivity.this, ServiceA.class);
intent.putExtra("name", strName);
startService(intent);
/**Service*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 获取数据
String strName = intent.getStringExtra("name");
// ... 数据库操作
new Thread(new Runnable() {
@Override
public void run() {
//耗时的操作
}
}).run();
return Service.START_STICKY;
}
写服务有2种,继承service或者IntentService。后者是前者的子类。前者包含上面介绍的各种方法,用于普通的服务。后者可以自己开一个工作线程一个接一个处理多个请求。
继承IntentService
大多数服务不需要同时处理多个请求,继承IntentService是最好的选择。
IntentService处理流程:
创建默认的一个worker线程处理传递给onStartCommand()的所有intent,不占据应用的主线程
创建一个工作队列一次传递一个intent到你实现的onHandleIntent()方法,避免了多线程
在所以启动请求被处理后自动关闭服务,不需要调用stopSelf()
默认提供onBind()的实现,并返回null
默认提供onStartCommand()的实现,实现发送intent到工作队列再到你的onHandleIntent()方法实现。
这些都加入到IntentService中了,你需要做的就是实现构造方法和onHandleIntent(),如下:
public class HelloIntentService extends IntentService {
//构造器指明服务中线程的名字,方便调试使用
public HelloIntentService() {
super("HelloIntentService");
}
//子线程执行的代码
@Override
protected void onHandleIntent(Intent intent) {
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}
如果需要重写其他回调方法,如onCreate(),onStartCommand()等,一定要调用super()方法,保证IntentService正确处理worker线程,只有onHandleIntent()和onBind()不需要这样。如:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting",Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
继承Service
继承Service就可以实现对请求多线程的处理,前面介绍了service的生命周期,可以按照生命周期实现方法。就不放示例了。
onStartCommand()的返回值:
1.START_NOT_STICKY
系统不重新创建服务,除非有将要传递来的intent。这是最安全的选项,可以避免在不必要的时候运行服务。
2.START_STICKY
系统重新创建服务并且调用onStartCommand()方法,但并不会传递最后一次传递的intent,只是传递一个空的intent。除非存在将要传递来的intent,那么就会传递这些intent。这个适合播放器一类的服务,不需要执行命令,只需要独自运行,等待任务。
3.START_REDELIVER_INTENT
系统重新创建服务并且调用onStartCommand()方法,传递最后一次传递的intent。其余存在的需要传递的intent会按顺序传递进来。这适合像下载一样的服务,立即恢复,积极执行。
注意:如果想从服务获得结果,可以用广播来处理。
创建“绑定的”服务
用bindService()方法将应用组件绑定到服务,建立一个长时间保持的联系。
如果需要在activity或其他组件和服务交互或者通过进程间通信给其他应用程序提供本应用的功能,就需要绑定的服务。
建立一个绑定的服务需要实现onBind()方法返回一个定义了与服务通信接口的IBinder对象。其他应用程序组件可以调用bindService()方法获取接口并且调用服务上的方法。
创建一个绑定的服务,第一件事就是定义一个说明客户端与服务通信方式的接口。这个接口必须是IBinder的实现,并且必须要从onBind()方法返回。一旦客户端接收到了IBinder,就可以通过这个接口进行交互。
多个客户端可以绑定到一个服务,可以用unbindService()方法解除绑定,当没有组件绑定在服务上,这个服务就会被销毁。
/**Activity*/
private ServiceConnection connB = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v(tag, "Service B disconnected");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v(tag, "Service B connected");
MyBinderB binder = (MyBinderB) service;
ServiceB SB = binder.getService();
SB.showLog();
}
};
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent a = new Intent(MainActivity.this, ServiceB.class);
bindService(a, connB, BIND_AUTO_CREATE);
}
}
}
/**Service*/
public class ServiceB extends Service {
public void showLog() {
Log.i(tag, "serviceB-->showLog()");
}
public class MyBinderB extends Binder {
public ServiceB getService() {
return ServiceB.this;
}
}
private MyBinderB myBinderB = new MyBinderB();
@Override
public IBinder onBind(Intent intent) {
return myBinderB;
}
}
启动前台服务
前台服务是被认为是用户已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。前台进程必须发一个notification在状态栏中显示,直到进程被杀死。因为前台服务会一直消耗一部分资源,但不像一般服务那样会在需要的时候被杀掉,所以为了能节约资源,保护电池寿命,一定要在建前台服务的时候发notification,提示用户。当然,系统提供的方法就是必须有notification参数的,所以不要想着怎么把notification隐藏掉。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification noti = new Notification.Builder(this)
.setContentTitle("Title")
.setContentText("Message")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(12346, noti);
return Service.START_STICKY;
}
startForeground()方法就是将服务设为前台服务。参数12346就是这个通知唯一的id,只要不为0即可。
Service的生命周期图
启动的服务的生命周期:
startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped
其中,服务未运行时会调用一次onCreate(),运行时不调用。
绑定的服务的生命周期:
bindService()->onCreate()->onBind()->running->onUnbind()->onDestroy()->stopped
服务起始于onCreate(),终止于onDestory()
服务的开关过程,只有onStartCommand()可多次调用,其他在一个生命周期只调用一次。
这两个过程并不完全独立,也可以绑定一个由startService()启动过的服务。
学习Android的同学注意了!!!
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Android学习交流群,群号码:364595326 我们一起学Android!
网友评论