四大组件-----Service详解

什么是Service

先看一下google官方的介绍:

Service
是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行

总结一下,Service是一个应用组件,它有以下特点:
1.不需要提供用户界面
2.可以在后台长时间运行
默认情况下,服务运行在UI线程,执行耗时操作需要开辟子线程,否则会引起ANR。

服务通常分为两种形式:

1.普通服务,通过startService启动,一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。
直到stopService被调用,或者Service本身调用了stopSelf,才会停止。
2.Bound 服务,当应用组件通过调用 [bindService()](https://developer.android.google.cn/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

实际上,普通Service也可以与组件绑定,关键在于是否实现了[onStartCommand()](https://developer.android.google.cn/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))(允许组件启动服务)和 onBind()(允许绑定服务)这两个回调方法。这样的服务需要解除绑定并stop才会销毁。

关键方法和生命周期

onCreate()
首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果服务已在运行,则不会调用此方法。

onStartCommand()
当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf() 或 stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)

onBind()
当另一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法。在此方法的实现中,必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。

onUnbind()
当组件通过unbindService与Service解绑时,系统会调用此方法。

onDestroy()
当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用

如果组件通过调用 startService() 启动服务(这会导致对 onStartCommand() 的调用),则服务将一直运行,直到服务使用 stopSelf() 自行停止运行,或由其他组件通过调用 stopService() 停止它为止。

如果组件是通过调用 bindService() 来创建服务(且未调用 onStartCommand(),则服务只会在该组件与其绑定时运行。一旦该服务与所有客户端之间的绑定全部取消,系统便会销毁它。

也就是说,startService与stopService,bindService与unbindService是对应关系,startService必须通过stopService来停止,这时候调用的是onStartCommand()和onDestroy();bindService必须通过unbindService来停止,这时候调用的是onBind()和onDestroy()。
到这里,Service的生命周期已经很清楚了:

onCreate()onStartCommand()/onBind()(→onUnbind())→onDestroy()
注:系统因内存过低等原因,回收掉服务的时候,onDestroy是不会执行的。 如果服务已经运行,调用startService时,不会重新执行onCreate,只会执行onStartCommand();

Service实例:

1.普通Service:

首先定义一个Service,并重写相应方法

public class MyService  extends Service{
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        Log.d("Service", "----->onCreate");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        Log.d("Service", "----->onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d("Service", "----->onDestroy");
    }
}

然后在Activity中启动/停止该服务

public class ServiceActivity  extends Activity implements OnClickListener{
    private Button startButton;
    private Button stopButton;
    Intent i;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.service_activity);
        startButton=(Button) findViewById(R.id.start_service);
        stopButton=(Button) findViewById(R.id.stop_service);
        startButton.setOnClickListener(this);
        stopButton.setOnClickListener(this);
    }   
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.start_service:
            i=new Intent(this,MyService.class);
            startService(i);
            break;
        case R.id.stop_service:
            i=new Intent(this,MyService.class);
            stopService(i);
            break;
        default:
            break;
        }   
    }
}

当然,也不要忘记在manifest文件中注册,四大组件都是需要注册的。
这样,一个简单的服务就完成了。

2.Bound服务

Bound服务是将启动Service的组件(通常是Activity)与Service绑定起来,这样Activity和Service可以非常简单的进行通信。实例如下:

1.定义一个Binder类,并在onbind方法中返回它的实例:

public class MyService  extends Service{
    private MyBinder mBinder=new MyBinder();
    public class MyBinder extends Binder{
        public void startDownload(){
            Log.d("Service", "---->Start Download.....");
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        Log.d("Service", "----->onBind");
        return mBinder;
    }
}

2.在Activity中启动时,使用bindService方法。

该方法有三个参数:
Intent,表示要启动的Service;
connection:实现ServiceConnection接口的类,该接口中有两个方法:onServiceDisconnected和onServiceConnected,onbind方法返回的Binder会传入onServiceConnected,从而对服务进行操作;
Flag:通常选用BIND_AUTO_CREATE,bindService时会自动创建服务。
代码如下:

private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("Service", "----->onServiceDisconnected");
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("Service", "----->onServiceConnected");
            mBinder=(MyService.MyBinder) service;
            mBinder.startDownload();
        }
    };

Intent i=new Intent(this,MyService.class);
bindService(i, connection   , BIND_AUTO_CREATE);

注意:onServiceDisconnected函数并不是unbind时候调用的,正常情况下该函数不会被调用,只有意外断开连接的时候,比如Service被系统回收等,才会调用。
同一个Service可以被许多组件同时绑定,返回的也是相同的Binder对象。只有当所有组件都解绑时,Service才会销毁。

3.远程Service:

远程服务是指,单独运行在一个进程中的服务。使用远程Service也很简单,只要设置manifest文件里Service的属性:android:process=":remote"(意思是在当前应用下新建一个进程,名字是包名+remote)
使用远程Service不会产生ANR问题,它独立运行在新进程中,会产生一个问题,如何与Activity进行通信?可以试一下,远程Service是不能与Activity绑定的,bindService不能用在远程Service上,所以这里就涉及到IPC的概念了。
介绍几种比较常用的方法:

AIDL通信
首先新建一个AIDL文件,在文件中定义好Activity与Service通信的方法:
在名为ServiceAIDL.aidl的文件中,定义以下方法:

package com.training.service;
interface ServiceAIDL {
    int startDownload();
}

保存之后在gen文件夹下会生成对应的.java文件。
然后在Service中实现该接口,代码如下:

ServiceAIDL.Stub mBinder = new ServiceAIDL.Stub() {
        @Override
        public void startDownload() throws RemoteException {
            // TODO Auto-generated method stub
            Log.d("Service", "----->Remote Service Start download.....");
        }
    };
@Override
public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        Log.d("Service", "----->onBind");
        return mBinder;
    }

ServiceAIDL.Stub是一个实现了AIDL文件中定义的接口ServiceAIDL的类。它继承自Binder类,Service中取得它的实例之后,在onBind方法中返回,就可以把该实例传递到Activity中了。
Activity中通过ServiceConnection来操作binder:

private ServiceConnection reoteConnection=new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // TODO Auto-generated method stub
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // TODO Auto-generated method stub
            mServiceAIDL=ServiceAIDL.Stub.asInterface(service) ;
            try {
                int result=mServiceAIDL.plus(3, 5);
                Log.d("Service", "Result is "+ result);
                mServiceAIDL.startDownload();
            } catch (Exception e) {
                 e.printStackTrace();
            }
        }
    };
Intent i=new Intent(this,RemoteService.class);
bindService(i, reoteConnection  , BIND_AUTO_CREATE);

这样,一个简单的AIDL通信就完成了。
远程Service是可以垮应用共享的,可以通过隐式Intent从任何Activity启动并操作他。

Messager通信

1:Service中定义两个Messenger:
一个server端的messenger,该messenger会通过onbind方法传递给client端,在client端通过该messenge发送消息给server一个client端的messenger,用来接收client端的messenger,用该messenger发送消息回client
代码如下:

    static final int MSG_CLIENT_TO_SERVER=1;
    static final int MSG_SERVER_TO_CLIENT=2;
    private Messenger clientMessenger;
    private Messenger serverMessenger = new Messenger(new ServerHandler());
    class ServerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            Log.d("Service", "ThreadName:"+Thread.currentThread().getName());
            switch (msg.what) {
            case MSG_CLIENT_TO_SERVER:
                Log.d("Service", "Get message from client to server!");
                //msg.replyTo用来携带一个Messenger,此处接收到消息是客户端传来的,所以携带的是client Messenger,这样就获取到了客户端的messenger,然后可以通过此messenger发送msg给客户端
                //注意,客户端发送消息时,必须将自己的messenger赋值给replyTo
                clientMessenger = msg.replyTo;
                Message toClientMsg=Message.obtain(null, MSG_SERVER_TO_CLIENT);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                try {
                    clientMessenger.send(toClientMsg);
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
            default:
                break;
            }
        }

这段代码的意思是,接收到客户端发送的消息时,打印log,然后获取到client的messenger,等待2秒之后,发送消息回client。

2:client端,通用也要定义两个messenger。

    private Messenger serverMessenger;
    private Messenger clientMessenger=new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            if(msg.what==MessagerService.MSG_SERVER_TO_CLIENT)
                Log.d("Service", "Receive message from server");
        }
    });

然后在connection中获取到Service的messenger:

    private ServiceConnection messengerConnection=new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // TODO Auto-generated method stub
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // TODO Auto-generated method stub
            serverMessenger=new Messenger(service);
        }
    };

定义了一个Button,点击的时候,会发送消息给Service:

Message msg=Message.obtain(null, MessagerService.MSG_CLIENT_TO_SERVER);
            msg.replyTo=clientMessenger;
            try {
                serverMessenger.send(msg);
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

这样,一个简单的messenger通信就完成了。
当然也不要忘记 注册Service,定义成remote模式让Service运行在独立进程中,还要将Service与Activity绑定起来。

IntentService

IntentService是系统提供给的一个已经继承自Service类的特殊类,用户只需要覆写onHandlerIntent()方法,该方法会将耗时任务自动运行在子线程当中,运行完毕后,系统会调用stopself来销毁Service。

public class MyIntentService extends IntentService {
    public MyIntentService(String name) {
        super(name);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.w("IntentService", "Start download in onHandleIntent......");
        Log.w("IntentService", "thread name:" + Thread.currentThread().getName());
    }
}

前台Service

Service是默认运行在后台的,优先级相对比较低,容易被系统回收掉。想让Service一直处于运行状态的话,前台服务会在通知栏显示一条消息,一般不会被系统回收掉。
主要代码如下:

public class ForegroundService extends Service{
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        showNotification();
    }
    private void showNotification(){
        NotificationCompat.Builder mBuilder=new NotificationCompat.Builder(this)
        .setContentTitle("前台Service")
        .setContentText("Service is running")
        .setSmallIcon(R.drawable.ic_launcher);//设置通知内容
        Intent intent=new Intent(this,ServiceActivity.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this  , 0 ,intent, 0);
        mBuilder.setContentIntent(pendingIntent);//设置点击响应
        
        Notification notification=mBuilder.build();//构建通知
        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        notificationManager.notify(1,notification);//显示通知
        startForeground(1, notification);//启动服务
    }
}

然后在Activity中启动服务就可以看见了

关于Service的其他:

1:onStartCommand(Intent intent, int flags, int startId)方法:
intent:启动Service时候传递进来的intent
flags:通常为 0,或者START_FLAG_REDELIVERY(1), START_FLAG_RETRY(2).表示Service的启动方式
startid:一个唯一的整型,用于表示此次Client执行startService(...)的请求请求标识,在多次startService(...)的情况下,呈现0,1,2....递增
还有一个关键的返回值,通常有三个值可选:

START_NOT_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service。

START_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外。

START_REDELIVER_INTENT:与START_STICKY唯一不同的是,回调onStartCommand(...)方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent。

2:Broadcast Receiver由于生命周期非常短,只要几秒钟,所以不能作为 Bound Service的发起者。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容