Android基础 - Service(服务)

老婆保佑,代码无BUG

目录

  • 什么是服务
  • 分类
    • 更具是否在同一进程分类
    • 更具运行类别分类
    • 启动方式上分类
  • 服务如何写
    • 启动式服务
    • 绑定式服务
    • 前台服务
    • 远程服务(AIDL 进程间通信)
    • intentService

一:什么是服务?

Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件。(其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行,你可以启动一个服务Service来播放音乐,或者记录你地理信息位置的改变,或者启动一个服务来运行并一直监听某种动作)

二:分类

更具是否在同一进程分类

类型 特点 优点 缺点 适用场景
本地服务 指的是服务和启动服务的activity在同一个进程中,开启的服务给本应用程序使用 (本app使用的) 节约资源。通信方便。不需要AIDL和IPC 限制大,主进程被杀死,服务也会停止 依附在某个进程的服务 如音乐播放器
远程服务 指的是服务和启动服务的activity不在同一个进程中。开启的服务给其他的应用程序使用 (给其他任何app使用的 基于IPC协议) 灵活,服务常住在后台,不受其他Activity影响 消耗资源:单独进程,使用AIDL,和IPC 系统级别服务

更具运行类别分类

类型 特点 适用场景
前台服务 在通知栏通知(用户可以看到) 服务运行时,让用户知道,并操作,音乐播放器
后台服务 处于后台的服务(用户无法看到) 服务终止时,用户无法知道

启动方式上分类

类型 特点
启动式 生命周期不依赖其他组件
绑定式 生命周期和其他组件绑定

三. 服务如何写

1. 启动式服务

生命周期

  onCreate-->onStartCommand-->onDestroy 

(1) 继承Service

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * 描述:本地服务
 * <p>
 * Created by allens on 2017/12/27.
 */

public class LocalService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG", "onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("TAG", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("TAG", "onDestroy");
    }
}

onBind方法补充说明

onBind(...)函数是Service基类中的唯一抽象方法,子类都必须重写实现,此函数的返回值是针对Bound Service类型的Service才有用的,在Started Service类型中,此函数直接返回 null 即可

onStartCommand 方法补充说明

  1. 可以多次被调用。

  2. 这个方法的作用是处理服务和系统的关系,默认是START_STICKY_COMPATIBILITY , 返回值有四种具体如下

类型 名称 说明
START_STICKY 粘性服务 如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null
START_NOT_STICKY 非粘性 如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务
START_REDELIVER_INTENT 粘性服务强化 如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY START_STICKY的兼容版本 不保证服务被kill后一定能重启,此值一般不会使用

强调一点

当Service因为内存不足而被系统kill后一定要非常注意,因为此函数的返回值设定只是针对此种情况才有意义的,换言之,当认为的kill掉Service进程,此函数返回值无论怎么设定,接下来未来的某个时间内,即使系统内存足够可用,Service也不会重启。

onDestroy 方法补充说明

当用户强制kill掉进程时,onDestroy()是不会执行的

(2) 清单文件注册

 <service android:name=".Service.LocalService" />

清单文件申明补充说明

属性 说明
name Service的类名
process 设置具体的进程名称,不设置默认false为本地服务
exported 该服务是否能够被其他应用程序所控制或连接
icon Service的图标
label Service的名字,若不设置,默认为Service类名
permission 申明此Service的权限

(3) Activity 中启动

 
//启动服务
//生命周期 先执行onCreate 然后 onStartCommand
        findViewById(R.id.local_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, LocalService.class);
                startService(intent);
            }
        });

//关闭服务
//生命周期 onDestroy
        findViewById(R.id.local_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, LocalService.class);
                stopService(intent);
            }
        });

2. 绑定式服务

生命周期

  onCreate-->onBind-->onUnbind-->onDestroy 

(1) 继承Service


public class BindService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG", "onCreate");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG", "onBind");
        //获取Acitivity传递的值
        String value = intent.getStringExtra("data1");
        Log.e("TAG", "获取的内容--->" + value);
        return new MyBinder();
    }


    @Override
    public boolean onUnbind(Intent intent) {
        Log.e("TAG", "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.e("TAG", "onDestroy");
        super.onDestroy();
    }

    //自定义一个Binder类继承 Binder
    public class MyBinder extends Binder {
        //实现自己的逻辑
        public int getNumber() {
            //返回给Activity一个随机数,Activity显示就可以了
            return new Random().nextInt(20);
        }
    }
}

(2) 清单文件注册

 <service android:name=".Service.BindService" />

(3) Activity

 //绑定服务
        findViewById(R.id.bind_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, BindService.class);

                connection = new ServiceConnection() {
                    @Override
                    public void onServiceConnected(ComponentName name, IBinder service) {
                        Log.e("TAG", "绑定服务成功了");
                        binder = (BindService.MyBinder) service;
                    }

                    @Override
                    public void onServiceDisconnected(ComponentName name) {
                        Log.e("TAG", "绑定服务断开了");
                    }
                };
                //通过intent的extras进行值的传递
                intent.putExtra("data1", "前台的界面绑定服务了");
                bindService(intent, connection, Service.BIND_AUTO_CREATE);
            }
        });

//下面是Log信息
//12-27 01:06:42.499 3300-3300/com.allens.servicedemo E/TAG: onCreate
//12-27 01:06:42.499 3300-3300/com.allens.servicedemo E/TAG: onBind
//12-27 01:06:42.499 3300-3300/com.allens.servicedemo E/TAG: 获取的内容--->前台的界面绑定服务了
//12-27 01:06:42.506 3300-3300/com.allens.servicedemo E/TAG: 绑定服务成功了


        //获取服务中传过来的参数
        findViewById(R.id.bind_getData).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (binder != null) {
                    int number = binder.getNumber();
                    Log.e("TAG", "NUMBER---->" + number);
                }
            }
        });
//下面是Log信息
//12-27 01:08:23.518 3300-3300/com.allens.servicedemo E/TAG: NUMBER---->13
        
        //解除绑定
        findViewById(R.id.bind_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //解除绑定的方法
                if (connection != null) {
                    unbindService(connection);
                    //自己控制引用的myBinder对象
                    binder = null;
                }
            }
        });

//下面是Log信息
//12-27 01:08:39.856 3300-3300/com.allens.servicedemo E/TAG: onUnbind
//12-27 01:08:39.856 3300-3300/com.allens.servicedemo E/TAG: onDestroy

注意

在四大基本组件中,需要注意的的是BroadcastReceiver不能作为Bind ServiceClient(依附对象),因为BroadcastReceiver的生命周期很短,当执行完onReceive(..)回调时,BroadcastReceiver生命周期完结。而Bind Service又与Client本身的生命周期相关,因此,Android中不允许BroadcastReceiverbindService(..),当有此类需求时,可以考虑通过startService(..)替代。


3. 前台服务

前台服务其实只是在启动式服务基础上,加上了Notification通知

给出onCreate()中的代码

 @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG", "onCreate");

        //添加下列代码将后台Service变成前台Service
        //构建"点击通知后打开MainActivity"的Intent对象
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        //新建Builer对象
        Notification.Builder builer = new Notification.Builder(this);
        //设置通知的标题
        builer.setContentTitle("前台服务通知的标题")
                //设置通知的内容
                .setContentText("前台服务通知的内容")
                //设置通知的图标
                .setSmallIcon(R.mipmap.ic_launcher)
                //设置点击通知后的操作
                .setContentIntent(pendingIntent);
        //将Builder对象转变成普通的notification
        Notification notification = builer.getNotification();
        //让Service变成前台Service,并在系统的状态栏显示出来
        startForeground(1, notification);
    }

效果

效果图片

4. 远程服务

为了让远程Service与多个应用程序的组件(四大组件)进行跨进程通信(IPC),需要使用AIDL

  1. IPC:Inter-Process Communication,即跨进程通信
  2. AIDL:Android Interface Definition Language,即Android接口定义语言;用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。

服务端 Service

(1)兴建一个AIDL 文件

新建一个AIDL 文件

建立好以后好会在与java同级的目录下看到一个aidl的文件夹,如下图所示


屏幕快照 2017-12-27 下午3.10.10.png

(2) Rebuild Project

一定不能忘了,


屏幕快照 2017-12-27 下午3.10.29.png

完成以后,可以在 build 中看到 已经编译好了


屏幕快照 2017-12-27 下午3.11.10.png

(3) 在Service子类中实现AIDL中定义的接口方法

package com.allens.servicedemo.Service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * 描述:  AIDL 服务端service
 * <p>
 * Created by allens on 2017/12/27.
 */

public class ServerService extends Service {
    private MyBinder myBinder;

    //继承IServer.Stub 并实现其接口
    public class MyBinder extends IServer.Stub {
        @Override
        public void onServer() throws RemoteException {
            Log.e("TAG", "客户端与远程服务通信成功");
            System.out.print("客户端与远程服务通信成功  " + Thread.currentThread().getName());
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG", "onBind");
        return myBinder;
    }


    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG", "onCreate");
        myBinder = new MyBinder();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("TAG", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("TAG", "onDestroy");
    }
}

(4) 清单文件注册信息

        <!-- process 设置具体的进程名称-->
        <!-- exported 该服务能被其他应用程序所控制或连接 -->
        <service
            android:name=".Service.ServerService"
            android:exported="true"
            android:process=":div">
            <!--此处Intent的action必须写成“服务器端包名.aidl文件名”-->
            <intent-filter>
                <action android:name="com.allens.servicedemo.IServer" />
            </intent-filter>
        </service>

客户端 Service

(1) 将服务端的aidl 文件直接复制到 客户端

复制之后的目录

小提示

最好将AS 的目录切换到Project,在进行复制


在这里可以切换

(2) Activity

package com.allens.servicecliet;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.allens.servicedemo.Service.IServer;

public class MainActivity extends AppCompatActivity {


    //创建ServiceConnection的匿名类
    private ServiceConnection connection = new ServiceConnection() {

        //重写onServiceConnected()方法和onServiceDisconnected()方法
        //在Activity与Service建立关联和解除关联的时候调用
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e("TAG", "onServiceDisconnected");
        }

        //在Activity与Service建立关联时调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e("TAG", "onServiceConnected");
            //IService.Stub.asInterface()方法获取服务器端返回的IBinder对象
            //将IBinder对象传换成了mAIDL_Service接口对象
            IServer iServer = IServer.Stub.asInterface(service);
            try {
                //通过该对象调用在IService.aidl文件中定义的接口方法,从而实现跨进程通信
                iServer.onServer();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };


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

        findViewById(R.id.btn_bind).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG", "onClick");
                //通过Intent指定服务端的服务名称和所在包,与远程Service进行绑定
                //参数与服务器端的action要一致,即"服务器包名.aidl接口文件名"
                Intent intent = new Intent("com.allens.servicedemo.IServer");

                //Android5.0后无法只通过隐式Intent绑定远程Service
                //需要通过setPackage()方法指定包名
                intent.setPackage("com.allens.servicedemo");

                //绑定服务,传入intent和ServiceConnection对象
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        });
    }
}

最后看Log

当我们点击Client 中的Button时候,

12-27 02:52:06.702 5661-5661/com.allens.servicecliet E/TAG: onClick
12-27 02:52:06.979 5661-5661/com.allens.servicecliet E/TAG: onServiceConnected

然后去看Server中的log

12-27 02:52:06.905 5687-5687/com.allens.servicedemo:div E/TAG: onCreate
12-27 02:52:06.906 5687-5687/com.allens.servicedemo:div E/TAG: onBind
12-27 02:52:06.980 5687-5704/com.allens.servicedemo:div E/TAG: 客户端与远程服务通信成功

如果没看服务端的Log,请注意看一下,是否把进程切换到自己定义的进程下

我是div 自己定义的

最后 双手奉上代码

ServiceServerDemo 点击跳转
ServiceClietDemo 点击跳转


5. IntentService

特点

  • 不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。

  • 可以在onHandleIntent中做耗时操作

  • 当多次点击时候,会将任务添加到队列中,直到所有的任务都完成了,销毁服务

生命周期

onCreate-->onStartCommand-->onHandleIntent-->onDestroy

继承IntentService

/**
 * 描述:intentService
 * <p>
 * Created by allens on 2017/12/27.
 */

public class MyIntentService extends IntentService {
    /***
     * 比较规范的action条件,包名+action+功能意义的描述
     */
    private static final String ACTION_FOO = "com.allens.servicedemo.Service.FOO";
    private static final String ACTION_BAZ = "com.allens.servicedemo.Service.BAZ";

    /***
     * 规范的key的写法
     */
    private static final String EXTRA_PARAM1 = "com.allens.servicedemo.Service.PARAM1";
    private static final String EXTRA_PARAM2 = "com.allens.servicedemo.Service.PARAM2";

    public MyIntentService() {
        //给IntentService内部创建的工作线程起名字
        super("MyIntentService");
    }

    /**
     * 外部直接调用该方法就可以启动该服务了(设置了action条件,传递的参数,并且启动了服务)
     *
     * @see IntentService
     */
    public static void startActionFoo(Context context, String param1, String param2) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_FOO);
        intent.putExtra(EXTRA_PARAM1, param1);
        intent.putExtra(EXTRA_PARAM2, param2);
        context.startService(intent);
    }


    public static void startActionBaz(Context context, String param1) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_BAZ);
        intent.putExtra(EXTRA_PARAM1, param1);
        context.startService(intent);
    }

    /**
     * 进行耗时操作的处理 (当该方法执行完毕以后,服务会自动关闭)
     * 通过action区分不同的耗时操作的逻辑,各自实现各自的逻辑
     * <p/>
     * 从核心代码入手,分析每一行代码表达意思,(可以中间涉及其他方法和成员变量的定义,以此扩展出去查看)
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.e("TAG", "onHandleIntent");
        if (intent != null) {
            //获取action条件
            final String action = intent.getAction();
            if (ACTION_FOO.equals(action)) {
                //获取字符串内容
                final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                final String param2 = intent.getStringExtra(EXTRA_PARAM2);
                handleActionFoo(param1, param2);
            } else if (ACTION_BAZ.equals(action)) {
                final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                handleActionBaz(param1);
            }
        }
    }

    //模拟耗时
    private void handleActionBaz(String param1) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e("TAG", "param1 " + param1);
    }

    private void handleActionFoo(String param1, String param2) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onCreate() {
        Log.e("TAG", "onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e("TAG", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.e("TAG", "onDestroy");
        super.onDestroy();
    }
}

清单文件注册

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

Activity

  MyIntentService.startActionBaz(MainActivity.this,"test1");

写在最后

首先本文全部的DEMO 都可以在GitHub 上看到

ServiceServerDemo 点击跳转

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

推荐阅读更多精彩内容

  • 一.基础知识 1.Service是什么? Service是一个一种在后台执行长时间运行操作而没有用户界面的应用组件...
    AKyS佐毅阅读 345评论 0 0
  • 描述 Service通常总是称之为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于...
    pkqgo阅读 648评论 1 3
  • Service概念 Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组...
    水月心刀阅读 811评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,590评论 18 139
  • 定义 service Android 中的服务,可以理解为一个没有界面的 activity,存在的意义就是运行本应...
    前行的乌龟阅读 667评论 0 1