Android中的Service--基础篇

目录

  • Service 介绍
  • Service两种启动方式
  • 使用
  • 测试
  • IntentService
  • Activity与Service之间的通信
  • 继承Binder类
  • Messenger
  • AIDL

Service 介绍

A Service is an application component that can perform long-running operations in the background and does not provide a user interface. Another application component can start a service and it will continue to run in the background even if the user switches to another application. Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC). For example, a service might handle network transactions, play music, perform file I/O, or interact with a content provider, all from the background.

Service是一个没有用户界面的应用程序组件,可以在后台长时间运行。另外一个应用程序组件可以启动一个Service,它可以在用户切换到其他应用的时候依旧保持在后台运行。另外,一个组件可以绑定到Service上,与它进行通信,甚至是IPC(进程间通信)。

Service两种启动方式

  • startService
  • bindService
    其生命周期官方API介绍:


    Paste_Image.png

    需要注意的点:
    1.通过startService启动的Service,只有调用了stopService(外部组件调用)或stopSelf(Service内部自己调用),才会停止。
    2.通过startService启动的Service,在Service运行中无法与Service进行交互,即外部组件只能控制其开关,无法进行交互。
    3.通过startService启动的Service,与外部组件之间没有关系,外部组件的生死跟它没有联系。
    3.通过startService启动的Service,启动之后重复启动的话不会触发onCreate方法,但是会重复触发onStartCommand,其实貌似也算是数据交流了吧,不过是单向的。
    4.bindService启动的Service表示将一个Service绑定到一个组件上,其生命周期与该组件的生命周期绑定在一起,比如绑到一个Activity,Activity在Destroy后Service跟着就Destroy了。
    4.注意是不能绑定广播的,因为广播发完了其生命就到头了,常用的是绑Activity,还可以是Service。
    5.bindService启动的Service在使用完之后可以解除绑定,当一个Service上的所有绑定的组件都解绑之后,它就会被销毁。
    6.可以同时使用两种启动方式,此时的生命周期就变的有些复杂了,两种关联到一起,总结来说的话,先startService与先bindService两种方式达到的效果是一样的,即此时unbindService的话,Service并不会结束,而是要等到stopService才会结束(onDestroy);若是此时stopService,也不会结束,而是要等到unbindService时才会结束(由于已经调用过stopService,此时会直接onDestroy)。
    7.onRebind调用时机:


    Paste_Image.png

    当旧client与service之间的关联在onUnbind中都结束之后,新client绑定时,
    必须是onUnbind返回true,且服务在解绑之后没有销毁

使用

  • startService
    创建类继承Service,在启动组件中调用:

     Intent intent1 = new Intent(ServiceActivity1.this, MyService1.class);
     startService(intent1);
    

停止方法同上

  • bindService
    1.创建类继承Service,其中默认会有一个onBind方法,返回是一个IBinder类型对象,这个返回值就是与组件之间通信的关键。可以通过自定义内部类继承Binder,在这个类中返回Service,然后在组件中通过返回的IBinder类型对象获取到Service对象从而进行操作;
    2.在组件中新建一个ServiceConnection对象,必须重写两个方法,onServiceConnected(建立连接(bind_service)时调用)、onServiceDisconnected(一般都不调用,除非是意外情况,unbind_service并不调用这个),在onServiceConnected中获取到Service对象进行操作。
    MyService:

     public class MyService1 extends Service {
    
         @Override
         public void onCreate() {
             super.onCreate();
             Log.i("test_out","----->onCreate");
         }
    
         @Override
         public int onStartCommand(Intent intent, int flags, int startId) {
             Log.i("test_out","----->onStartCommand");
             return super.onStartCommand(intent, flags, startId);
         }
    
         @Override
         public void onDestroy() {
             super.onDestroy();
             Log.i("test_out","----->onDestroy");
         }
    
         public class MyBinder extends Binder {
             public MyService1 getService(){
                 return MyService1.this;
             }
         }
    
         private MyBinder mBinder = new MyBinder();
    
         @Override
         public IBinder onBind(Intent intent) {
             Log.i("test_out","----->onBind");
             return mBinder;
         }
    
         @Override
         public boolean onUnbind(Intent intent) {
    
             Log.i("test_out","----->onUnbind");
             return true;
         }
    
         @Override
         public void onRebind(Intent intent) {
             super.onRebind(intent);
             Log.i("test_out","----->onRebind");
         }
    
         public int getCount(){
             return (int) (Math.random() * 10);
         }
    
     }
    

在Activity中建立连接:
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = ((MyService1.MyBinder)service).getService();
textView.setText("" + mService.getCount());
}

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i("test_out","----->onServiceDisconnected");
        }
    };

启动与停止:

    bindService(intent3, serviceConnection, Service.BIND_AUTO_CREATE);

第一个参数是intent,第二个是上面的serviceConnection,第三个表示绑定方式,Service.BIND_AUTO_CREATE表示绑定时不存在的话就自动创建。
解绑:unbindService(serviceConnection);

测试

startService -> stopService

Paste_Image.png

startService -> startService -> startService -> stopService
Paste_Image.png

果然重复start会调用onStartCommand,那么就相当于可以发指令。
bindService -> unbindService
Paste_Image.png

bindService -> 按下返回键
Paste_Image.png

startService -> bindService -> unbindService
Paste_Image.png

startService -> bindService -> unbindService -> bindService -> unbindService
Paste_Image.png

startService -> bindService -> stopService
Paste_Image.png

此时unbindService :
Paste_Image.png

IntentService

Android封装好的Service,在其中的onHandleIntent(Intent intent)中处理需要在子线程中处理的逻辑,在处理完毕后,会自动onDestroy。

Activity与Service之间的通信

一般来讲,我们将绑定到Service上的组件称为客户端,Service称为服务端,他们之间的通信可以在同一个进程,也可以在不同的进程。
要进行通信,那个前面已经提到,就要在客户端获取到一个IBinder对象,而获取这个对象的方式有三种:
继承Binder类,使用Messenger类,使用AIDL
第一种上面已经用过了,不过这种方式是适用于同进程内通信,因为不同的进程使用的是不同的内存区域。

Messenger

其实现是通过Message以及Handler来进行通信,Handler应该都有所了解,一般使用最多的都是用来解决非UI线程更新UI的问题,Handler机制有待仔细研究一下,后面写一篇。
其实其底层实现也是通过AIDL。
服务端(Service):
基本流程跟上面的继承Binder类的方式差不多,如下:
首先创建一个Service,在其中实现一个Handler,用于接受消息。
然后通过Handler创建一个Messenger实例,在onBind中返回Messager实例的Binder。

public class MyService2 extends Service {

    class ServiceHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 1){
                Log.i("test_out","------>receive data from client!");
            }
        }
    }

    final Messenger messager = new Messenger(new ServiceHandler());

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("test_out","----->onBind");
        return messager.getBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("test_out","----->onUnbind");
        return true;
    }

}

客户端:
通过serviceConnection中的onServiceConnected获取到服务端传来的Binder对象,实例化Client端的Messenger。
创建Message,通过Messager发送消息。

ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mMessager = new Messenger(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i("test_out","----->onServiceDisconnected");
    }
};

public void sendToMyService(){
    Message msg = new Message();
    msg.what = 1;
    try {
        mMessager.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

@Override
protected void onStart() {
    super.onStart();
    Intent intent = new Intent();
    intent.setAction("com.gsq.service2");
    intent.setClassName("com.example.gsq.servicetest", "com.example.gsq.servicetest.service.MyService2");
    bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}

@Override
protected void onStop() {
    super.onStop();
    unbindService(serviceConnection);
}

对了,这里我的客户端是另外一个APK,验证进程间通信,那么就要在Service注册的时候指定其action,在Intent跳转的时候设置完整包名和action,这个在前面学习Intent中有介绍的。

    <service android:name=".service.MyService2"
        android:exported="true"
        android:permission="com.gsq.permission.service2">
        <intent-filter>
            <action android:name="com.gsq.service2" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>

注意,这里写了android:exported之后,AS会提示你:

Exported services (services which either set exported=true or contain an intent-filter and do not specify exported=false) should define a permission that an entity must have in order to launch the service or bind to it. Without this, any application can use this service.

大意就是,最好是加一个权限,android:permission。
自定义权限过程:在被调用的应用中先定义

<permission
    android:name="com.gsq.permission.service2"
    android:protectionLevel="normal">
</permission>

然后使用,android:permission="com.gsq.permission.service2
然后在调用者(客户端)声明:

<uses-permission android:name="com.gsq.permission.service2" />

坑:自定义权限名称一定要是 *.permission.*
好了,这样子Messenger的使用流程就说完了,下面就可以看一下结果了。
启动第二个应用:

Paste_Image.png

点击button:
Paste_Image.png

OK ! 大功告成!
再点一下:
Paste_Image.png

关闭:
Paste_Image.png

上面就实现了客户端向服务端发送消息。
下面就来实现一下服务端在受到消息之后给一个response。
流程跟上面类似,要想发送消息就要有一个Messenger,可是我们前面不是分别在服务端创建了一个Messenger了么,然后将这个Messenger返回给客户端,这样客户端就可以通过这个Messenger向服务器端发送消息,那么既然已经通道已经存在了,我们能不能在服务端使用这个Messenger来想客户端发送消息呢?
答案是否定的。因为这个Messenger是在服务端的,而且是通过服务端的handler实例化的,然后通过binder传到客户端,也就是说告诉客户端你就用这个给我发消息就行了,我就能收到,但是服务端并不知道客户端是谁,谁拿到了这个Messenger,只知道处理通过这个Messenger传递来的消息。
那么按照这个分析的话,要想服务端向客户端发送消息方式就显而易见了,就是在客户端实例化一个Messenger,然后传递给服务端,告诉它用这个Messenger给我发消息就行了,那么客户端可以通过binder获取到服务端的Messenger实例,那么服务端怎么获取到客户端的实例呢?这个就很简单了,因为前面通道已经创建了,只需要在客户端向服务端发送消息的时候将客户端自己的Messenger传递过去就行了。而Message类刚好有一个参数replyTo,这个参数就是Messenger类型的,那么我们只需要在客户端建一个handler,然后实例化一个Messenger传递过去就ok了。
分析完毕,下面是实现:
首先是客户端代码:

//创建一个处理服务端发来的消息的handler
private class MyHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch(msg.what){
            case 2:
                Log.i("test_out", "------>receive response from server!");
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

然后在向服务端发送数据的时候给msg指定replyTo:

//在client向服务端发送消息的时候告诉服务端通过这个messenger进行回复
msg.replyTo = new Messenger(new MyHandler());

接下来是服务端代码,收到消息后获取到messenger并发送消息即可:

class ServiceHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        if(msg.what == 1){
            Log.i("test_out","------>receive data from client!");
            Messenger messengerFromClient = msg.replyTo;
            Message replyMessage = new Message();
            replyMessage.what = 2;
            try {
                messengerFromClient.send(replyMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

接下来就是测试了:
先启动服务端,按下home,再启动客户端:
此时服务端:

Paste_Image.png

然后在客户端点击发送消息按钮:
Paste_Image.png

再看一下客户端:
Paste_Image.png

这样就完成了双向通信。
小结:要实现双向通信需要首先在服务端创建一个handler,通过这个handler实例化一个Messenger,通过binder将这个Messenger返回给客户端,客户端收到Messenger之后即可向服务端发消息,此时在客户端创建一个handler,并实例化一个Messenger,在向服务端发消息时将这个Messenger传递过去,服务端收到消息的同时也得到了客户端的Messenger,就可以发消息给客户端。

Messenger是使用串行的方式来进行通信,数据量比较大时显然就有点吃力了,而且主要是用来传递消息,但是我们还会出现在客户端调用服务端的方法的情况,这种情况下Messenger就无能为力了,这时候就需要AIDL了,而且Messenger底层实现的本质上也是AIDL。

AIDL

由于水平很菜,感觉AIDL太高深了,后面单独写一个。

参考:
http://www.jianshu.com/p/a8e43ad5d7d2
http://blog.csdn.net/luoyanglizi/article/details/51586437
http://blog.csdn.net/luoyanglizi/article/details/51594016
非常感谢这位大神。

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

推荐阅读更多精彩内容