服务(Service)

Service是一种可以在后台执行耗时操作而没有用户界面的应用组件。它默认运行在主线程中,不可以直接进行耗时操作,关于在Service中进行耗时操作详见本文末尾 —— IntentService。

Android四大组件中只有Activity和Service是Context的子类

如果用户在应用管理界面手动停止了Service所在进程,Service就会停止;如果是内存不足导致Android系统杀死了Service所在进程,Service也会停止,只是当内存充足时系统又会重启该Service所在进程(服务进程),Service也会被重新启动。

Android进程优先级

  1. 前台进程(Foreground process):优先级最高,最重要并且最后一个被Android系统杀死。满足下面任意一条的进程就是前台进程

    • 拥有一个正在与用户交互的Activity(onResume()被调用)
    • 拥有一个与其他进程中正在与用户交互的Activity绑定的Service
    • 拥有一个正在执行onCreate()onStartCommand()onDestroy()其中一个生命周期方法的Service(Service在执行生命周期方法时,短暂提高其所在进程的优先级,以保证进程不会被系统杀死)
    • 拥有一个正在执行onReceive()的BroadcastReceiver(BroadcastReceiver在收到广播时,短暂提高其所在进程的优先级,以保证进程不会被系统杀死)
  2. 可见进程(Visible process):满足下面任意一条的进程就是可见进程

    • 拥有一个不在前台(失去焦点)但是对用户依然可见的Activity(onPause()被调用)
    • 拥有一个与其他进程中可见Activity绑定的Service
    • 拥有一个调用了service.startForeground(id, notification)运行在前台的Service
  3. 服务进程(Service process):拥有一个通过content.startService(intent)启动的Service,不到万不得已时不会被Android系统杀死。即使在内存不足时被系统杀死了,等到内存充足时仍然可以被重新启动,继续运行该Service。只有服务进程才可以用来做文件下载、音乐播放等后台操作

  4. 后台进程(Background process):拥有一个用户不可见的Activity(onStop()被调用),很容易被Android系统杀死,且不会被重新启动

  5. 空进程(Empty process):不含有任何活动的应用组件(主要是Activity和Service,BroadcastReceiver的生命周期很短),保留空进程的唯一目的就是作为缓存,以加快下次在此进程中运行组件的启动速度,优先级最低,最容易被Android系统杀死,且不会被重新启动

对于优先级相同的进程,当内存不足时,Android系统会依据LRU算法决定杀死哪一个进程


Service的定义

创建一个类XxxService继承Service,并重写onBind()

public class XxxService extends Service {

    /**
     * bindService时才会回调,必须实现的方法
     *
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

在清单文件中配置该Service

<service android:name=".XxxService"></service>

Service的两种启动方式

从Android 5.0开始,Google要求必须使用显式Intent启动Service
在Android系统中启动Service有如下两种方式:

1. startService

通过content.startService(intent)启动Service,将触发Service的生命周期方法:onCreate() —> onStartCommand()
因为Service没有前台界面所以Google使用onStartCommand()替代onStart(),它有一个int类型的返回值,如果返回START_STICKY,意味着如果Service所在进程因为系统内存不足而被杀掉,当内存充足时系统还会尝试重新创建这个Service,重新创建Service又会回调onStartCommand(),但这次传入onStartCommand()中的Intent将为null

Intent intent = new Intent(this, XxxService.class);
startService(intent);

重复的startService()不会回调onCreate(),只会回调onStartCommand()

不再使用时,可以通过service.stopSelf()context.stopService(intent)停止Service,将触发Service的生命周期方法:onDestroy()

Intent intent = new Intent(this, XxxService.class);
stopService(intent);

通过startService()启动的Service与启动它的Activity没有任何关系,即使Activity被销毁了,Service也不会停止。另外,通过这种方式启动Service,该Service所在进程的优先级将不会低于服务进程。

2. bindService

通过content.bindService(intent, conn, flags)启动Service,需要定义ServiceConnection接口的实现类

public class XxxConnection implements ServiceConnection {


    /**
     * 当到Service的连接被建立了(Service的onBind()执行成功)
     * 并返回了一个非空的IBinder对象,此方法才会回调
     *
     * @param name
     * @param service   这个对象就是onBind()返回的中间人IBinder
     */
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {

    }

    /**
     * 当到Service的连接因为异常而中断,此方法才会回调,正常的解绑不会回调此方法
     *
     * @param name
     */
    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
}

然后创建该实现类的对象

XxxConnection conn = new XxxConnection();

通过bindService启动(绑定)Service,将触发Service的生命周期方法:onCreate() —> onBind()

Intent intent = new Intent(this, XxxService.class);
bindService(intent, conn, BIND_AUTO_CREATE);  // BIND_AUTO_CREATE表示如果XxxService不存在则自动创建它

重复的bindService不会回调onCreate()onBind().

通过context.unbindService(conn)停止(解绑)Service将触法生命周期方法:onUnbind() —> onDestroy()

unbindService(conn);

通常不推荐使用ApplicationContext去bindService,如果通过这种方式bind了一个Local Service,此后每次start Remote Service都会抛出异常:android.os.BinderProxy cannot be cast to XxxService$XxxServiceBinder
如果Service想要同整个应用的生命周期一致,可与MainActivity进行bind

通过bindService()启动Service,也叫绑定(多个Activity可以绑定一个Service),它使Service与启动它的Activity建立连接:如果Activity被销毁了,Service也会被解绑并销毁;但是如果Service被销毁了,Activity则不会被销毁。另外,通过这种方式启动(绑定)Service,该Service所在的进程优先级不变(仍取决于启动服务的Activity)。

startService的应用:后台操作

Android系统通话有三种状态:空闲、响铃、摘机,我们可以在摘机时通过Service进行后台录音来实现通话录音机。

定义一个RecorderService让其在创建时就开始监听电话状态

public class RecorderService extends Service {


    @Override
    public void onCreate() {
        super.onCreate();
        TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); // 获取电话管理器
        tm.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE);  // 监听电话状态
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

自定义电话状态监听器,当响铃时进行音频录制的初始化(申请硬件资源),摘机时开始录制,空闲时回收音频录制所占用的资源

class MyListener extends PhoneStateListener {


    /**
     * 电话状态改变时回调
     *
     * @param state
     * @param incomingNumber
     */
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        super.onCallStateChanged(state, incomingNumber);
        switch (state) {    // 判断当前是什么状态
            case TelephonyManager.CALL_STATE_IDLE:  // 空闲
                if (mediaRecorder != null) {
                    mediaRecorder.stop();       // 停止录制音频
                    mediaRecorder.release();    // 释放录音所占用的硬件资源(C代码所占用的)
                    mediaRecorder = null;       // 等待垃圾回收器回收java对象资源
                }
                break;
            case TelephonyManager.CALL_STATE_RINGING:   // 响铃,进行音频录制的初始化
                if (mediaRecorder == null) {
                    mediaRecorder = new MediaRecorder();
                    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);        // 设置音频源为麦克风
                    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);// 设置所录制的音频文件格式为3gp
                    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置所录制音频的编码格式AMR_NB(3gp文件格式的一种最常见的音频编码格式)
                    mediaRecorder.setOutputFile("sdcard/voice.3gp");// 设置所录制的音频文件的保存位置
                    try {
                        mediaRecorder.prepare();// 准备录制音频
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:   // 摘机
                if (mediaRecorder == null) {
                    mediaRecorder.start();  // 开始录制音频
                }
                break;
        }
    }
}

注意不要忘了在清单文件中配置RecorderService以及申请所需要的权限

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>       <!-- 读取通话状态权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>           <!-- 录制音频权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- 写SD卡权限 -->

<service android:name=".RecorderService"></service>     <!-- 配置RecorderService -->

bindService的应用:调用Service中的方法

在应用中启动Service,系统会自动为我们创建这个Service对象,但是我们无法直接拿到这个Service对象的引用,也就无法在前台调用这个Service中的非静态方法。

想要找人办个证,但不认识领导,需要通过中间人冯秘书牵线!
把Service看成一个领导,服务中有一个banZheng(),用bindService()绑定Service时,会触发Service的onBind(),此方法会返回一个中间人Ibinder对象,前台可以在bindService()时传入的ServiceConnection实现类对象的onServiceConnected()中拿到这个Ibinder对象,通过这个对象就可以访问Service中的banZheng()

定义一个服务LeaderService,在服务中定义一个FengMiShu继承Binder(实现了Ibinder接口)作为中间人,以在onBind()中返回该类对象。

public class LeaderService extends Service {


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new FengMiShu();     // 返回中间人IBinder对象
    }

    /**
     * 办证
     */
    public void banzheng() {
        System.out.println("成功办证");
    }


    /**
     * 冯秘书(中间人)
     */
    class FengMiShu extends Binder implements PublicBusiness {

        /**
         * 中间人的牵线
         */
        @Override
        public void qianXian() {
            banzheng();             // 调用领导的办证
        }

        /**
         * 没有抽象到接口中,不能随便调用(捡肥皂不是对公业务)
         */
        public void jianFeiZao() {

        }
    }
}

qianXian()抽象到PublicBusiness接口中

public interface PublicBusiness {

    void qianXian();  // 把需要被Activity调用的方法抽象到接口中(牵线属于对公业务)
}

在Activity中绑定服务时传入一个ServiceConnection实现类的对象,如果不考虑解绑服务可直接用匿名内部类定义

Intent intent = new Intent(this, LeaderService.class);

bindService(intent, new ServiceConnection() {

    /**
     * 到Service的连接被建立了(Service的onBind()执行成功),并返回了一个非空的IBinder对象,此方法才会回调
     *
     * @param name
     * @param service   这个对象就是onBind()返回的中间人IBinder
     */
    @Override
    public void onServiceConnected (ComponentName name, IBinder service){
        publicBusiness = (PublicBusiness) service; // 拿到中间人冯秘书,强转成PublicBusiness使其只能调用对公业务
    }

    /**
     * 到Service的连接因为异常而中断,此方法才会回调,正常的解绑不会回调此方法
     *
     * @param name
     */
    @Override
    public void onServiceDisconnected (ComponentName name){

    }

},BIND_AUTO_CREATE);

最后在Activity需要调用LeaderService的banzheng()处调用以下代码

publicBusiness.qianXian();  // 冯秘书的对公业务牵线
//    publicBusiness.jianFeiZao();    // 非对公业务,调用不了

Service的混合启动:音乐播放器

实现音乐播放时,要保证在Activity销毁后音乐仍在后台播放,进程不会变成空进程而在内存不足时被系统杀死,必须要通过startService()把进程变成服务进程。可是音乐Service中的方法,需要被前台Activity所调用(例如当用户点击开始或暂停按钮要分别触发Service中的播放音乐和暂停音乐),又必须要用bindService()绑定服务,获取Binder对象。所以需要用到上述两种方式混合启动音乐Service,并且startService()必须在bindService()之前执行。详见多媒体编程:二、音频播放

混合启动服务时先startService(),再bindService();停止服务时先unbindService(),再stopService()


Local Service 与 Remote Service

  • 对于Web开发,Local Service是指提供服务的程序在本地,而Remote Service是指提供服务的程序在服务器

  • 对于Android开发,Local Service和Remote Service都运行在我们的手机上

Local Service

Local Service是指与启动它的组件都在同一进程的Service,前面启动的Service均是Local Service.

Remote Service

Remote Service是指与启动它的组件不在同一进程的Service,它分为两种:

  • 同一应用中的Remote Service:在清单文件中明确指定了Service所在的进程,对于该应用中位于不同进程的其它组件,该Service就是一个Remote Service

    <service
        android:name=".RemoteService"
        android:process=":newProcess" />
    
  • 不同应用中的Remote Service:在清单文件中配置了intent-filter子节点(此时 android:exported="true"),并指定action,对于其他应用(一般位于不同进程,特例Android中如何设置两个应用程序为同一个进程?)中的组件,该Service就是一个Remote Service

    <service android:name=".RemoteService">
        <intent-filter>
            <action android:name="a.b.c" />
        </intent-filter>
    </service>
    

    从Android 5.0以后,开发者只能显式的(指定包名)跨应用启动Remote Service,下面是通过startService的方式跨应用启动Remote Service的代码:

    Intent intent = new Intent();
    intent.setPackage("edu.neu.steve.remoteservice");// Android 5.0之  后必须指明远程Service所在的应用包名    
    intent.setAction("a.b.c");        // 与远程Service注册时的action匹配
    startService(intent);
    

AIDL

AIDL(Android Interface Defination Language)即Android接口定义语言,用于Android进程间通信。
下面使用AIDL实现在Client端Bind Server端的Remote Service,从而调用Server端提供的远程服务,这里假设Client和Server不在同一应用中。

Server端
  1. 在java目录上右键,创建一个AIDL文件PublicBusiness.aidl,并在其中声明中间人的对公业务qianXian(),然后Make一下,会自动生成一个PublicBusiness.java(位于app/build/generated/source/aidl/debug下)
interface PublicBusiness {

    void qianXian();
}

aidl接口中所有成员都是public的,不需要也不能自己设置访问修饰符

  1. 在RemoteService内定义中间人FengMiShu直接继承抽象类PublicBusiness.Stub(查看生成的PublicBusiness.java源码可知,这个内部类Stub继承自Binder并实现了PublicBusiness接口),实现对公业务qianXian(),然后在onBind()中返回FengMiShu的实例。RemoteService在清单文件中的配置同上
public class RemoteService extends Service {


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new FengMiShu();  // 返回中间人的实例
    }

    public void remoteBanzheng() {
        System.out.println("领导在国外远程办证");
    }


    /**
     * 中间人冯秘书直接继承Stub并实现qianXian()
     */
    class FengMiShu extends PublicBusiness.Stub {
        @Override
        public void qianXian() {
            remoteBanzheng();
        }
    }
}
Client端
  1. 由于Client和Server不在同一应用中,需要把Server端的PublicBusiness.aidl文件复制一份放到Client端,保持该AIDL文件所在包名与Server端的AIDL文件所在包名一致。(如果Client和Server在同一应用中,则跳过该步)

  2. 在客户端bind RemoteService,绑定成功后使用PublicBusiness.Stub.asInterface()将从onServiceConnected()中获取到的中间人IBinder对象强转成PublicBusiness类型(不同进程中实际上返回的是一个代理),在需要时调用这个PublicBusiness对象的qianXian()即可实现远程办证

public class MainActivity extends AppCompatActivity {

    public PublicBusiness publicBusiness;
    private MyConnection myConn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myConn = new MyConnection();
    }

    class MyConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            publicBusiness = PublicBusiness.Stub.asInterface(service);  // 强转成PublicBusiness
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

    /**
     * 绑定服务
     *
     * @param v
     */
    public void bind(View v) {
        Intent intent = new Intent();
        intent.setPackage("edu.neu.steve.remoteservice");
        intent.setAction("a.b.c");
        bindService(intent, myConn, BIND_AUTO_CREATE);
    }

    /**
     * 解绑服务
     *
     * @param v
     */
    public void unbind(View v) {
        unbindService(myConn);
    }

    /**
     * 在Activity中办证
     *
     * @param v
     */
    public void banZheng(View v) {
        try {
            publicBusiness.qianXian();  // 中间人前线,领导在国外远程办证
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

IntentService

在Android开发中,我们一般需要在Service中开启一个子线程来执行类似下载这样的后台耗时操作,因为Activity可能会被用户退出,而BroadcastReceiver的生命周期本身就很短,在Service中开启子线程进行耗时操作可以避免应用进程变为空进程在内存不足时被系统杀死。如果仍然担心Service所在进程被杀,还可以通过调用service.startForeground(id, notification)提升应用进程的优先级,这是一种比较常见的进程保活方式。

然而自己去管理Service的生命周期以及子线程并非是个优雅的做法,好在Android给我们提供了IntentService,IntentService是Service的子类,用来处理异步请求,在IntentService内有一个worker线程来处理耗时操作。使用IntentService,我们不需要在Service中自己去开启一个子线程,也不需要考虑在什么时候停止Service.

扩展IntentService实现Service无须重写onBind()onStartCommand(),只需要重写onHandleIntent()

public class MyIntentService extends IntentService {


    /**
     * IntentService的构造函数一定是参数为空的构造函数
     * 然后再在其中调用super("name")这种形式的构造函数
     * 因为IntentService的实例化是系统用参数为空的构造函数来完成的
     */
    public MyIntentService() {
        super("MyIntentService");
    }

    /**
     * IntentService会使用单独的线程来执行该方法内的代码
     * 该方法内可以执行任何耗时任务,比如下载文件等,此处让线程暂停20秒来模拟耗时操作
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        long endTime = System.currentTimeMillis() + 20 * 1000;
        while (System.currentTimeMillis() < endTime) {
            synchronized (this) {
                try {
                    wait(endTime - System.currentTimeMillis());
                } catch (Exception e) {
                }
            }
        }
        System.out.println("---耗时任务执行完成---");
    }
}

不要忘了在清单文件中配置IntentService,因为它继承于Service,所以它仍是一个Service

<service android:name=".MyIntentService"></service>

客户端可以通过startService(Intent)方法传递请求给IntentService

Intent intent = new Intent(this, MyIntentService.class);// 创建需要启动的IntentService的Intent
startService(intent);       // 启动IntentService

IntentService会将该Intent加入到队列中,然后开启一条新的worker线程来处理该Intent以及onHandleIntent()中的耗时操作,不会阻塞主线程。
对于异步的startService()请求,IntentService会按次序依次处理队列中的Intent,worker线程保证同一时刻只处理一个Intent。当所有请求处理完成后,IntentService会自动停止

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

推荐阅读更多精彩内容

  • 服务是什么?Service是Android系统中的四大组件之一,主要有两个应用场景:后台运行和跨进程访问。Serv...
    飞行员suke阅读 968评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 概述 进程 :进程是一个应用程序运行的载体。在Android中,进程的底层是Linux管理的。Android中应用...
    啸天AskSky阅读 625评论 4 5
  • 有人如梦 有人如风 梦醒时分,清风拂面 泪光中闪烁着不悔还是无奈 就这样吧 我已无言,直面你的美
    arman007阅读 248评论 0 1
  • 突然,我就多了一个小院。表面上看起来,我只是多了一个小院。可是,我的内心是狂喜的,在这个小院出现的时刻,已经呈现出...
    梅园遗珠阅读 503评论 3 3