Service 是Android 中的(四大)组件之一。服务是没有界面的组件,运行在后台,服务是运行在当前应用程序进程里。如果有耗时的操作,不想有界面、而且还不想程序退出就停止运行的逻辑,放在服务里。要注意的是,服务也是运行在主线程中,如果有耗时操作,要放在子线程里,如果服务被系统杀死了,会默认重启。另外,组件也可以通过绑定的形式跟一个Service进行交互,甚至完成进程间通信。比如:Service 可以在后台处理网络传输、播放音乐、进行I/O 流的读写或者跟内容提供者进行交互。startService()和bindService()是开启服务的两种方式,startService()不能调用服务里的方法,不可以与服务进行通信,服务一旦开启,会长时间在后台运行,与开启在不再有关系, 开启在退出了,服务还是在运行的,而且不能调用服务里的方法;bindService()可以间接的调用服务里的方法,可以与服务进程通信,服务开启了是和开启在的生命周期绑定的,如果开启在关闭了,服务也就关闭了, 开启者可以间接的调用服务里的方法。如果服务同时被开启和绑定,那么服务就停不掉了,需要解除绑定服务才能停止服务。需要服务长期在后台运行,还需要调用服务里的方法,用混合方式开启服务,即采用两种方式,但调用方式需要严格的顺序。首先,用start方式开启服务,目的是保证服务在后台能够长时间运行;其次,用bind方式绑定服务,绑定了服务,方便调用服务里的方法;然后解绑时,要先用unBind方式解绑,最后才stop方式停止服务。
以上就是对服务的基本描述,接下来实现服务的具体操作。首先,需要明确一下服务的具体操作步骤。
startService()的方式开启服务的大体流程如下:
1. 写一个类继承 Service;
2. 重写onCreate()方法;
3. 在清单文件的下面声明service,在name标签中写下服务的包名加类名;
bindService()的方式绑定服务的具体写法:
1. 创建服务类, 继承 Service;
2. 定义一个接口,暴露对外提供的方法;
public interface IService{ public void callServiceMethed(); }
3. 在服务类里定义代理对象,定义一个方法可以间接的调用服务的方法, 这样写可以防止不想被暴露的方法被别人调用了,将希望被调用的方法写在接口中,其他方法不被其他类调用;
private class MyBinder extends Binder implements IService{ public void callServiceMethed(){ 调用服务的方法 } ... }
4. 在onBinder方法里返回代理对象,如果不返回,调用方拿到的对象就是空的
public IBinder onBind(Intent intent) { return new MyBinder(); }
5. 创建类实现 ServiceConnection,实现里面的两个方法
private class MyConn implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { //当服务连接成功时候调用 } @Override public void onServiceDisconnected(ComponentName name) { //当服务断开连接时调用 } }
6. activity采用绑定的方式开启服务,bindService()方法绑定服务;
7. 调用代理对象的方法,间接的调用了服务里的方法
接下来了解一下服务的生命周期
跟Activity 一样,Service 也是有生命周期的,不一样的是Service 的生命周期,从它被创建开始,到它被销毁为止,可以有两条不同的路径:标准开启模式和绑定模式。两种生命周期的可以用下面箭头表示:
标准开启模式的生命周期:
startService()->onCreate() -> onstartcommand() -> onDestroy()
绑定模式的生命周期:
bindService()->onBind() -> onunBind() -> onDestroy()
被开启的service 通过其他组件调用startService()被创建。这种service 可以无限地运行下去,必须调用stopSelf()方法或者其他组件调用stopService()方法来停止它。当service 被停止时,系统会销毁它。用start方法开启服务,服务只会被创建一次,执行一次onCreate方法,一旦服务创建完成,后续调用start去开启服务只会执行onstart和onstartcommand方法,当调用了stop方法,服务只会调用一次onDestroy方法。
被绑定的service 是当其他组件模拟一个客户时,调用bindService()来创建的。客户可以通过一个IBinder接口和service 进行通信。客户可以通过unbindService()方法来关闭这种连接。一个service 可以同时和多个客户绑定,当多个客户都解除绑定之后,系统会销毁service。你可以和一个已经调用了startService()而被开启的service 进行绑定。比如,一个后台音乐service 可能因调用startService()方法而被开启了,稍后可能用户想要控制播放器或者得到一些当前歌曲的信息,可以通过bindService()将一个activity 和service 绑定。这种情况下,stopService()或stopSelf()实际上并不能停止这个service,除非所有的客户都解除绑定。
了解服务的生命周期是为了更好的理解服务在整个工程下的运行原理。因此有必要了解一下安卓中进程的工作原理。
在Android 中进程优先级由高到低,依次分为:前台进程(Foreground process),可视化进程(Visible process),服务进程(Service process),后台进程(Background process),空进程(Empty process)。下面一次给出每一种进程的概念和应用场景。
前台进程(Foreground process): 用户正在操作的应用程序进程叫做前台进程。通常情况下,在任何时候系统只存在一小部分前台进程。这些进程只会作为最后的手段才会被杀死,即当内存不足以继续运行他们的时候。在这个时刻,设备已经达到内存分页状态,当系统达到内存分页状态时只能通过虚拟地址访问内存,可理解为达到这个状态时系统已经无法继续分配新的内存空间即可,因此杀死一些前台进程,释放内存空间以保证应用能够继续响应用户的交互是必要的手段。
可视化进程(Visible process): 用户已经不能操作这个应用程序了,但是用户还能看到应用程序界面。一个可视进程被认为是极其重要的并且一般不会被系统杀死,除非为了保证所有的前台进程去运行不得已为之。
服务进程(Service process): 应用程序服务在后台运行。一个拥有正在运行的Service,并且该Service 是被startService()方法启动起来的进程,并且该进程没有被归类到前面的两种(前置进程和可视进程)类型,那么该进程就是服务进程。尽管服务进程没有与用户可见的控件直接绑定,但是这些进程干的工作依然是用户关心的(比如在后台播放音乐或者从网络上下载数据),因此系统保留这些进程一直运行除非系统没有足够的内存去运行前台进程和可视进程。
后台进程(Background process):应用程序界面被用户最小化。一个拥有对用户不可见的Activity,该Activity 已经被执行了onStop()方法进程叫做后台进程。后台进程对用户体验没有直接的影响,并且系统会在任何需要为前台进程,可视进程,或服务进程申请内存的时候杀死后台进程。通常系统中运行着大量的后台进程,这些后台进程保存在一个LRU(最少最近使用的)列表中,使用LRU 规则是为了保证让最近被用户使用的Activity 进程最后被杀死,就是谁最近被使用了,谁最后再被杀死。如果一个Activity 正确实现了它的生命周期方法,并且保存了它的状态,通常这个状态是系统自动保存的,那么当系统杀死它的进程的时候是对用户的体验没有看得见的影响的,因为当用户导航到之前的Activity 的时候,这个Activity 会自动恢复之前保存的视图状态。查看Activity 文档去获取更多关于Activity状态的保存和恢复信息。
空进程(Empty process): 应用程序没有任何的activity和service运行。不拥有任何系统四大组件的进程叫空进程。保持空进程存活的唯一理由是为了缓存,这样可以提高下次启动组件的打开速度。当系统需要维持缓存进程和底层内核缓存的资源均衡的时候系统经常会或者随时会杀死该类进程。
Android 系统有一套内存回收机制,会根据优先级进行回收。Android 系统会尽可能的维持程序的进程,但是终究还是需要回收一些旧的进程节省内存提供给新的或者重要的进程使用。进程的回收顺序是:从低到高,即当系统内存不够用时, 会把空进程一个一个回收掉,当系统回收所有的完空进程不够用时, 继续向上回收后台进程, 依次类推。但是当回收服务, 可视, 前台这三种进程时, 系统非必要情况下不会轻易回收, 如果需要回收掉这三种进程, 那么在系统内存够用时, 会再给重新启动进程;但是服务进程如果用户手动的关闭服务, 这时服务不会再重启了。
文章最后给出两个案例来加深对服务的理解,一个是用startService()开启服务,一个是利用绑定服务的原理实现远程服务。
用startService()开启服务。流程就不再介绍,直接给出相关的代码。
开启服务和停止服务的java代码:
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /**
*开启服务
*/ public void start(View view){ Intent intent = new Intent(this,DemoService.class); startService(intent); } /**
*停止服务
*/ public void stop(View viwe){ Intent intent = new Intent(this,DemoService.class); stopService(intent); } }
startService()方式开启服务时,服务的java代码:
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.SystemClock; public class DemoService extends Service { private boolean flag; @Override public IBinder onBind(Intent intent) { return null; } /** * 服务一般用于检测设备 * */ @Override public void onCreate() { super.onCreate(); new Thread() { public void run() { flag = true; while (flag) { System.out.println("检查是否有设备插入进来了."); SystemClock.sleep(2000); System.out.println("服务被创建了,运行在:" + Thread.currentThread().getName() + "线程中"); } }; }.start(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println("服务被开启"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); flag = false; System.out.println("服务被销毁"); } }
相应得布局文件较为简单,仅仅需要两个按键控制服务的开和关,在此也给出布局文件的相关代码。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="start" android:text="开始服务" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="stop" android:text="结束服务" /> </LinearLayout>
用bindService()绑定服务的案例,利用服务实现远程通信。在Android 平台中,各个组件运行在自己的进程中,他们之间是不能相互访问的,但是在程序之间是不可避免的要传递一些对象,在进程之间相互通信。为了实现进程之间的相互通信,Android 采用了一种轻量级的实现方式RPC(Remote Procedure Call 远程进程调用)来完成进程之间的通信,并且Android 通过接口定义语言(Android Interface Definition Language ,AIDL)来生成两个进程之间相互访问的代码。如果想让我们的Service 可以提供远程服务,那么就必须定义一个.aidl 文件,该文件使用的是java 语法,类似java 的接口。然后将该文件在客户端和服务端的src 目录下各自保存一份,这样编译器就会根据aidl 文件自动生成一个java 类,也就说在客户端和服务端都拥有了相同的类文件了。
远程服务需要服务提供者以及服务调用者。因此需要两个安卓项目。
远程服务提供者的java代码包括三个部分:
绑定服务的JAVA代码:
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; public class remote extends Service { public class myBind extends IService.Stub { @Override public void callMethodInService() throws RemoteException { methodInService(); } } @Override public IBinder onBind(Intent intent) { return new myBind(); } @Override public void onCreate() { System.out.println("服务被创建"); super.onCreate(); } @Override public void onDestroy() { System.out.println("服务被销毁"); super.onDestroy(); } private void methodInService(){ System.out.println("服务中的方法被调用"); } }
绑定服务的主界面,服务是运行在后台的不可见,因此为了显示两个程序之间传递数据,在此写了一个主界面。界面的布局可以随意写。
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
aidl文件和捷口类似:
package com.example.remote; interface IService { void callMethodInService(); }
接下来是服务调用者中的代码。
import com.example.remote.IService; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.view.View; public class MainActivity extends Activity { private IService iservice; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * 绑定服务 * @param view */ public void bind(View view) { Intent intent = new Intent(); intent.setAction("com.example.remote"); bindService(intent, new myConn(), BIND_AUTO_CREATE); } /** * 调用远程服务的方法 * @param view */ public void call(View view) { try { iservice.callMethodInService(); } catch (RemoteException e) { e.printStackTrace(); } } private class myConn implements ServiceConnection { //链接成功 @Override public void onServiceConnected(ComponentName name, IBinder service) { iservice = IService.Stub.asInterface(service); } //链接失败 @Override public void onServiceDisconnected(ComponentName name) { System.out.println("拒绝链接"); } } }
单独定义一个文件夹,文件夹的名字和远程服务的包名一直,里面存放aidl文件:
package com.example.remote; interface IService { void callMethodInService(); }
此外,在服务调用者的布局问价中写两个按钮,用来绑定扶服务和调用服务中的方法。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="bind" android:text="绑定服务" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="call" android:text="调用服务的方法" /> </LinearLayout>