您的位置:首页 > 移动开发 > Android开发

Android Service组件开发用到的几个知识点

2016-07-01 12:26 691 查看
请尊重他人劳动成果,请勿随意剽窃,转载请注明,谢谢!转载请注明出处:http://blog.csdn.net/evan_man/article/details/51800263

启动方式

启动Local Service(Client跟Service在同一个进程)

这类服务有个特点,就是它主要用于为某一个客户(Activity)提供单独的后台服务;
Context.startService()启动

如果调用该方法时对应Service还没有创建出来,则会调用onCreate方法;即Service只会创建一个实例;
调用多少次该方法就对应调用多少次Service端的onStartCommand方法;

Context.stopService()结束

无论之前调用了多少次startService,只需要调用一次该方法

启动Remote Service(Client跟Service不在同一个进程)

这类服务有个特点,就是它会定义一些接口并把接口暴露出来,以便其他应用进行操作;
多个客户端可以绑定同一个服务;
Context.bindService()方法建立连接,并启动

如果调用该方法时对应Service还没有创建出来,则会调用onCreate方法;即Service只会创建一个实例;
Context如果之前没有和该Service绑定过,即执行bindService方法,那么就会执行Service的bindService方法;如果绑定过了就不会执行onBind方法

 Context.unbindService()关闭连接

注意:

上面只是默认一种约定行为;如果是Remote Service建议使用bindService,因为bindService可以获取 Remote Service提供的接口;如果是Local Service建议使用sartService,因为startService很简单;

生命周期

Part1(纯粹的Start)

启动:startService方法启动
终止:调用stopService,或自身的stopSelf方法,系统资源不足android系统结束服务;(执行Service的onDestroy方法)

Part2(纯粹的Bind)

启动:bindService方法启动
终止:调用unbindService断开连接,之前调用bindService 的 Context 不存在了(如Activity被finish的时候);(执行Service的unBind和onDestroy方法)

Part3(混合的Start&Bind)

启动:如先startService,后bindService
终止:(调用stopService 或者 调用stopSelf)并且(不再有绑定的连接)==》onDestroy方法将会被调用;

注意:

Activity旋转的时候,意味着Activity重建了;旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了);
使用了startService启动Service就需要调用stopService关闭服务;使用了bindService启动Service就需要调用unbindService关闭服务;它们都是成对出现的!!!
这里对bindService启动的Service会和启动它的Context共生死,这一点进行一下说明;如果ActivityA进行了bindService,ActivityB也bindService,那么当ActivityA进行了unbindService时,Service并不会调用onDestroy,因为它还有个bind没有解除(即跟ActivityB之间的bind)~;但是activityA跳转到ActivityB,有可能导致activityA被系统销毁,这样即使activityA代码中没有调用unbind,系统也会自动将activityA和service解绑,进而可能service就onDestroy了;当Activity2在bind的时候该service已经不存在了,最终还是要去创建一个service实例;为了避免上述问题这里建议,通过startService启动service,随后bindService进行activity和service的绑定,这样即使activity内容被销毁了,service还是不会调用onDestroy方法!

实现原理

Local Service:(Client和Service在同一个进程中)

Service端:

编写类继承android.app.Service类;实现onCreate、onbind、onStartCommand、onDestroy等方法
在Manifest.xml文件中注册该Service:如下
<!-- android:exported 这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。
如果设置为true,则能够被调用或交互,否则不能。设置为false时,
只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。-->
<service android:name=".ServiceDemo" android:exported="false">
<!--android:name=“” The name of the Service subclass that implements the service 继承了Service的类,绝对路径:包名+类名-->
<intent-filter><!--隐式调用才会用到这个;代表了一种Intent的组合方式;一个组件可以有多个Intent-filter-->
<action android:name="com.demo.SERVICE_DEMO" /> <!--外界使用的名字-->
<category android:name="android.intent.category.DEFAULT" /> <!--只有加上这个别的应用才能通过上面的action找到该Service-->
</intent-filter>
</service>


Client端:

bindService方式启动(关闭调用对应的unbindService())
bindService(new Intent(ServiceDemo.ACTION).setPackage(getPackageName()), conn, BIND_AUTO_CREATE);
参数1说明:ServiceDemo.ACTION=="com.demo.SERVICE_DEMO";即上面Service在Manifest的定义的Action!public Intent (String action);Android5.0不支持隐式调用,需要指定packegename,如上面的方式,packegename指明了在哪个包中找和Action对应的组件,如果不指名package那么则在当前系统全局搜索,每个app对应一个package,定义在Manifest标签里面,此package并非指我们想要启动服务类所在的包名;
或者用Intent intent = new Intent();  intent.setClass(this,MyLocalService.class);的方式启动服务;之前我们经常用这个方式,而不是用Action;其实这部分是Intent的内容,可以去研究一下intent的使用方法和构造器!

参数3说明:BIND_AUTO_CREATE,即当服务不存在时,自动创建,如果服务已经启动了或者创建了,那么只会掉调用onBind方法
参数2说明:conn是一个实现了ServiceConnection接口的对象;实现了下面两个方法
onServiceConnected(ComponentName name, IBinder service)
客户端调用bindService;Service端对应调用onBind方法;如果onBind方法返回值不为null则执行这里的onServiceConnected方法!(就是该方法的第二个参数,就是onBind方法的返回值);无论这里是不是执行onServiceConnected方法,都已经完成了客户端和Service的绑定,最后还是需要调用unBindService进行解绑;
通过该方法的第二个参数service可以获得Service提供的接口!!如:
MyService.SimpleBinder sBinder = (MyService.SimpleBinder)service;
//SimpleBinder是MyService的内部类,继承了Binder类,定义了add方法;
sBinder.add(3, 5);

onServiceDisconnected(ComponentName name)
同上

startService方式启动(关闭调用对应的stopService())
startService(new Intent(ServiceDemo.ACTION)).setPackage(getPackageName());

Remote Service:(Client和Service不在同一个进程中)(进程间通信毫无疑问使用了Binder)

Service端:

编写一个服务接口,不需要继承任何接口(假设该接口名为: ITestRemoteService);定义该服务会提供的服务(如一个add方法);以aidl格式保存!!
android会自动将该文件转换为java文件,并添加一些必要的内容,使其能够进行IPC(我们需要做的就是写aidl文件,随后编译一下工程即可!)
IPC即进程间通信,底层通过Binder技术实现

编写类继承android.app.Service类;
有一个如下的Field:private ITestRemoteService.Stub stub= new ITestRemoteService.Stub(){ ..//实现了上面定义的如add方法 } 
上面的ITestRemoteService是android对aidl文件转换为java文件后得到的java类,类中自动生成了一个Stub内部类;

在实现的onbind方法中返回上面生成的stub;
实现onCreate、onStartCommand、onDestroy等方法

在Manifest.xml文件中注册该Service
<service android:name="com.yq.RemoteService"  android:process=":remote" android:exported="true">
<!--android:name=“”The name of the Service subclass that implements the service 继承了Service的类,绝对路径:包名+类名-->
<intent-filter><!--隐式调用才会用到这个;代表了一种Intent的组合方式;一个组件可以有多个Intent-filter-->
<action android:name="com.yq.RemoteService" /> <!--Service对应名字-->
<category android:name="android.intent.category.DEFAULT" /> <!--只有加上这个别的应用才能通过上面的action找到该Service-->
</intent-filter>
</service>

注意:对于android:process="remote"和android:process=":remote"的区别
android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。
android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。
具体的因为解释参见:http://wear.techbrood.com/guide/topics/manifest/service-element.html

Client端:

bindService方式启动(关闭调用对应的unbindService())
bindService(intent, conn,Context.BIND_AUTO_CREATE);
参数1说明:Intent intent = new Intent("com.yq.RemoteService").setPackage(getPackageName());//packegename指明了在哪个包中找和Action对应的组件,如果不指名package那么则在当前系统全局搜索,每个app对应一个package,定义在Manifest标签里面,此package并非指我们想要启动服务类所在的包名;
参数2说明:BIND_AUTO_CREATE,即当服务不存在时,自动创建,如果服务已经启动了或者创建了,那么只会调用onBind方法
参数3说明:conn是一个实现了ServiceConnection接口的对象;实现了下面两个方法
onServiceConnected(ComponentName name, IBinder service)
客户端调用bindService;Service端对应调用onBind方法;如果onBind方法返回值不为null则执行这里的onServiceConnected方法!(就是该方法的第二个参数,就是onBind方法的返回值);无论这里是不是执行onServiceConnected方法,都已经完成了客户端和Service的绑定,最后还是需要调用unBindService进行解绑;
通过该方法的第二个参数service可以获得Service提供的接口!!如:
ITestRemoteService remoteService = ITestRemoteService.Stub.asInterface(service);
ITestRemoteService就是我们定义了的接口,里面定义了add方法;
之后我们的Client就获得了这个Service,就可以任意时间访问对应的服务了~~

onServiceDisconnected(ComponentName name)
同上

startService方式启动(关闭调用对应的stopService())
startService(intent);
参数说明:Intent intent = new Intent(); intent.setClassName("com.demo","com.demo.SERVICE_DEMO");
可以发现如果通过startService启动服务那么就客户端没有任何返回可以收到!只能单向的向Service发送一个信号;



相关概念的区分(易模糊点)

StartService和bindService启动方法使用场景:

如果你只是想要启动一个后台服务长期进行某项任务那么使用
startService 便可以了;证明startService启动服务,你的期望不要太高;因为它不给客户端任何反馈;你可以通过broadcast来向客户端传数据,然而BroadcastReceiver 本身执行代码的时间是很短的!!因此不能对该方法期望过高!!总之startService很弱很弱的;
想要与正在运行的 Service 取得联系,推荐我们的终极武器bindService!!bindService很好很强大;

AndroidManifest.xml 里 Service 元素的常见选项

android:name,服务类名
android:label,服务的名字,如果此项不设置,那么默认显示的服务名则为类名
android:process,表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
android:enabled,如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false
android:exported,表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false
android:icon,服务的图标

Service&Thread

Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的(运行耗时操作会导致ANR);如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上;
用Service而不用Thread的好处就是我们可以随时通过BindService或者startService跟Service进行通信,换成Thread一旦start我们将无能为力了~;Service很乖,Thread很野;
Service的职责是负责后台的耗时的工作;
Android的后台就是指,它的运行是完全不依赖UI的,即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行;所以在Service中创建Thread比在Acitivity中创建线程的好处在于,Service在一个应用中是稳定存在的,而Activity朝不保夕(应用中经常有各种Activity之间的跳转操作),一旦它销毁了,那么它所创建的线程,将处于游离状态,不受控制,Service不会出现这样的问题;

一个比较好的Service往往是如下的样子:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行后台任务
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
class MyBinder extends Binder {
public void startDownload() {
new Thread(new Runnable() {
@Override
public void run() {
// 执行具体的下载任务
}
}).start();
}
}


前台Service和后台Service的区别于实现

后台Service存在被回收的隐患
我们启动的Service一般都是后台运行的Service;因为Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service;
前台Service避免被回收的隐患
前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果;这让人想到,小米一个rom开发工程师控诉qq虽然进入了后台,但是在屏幕中展示一个像素点,使得该应用永远处于前台,所以Service永远不会被清除掉!!
创建一个前台Service(以下操作都是在Service里面进行的)

创建一个Notification
CharSequence text = getText(R.string.remote_service_started);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Controller.class), 0); //指定点击该Notification的时候,应该跳转到的Activity
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample)  // the status icon
.setTicker(text)  // the status text
.setWhen(System.currentTimeMillis())  // the time stamp
.setContentTitle(getText(R.string.local_service_label))  // the label of the entry
.setContentText(text)  // the contents of the entry
.setContentIntent(contentIntent)  // The intent to send when the entry is clicked
.build();


让Service变成一个前台Service,并会将通知显示出来
startForeground(R.string.remote_service_started, notification);   //等价于NotificationManager.notify(int, Notification)

让Service变成一个后台Service,并会将通知从通知栏撤销
stopForeground(true); //该动作不会使得Service被destroy但是将Service转为后台应用

远程Service和本地Service的优劣比较

ANR问题
ANR问题,本地Service会运行本进程的主线程即UI线程中,因此运行耗时程序,触发ANR;如果是远程Service则阻塞的是另外一个进程的主线程,对当前进程无影响,因此不会触发ANR;
远程Service比本地Service要好?
其实远程Service实现起来比本地Service要复杂一点,大体流程如下,具体实现代码参考:
本地service:

写一个继承Service类的子类
xml文件注册该Serive
客户端startService、bindServcie;传入的intent为(this. MyService.class)的形式
远程Service:

写一个aidl文件(RemoteServce.aidl),内容就是定义的一个普通java接口的代码;随后编译得到一个对应同名的java文件(RemoteService.java)
写一个实现了RemoteService.Stub接口的类(RemoteServiceImp)
写一个继承Service类的子类,该类中有一个RemoteServiceImp的对象,并在onBind方法中返回该对象
xml注册上面的service:注意添加标签android:process = ":remote" <intent-flter> <action>android:name = "com.evan.MyService"</action></Intent-filter>
客户端startService、bindServcie;传入的intent为(this. "com.evan.MyService")的形式
ServiceConnection中得到的IBinder对象,转换RemoteServiceImp remoteService = RemoteService.Stub.AsInterface(ibinder); 

Client和Service之间通信方式的概述:

通过startAcitvity启动Service,可以传输一个Intent过去;这是单向的传输,适用于命令模式;
通过bindActivity启动Service,可以利用ServiceConnection接口,回调得到Iservice对象(注意这里的Iservice是我们定义的一个接口),通过该对象我们可以获取到所有Service向外界提供的服务;
用法一:(Android关于Serive给的例子中有这个案例)
获得来自Service的一个android.os.Messenger对象;该Messenger跟Handler使用方法类似,其实Messenger的某一个构造器参数就可以是一个Handler对象;
这样我们就可以利用该Messenger向Remote Service发送信息了;
Client也可以创建一个Messenger,将该Messenger注册到Remote Service那边,这样Service就可以通过这个Messenger向Client发送数据了;上面就实现了双向通行
注意:服务端Messager就是通过Handler来构建的;利用mMessenger.getBinder()返回IBinder对象;客户单使用IBinder对象构造出Messager对象;而且Messager实现了Parcel接口!!意味着可以利用Binder在进程间进行传递了。

用法二:利用获得的Iservice对象,调用其中的服务接口,获取相关数据,或者发送某种命令;
跟StartActivity的区别:可以得到服务的返回值,因为是实实在在的调用了Service的一个方法;
跟StartActivity相同的地方:Client占据了主动权;

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息